refactor: simplify project data retrieval and enhance OAuth settings handling

This commit is contained in:
Andras Bacsai 2025-10-26 23:39:40 +01:00
parent 385af3143b
commit 1ab4b9aa31
14 changed files with 158 additions and 187 deletions

View file

@ -18,20 +18,7 @@ class Index extends Component
public function mount()
{
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get()->map(function ($project) {
$project->settingsRoute = route('project.edit', ['project_uuid' => $project->uuid]);
$project->canUpdate = auth()->user()->can('update', $project);
$project->canCreateResource = auth()->user()->can('createAnyResource');
$firstEnvironment = $project->environments->first();
$project->addResourceRoute = $firstEnvironment
? route('project.resource.create', [
'project_uuid' => $project->uuid,
'environment_uuid' => $firstEnvironment->uuid,
])
: null;
return $project;
});
$this->projects = Project::ownedByCurrentTeam()->get();
$this->servers = Server::ownedByCurrentTeam()->count();
}
@ -39,11 +26,4 @@ public function render()
{
return view('livewire.project.index');
}
public function navigateToProject($projectUuid)
{
$project = collect($this->projects)->firstWhere('uuid', $projectUuid);
return $this->redirect($project->navigateTo(), navigate: false);
}
}

View file

@ -33,6 +33,8 @@ class GetLogs extends Component
public ?string $container = null;
public ?string $displayName = null;
public ?string $pull_request = null;
public ?bool $streamLogs = false;

View file

@ -7,7 +7,6 @@
use App\Models\Team;
use App\Support\ValidationPatterns;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection;
use Livewire\Attributes\Locked;
use Livewire\Component;
@ -39,25 +38,12 @@ class ByIp extends Component
public int $port = 22;
public bool $is_swarm_manager = false;
public bool $is_swarm_worker = false;
public $selected_swarm_cluster = null;
public bool $is_build_server = false;
#[Locked]
public Collection $swarm_managers;
public function mount()
{
$this->name = generate_random_name();
$this->private_key_id = $this->private_keys->first()?->id;
$this->swarm_managers = Server::isUsable()->get()->where('settings.is_swarm_manager', true);
if ($this->swarm_managers->count() > 0) {
$this->selected_swarm_cluster = $this->swarm_managers->first()->id;
}
}
protected function rules(): array
@ -72,9 +58,6 @@ protected function rules(): array
'ip' => 'required|string',
'user' => 'required|string',
'port' => 'required|integer|between:1,65535',
'is_swarm_manager' => 'required|boolean',
'is_swarm_worker' => 'required|boolean',
'selected_swarm_cluster' => 'nullable|integer',
'is_build_server' => 'required|boolean',
];
}
@ -94,11 +77,6 @@ protected function messages(): array
'port.required' => 'The Port field is required.',
'port.integer' => 'The Port field must be an integer.',
'port.between' => 'The Port field must be between 1 and 65535.',
'is_swarm_manager.required' => 'The Swarm Manager field is required.',
'is_swarm_manager.boolean' => 'The Swarm Manager field must be true or false.',
'is_swarm_worker.required' => 'The Swarm Worker field is required.',
'is_swarm_worker.boolean' => 'The Swarm Worker field must be true or false.',
'selected_swarm_cluster.integer' => 'The Swarm Cluster field must be an integer.',
'is_build_server.required' => 'The Build Server field is required.',
'is_build_server.boolean' => 'The Build Server field must be true or false.',
]);
@ -140,9 +118,6 @@ public function submit()
'team_id' => currentTeam()->id,
'private_key_id' => $this->private_key_id,
];
if ($this->is_swarm_worker) {
$payload['swarm_cluster'] = $this->selected_swarm_cluster;
}
if ($this->is_build_server) {
data_forget($payload, 'proxy');
}
@ -150,13 +125,6 @@ public function submit()
$server->proxy->set('status', 'exited');
$server->proxy->set('type', ProxyTypes::TRAEFIK->value);
$server->save();
if ($this->is_build_server) {
$this->is_swarm_manager = false;
$this->is_swarm_worker = false;
} else {
$server->settings->is_swarm_manager = $this->is_swarm_manager;
$server->settings->is_swarm_worker = $this->is_swarm_worker;
}
$server->settings->is_build_server = $this->is_build_server;
$server->settings->save();

