Skip to content

Tenant switcher #5

@vpratfr

Description

@vpratfr

I don't really have the time to submit a proper PR, but here is what it takes to make a tenant switcher for Nova.

My use cas is the following:

I needed that:

  • Nova only shows "system" resources when viewing the admin tenant
  • Nova only shows "tenant" resources when virtually switched to a client tenant

New Middleware

namespace App\Nova\Middleware;

use Closure;
use Domain\CustomerAccounts\Models\Website;
use Hyn\Tenancy\Environment;

class TenantSwitcherMiddleware
{

    /** @var \Hyn\Tenancy\Environment */
    private $environment;

    public function __construct(Environment $environment)
    {
        $this->environment = $environment;
    }

    public function handle($request, Closure $next)
    {
        $currentTenant = Website::find(session()->get('nova.tenant', 0))
                         ?? Website::whereUuid(Website::ADMIN_TENANT_UUID)->first();

        $this->environment->tenant($currentTenant);
        $this->environment->hostname($currentTenant->hostname);
        
        config()->set('tenancy.hostname.auto-identification', false);

        return $next($request);
    }
}

New view for the switcher dropdown

Create a new file resources/views/vendor/nova/partials/tenant-switcher.blade.php

@php($currentWebsite = app(Hyn\Tenancy\Environment::class)->website())
@php($websites =  Domain\CustomerAccounts\Models\Website::get())

<dropdown-trigger class="h-9 flex items-center" slot-scope="{toggle}" :handle-click="toggle">
    <span class="text-90">
        @if($currentWebsite->is_admin_tenant)
            Switch tenant...
        @else
            {{-- We have an extra customer table for billing, could use hostname->first()->fqdn instead of customer->name --}}
            {{ $currentWebsite->customer->name ?? $currentWebsite->uuid }}
        @endif
    </span>
</dropdown-trigger>

<dropdown-menu slot="menu" width="200" direction="rtl">
    <ul class="list-reset">
        <li>
            <a href="{{ route('web.nova.tenant.switch') }}" class="block no-underline text-90 hover:bg-30 p-3">
                Admin tenant
            </a>
        </li>
        @foreach($websites->reject->is_admin_tenant as $website)
        <li>
            <a href="{{ route('web.nova.tenant.switch', $website) }}" class="block no-underline text-90 hover:bg-30 p-3">
                {{ $website->customer->name ?? $website->uuid }} 
            </a>
        </li>
        @endforeach
    </ul>
</dropdown-menu>

New controller to make the switch

namespace App\Nova\Web\Controllers;

use App\Shared\Http\Controllers\Controller;
use Domain\CustomerAccounts\Models\Website;
use Illuminate\Http\Request;

class TenantController extends Controller
{
    public function switchTo(Request $request, ?Website $website = null)
    {
        session()->put('nova.tenant', $website->id ?? 0);
        return \redirect()->back();
    }
}

New route to that controller

You should add that route so that it is only accessible to the admin middleware. We have automatted

Route::domain('admin.' . env('APP_DOMAIN'))->prefix('/nova')->group(function () {
    Route::get('/switch-tenant/{website?}')->name('tenant.switch')->uses([TenantController::class, 'switchTo']);
});

Nova configuration

Add our manual tenant switcher middleware as last item in the nova configuration entry: nova.middleware

Nova layout

We need to change the Nova default layout in order to include our tenant switcher dropdown in the top bar.

Copy vendor/laravel/nova/resources/views/layout.blade.php to resources/views/vendor/nova/layout.blade.php

Then you can replace:

<dropdown class="ml-auto h-9 flex items-center dropdown-right">
    @include('nova::partials.user')
</dropdown>

with

<div class="ml-auto flex">
    <dropdown class="h-9 items-center dropdown-right">
        @include('nova::partials.tenant-switcher')
    </dropdown>

    <dropdown class="h-9 ml-6 items-center dropdown-right">
        @include('nova::partials.user')
    </dropdown>
</div>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions