├── dist ├── css │ └── tool.css ├── mix-manifest.json └── js │ └── tool.js ├── resources ├── sass │ └── tool.scss ├── js │ ├── tool.js │ └── components │ │ ├── Settings.vue │ │ └── Tenants.vue └── views │ └── navigation.blade.php ├── config └── nova-multi-tenant-manager.php ├── webpack.mix.js ├── src ├── Exceptions │ ├── TenantExistsException.php │ └── TenantDoesNotExistException.php ├── Contracts │ └── SettingsFieldsInterface.php ├── Providers │ ├── Nova.php │ └── Tool.php ├── Console │ └── Commands │ │ ├── Publish.php │ │ ├── CreateTenant.php │ │ ├── AliasTenant.php │ │ └── DeleteTenant.php ├── NovaMultiTenantManager.php ├── Http │ ├── Controllers │ │ └── Api │ │ │ ├── AliasController.php │ │ │ └── TenantController.php │ └── Middleware │ │ └── Authorize.php ├── Tenant.php ├── Nova │ ├── Alias.php │ └── Tenant.php └── Services │ └── Tenant.php ├── routes └── api.php ├── package.json ├── composer.json └── README.md /dist/css/tool.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/sass/tool.scss: -------------------------------------------------------------------------------- 1 | // Nova Tool CSS 2 | -------------------------------------------------------------------------------- /dist/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/tool.js": "/js/tool.js", 3 | "/css/tool.css": "/css/tool.css" 4 | } -------------------------------------------------------------------------------- /config/nova-multi-tenant-manager.php: -------------------------------------------------------------------------------- 1 | null, 5 | ]; 6 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix') 2 | 3 | mix.setPublicPath('dist') 4 | .js('resources/js/tool.js', 'js') 5 | .sass('resources/sass/tool.scss', 'css') 6 | -------------------------------------------------------------------------------- /src/Exceptions/TenantExistsException.php: -------------------------------------------------------------------------------- 1 | { 2 | router.addRoutes([ 3 | { 4 | name: 'tenants', 5 | path: '/tenants', 6 | component: require('./components/Tenants'), 7 | }, 8 | { 9 | name: 'tenant-settings', 10 | path: '/tenant-settings', 11 | component: require('./components/Settings'), 12 | }, 13 | ]) 14 | }) 15 | -------------------------------------------------------------------------------- /src/Providers/Nova.php: -------------------------------------------------------------------------------- 1 | option('config')) { 14 | $this->call('vendor:publish', [ 15 | '--provider' => Tool::class, 16 | '--tag' => ['config'], 17 | '--force' => true, 18 | ]); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | argument('domain'); 15 | $name = $this->argument('name'); 16 | 17 | try { 18 | (new Tenant)->create($domain, $name); 19 | $this->info("✅ New tenant created and now accessible at 'https://{$domain}'."); 20 | } catch (TenantExistsException $exception) { 21 | $this->error($exception->getMessage()); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Http/Controllers/Api/AliasController.php: -------------------------------------------------------------------------------- 1 | hostname(); 16 | (new TenantService)->createAlias($hostname->fqdn, $request->alias); 17 | 18 | return (new Tenant)->findCurrent(); 19 | } 20 | 21 | public function destroy(int $hostnameId) : Response 22 | { 23 | $hostname = (new Hostname)->find($hostnameId); 24 | $hostname->delete(); 25 | 26 | return response(null, 204); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Console/Commands/AliasTenant.php: -------------------------------------------------------------------------------- 1 | argument('alias'); 15 | $domain = $this->argument('domain'); 16 | 17 | try { 18 | (new Tenant)->createAlias($domain, $alias); 19 | $this->info("✅ New alias for tenant '{$domain}' created and now accessible at 'https://{$alias}'."); 20 | } catch (TenantExistsException $exception) { 21 | $this->error($exception->getMessage()); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Http/Middleware/Authorize.php: -------------------------------------------------------------------------------- 1 | first([$this, 'matchesTool']); 18 | 19 | return optional($tool)->authorize($request) ? $next($request) : abort(403); 20 | } 21 | 22 | /** 23 | * Determine whether this tool belongs to the package. 24 | * 25 | * @param \Laravel\Nova\Tool $tool 26 | * @return bool 27 | */ 28 | public function matchesTool($tool) 29 | { 30 | return $tool instanceof NovaMultiTenantManager; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run production", 10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" 11 | }, 12 | "devDependencies": { 13 | "cross-env": "^5.0.0", 14 | "laravel-mix": "^1.0" 15 | }, 16 | "dependencies": { 17 | "vue": "^2.5.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Tenant.php: -------------------------------------------------------------------------------- 1 | "array", 17 | ]; 18 | protected $fillable = [ 19 | "domain", 20 | "name", 21 | ]; 22 | 23 | public function website() : BelongsTo 24 | { 25 | return $this->belongsTo(config('tenancy.models.website')); 26 | } 27 | 28 | public function getAliasesAttribute() : Collection 29 | { 30 | $this->load("website.hostnames"); 31 | 32 | if (! $this->website 33 | || !$this->website->hostnames 34 | ) { 35 | return collect(); 36 | } 37 | 38 | $hostnames = $this 39 | ->website 40 | ->hostnames; 41 | $hostnames->shift(); 42 | 43 | return $hostnames; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Nova/Alias.php: -------------------------------------------------------------------------------- 1 | sortable(), 33 | Text::make("Domain", "fqdn"), 34 | Text::make("Redirect To"), 35 | Boolean::make("Force HTTPS", "force_https"), 36 | DateTime::make("Under Maintenance Since"), 37 | ]; 38 | } 39 | 40 | public static function label() 41 | { 42 | return "Alias"; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Nova/Tenant.php: -------------------------------------------------------------------------------- 1 | sortable(), 31 | Text::make("Name"), 32 | Text::make("Domain") 33 | ->rules("required"), 34 | ]; 35 | 36 | if ($settings = config("nova-multi-tenant-manager.settings-fields-class")) { 37 | $fields = array_merge($fields, (new $settings)->make()); 38 | } 39 | 40 | return $fields; 41 | } 42 | 43 | public static function label() 44 | { 45 | return "Site"; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Http/Controllers/Api/TenantController.php: -------------------------------------------------------------------------------- 1 | with("website.hostnames") 16 | ->orderBy("name") 17 | ->get(); 18 | } 19 | 20 | public function show() : Tenant 21 | { 22 | $tenant = (new Tenant)->findCurrent(); 23 | $tenant->load("website.hostnames"); 24 | 25 | return $tenant; 26 | } 27 | 28 | public function store(Request $request) : Tenant 29 | { 30 | $tenancyService = (new TenantService) 31 | ->create($request->domain); 32 | $tenant = (new Tenant)->firstOrNew([ 33 | "website_id" => $tenancyService->website->id, 34 | ]) 35 | ->fill($request->all()); 36 | $tenant->save(); 37 | 38 | return $tenant; 39 | } 40 | 41 | public function destroy(int $id) : Response 42 | { 43 | $tenant = (new Tenant)->findOrFail($id); 44 | (new TenantService) 45 | ->delete($tenant->domain); 46 | $tenant->delete(); 47 | 48 | return response(null, 204); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "genealabs/nova-multi-tenant-manager", 3 | "description": "A Laravel Nova package to manage multi-tenants.", 4 | "keywords": [ 5 | "laravel", 6 | "nova" 7 | ], 8 | "license": "MIT", 9 | "repositories": [ 10 | { 11 | "type": "composer", 12 | "url": "https://nova.laravel.com" 13 | } 14 | ], 15 | "require": { 16 | "hyn/multi-tenant": "*", 17 | "illuminate/console": "^10.0|^11.0", 18 | "illuminate/database": "^10.0|^11.0", 19 | "illuminate/http": "^10.0|^11.0", 20 | "illuminate/routing": "^10.0|^11.0", 21 | "illuminate/support": "^10.0|^11.0", 22 | "laravel/nova": "^4.0" 23 | }, 24 | "require-dev": { 25 | "orchestra/testbench": "^8.0", 26 | "orchestra/testbench-browser-kit": "^8.0", 27 | "orchestra/testbench-dusk": "^8.0", 28 | "php-coveralls/php-coveralls": "^2.2", 29 | "phpmd/phpmd": "^2.7", 30 | "phpunit/phpunit": "^10.0", 31 | "squizlabs/php_codesniffer": "^3.4", 32 | "symfony/thanks": "^1.2" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "GeneaLabs\\NovaMultiTenantManager\\": "src/" 37 | } 38 | }, 39 | "extra": { 40 | "laravel": { 41 | "providers": [ 42 | "GeneaLabs\\NovaMultiTenantManager\\Providers\\Tool", 43 | "GeneaLabs\\NovaMultiTenantManager\\Providers\\Nova" 44 | ] 45 | } 46 | }, 47 | "config": { 48 | "sort-packages": true 49 | }, 50 | "minimum-stability": "dev", 51 | "prefer-stable": true 52 | } 53 | -------------------------------------------------------------------------------- /src/Console/Commands/DeleteTenant.php: -------------------------------------------------------------------------------- 1 | option("all")) { 16 | $this->deleteAllTenants(); 17 | 18 | return; 19 | } 20 | 21 | $domain = $this->argument('domain'); 22 | 23 | try { 24 | if ($this->confirmDeletion($domain)) { 25 | $this->deleteTenant($domain); 26 | } 27 | } catch (TenantDoesNotExistException $exception) { 28 | $this->error($exception->getMessage()); 29 | } 30 | } 31 | 32 | protected function confirmDeletion(string $domain) : bool 33 | { 34 | return "y" === $this->ask("Are you sure you want to delete tenant '{$domain}'? Type 'y' to confirm. [n]"); 35 | } 36 | 37 | protected function confirmDeletionOfAllTenants() : bool 38 | { 39 | return "y" === $this->ask("Are you sure you want to delete ALL tenants? This operation is irreversible! Type 'y' to confirm. [n]"); 40 | } 41 | 42 | protected function deleteAllTenants() 43 | { 44 | if (! $this->confirmDeletionOfAllTenants()) { 45 | return; 46 | } 47 | 48 | $hostnames = (new Hostname) 49 | ->with("website") 50 | ->get() 51 | ->each(function (Hostname $hostname) { 52 | $this->deleteTenant($hostname->fqdn); 53 | }); 54 | } 55 | 56 | protected function deleteTenant(string $domain) 57 | { 58 | (new Tenant)->delete($domain); 59 | $this->info("✅ Tenant '{$domain}' successfully deleted."); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nova Multi-Tenant Manager 2 | Manage tenants and their settings in Laravel Nova. 3 | 4 | ![Multi-Tenant Manager for Laravel Nova masthead image.](https://repository-images.githubusercontent.com/186168087/0a8f8d80-f1b7-11e9-93ee-6399860d51f4) 5 | 6 | ## Requirements 7 | - PHP >= 7.1.3 8 | - Laravel 8.* (https://laravel.com) 9 | - Laravel Nova 3.* (https://nova.laravel.com) 10 | - Laravel Tenancy 5.7.* (https://laravel-tenancy.com) 11 | 12 | ## Installation 13 | 1. ```sh 14 | composer require "genealabs/nova-multi-tenant-manager:*" 15 | ``` 16 | 2. Add the tool to your `app\Providers\NovaServiceProvider.php`: 17 | ```php 18 | public function tools() 19 | { 20 | return [ 21 | // ... 22 | new GeneaLabs\NovaMultiTenantManager\NovaMultiTenantManager, 23 | // ... 24 | ]; 25 | } 26 | ``` 27 | 28 | ### Planned Features 29 | - Configurable settings fields (implemented, needs documentation). 30 | - Automatic integration with `genealabs/laravel-governor`. 31 | - Extendible classes to allow custom integration with 3rd-party packages. 32 | - Integrate CLI commands into tenancy namespace. 33 | - Add unit tests. 34 | 35 | ## Usage 36 | ### CLI Commands 37 | #### `tenant:create` 38 | Creates a tenant with the provided domain name. 39 | ```sh 40 | php artisan tenant:create 41 | ``` 42 | 43 | #### `tenant:alias` 44 | Creates an alias for an existing tenant with the provided domain name. 45 | ```sh 46 | php artisan tenant:alias 47 | ``` 48 | 49 | #### `tenant:delete` 50 | Deletes tenant by the provided domain, or delete all tenants. 51 | ```sh 52 | php artisan tenant:delete 53 | php artisan tenant:delete --all 54 | ``` 55 | 56 | ### Nova Tools 57 | #### Tenants Management 58 | Screen Shot 2019-05-11 at 5 19 20 PM 59 | 60 | #### Tenant Settings 61 | Screen Shot 2019-05-11 at 5 19 07 PM 62 | -------------------------------------------------------------------------------- /src/Providers/Tool.php: -------------------------------------------------------------------------------- 1 | loadViewsFrom( 19 | __DIR__ . '/../../resources/views', 20 | 'genealabs-nova-multi-tenant-manager' 21 | ); 22 | 23 | $this->publishes([ 24 | __DIR__ . '/../../config/nova-multi-tenant-manager.php' => config_path('nova-multi-tenant-manager.php') 25 | ], 'config'); 26 | 27 | $this->app->singleton("tenant", function () { 28 | $website = app(Environment::class)->tenant(); 29 | 30 | if (! $website) { 31 | $websiteUuid = config('database.connections.tenant.uuid'); 32 | $website = (new Website)->where("uuid", $websiteUuid)->first(); 33 | $environment = app(Environment::class); 34 | $environment->tenant($website); 35 | } 36 | 37 | return (new Tenant) 38 | ->where("website_id", $website->id) 39 | ->first(); 40 | }); 41 | 42 | $this->app->booted(function () { 43 | $this->routes(); 44 | }); 45 | } 46 | 47 | protected function routes() 48 | { 49 | if ($this->app->routesAreCached()) { 50 | return; 51 | } 52 | 53 | Route::middleware(['nova', Authorize::class]) 54 | ->prefix('nova-vendor/genealabs-nova-multi-tenant-manager') 55 | ->group(__DIR__ . '/../../routes/api.php'); 56 | } 57 | 58 | public function register() 59 | { 60 | $this->mergeConfigFrom( 61 | __DIR__ . '/../../config/nova-multi-tenant-manager.php', 62 | 'nova-multi-tenant-manager' 63 | ); 64 | 65 | $this->commands(Publish::class); 66 | $this->commands(AliasTenant::class); 67 | $this->commands(CreateTenant::class); 68 | $this->commands(DeleteTenant::class); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /resources/views/navigation.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | Tenants 16 | 17 | 18 | {{-- 19 | 31 | 32 | Site Settings 33 | 34 | --}} 35 | id }}?viaResource=&viaResourceId=&viaRelationship='}" class="cursor-pointer flex items-center font-normal dim text-white mb-6 text-base no-underline"> 36 | 48 | 49 | Site Settings 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/Services/Tenant.php: -------------------------------------------------------------------------------- 1 | hostname = $hostname; 22 | $this->website = $website; 23 | } 24 | 25 | public function create(string $domain = "", string $name = "") : self 26 | { 27 | $domain = $this->cleanDomain($domain); 28 | 29 | if ($this->exists($domain)) { 30 | if (! $this->website) { 31 | $this->createWebsite($domain); 32 | } 33 | 34 | $this->createTenant($domain, $name); 35 | 36 | throw new TenantExistsException("Tenant with domain '{$domain}' already exists."); 37 | } 38 | 39 | $this->createWebsite($domain); 40 | $this->createHostname($domain); 41 | $this->switchToTenant(); 42 | $this->createTenant($domain, $name); 43 | 44 | return $this; 45 | } 46 | 47 | public function createAlias(string $domain = "", string $alias = "") : self 48 | { 49 | $alias = $this->cleanDomain($alias); 50 | 51 | if ($this->exists($alias)) { 52 | return $this; 53 | } 54 | 55 | $originalHostname = (new Hostname) 56 | ->with("website") 57 | ->where("fqdn", $domain) 58 | ->first(); 59 | $newHostname = new Hostname; 60 | $newHostname->fqdn = $alias; 61 | $newHostname = app(HostnameRepository::class) 62 | ->create($newHostname); 63 | app(HostnameRepository::class) 64 | ->attach($newHostname, $originalHostname->website); 65 | 66 | return $this; 67 | } 68 | 69 | public function delete(string $domain = "") : self 70 | { 71 | if (! $this->exists($domain)) { 72 | throw new TenantDoesNotExistException("A tenant with domain '{$domain}' does not exist."); 73 | } 74 | 75 | if ($domain) { 76 | $this->findByDomain($domain); 77 | } 78 | 79 | if ($this->hostname) { 80 | $this->hostname = app(HostnameRepository::class)->delete($this->hostname, true); 81 | } 82 | 83 | if ($this->website) { 84 | $this->website->load("hostnames"); 85 | 86 | if ($this->website->hostnames->isEmpty()) { 87 | $this->website = app(WebsiteRepository::class)->delete($this->website, true); 88 | } 89 | } 90 | 91 | return $this; 92 | } 93 | 94 | public function findByDomain(string $domain) : self 95 | { 96 | $this->hostname = (new Hostname) 97 | ->with("website") 98 | ->where("fqdn", $domain) 99 | ->first(); 100 | 101 | if ($this->hostname) { 102 | $this->website = $this->hostname->website; 103 | } 104 | 105 | return $this; 106 | } 107 | 108 | public function findCurrent() : ?self 109 | { 110 | $website = app(Environment::class)->tenant(); 111 | 112 | if (! $website) { 113 | return $this; 114 | } 115 | 116 | return $this 117 | ->where("website_id", $website->id) 118 | ->first(); 119 | } 120 | 121 | public function findCurrentHostname() : Hostname 122 | { 123 | return app(Environment::class) 124 | ->hostname(); 125 | } 126 | 127 | protected function cleanDomain(string $domain) : string 128 | { 129 | return preg_replace('/.*?\:?\/\/(.*)/', "$1", $domain); 130 | } 131 | 132 | protected function createTenant(string $domain, string $name) 133 | { 134 | $tenant = (new TenantModel) 135 | ->firstOrNew([ 136 | "domain" => $domain, 137 | ]); 138 | $tenant->name = $name; 139 | $tenant->website_id = $this->website->id; 140 | $tenant->save(); 141 | } 142 | 143 | protected function createWebsite(string $domain) : Website 144 | { 145 | $this->website = new Website; 146 | 147 | if (config('tenancy.website.disable-random-id') === true) { 148 | $this->website->uuid = Str::slug($domain); 149 | 150 | if (config('tenancy.website.uuid-limit-length-to-32')) { 151 | $this->website->uuid = substr($this->website->uuid, -32); 152 | } 153 | } 154 | 155 | return app(WebsiteRepository::class) 156 | ->create($this->website); 157 | } 158 | 159 | protected function createHostname(string $domain) 160 | { 161 | $this->hostname = new Hostname; 162 | $this->hostname->fqdn = $domain; 163 | $this->hostname = app(HostnameRepository::class) 164 | ->create($this->hostname); 165 | app(HostnameRepository::class) 166 | ->attach($this->hostname, $this->website); 167 | } 168 | 169 | public function switchToTenant(TenantModel $tenant = null) 170 | { 171 | if ($tenant) { 172 | $tenant->load("website"); 173 | $website = $tenant->website; 174 | } 175 | 176 | app(Environment::class)->tenant($website ?? $this->website); 177 | } 178 | 179 | public function exists(string $domain) : bool 180 | { 181 | $hostname = (new Hostname) 182 | ->with("website") 183 | ->where('fqdn', $domain) 184 | ->first(); 185 | $this->hostname = $hostname; 186 | $this->website = optional($hostname)->website; 187 | 188 | return optional($hostname)->exists() 189 | ?: false; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /resources/js/components/Settings.vue: -------------------------------------------------------------------------------- 1 | 161 | 162 | 322 | 323 | 346 | -------------------------------------------------------------------------------- /resources/js/components/Tenants.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 259 | 260 | 263 | -------------------------------------------------------------------------------- /dist/js/tool.js: -------------------------------------------------------------------------------- 1 | !function(t){var e={};function n(a){if(e[a])return e[a].exports;var s=e[a]={i:a,l:!1,exports:{}};return t[a].call(s.exports,s,s.exports,n),s.l=!0,s.exports}n.m=t,n.c=e,n.d=function(t,e,a){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:a})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=3)}([function(t,e){t.exports=function(t){var e=[];return e.toString=function(){return this.map(function(e){var n=function(t,e){var n=t[1]||"",a=t[3];if(!a)return n;if(e&&"function"==typeof btoa){var s=(i=a,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(i))))+" */"),r=a.sources.map(function(t){return"/*# sourceURL="+a.sourceRoot+t+" */"});return[n].concat(r).concat([s]).join("\n")}var i;return[n].join("\n")}(e,t);return e[2]?"@media "+e[2]+"{"+n+"}":n}).join("")},e.i=function(t,n){"string"==typeof t&&(t=[[null,t,""]]);for(var a={},s=0;sn.parts.length&&(a.parts.length=n.parts.length)}else{var i=[];for(s=0;s0},currentImage:function(){return this.imagePreviewData.length>0?this.imagePreviewData:this.hasLogo?"/"+this.tenant.settings.logo:""}},methods:{addAlias:function(){var t=this;axios.post("/nova-vendor/genealabs-nova-multi-tenant-manager/aliases",{alias:t.newAlias}).then(function(e){t.tenant=e.data,t.newAlias=""}).catch(function(e){console.error(e),t.$toasted.show("Error "+e.response.status+": "+e.response.statusText,{type:"error"})})},deleteAlias:function(t){var e=this;axios.delete("/nova-vendor/genealabs-nova-multi-tenant-manager/aliases/"+t).then(function(n){n.status>=200&&n.status<300&&(e.tenant.aliases=_.reject(e.tenant.aliases,function(e){return e.id==t}))}).catch(function(t){console.error(t,t.response),e.$toasted.show("Error "+t.response.status+": "+t.response.statusText,{type:"error"})})},formData:function(){var t=new FormData;return t.append("_method","PATCH"),null!=this.tenant.name&&t.append("name",this.tenant.name),null!=this.tenant.settings.logo&&t.append("logo",this.tenant.settings.logo),t},getName:function(){return this.tenant.name},loadTenant:function(){var t=this;axios.get("/nova-vendor/genealabs-nova-multi-tenant-manager/tenants/0").then(function(e){t.tenant=e.data,t.fields=[{name:"Name",value:t.tenant.name,component:"text-field",resourceName:"Tenant"},{name:"Logo",value:t.tenant.logo,component:"file-field",resourceName:"Tenant"}]}).catch(function(e){console.error(e,e.response),t.$toasted.show("Error "+e.response.status+": "+e.response.statusText,{type:"error"})})},previewImage:function(t){var e=t.target,n=this;if(e.files&&e.files[0]){var a=new FileReader;this.tenant.settings=Object.assign({},this.tenant.settings,{logo:e.files[0]}),a.onload=function(t){var e=t.target.result;n.imagePreviewData="",0===e.indexOf("data:image")&&(n.imagePreviewData=e)},a.readAsDataURL(e.files[0]),this.updateTenant()}},updateTenant:_.debounce(function(){var t=this,e=this.formData();axios.post("/nova-vendor/genealabs-nova-multi-tenant-manager/tenants/"+this.tenant.id,e).then(function(e){t.tenant=e.data,t.$toasted.show("Site settings updated successfully.",{type:"success"}),setTimeout(function(){window.location.reload(!0)},1e3)}).catch(function(e){console.error(e,e.response),t.$toasted.show("Error "+e.response.status+": "+e.response.statusText,{type:"error"})})},500)}}},function(t,e){t.exports={render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",[n("heading",{staticClass:"mb-6"},[t._v("Site Details")]),t._v(" "),n("card",[n("div",[n("div",{staticClass:"flex border-b border-40",attrs:{"via-resource":"","via-resource-id":"","via-relationship":""}},[n("div",{staticClass:"w-1/5 py-6 px-8"},[n("label",{staticClass:"inline-block text-80 pt-2 leading-tight",attrs:{for:"name"}},[t._v("\n Name\n ")])]),t._v(" "),n("div",{staticClass:"py-6 px-8 w-1/2"},[n("input",{directives:[{name:"model",rawName:"v-model",value:t.tenant.name,expression:"tenant.name"}],staticClass:"w-full form-control form-input form-input-bordered",attrs:{type:"text",placeholder:"Name"},domProps:{value:t.tenant.name},on:{keyup:t.updateTenant,input:function(e){e.target.composing||t.$set(t.tenant,"name",e.target.value)}}}),t._v(" "),n("div",{staticClass:"help-text help-text mt-2"})])]),t._v(" "),n("div",{staticClass:"flex border-b border-40",attrs:{"via-resource":"","via-resource-id":"","via-relationship":""}},[n("div",{staticClass:"w-1/5 py-6 px-8"},[n("label",{staticClass:"inline-block text-80 pt-2 leading-tight",attrs:{for:"name"}},[t._v("\n Domain\n ")])]),t._v(" "),n("div",{staticClass:"w-3/4 py-8 px-8"},[n("p",{staticClass:"text-90",domProps:{textContent:t._s(t.tenant.domain)}})])]),t._v(" "),n("div",{staticClass:"flex border-b border-40",attrs:{"via-resource":"","via-resource-id":"","via-relationship":""}},[n("div",{staticClass:"w-1/5 py-6 px-8"},[n("label",{staticClass:"inline-block text-80 pt-2 leading-tight",attrs:{for:"name"}},[t._v("\n Aliases\n ")])]),t._v(" "),n("div",{staticClass:"py-8 px-8 w-3/4"},[t._l(t.tenant.aliases,function(e){return n("p",{staticClass:"mb-3"},[t._v("\n "+t._s(e.fqdn)+"\n "),n("span",{staticClass:"ml-3 text-danger trashcan",on:{click:function(n){return t.deleteAlias(e.id)}}},[n("i",{staticClass:"fal fa-trash-alt"}),t._v(" "),n("i",{staticClass:"fas fa-trash-alt"})])])}),t._v(" "),n("input",{directives:[{name:"model",rawName:"v-model",value:t.newAlias,expression:"newAlias"}],staticClass:"w-2/3 form-control form-input form-input-bordered rounded-r-none",attrs:{type:"text",placeholder:"Add Alias"},domProps:{value:t.newAlias},on:{keyup:function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:t.addAlias(e)},input:function(e){e.target.composing||(t.newAlias=e.target.value)}}}),n("button",{staticClass:"btn btn-default btn-primary rounded-l-none",on:{click:t.addAlias}},[n("svg",{staticClass:"fill-current h-3 w-3",attrs:{"aria-hidden":"true",focusable:"false",role:"img",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 448 512"}},[n("path",{attrs:{d:"M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z"}})])]),t._v(" "),n("div",{staticClass:"help-text help-text mt-2"})],2)])])]),t._v(" "),n("heading",{staticClass:"mb-4 mt-8"},[t._v("Settings")]),t._v(" "),n("card",{staticClass:"mt-4"},[n("div",[n("div",{staticClass:"flex border-b border-40"},[n("div",{staticClass:"w-1/5 py-6 px-8"},[n("label",{staticClass:"inline-block text-80 pt-2 leading-tight"},[t._v("\n Logo\n ")])]),t._v(" "),n("div",{staticClass:"py-6 px-8 w-4/5"},[n("span",{staticClass:"form-file mr-4"},[n("input",{ref:"logo",staticClass:"form-file-input",attrs:{type:"file",id:"logo",accept:"image"},on:{change:t.previewImage}}),t._v(" "),n("label",[n("img",{staticClass:"image-preview",attrs:{src:t.currentImage}})]),t._v(" "),n("div"),t._v(" "),n("label",{staticClass:"mt-4 form-file-btn btn btn-default btn-primary",attrs:{for:"logo"}},[t._v("\n Choose File\n ")])]),t._v(" "),n("div",{staticClass:"help-text help-text mt-2"},[t._v("\n Current File: "+t._s(t.tenant.logo||"no file selected")+"\n ")])])])])])],1)},staticRenderFns:[]}},function(t,e){}]); --------------------------------------------------------------------------------