View file

@ -29,7 +29,16 @@ public function mount()
return redirect()->route('home');
}
$this->oauth_settings_map = OauthSetting::all()->sortBy('provider')->reduce(function ($carry, $setting) {
$carry[$setting->provider] = $setting;
$carry[$setting->provider] = [
'id' => $setting->id,
'provider' => $setting->provider,
'enabled' => $setting->enabled,
'client_id' => $setting->client_id,
'client_secret' => $setting->client_secret,
'redirect_uri' => $setting->redirect_uri,
'tenant' => $setting->tenant,
'base_url' => $setting->base_url,
];
return $carry;
}, []);
@ -38,16 +47,48 @@ public function mount()
private function updateOauthSettings(?string $provider = null)
{
if ($provider) {
$oauth = $this->oauth_settings_map[$provider];
$oauthData = $this->oauth_settings_map[$provider];
$oauth = OauthSetting::find($oauthData['id']);
$oauth->fill([
'enabled' => $oauthData['enabled'],
'client_id' => $oauthData['client_id'],
'client_secret' => $oauthData['client_secret'],
'redirect_uri' => $oauthData['redirect_uri'],
'tenant' => $oauthData['tenant'],
'base_url' => $oauthData['base_url'],
]);
if (! $oauth->couldBeEnabled()) {
$oauth->update(['enabled' => false]);
throw new \Exception('OAuth settings are not complete for '.$oauth->provider.'.<br/>Please fill in all required fields.');
}
$oauth->save();
// Update the array with fresh data
$this->oauth_settings_map[$provider] = [
'id' => $oauth->id,
'provider' => $oauth->provider,
'enabled' => $oauth->enabled,
'client_id' => $oauth->client_id,
'client_secret' => $oauth->client_secret,
'redirect_uri' => $oauth->redirect_uri,
'tenant' => $oauth->tenant,
'base_url' => $oauth->base_url,
];
$this->dispatch('success', 'OAuth settings for '.$oauth->provider.' updated successfully!');
} else {
foreach (array_values($this->oauth_settings_map) as &$setting) {
$setting->save();
foreach (array_values($this->oauth_settings_map) as $settingData) {
$oauth = OauthSetting::find($settingData['id']);
$oauth->update([
'enabled' => $settingData['enabled'],
'client_id' => $settingData['client_id'],
'client_secret' => $settingData['client_secret'],
'redirect_uri' => $settingData['redirect_uri'],
'tenant' => $settingData['tenant'],
'base_url' => $settingData['base_url'],
]);
}
}
}

View file

@ -79,7 +79,7 @@
}">
<div class="flex lg:pt-6 pt-4 pb-4 pl-2">
<div class="flex flex-col w-full">
<div class="text-2xl font-bold tracking-wide dark:text-white">Coolify</div>
<a href="/" class="text-2xl font-bold tracking-wide dark:text-white hover:opacity-80 transition-opacity">Coolify</a>
<x-version />
</div>
<div>

View file

@ -47,7 +47,7 @@
<div class="sticky top-0 z-40 flex items-center justify-between px-4 py-4 gap-x-6 sm:px-6 lg:hidden bg-white/95 dark:bg-base/95 backdrop-blur-sm border-b border-neutral-300/50 dark:border-coolgray-200/50">
<div class="flex items-center gap-3 flex-shrink-0">
<div class="text-xl font-bold tracking-wide dark:text-white">Coolify</div>
<a href="/" class="text-xl font-bold tracking-wide dark:text-white hover:opacity-80 transition-opacity">Coolify</a>
<livewire:switch-team />
</div>
<button type="button" class="-m-2.5 p-2.5 dark:text-warning" x-on:click="open = !open">

