fix(ui): align GitLab App create modal with GitHub

Match the GitHub create modal layout: intro copy, name/group row,
system-wide warning, self-hosted accordion (URL/API/SSH), and a
bottom Continue button instead of a duplicate header Save.
This commit is contained in:
Andras Bacsai
2026-07-21 20:54:59 +02:00
parent 6f557cf17f
commit 418287d511
3 changed files with 156 additions and 18 deletions
+41 -5
View File
@@ -6,6 +6,7 @@ use App\Models\GitlabApp;
use App\Rules\SafeExternalUrl;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Livewire\Component;
class Create extends Component
@@ -16,34 +17,62 @@ class Create extends Component
public string $html_url = 'https://gitlab.com';
public string $api_url = 'https://gitlab.com/api/v4';
public string $custom_user = 'git';
public int $custom_port = 22;
public bool $is_system_wide = false;
public ?string $group_name = null;
private bool $shouldDeriveApiUrlAfterHtmlUrlUpdate = false;
public function mount()
{
$this->name = substr(generate_random_name(), 0, 30);
}
public function updatingHtmlUrl(): void
{
$this->shouldDeriveApiUrlAfterHtmlUrlUpdate = blank($this->api_url)
|| $this->api_url === $this->gitlabApiUrlFromHtmlUrl($this->html_url);
}
public function updatedHtmlUrl(): void
{
if ($this->shouldDeriveApiUrlAfterHtmlUrlUpdate) {
$this->api_url = $this->gitlabApiUrlFromHtmlUrl($this->html_url);
}
}
public function createGitLabApp()
{
try {
$this->authorize('createAnyResource');
$this->html_url = rtrim($this->html_url, '/');
$this->api_url = filled($this->api_url)
? rtrim($this->api_url, '/')
: $this->gitlabApiUrlFromHtmlUrl($this->html_url);
$this->validate([
'name' => 'required|string',
'html_url' => ['required', 'string', 'url', new SafeExternalUrl],
'api_url' => ['required', 'string', 'url', new SafeExternalUrl],
'custom_user' => 'required|string',
'custom_port' => 'required|int',
'is_system_wide' => 'required|bool',
'group_name' => 'nullable|string',
]);
$htmlUrl = rtrim($this->html_url, '/');
$apiUrl = $htmlUrl.'/api/v4';
$gitlab_app = GitlabApp::create([
'name' => $this->name,
'api_url' => $apiUrl,
'html_url' => $htmlUrl,
'api_url' => $this->api_url,
'html_url' => $this->html_url,
'custom_user' => $this->custom_user,
'custom_port' => $this->custom_port,
'is_system_wide' => $this->is_system_wide,
'group_name' => $this->group_name,
'webhook_token' => Str::random(32),
@@ -55,8 +84,15 @@ class Create extends Component
}
return redirectRoute($this, 'source.gitlab.show', ['gitlab_app_uuid' => $gitlab_app->uuid]);
} catch (ValidationException $e) {
throw $e;
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
private function gitlabApiUrlFromHtmlUrl(string $htmlUrl): string
{
return rtrim($htmlUrl, '/').'/api/v4';
}
}
@@ -1,18 +1,70 @@
<div>
<form class="flex flex-col gap-2" wire:submit='createGitLabApp'>
@can('createAnyResource')
<form wire:submit='createGitLabApp' class="flex flex-col w-full gap-2">
<div class="pb-2">This is required if you would like to get full integration (deployments from
private repositories, webhooks, etc) with GitLab.</div>
<div class="flex gap-2">
<h2>New GitLab App</h2>
<x-forms.button type="submit">Save</x-forms.button>
<x-forms.input id="name" label="Name" required />
<x-forms.input id="group_name" label="Group Name"
helper="Optional. Comma-separated group names to filter repositories (e.g., myorg,myteam)."
placeholder="If empty, all accessible repositories are listed." />
</div>
<div class="subtitle">Add a self-hosted or GitLab.com instance as a source for your applications.</div>
<x-forms.input id="name" label="Name" required />
<x-forms.input id="html_url" label="GitLab URL" required
helper="For self-hosted GitLab, enter your instance URL (e.g., https://gitlab.example.com)." />
<x-forms.input id="group_name" label="Group Name"
helper="Optional. Comma-separated group names to filter repositories (e.g., myorg,myteam)." />
@if (!isCloud())
<x-forms.checkbox label="System Wide?" id="is_system_wide"
helper="If checked, this GitLab App will be available for everyone in this Coolify instance." />
<div x-data="{ showWarning: @entangle('is_system_wide') }">
<div class="w-48">
<x-forms.checkbox id="is_system_wide" label="System Wide"
helper="If checked, this GitLab App will be available for everyone in this Coolify instance." />
</div>
<div x-show="showWarning" x-transition x-cloak class="w-full max-w-2xl mx-auto pt-2">
<x-callout type="warning" title="Not Recommended">
<div class="whitespace-normal break-words">
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.
</div>
</x-callout>
</div>
</div>
@endif
<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>Self-hosted GitLab</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="html_url" label="GitLab URL" required
helper="For self-hosted GitLab, enter your instance URL (e.g., https://gitlab.example.com)." />
<x-forms.input id="api_url" label="API URL" required
helper="Usually your GitLab URL with /api/v4 appended." />
</div>
<div class="flex gap-2">
<x-forms.input id="custom_user" label="Custom Git User" required />
<x-forms.input id="custom_port" type="number" label="Custom Git Port" required />
</div>
</div>
</div>
</div>
</div>
<x-forms.button class="mt-4" type="submit">
Continue
</x-forms.button>
</form>
</div>
@else
<x-callout type="danger" title="Insufficient Permissions">
You don't have permission to create new GitLab Apps. Please contact your team administrator for access.
</x-callout>
@endcan
@@ -0,0 +1,50 @@
<?php
use App\Livewire\Source\Gitlab\Create;
use App\Models\GitlabApp;
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]);
});
describe('GitLab source create modal', function () {
test('matches github create modal structure', function () {
Livewire::test(Create::class)
->assertSee('This is required if you would like to get full integration')
->assertSee('Self-hosted GitLab')
->assertSee('Continue')
->assertDontSee('>Save</', false)
->assertDontSeeHtml('<h2>New GitLab App</h2>');
});
test('creates a gitlab app with defaults for gitlab.com', function () {
Livewire::test(Create::class)
->set('name', 'my-gitlab')
->call('createGitLabApp')
->assertRedirect();
$app = GitlabApp::where('name', 'my-gitlab')->first();
expect($app)->not->toBeNull()
->and($app->html_url)->toBe('https://gitlab.com')
->and($app->api_url)->toBe('https://gitlab.com/api/v4')
->and($app->custom_user)->toBe('git')
->and($app->custom_port)->toBe(22);
});
test('derives api url when html url changes', function () {
Livewire::test(Create::class)
->set('html_url', 'https://gitlab.example.com')
->assertSet('api_url', 'https://gitlab.example.com/api/v4');
});
});