fix(ui): simplify GitLab source setup view

Use the red incomplete-setup alert like GitHub, keep name + OAuth
credentials front-and-center, and tuck GitLab URL / API / SSH / system
wide options into an Advanced accordion for self-hosted users.
This commit is contained in:
Andras Bacsai
2026-07-21 20:59:09 +02:00
parent 418287d511
commit fda8e91396
3 changed files with 166 additions and 37 deletions
+15
View File
@@ -59,6 +59,8 @@ class Change extends Component
public ?string $oauthState = null;
private bool $shouldDeriveApiUrlAfterHtmlUrlUpdate = false;
protected function rules(): array
{
return [
@@ -76,6 +78,19 @@ class Change extends Component
];
}
public function updatingHtmlUrl(): void
{
$this->shouldDeriveApiUrlAfterHtmlUrlUpdate = blank($this->apiUrl)
|| $this->apiUrl === rtrim($this->htmlUrl, '/').'/api/v4';
}
public function updatedHtmlUrl(): void
{
if ($this->shouldDeriveApiUrlAfterHtmlUrlUpdate) {
$this->apiUrl = rtrim($this->htmlUrl, '/').'/api/v4';
}
}
public function mount()
{
try {
@@ -29,28 +29,67 @@
</div>
<div class="flex flex-col gap-2">
<x-forms.input canGate="update" :canResource="$gitlab_app" id="name" label="Name" />
<div class="flex gap-2">
<x-forms.input canGate="update" :canResource="$gitlab_app" id="htmlUrl" label="GitLab URL" />
<x-forms.input canGate="update" :canResource="$gitlab_app" id="apiUrl" label="API URL" />
</div>
<x-forms.input canGate="update" :canResource="$gitlab_app" id="groupName" label="Group Name"
helper="Comma-separated group names to filter visible repositories." />
<div class="flex gap-2">
<x-forms.input canGate="update" :canResource="$gitlab_app" id="customUser" label="SSH User" />
<x-forms.input canGate="update" :canResource="$gitlab_app" type="number" id="customPort" label="SSH Port" />
</div>
@if (!isCloud())
<div class="w-48">
<x-forms.checkbox canGate="update" :canResource="$gitlab_app" label="System Wide?"
<x-forms.checkbox canGate="update" :canResource="$gitlab_app" label="System Wide"
helper="If checked, this GitLab App will be available for everyone in this Coolify instance."
instantSave id="isSystemWide" />
</div>
@if ($isSystemWide)
<x-callout type="warning" title="Not Recommended">
System-wide GitLab Apps are shared across all teams on this Coolify instance. This means any team
can use this GitLab App to deploy applications from your repositories. For better security and
isolation, it's recommended to create team-specific GitLab Apps instead.
</x-callout>
@endif
@endif
<h3 class="pt-4">OAuth Credentials</h3>
<x-forms.input canGate="update" :canResource="$gitlab_app" id="clientId" label="Application ID" />
<x-forms.input canGate="update" :canResource="$gitlab_app" id="clientSecretInput" label="Application Secret" type="password" />
<div x-data="{
activeAccordion: '',
setActiveAccordion(id) {
this.activeAccordion = (this.activeAccordion == id) ? '' : id
}
}" class="relative w-full py-2 mx-auto overflow-hidden text-sm font-normal rounded-md">
<div x-data="{ id: $id('accordion') }" class="cursor-pointer">
<button @click="setActiveAccordion(id)"
class="flex items-center justify-between w-full px-1 py-2 text-left select-none dark:hover:text-white hover:bg-white/5"
type="button">
<h4>Advanced / Self-hosted</h4>
<svg class="w-4 h-4 duration-200 ease-out" :class="{ 'rotate-180': activeAccordion == id }"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<div x-show="activeAccordion==id" x-collapse x-cloak class="px-2">
<div class="flex flex-col gap-2 pt-0 opacity-70">
<div class="flex gap-2">
<x-forms.input canGate="update" :canResource="$gitlab_app" id="htmlUrl" label="GitLab URL" />
<x-forms.input canGate="update" :canResource="$gitlab_app" id="apiUrl" label="API URL" />
</div>
<div class="flex gap-2">
<x-forms.input canGate="update" :canResource="$gitlab_app" id="customUser" label="SSH User" />
<x-forms.input canGate="update" :canResource="$gitlab_app" type="number" id="customPort" label="SSH Port" />
</div>
<div class="flex gap-2">
<x-forms.select canGate="update" :canResource="$gitlab_app" id="privateKeyId" label="SSH Private Key (optional)">
<option value="">None</option>
@foreach ($privateKeys as $key)
<option value="{{ $key->id }}">{{ $key->name }}</option>
@endforeach
</x-forms.select>
</div>
</div>
</div>
</div>
</div>
<h3 class="pt-4">Webhook</h3>
<div class="flex flex-col gap-1">
<div class="text-sm">
@@ -63,19 +102,6 @@
helper="Set this same token in your GitLab webhook's 'Secret token' field." />
</div>
<h3 class="pt-4">SSH Key (Optional)</h3>
<div class="text-sm text-neutral-500 dark:text-neutral-400">
Only needed if you prefer SSH-based git clone over HTTPS OAuth token.
</div>
<div class="flex gap-2">
<x-forms.select canGate="update" :canResource="$gitlab_app" id="privateKeyId" label="Private Key">
<option value="">None</option>
@foreach ($privateKeys as $key)
<option value="{{ $key->id }}">{{ $key->name }}</option>
@endforeach
</x-forms.select>
</div>
@if ($applications->count() > 0)
<h3 class="pt-4">Applications Using This Source</h3>
<div class="flex flex-col gap-2">
@@ -112,13 +138,13 @@
</div>
<div class="subtitle">Connect your GitLab instance to deploy private repositories.</div>
<div class="mb-6 rounded-sm alert-warning">
<div class="mb-6 rounded-sm alert-error">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<span>Complete the setup below to connect this GitLab source.</span>
<span>You must complete this step before you can use this source!</span>
</div>
<div class="flex flex-col gap-4">
@@ -140,24 +166,53 @@
<h3 class="pt-2">Step 2: Enter the credentials</h3>
<form wire:submit='submit' class="flex flex-col gap-2">
<x-forms.input id="name" label="Name" />
<div class="flex gap-2">
<x-forms.input id="htmlUrl" label="GitLab URL" />
<x-forms.input id="apiUrl" label="API URL" />
</div>
<x-forms.input id="clientId" label="Application ID" required
helper="The Application ID from your GitLab OAuth Application." />
<x-forms.input id="clientSecretInput" label="Application Secret" type="password" required
helper="The Secret from your GitLab OAuth Application." />
<x-forms.input id="groupName" label="Group Name"
helper="Optional. Comma-separated group names to filter repositories." />
<div class="flex gap-2">
<x-forms.input id="customUser" label="SSH User" />
<x-forms.input type="number" id="customPort" label="SSH Port" />
<div x-data="{
activeAccordion: '',
setActiveAccordion(id) {
this.activeAccordion = (this.activeAccordion == id) ? '' : id
}
}" class="relative w-full py-2 mx-auto overflow-hidden text-sm font-normal rounded-md">
<div x-data="{ id: $id('accordion') }" class="cursor-pointer">
<button @click="setActiveAccordion(id)"
class="flex items-center justify-between w-full px-1 py-2 text-left select-none dark:hover:text-white hover:bg-white/5"
type="button">
<h4>Advanced / Self-hosted</h4>
<svg class="w-4 h-4 duration-200 ease-out" :class="{ 'rotate-180': activeAccordion == id }"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<div x-show="activeAccordion==id" x-collapse x-cloak class="px-2">
<div class="flex flex-col gap-2 pt-0 opacity-70">
<div class="flex gap-2">
<x-forms.input id="htmlUrl" label="GitLab URL"
helper="Only change this for self-hosted GitLab (e.g. https://gitlab.example.com)." />
<x-forms.input id="apiUrl" label="API URL"
helper="Usually your GitLab URL with /api/v4 appended." />
</div>
<x-forms.input id="groupName" label="Group Name"
helper="Optional. Comma-separated group names to filter repositories." />
<div class="flex gap-2">
<x-forms.input id="customUser" label="SSH User" />
<x-forms.input type="number" id="customPort" label="SSH Port" />
</div>
@if (!isCloud())
<div class="w-48">
<x-forms.checkbox label="System Wide" id="isSystemWide"
helper="If checked, this GitLab App will be available for everyone in this Coolify instance." />
</div>
@endif
</div>
</div>
</div>
</div>
@if (!isCloud())
<x-forms.checkbox label="System Wide?" id="isSystemWide"
helper="If checked, this GitLab App will be available for everyone in this Coolify instance." />
@endif
<x-forms.button type="submit" class="mt-2">Save Credentials</x-forms.button>
</form>
@@ -0,0 +1,59 @@
<?php
use App\Livewire\Source\Gitlab\Change;
use App\Models\GitlabApp;
use App\Models\InstanceSettings;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->team = Team::factory()->create();
$this->user = User::factory()->create();
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
$this->actingAs($this->user);
session(['currentTeam' => $this->team]);
InstanceSettings::forceCreate([
'id' => 0,
'fqdn' => null,
'public_ipv4' => null,
'public_ipv6' => null,
]);
$this->gitlabApp = GitlabApp::create([
'name' => 'Self-hosted GitLab',
'api_url' => 'https://gitlab.com/api/v4',
'html_url' => 'https://gitlab.com',
'custom_user' => 'git',
'custom_port' => 22,
'team_id' => $this->team->id,
'is_system_wide' => false,
'is_public' => false,
]);
});
describe('GitLab source setup view', function () {
test('shows red incomplete-setup alert and keeps advanced fields collapsed', function () {
Livewire::withQueryParams(['gitlab_app_uuid' => $this->gitlabApp->uuid])
->test(Change::class)
->assertSee('You must complete this step before you can use this source!')
->assertSeeHtml('alert-error')
->assertSee('Advanced / Self-hosted')
->assertSee('Application ID')
->assertSee('Application Secret')
->assertSee('Save Credentials')
->assertDontSee('alert-warning');
});
test('derives api url when gitlab url changes', function () {
Livewire::withQueryParams(['gitlab_app_uuid' => $this->gitlabApp->uuid])
->test(Change::class)
->set('htmlUrl', 'https://gitlab.example.com')
->assertSet('apiUrl', 'https://gitlab.example.com/api/v4');
});
});