View file

@ -1,19 +1,19 @@
<div>
<x-slot:title>
{{ data_get_str($project, 'name')->limit(10) }} > Edit | Coolify
</x-slot>
<form wire:submit='submit' class="flex flex-col pb-10">
<div class="flex gap-2">
<h1>Project: {{ data_get_str($project, 'name')->limit(15) }}</h1>
<div class="flex items-end gap-2">
<x-forms.button type="submit">Save</x-forms.button>
<livewire:project.delete-project :disabled="!$project->isEmpty()" :project_id="$project->id" />
</x-slot>
<form wire:submit='submit' class="flex flex-col pb-10">
<div class="flex gap-2">
<h1>{{ data_get_str($project, 'name')->limit(15) }}</h1>
<div class="flex items-end gap-2">
<x-forms.button type="submit">Save</x-forms.button>
<livewire:project.delete-project :disabled="!$project->isEmpty()" :project_id="$project->id" />
</div>
</div>
</div>
<div class="pt-2 pb-10">Edit project details here.</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="name" />
<x-forms.input label="Description" id="description" />
</div>
</form>
</div>
<div class="pt-2 pb-10">Edit project details here.</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="name" />
<x-forms.input label="Description" id="description" />
</div>
</form>
</div>

View file

@ -11,29 +11,38 @@
@endcan
</div>
<div class="subtitle">All your projects are here.</div>
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2 -mt-1" x-data="{ projects: @js($projects) }">
<template x-for="project in projects" :key="project.uuid">
<div class="box group cursor-pointer" @click="$wire.navigateToProject(project.uuid)">
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2 -mt-1">
@foreach ($projects as $project)
<div class="relative gap-2 cursor-pointer box group">
<a href="{{ $project->navigateTo() }}" class="absolute inset-0"></a>
<div class="flex flex-1 mx-6">
<div class="flex flex-col justify-center flex-1">
<div class="box-title" x-text="project.name"></div>
<div class="box-title">{{ $project->name }}</div>
<div class="box-description">
<div x-text="project.description"></div>
{{ $project->description }}
</div>
</div>
<div class="relative z-10 flex items-center justify-center gap-4 text-xs font-bold"
x-show="project.canUpdate || project.canCreateResource">
<a class="hover:underline" wire:click.stop x-show="project.addResourceRoute"
:href="project.addResourceRoute">
+ Add Resource
</a>
<a class="hover:underline" wire:click.stop x-show="project.canUpdate"
:href="`/project/${project.uuid}/edit`">
Settings
</a>
<div class="relative z-10 flex items-center justify-center gap-4 text-xs font-bold">
@if ($project->environments->first())
@can('createAnyResource')
<a class="hover:underline"
href="{{ route('project.resource.create', [
'project_uuid' => $project->uuid,
'environment_uuid' => $project->environments->first()->uuid,
]) }}">
+ Add Resource
</a>
@endcan
@endif
@can('update', $project)
<a class="hover:underline"
href="{{ route('project.edit', ['project_uuid' => $project->uuid]) }}">
Settings
</a>
@endcan
</div>
</div>
</div>
</template>
@endforeach
</div>
</div>

View file

@ -34,7 +34,9 @@
}
}">
<div class="flex gap-2 items-center">
@if ($resource?->type() === 'application' || str($resource?->type())->startsWith('standalone'))
@if ($displayName)
<h4>{{ $displayName }}</h4>
@elseif ($resource?->type() === 'application' || str($resource?->type())->startsWith('standalone'))
<h4>{{ $container }}</h4>
@else
<h4>{{ str($container)->beforeLast('-')->headline() }}</h4>

View file

