- Removed the old `connected-apps.create` implementation and components, simplifying the codebase. - Introduced modular OIDC integration using reusable components and traits, such as `WithSearchableChoices` and `WithRepeaterFields`. - Added `StoreOIDCAppForm` for handling OpenID Connect app-specific fields and validation. - Updated `choices.blade.php` and related components to leverage configuration-driven, dynamic multi-select functionality. - Updated routes to support new OIDC app creation under distinct connection protocol namespaces. - Enhanced app creation flow with support for dynamic fields like URIs (`repeater-input`) and scoped user roles.
197 lines
7.0 KiB
PHP
197 lines
7.0 KiB
PHP
<?php
|
|
|
|
use App\Concerns\HandlesOperations;
|
|
use App\Data\ConnectedApp\{ConnectAppRequest, ConnectionStatusData};
|
|
use App\Enums\{ApplicationTypeEnum, ConnectionProtocolEnum, ConnectionStatusEnum, UserAccessTypeEnum};
|
|
use App\Livewire\Forms\StoreOIDCAppForm;
|
|
use App\Livewire\Concerns\Choices\{ChoiceField, WithSearchableChoices};
|
|
use App\Livewire\Concerns\WithRepeaterFields;
|
|
use App\Models\{ConnectionProtocol, ConnectionProvider, ConnectionStatus};
|
|
use App\Services\{ConnectedAppService,
|
|
ConnectionProtocolService,
|
|
ConnectionProviderService,
|
|
ConnectionStatusService,
|
|
RoleService
|
|
};
|
|
use Illuminate\Support\Collection;
|
|
use Livewire\Attributes\{Computed, Title, Url};
|
|
use Livewire\Component;
|
|
use Livewire\WithFileUploads;
|
|
use Mary\Traits\Toast;
|
|
use Spatie\LaravelData\DataCollection;
|
|
|
|
new #[Title("Create a service")]
|
|
class extends Component {
|
|
use HandlesOperations, WithRepeaterFields, WithSearchableChoices, WithFileUploads;
|
|
|
|
#[Url]
|
|
public string $protocol = '';
|
|
|
|
#[Url]
|
|
public ?string $type = null;
|
|
|
|
public StoreOIDCAppForm $form;
|
|
public ConnectionProtocolEnum $connectionProtocol = ConnectionProtocolEnum::OIDC;
|
|
public ?ApplicationTypeEnum $applicationType = null;
|
|
private RoleService $roleService;
|
|
|
|
public function boot(RoleService $roleService): void
|
|
{
|
|
$this->roleService = $roleService;
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->applicationType = ApplicationTypeEnum::tryFrom($this->type);
|
|
}
|
|
|
|
public function save(bool $goBack = true): void
|
|
{
|
|
$this->form->validate();
|
|
ds($this->form->pull());
|
|
}
|
|
|
|
|
|
public function goBack(): void
|
|
{
|
|
$this->redirectRoute("apps.index", navigate: true);
|
|
}
|
|
|
|
public function title(): string
|
|
{
|
|
$title = "New ";
|
|
$title .= match ($this->applicationType) {
|
|
ApplicationTypeEnum::Web => "Web Application ",
|
|
ApplicationTypeEnum::SPA => "Single Page Application ",
|
|
ApplicationTypeEnum::Machine => "Microservice Application ",
|
|
null => ""
|
|
};
|
|
$title .= "Integration";
|
|
return $title;
|
|
}
|
|
|
|
public function searchRoles(string $q = ''): void
|
|
{
|
|
$this->searchChoice('roles', $q);
|
|
}
|
|
|
|
public function selectAllRoles(): void
|
|
{
|
|
$this->selectAllChoice('roles');
|
|
}
|
|
|
|
public function clearRoles(): void
|
|
{
|
|
$this->clearChoice('roles');
|
|
}
|
|
|
|
protected function choiceFields(): array
|
|
{
|
|
return [
|
|
'roles' => new ChoiceField(
|
|
search: fn(string $q): array => $this->roleService->searchRolesForSelect($q)->toArray(),
|
|
allIds: $this->roleService->getAllRoleIds(...),
|
|
formPath: 'form.roleIds',
|
|
),
|
|
];
|
|
}
|
|
};
|
|
?>
|
|
|
|
<div>
|
|
<x-shared.card :title="$this->title()" icon="lucide-package-plus">
|
|
<x-slot:actions>
|
|
<x-mary-button wire:click="goBack" icon="lucide.corner-up-left" class="btn-ghost">Go Back
|
|
</x-mary-button>
|
|
</x-slot:actions>
|
|
<x-mary-form wire:submit="save" class="p-4 gap-y-8">
|
|
<!-- General settings start -->
|
|
<div class="flex gap-4 flex-col w-full md:flex-row">
|
|
<article class="w-full md:w-2/5">
|
|
<h2 class="font-bold">General Settings</h2>
|
|
</article>
|
|
<div class="w-full md:w-3/5">
|
|
<x-mary-input required label="App Name" wire:model="form.name" placeholder="Keka, MS 365 etc."
|
|
hint="A basic name that matches the actual application"/>
|
|
<x-mary-file wire:model="form.logo" label="Logo" hint="Optional"
|
|
accept="application/jpg, application/png, image/jpeg, image/png"/>
|
|
|
|
</div>
|
|
</div>
|
|
<!-- General settings end -->
|
|
|
|
<!-- Sign-in uris start -->
|
|
<hr>
|
|
<div class="flex gap-4 flex-col w-full md:flex-row">
|
|
<article class="w-full md:w-2/5">
|
|
<h2 class="font-bold">Sign In Redirect URIs</h2>
|
|
<p class="text-sm text-gray-500 max-w-70 mt-4">
|
|
{{config('app.name')}} sends response and ID Token back to these URIs.
|
|
</p>
|
|
</article>
|
|
<div class="w-full md:w-3/5">
|
|
<x-shared.repeater-input model="form.signInUris" :items="$form->signInUris"/>
|
|
</div>
|
|
</div>
|
|
<!-- Sign-in uris end -->
|
|
|
|
<!-- Sign-out uris start -->
|
|
<hr>
|
|
<div class="flex gap-4 flex-col w-full md:flex-row">
|
|
<article class="w-full md:w-2/5">
|
|
<h2 class="font-bold">Sign out Redirect URIs</h2>
|
|
<p class="text-sm text-gray-500 max-w-70 mt-4">
|
|
{{config('app.name')}} sends back the user to these URIs after logout.
|
|
</p>
|
|
</article>
|
|
<div class="w-full md:w-3/5">
|
|
<x-shared.repeater-input model="form.signOutUris" :items="$form->signOutUris"/>
|
|
</div>
|
|
</div>
|
|
<!-- Sign-out uris end -->
|
|
|
|
<!-- Access start -->
|
|
<hr>
|
|
<div class="flex gap-4 flex-col w-full md:flex-row">
|
|
<article class="w-full md:w-2/5">
|
|
<h2 class="font-bold">User Access</h2>
|
|
<p class="text-sm text-gray-500 max-w-70 mt-4">
|
|
Choose whether to allow all users be able to connect this app or selected ones.
|
|
</p>
|
|
</article>
|
|
<div class="w-full md:w-3/5">
|
|
<x-mary-radio
|
|
:options="UserAccessTypeEnum::asOptions()"
|
|
option-value="value"
|
|
wire:model.change.live="form.userAccessOption"
|
|
/>
|
|
<div class=""
|
|
x-show="$wire.form.userAccessOption === '{{UserAccessTypeEnum::Role->value}}'"
|
|
x-cloak
|
|
>
|
|
<x-shared.choices
|
|
wire:model.live="form.roleIds"
|
|
:options="$choicesSearchable['roles'] ?? []"
|
|
search-function="searchRoles"
|
|
select-all-action="selectAllRoles"
|
|
clear-action="clearRoles"
|
|
label="Roles"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<!-- Access end -->
|
|
|
|
<div class="flex gap-x-4 font-medium justify-end">
|
|
<x-mary-button wire:click.prevent="goBack" spinner="goBack">Cancel</x-mary-button>
|
|
<x-mary-button wire:click.prevent="save(false)">Save & Add Another</x-mary-button>
|
|
<x-mary-button type="submit"
|
|
class="btn-primary">
|
|
Save
|
|
</x-mary-button>
|
|
</div>
|
|
</x-mary-form>
|
|
</x-shared.card>
|
|
</div>
|