@ -31,45 +31,9 @@ class="font-bold underline" target="_blank"
helper="Build servers are used to build your applications, so you cannot deploy applications to it."
label="Use it as a build server?" />
</div>
<div class="">
<h3 class="pt-6">Swarm <span class="text-xs text-neutral-500">(experimental)</span></h3>
<div class="pb-4">Read the docs <a class='underline dark:text-white'
href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>.</div>
@if ($is_swarm_worker || $is_build_server)
<x-forms.checkbox disabled instantSave type="checkbox" id="is_swarm_manager"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@else
<x-forms.checkbox type="checkbox" instantSave id="is_swarm_manager"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@endif
@if ($is_swarm_manager || $is_build_server)
<x-forms.checkbox disabled instantSave type="checkbox" id="is_swarm_worker"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" />
@else
<x-forms.checkbox type="checkbox" instantSave id="is_swarm_worker"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" />
@endif
@if ($is_swarm_worker && count($swarm_managers) > 0)
<div class="py-4">
<x-forms.select label="Select a Swarm Cluster" id="selected_swarm_cluster" required>
@foreach ($swarm_managers as $server)
@if ($loop->first)
<option selected value="{{ $server->id }}">{{ $server->name }}</option>
@else
<option value="{{ $server->id }}">{{ $server->name }}</option>
@endif
@endforeach
</x-forms.select>
</div>
@endif
</div>
<x-forms.button type="submit">
Continue
</x-forms.button>
</form>
@endif
</div>
</div>

View file

@ -7,7 +7,7 @@
<x-server.sidebar-proxy :server="$server" :parameters="$parameters" />
<div class="w-full">
<h2 class="pb-4">Logs</h2>
<livewire:project.shared.get-logs :server="$server" container="coolify-proxy" />
<livewire:project.shared.get-logs :server="$server" container="coolify-proxy" displayName="Coolify Proxy" />
</div>
</div>
</div>

View file

@ -56,48 +56,53 @@
step2ButtonText="Update All
Packages" />
</div>
<table>
<thead>
<tr>
<th>Package</th>
@if ($packageManager !== 'dnf')
<th>Current Version</th>
@endif
<th>New Version</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@foreach ($updates as $update)
<div class="overflow-x-auto">
<table class="min-w-full">
<thead>
<tr>
<td class="inline-flex gap-2 justify-center items-center">
@if (data_get_str($update, 'package')->contains('docker') || data_get_str($update, 'package')->contains('kernel'))
<x-helper :helper="'This package will restart your currently running containers'">
<x-slot:icon>
<svg class="w-4 h-4 text-red-500 block"
viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16">
</path>
</svg>
</x-slot:icon>
</x-helper>
@endif
{{ data_get($update, 'package') }}
</td>
@if ($packageManager !== 'dnf')
<td>{{ data_get($update, 'current_version') }}</td>
@endif
<td>{{ data_get($update, 'new_version') }}</td>
<td>
<x-forms.button type="button"
wire:click="$dispatch('updatePackage', { package: '{{ data_get($update, 'package') }}' })">Update</x-forms.button>
</td>
<th>Package</th>
<th>Version</th>
<th>Action</th>
</tr>
@endforeach
</tbody>
</table>
</thead>
<tbody>
@foreach ($updates as $update)
<tr>
<td>
<div class="flex gap-2 items-center">
@if (data_get_str($update, 'package')->contains('docker') || data_get_str($update, 'package')->contains('kernel'))
<x-helper :helper="'This package will restart your currently running containers'">
<x-slot:icon>
<svg class="w-4 h-4 text-red-500 block flex-shrink-0"
viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16">
</path>
</svg>
</x-slot:icon>
</x-helper>
@endif
<span class="break-all">{{ data_get($update, 'package') }}</span>
</div>
</td>
<td class="whitespace-nowrap">
<div class="flex gap-1 items-center">
<span>{{ data_get($update, 'new_version') }}</span>
@if ($packageManager !== 'dnf' && data_get($update, 'current_version'))
<x-helper helper="Current: {{ data_get($update, 'current_version') }}" />
@endif
</div>
</td>
<td class="whitespace-nowrap">
<x-forms.button type="button"
wire:click="$dispatch('updatePackage', { package: '{{ data_get($update, 'package') }}' })">Update</x-forms.button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
@endif
</div>

View file

@ -337,7 +337,7 @@ class="w-full input opacity-50 cursor-not-allowed"
<x-slot:title>Sentinel Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server"
container="coolify-sentinel" lazy />
container="coolify-sentinel" displayName="Sentinel" lazy />
</x-slot:content>
<x-forms.button @click="slideOverOpen=true"
:disabled="$isValidating">Logs</x-forms.button>
@ -353,7 +353,7 @@ class="w-full input opacity-50 cursor-not-allowed"
<x-slot:title>Sentinel Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server"
container="coolify-sentinel" lazy />
container="coolify-sentinel" displayName="Sentinel" lazy />
</x-slot:content>
<x-forms.button @click="slideOverOpen=true"
:disabled="$isValidating">Logs</x-forms.button>

View file

@ -16,33 +16,33 @@
<div class="flex flex-col gap-2 pt-4">
@foreach ($oauth_settings_map as $oauth_setting)
<div class="p-4 border dark:border-coolgray-300 border-neutral-200">
<h3>{{ ucfirst($oauth_setting->provider) }}</h3>
<h3>{{ ucfirst($oauth_setting['provider']) }}</h3>
<div class="w-32">
<x-forms.checkbox instantSave="instantSave('{{ $oauth_setting->provider }}')"
id="oauth_settings_map.{{ $oauth_setting->provider }}.enabled" label="Enabled" />
<x-forms.checkbox instantSave="instantSave('{{ $oauth_setting['provider'] }}')"
id="oauth_settings_map.{{ $oauth_setting['provider'] }}.enabled" label="Enabled" />
</div>
<div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.client_id"
<x-forms.input id="oauth_settings_map.{{ $oauth_setting['provider'] }}.client_id"
label="Client ID" />
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.client_secret"
<x-forms.input id="oauth_settings_map.{{ $oauth_setting['provider'] }}.client_secret"
type="password" label="Client Secret" autocomplete="new-password" />
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.redirect_uri"
placeholder="{{ route('auth.callback', $oauth_setting->provider) }}" label="Redirect URI" />
@if ($oauth_setting->provider == 'azure')
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.tenant"
<x-forms.input id="oauth_settings_map.{{ $oauth_setting['provider'] }}.redirect_uri"
placeholder="{{ route('auth.callback', $oauth_setting['provider']) }}" label="Redirect URI" />
@if ($oauth_setting['provider'] == 'azure')
<x-forms.input id="oauth_settings_map.{{ $oauth_setting['provider'] }}.tenant"
label="Tenant" />
@endif
@if ($oauth_setting->provider == 'google')
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.tenant"
@if ($oauth_setting['provider'] == 'google')
<x-forms.input id="oauth_settings_map.{{ $oauth_setting['provider'] }}.tenant"
helper="Optional parameter that supplies a hosted domain (HD) to Google, which<br>triggers a login hint to be displayed on the OAuth screen with this domain.<br><br><a class='underline dark:text-warning text-coollabs' href='https://developers.google.com/identity/openid-connect/openid-connect#hd-param' target='_blank'>Google Documentation</a>"
label="Tenant" />
@endif
@if (
$oauth_setting->provider == 'authentik' ||
$oauth_setting->provider == 'clerk' ||
$oauth_setting->provider == 'zitadel' ||
$oauth_setting->provider == 'gitlab')
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.base_url"
$oauth_setting['provider'] == 'authentik' ||
$oauth_setting['provider'] == 'clerk' ||
$oauth_setting['provider'] == 'zitadel' ||
$oauth_setting['provider'] == 'gitlab')
<x-forms.input id="oauth_settings_map.{{ $oauth_setting['provider'] }}.base_url"
label="Base URL" />
@endif
</div>