feat(gitea): support Forgejo and Codeberg in the Gitea App source

This commit is contained in:
Andras Bacsai
2026-11-05 13:20:03 +01:00
parent 4a1228c611
commit a58588faa0
13 changed files with 103 additions and 71 deletions
+1
View File
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- Save more than one named SMTP server on the transactional email and email notification pages, and switch between them
- Give each SMTP server and Resend its own sender name and address, because each provider sends from its own verified domain
- Disable a scheduled task automatically when it fails at least 5 times in a row and has had no successful run for 3 days. Coolify sends a notification on the scheduled task failure channels and shows the reason on the task page until you enable the task again
- Connect Forgejo instances, including Codeberg and self-hosted Forgejo, with the Gitea / Forgejo App source. A new Provider list on the create form fills in the Gitea.com or Codeberg URLs, and webhooks also accept the `X-Forgejo-*` headers
### Changed
+5 -5
View File
@@ -48,7 +48,7 @@ class Gitea extends Controller
// Only users who may administer the source can complete OAuth and store tokens.
if (! $request->user()->can('update', $giteaApp)) {
return redirect()->route('source.all')->with('error', 'You are not authorized to connect this Gitea App.');
return redirect()->route('source.all')->with('error', 'You are not authorized to connect this Gitea / Forgejo App.');
}
$baseUrl = rtrim($giteaApp->html_url, '/');
@@ -85,7 +85,7 @@ class Gitea extends Controller
{
try {
$return_payloads = collect([]);
$x_gitea_event = Str::lower((string) $request->header('X-Gitea-Event'));
$x_gitea_event = Str::lower((string) ($request->header('X-Gitea-Event') ?? $request->header('X-Forgejo-Event')));
if ($x_gitea_event === 'ping') {
return response('pong');
@@ -98,7 +98,7 @@ class Gitea extends Controller
]);
}
$signature = (string) $request->header('X-Gitea-Signature');
$signature = (string) ($request->header('X-Gitea-Signature') ?? $request->header('X-Forgejo-Signature'));
if ($signature === '') {
$signature = Str::after((string) $request->header('X-Hub-Signature-256'), 'sha256=');
}
@@ -365,8 +365,8 @@ class Gitea extends Controller
{
try {
$return_payloads = collect([]);
$x_gitea_delivery = request()->header('X-Gitea-Delivery');
$x_gitea_event = Str::lower((string) $request->header('X-Gitea-Event'));
$x_gitea_delivery = $request->header('X-Gitea-Delivery') ?? $request->header('X-Forgejo-Delivery');
$x_gitea_event = Str::lower((string) ($request->header('X-Gitea-Event') ?? $request->header('X-Forgejo-Event')));
$x_hub_signature_256 = Str::after((string) $request->header('X-Hub-Signature-256'), 'sha256=');
$content_type = $request->header('Content-Type');
$payload = $request->collect();
+2 -2
View File
@@ -185,8 +185,8 @@ class Select extends Component
],
[
'id' => 'private-gitea-app',
'name' => 'Git Repository (with Gitea App)',
'description' => 'Deploy public or private Gitea repositories through a Gitea App, with automatic webhooks and pull request previews.',
'name' => 'Git Repository (with Gitea / Forgejo App)',
'description' => 'Deploy public or private Gitea, Forgejo, or Codeberg repositories through a Gitea / Forgejo App, with automatic webhooks and pull request previews.',
'logo' => asset('svgs/gitea.svg'),
],
];
+3 -3
View File
@@ -281,7 +281,7 @@ class Change extends Component
$this->syncData(true);
$this->gitea_app->save();
$this->dispatch('success', 'Gitea App updated.');
$this->dispatch('success', 'Gitea / Forgejo App updated.');
$this->dispatch('name-changed', uuid: $this->gitea_app->uuid, name: $this->gitea_app->name);
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -298,7 +298,7 @@ class Change extends Component
$this->gitea_app->is_system_wide = $this->isSystemWide;
$this->gitea_app->save();
$this->dispatch('success', 'Gitea App updated.');
$this->dispatch('success', 'Gitea / Forgejo App updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -310,7 +310,7 @@ class Change extends Component
$this->authorize('view', $this->gitea_app);
if (! $this->gitea_app->isConnected()) {
$this->dispatch('error', 'Gitea App is not connected. Please complete the OAuth flow first.');
$this->dispatch('error', 'Gitea / Forgejo App is not connected. Please complete the OAuth flow first.');
return;
}
+22
View File
@@ -13,8 +13,16 @@ class Create extends Component
{
use AuthorizesRequests;
/** @var array<string, string> Hosted instances that the provider choice fills in. */
public const HOSTED_PROVIDERS = [
'gitea.com' => 'https://gitea.com',
'codeberg.org' => 'https://codeberg.org',
];
public string $name;
public string $provider = 'gitea.com';
public string $html_url = 'https://gitea.com';
public string $api_url = 'https://gitea.com/api/v1';
@@ -34,6 +42,20 @@ class Create extends Component
$this->name = substr(generate_random_name(), 0, 30);
}
public function selectProvider(string $provider): void
{
$this->provider = $provider;
if (! isset(self::HOSTED_PROVIDERS[$provider])) {
return;
}
$this->html_url = self::HOSTED_PROVIDERS[$provider];
$this->api_url = $this->giteaApiUrlFromHtmlUrl($this->html_url);
$this->custom_user = 'git';
$this->custom_port = 22;
}
public function updatingHtmlUrl(): void
{
$this->shouldDeriveApiUrlAfterHtmlUrlUpdate = blank($this->api_url)
+1
View File
@@ -133,6 +133,7 @@ if (! function_exists('auditLogWebhookFailure')) {
'event_header' => $request?->header('X-GitHub-Event')
?? $request?->header('X-Gitlab-Event')
?? $request?->header('X-Gitea-Event')
?? $request?->header('X-Forgejo-Event')
?? $request?->header('X-Event-Key'),
];
+1 -1
View File
@@ -13,7 +13,7 @@ const GITEA_PAGE_SIZE = 50;
function refreshGiteaToken(GiteaApp $source): void
{
if (! $source->refresh_token) {
throw new RuntimeException('Gitea source has no refresh token. Please reconnect the Gitea app.');
throw new RuntimeException('Gitea / Forgejo source has no refresh token. Please reconnect the Gitea / Forgejo App.');
}
$safetyMargin = 60;
@@ -3,16 +3,16 @@
<section class="application-settings-section">
<div class="application-settings-section-header">
<div>
<h2>Gitea App</h2>
<p>Connect a Gitea App before selecting a private repository.</p>
<h2>Gitea / Forgejo App</h2>
<p>Connect a Gitea / Forgejo App before selecting a private repository.</p>
</div>
</div>
<div class="application-settings-section-body">
<x-empty title="No Gitea Apps"
<x-empty title="No Gitea / Forgejo Apps"
description="Create an app to grant Coolify access to selected repositories."
icon-name="sources">
<x-slot:contents>
<x-modal-input buttonTitle="+ Add Gitea App" title="New Gitea App" closeOutside="false">
<x-modal-input buttonTitle="+ Add Gitea / Forgejo App" title="New Gitea / Forgejo App" closeOutside="false">
<livewire:source.gitea.create />
</x-modal-input>
</x-slot:contents>
@@ -23,10 +23,10 @@
<section class="application-settings-section">
<div class="application-settings-section-header">
<div>
<h2>Choose Gitea App</h2>
<p>Select the Gitea App that can access the repository you want to deploy.</p>
<h2>Choose Gitea / Forgejo App</h2>
<p>Select the Gitea / Forgejo App that can access the repository you want to deploy.</p>
</div>
<x-modal-input buttonTitle="+ Add Gitea App" title="New Gitea App" closeOutside="false">
<x-modal-input buttonTitle="+ Add Gitea / Forgejo App" title="New Gitea / Forgejo App" closeOutside="false">
<livewire:source.gitea.create />
</x-modal-input>
</div>
@@ -59,7 +59,7 @@
<div class="application-settings-section-header">
<div>
<h2>Choose repository</h2>
<p>Search repositories available through the selected Gitea App.</p>
<p>Search repositories available through the selected Gitea / Forgejo App.</p>
</div>
@if ($gitea_app_id)
<x-forms.button wire:click.prevent="loadRepositories({{ $gitea_app_id }})">
@@ -85,7 +85,7 @@
</div>
@else
<x-empty size="sm" title="No repositories available"
description="Review this Gitea App configuration and grant access to a repository." />
description="Review this Gitea / Forgejo App configuration and grant access to a repository." />
@endif
</div>
</section>
@@ -1,6 +1,6 @@
<div>
<x-slot:title>
{{ $name ?: 'Gitea App' }} | Sources | Coolify
{{ $name ?: 'Gitea / Forgejo App' }} | Sources | Coolify
</x-slot>
@if ($isConnected)
@@ -8,12 +8,12 @@
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-2">
<h1 class="truncate text-[24px]! leading-7! font-semibold! tracking-tight!">
{{ $name ?: 'Gitea App' }}
{{ $name ?: 'Gitea / Forgejo App' }}
</h1>
<x-status-badge label="Connected" type="success" />
</div>
<p class="mt-1 text-[13px] text-neutral-500 dark:text-fg-dim">
{{ filled($organization) ? 'Gitea App for '.$organization : 'Private Gitea source' }}
{{ filled($organization) ? 'Gitea / Forgejo App for '.$organization : 'Private Gitea / Forgejo source' }}
</p>
</div>
<div class="flex shrink-0 flex-wrap items-center gap-2 sm:ml-auto">
@@ -23,11 +23,11 @@
</x-forms.button>
@endcan
@can('delete', $gitea_app)
<x-modal-confirmation title="Confirm Gitea App Deletion?" isErrorButton buttonTitle="Delete"
submitAction="delete" :actions="['The selected Gitea App will be permanently deleted.']"
<x-modal-confirmation title="Confirm Gitea / Forgejo App Deletion?" isErrorButton buttonTitle="Delete"
submitAction="delete" :actions="['The selected Gitea / Forgejo App will be permanently deleted.']"
confirmationText="{{ data_get($gitea_app, 'name') }}"
confirmationLabel="Please confirm by entering the Gitea App Name below"
shortConfirmationLabel="Gitea App Name" :confirmWithPassword="false"
confirmationLabel="Please confirm by entering the Gitea / Forgejo App Name below"
shortConfirmationLabel="Gitea / Forgejo App Name" :confirmWithPassword="false"
step2ButtonText="Permanently Delete" />
@endcan
</div>
@@ -44,20 +44,20 @@
@if (! isCloud())
<div class="lg:col-span-2 max-w-xs">
<x-forms.checkbox canGate="update" :canResource="$gitea_app" label="System wide"
helper="If checked, this Gitea App will be available for everyone in this Coolify instance."
helper="If checked, this Gitea / Forgejo App will be available for everyone in this Coolify instance."
instantSave id="isSystemWide" />
</div>
@if ($isSystemWide)
<div class="lg:col-span-2">
<x-callout type="warning" title="Shared with every team">
System-wide Gitea Apps are shared across all teams on this Coolify instance. Prefer team-specific apps when you need repository isolation.
System-wide Gitea / Forgejo Apps are shared across all teams on this Coolify instance. Prefer team-specific apps when you need repository isolation.
</x-callout>
</div>
@endif
@endif
</div>
<x-application.settings-subsection title="Webhook"
helper="Configure this webhook URL in Gitea (user, organization, or repository settings → Webhooks → Add webhook → Gitea).">
helper="Configure this webhook URL in Gitea or Forgejo (user, organization, or repository settings → Webhooks → Add webhook → Gitea or Forgejo).">
<div class="grid gap-4 lg:grid-cols-2">
<div class="lg:col-span-2">
<x-forms.input readonly label="Webhook URL"
@@ -65,7 +65,7 @@
</div>
<x-forms.input canGate="update" :canResource="$gitea_app" id="webhookSecret"
label="Webhook secret" type="password"
helper="Set this as the Secret in a Gitea webhook (user, organization, or repository settings → Webhooks → Add webhook → Gitea), content type application/json, trigger on Push and Pull Request events. Stored encrypted." />
helper="Set this as the Secret in a Gitea webhook (user, organization, or repository settings → Webhooks → Add webhook → Gitea or Forgejo), content type application/json, trigger on Push and Pull Request events. Stored encrypted." />
</div>
</x-application.settings-subsection>
</x-application.settings-section>
@@ -73,7 +73,7 @@
<x-application.settings-section id="connection-section" title="Connection"
helper="OAuth credentials and the endpoints Coolify uses to reach this source.">
<x-application.settings-subsection first title="OAuth credentials"
helper="Client credentials from your Gitea OAuth2 application.">
helper="Client credentials from your Gitea or Forgejo OAuth2 application.">
<div class="grid gap-4 lg:grid-cols-2">
<x-forms.input canGate="update" :canResource="$gitea_app" id="clientId"
label="Client ID" />
@@ -85,10 +85,10 @@
</div>
</x-application.settings-subsection>
<x-application.settings-subsection title="Self-hosted / advanced"
helper="Override endpoints and SSH settings for self-hosted Gitea.">
helper="Override endpoints and SSH settings for self-hosted Gitea or Forgejo.">
<div class="grid gap-4 lg:grid-cols-2">
<x-forms.input canGate="update" :canResource="$gitea_app" id="htmlUrl"
label="Gitea URL" />
label="Instance URL" />
<x-forms.input canGate="update" :canResource="$gitea_app" id="apiUrl" label="API URL" />
<x-forms.input canGate="update" :canResource="$gitea_app" id="customUser"
label="SSH user" />
@@ -110,10 +110,10 @@
<div x-data="{ search: '' }" class="application-settings-form mt-6">
<x-application.settings-section title="Resources"
description="Applications currently using this Gitea App." flush>
description="Applications currently using this Gitea / Forgejo App." flush>
@if ($applications->isEmpty())
<x-empty title="No resources use this source"
description="Applications will appear here after this Gitea App is selected as their source."
description="Applications will appear here after this Gitea / Forgejo App is selected as their source."
icon-name="sources" size="sm" />
@else
<div class="border-b border-neutral-200 p-3 dark:border-white/[0.08]">
@@ -164,19 +164,19 @@
<header class="mb-8 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div class="min-w-0">
<h1 class="truncate text-[24px]! leading-7! font-semibold! tracking-tight!">
{{ $name ?: 'Gitea App' }}
{{ $name ?: 'Gitea / Forgejo App' }}
</h1>
<p class="mt-1 text-[13px] text-neutral-500 dark:text-fg-dim">
Finish connecting this Gitea App before using it as a source
Finish connecting this Gitea / Forgejo App before using it as a source
</p>
</div>
@can('delete', $gitea_app)
<div class="shrink-0 sm:ml-auto">
<x-modal-confirmation title="Confirm Gitea App Deletion?" isErrorButton buttonTitle="Delete"
submitAction="delete" :actions="['The selected Gitea App will be permanently deleted.']"
<x-modal-confirmation title="Confirm Gitea / Forgejo App Deletion?" isErrorButton buttonTitle="Delete"
submitAction="delete" :actions="['The selected Gitea / Forgejo App will be permanently deleted.']"
confirmationText="{{ data_get($gitea_app, 'name') }}"
confirmationLabel="Please confirm by entering the Gitea App Name below"
shortConfirmationLabel="Gitea App Name" :confirmWithPassword="false"
confirmationLabel="Please confirm by entering the Gitea / Forgejo App Name below"
shortConfirmationLabel="Gitea / Forgejo App Name" :confirmWithPassword="false"
step2ButtonText="Permanently Delete" />
</div>
@endcan
@@ -194,7 +194,7 @@
}
}">
<x-application.settings-section title="Step 1 · Create an OAuth application"
description="In Gitea, open Manage OAuth2 Applications and create a new application with the redirect URI below.">
description="In Gitea or Forgejo, open Settings → Applications → Manage OAuth2 Applications and create a new application with the redirect URI below.">
<div class="flex flex-col gap-3 text-[12px] leading-5 text-neutral-600 dark:text-fg-dim">
<a href="{{ rtrim($htmlUrl, '/') }}/user/settings/applications" target="_blank"
class="inline-flex w-fit items-center gap-1 font-medium text-black underline-offset-2 hover:underline dark:text-fg">
@@ -216,7 +216,7 @@
<form wire:submit="submit" class="contents">
<x-application.settings-section title="Step 2 · Enter credentials"
description="Paste the Client ID and Client secret from Gitea, then save.">
description="Paste the Client ID and Client secret from Gitea or Forgejo, then save.">
<x-slot:actions>
<x-forms.button type="submit">Save</x-forms.button>
</x-slot:actions>
@@ -224,10 +224,10 @@
<div class="grid gap-4 lg:grid-cols-2">
<x-forms.input id="name" label="Name" />
<x-forms.input id="clientId" label="Client ID" required
helper="The Client ID from your Gitea OAuth2 application." />
helper="The Client ID from your Gitea or Forgejo OAuth2 application." />
<x-forms.input id="clientSecretInput" label="Client secret" type="password"
:required="blank($clientSecretInput) && blank(data_get($gitea_app, 'client_secret'))"
helper="The Client secret from your Gitea OAuth2 application. Saved encrypted." />
helper="The Client secret from your Gitea or Forgejo OAuth2 application. Saved encrypted." />
<x-forms.input id="organization" label="Organization"
helper="Optional. Comma-separated organization or user names to filter repositories." />
</div>
@@ -235,7 +235,7 @@
@if (! isCloud() || isDev())
<div class="mt-4 grid gap-4 lg:grid-cols-2">
<div class="lg:col-span-2 text-[12px] text-neutral-500 dark:text-fg-dim">
Gitea will redirect back to this Coolify URL. It must match the Redirect URI on your Gitea OAuth2 application exactly.
Gitea or Forgejo will redirect back to this Coolify URL. It must match the Redirect URI on your Gitea or Forgejo OAuth2 application exactly.
</div>
<div class="lg:col-span-2 max-w-md">
<x-forms.listbox id="use_custom_webhook_endpoint" label="Webhook endpoint"
@@ -249,7 +249,7 @@
<div class="lg:col-span-2" x-show="!useCustomWebhookEndpoint">
<x-forms.listbox id="webhook_endpoint" x-model="webhookEndpoint"
label="Selected endpoint"
helper="Gitea will use this endpoint unless custom mode is enabled."
helper="Gitea or Forgejo will use this endpoint unless custom mode is enabled."
:options="collect([$fqdn, $ipv4, $ipv6, config('app.url')])
->filter()->unique()->map(fn ($endpoint) => [
'value' => $endpoint,
@@ -260,7 +260,7 @@
<x-forms.input x-model="customWebhookEndpoint" id="custom_webhook_endpoint"
type="url" label="Custom endpoint"
placeholder="https://coolify.example.com"
helper="Gitea will use this custom public URL. Do not include /webhooks." />
helper="Gitea or Forgejo will use this custom public URL. Do not include /webhooks." />
</div>
</div>
@endif
@@ -276,16 +276,16 @@
</button>
<div x-cloak x-show="open" x-collapse.duration.200ms
class="mt-3 grid gap-4 rounded-lg border border-neutral-200 p-3 lg:grid-cols-2 dark:border-white/[0.08]">
<x-forms.input id="htmlUrl" label="Gitea URL"
helper="Only change this for self-hosted Gitea (e.g. https://gitea.example.com)." />
<x-forms.input id="htmlUrl" label="Instance URL"
helper="Only change this for self-hosted Gitea or Forgejo (e.g. https://git.example.com)." />
<x-forms.input id="apiUrl" label="API URL"
helper="Usually your Gitea URL with /api/v1 appended." />
helper="Usually your instance URL with /api/v1 appended." />
<x-forms.input id="customUser" label="SSH user" />
<x-forms.input type="number" id="customPort" label="SSH port" />
@if (! isCloud())
<div class="max-w-xs lg:col-span-2">
<x-forms.checkbox label="System wide" id="isSystemWide"
helper="If checked, this Gitea App will be available for everyone in this Coolify instance." />
helper="If checked, this Gitea / Forgejo App will be available for everyone in this Coolify instance." />
</div>
@endif
</div>
@@ -294,11 +294,11 @@
</form>
@if ($clientId)
<x-application.settings-section title="Step 3 · Authorize with Gitea"
description="Authorize Coolify with your Gitea instance. The redirect URI must match the one configured in Gitea.">
<x-application.settings-section title="Step 3 · Authorize with Gitea or Forgejo"
description="Authorize Coolify with your Gitea or Forgejo instance. The redirect URI must match the one configured there.">
<a href="{{ $this->getOAuthUrl() }}" wire:key="oauth-url-{{ md5((string) $redirectUri) }}"
class="button button-highlighted">
Connect to Gitea
Connect to Gitea / Forgejo
<x-external-link />
</a>
</x-application.settings-section>
@@ -1,9 +1,17 @@
@can('create', \App\Models\GiteaApp::class)
<form wire:submit="createGiteaApp" class="flex w-full flex-col gap-4">
<p class="text-[12px] leading-5 text-neutral-500 dark:text-fg-dim">
Connect a Gitea OAuth2 application for private repositories, webhooks, and deployments.
Connect a Gitea or Forgejo OAuth2 application (Gitea.com, Codeberg, or your own instance) for private repositories, webhooks, and deployments.
</p>
<x-forms.listbox id="provider" label="Provider" onChange="selectProvider" :onChangeArgs="[]"
:options="[
['value' => 'gitea.com', 'label' => 'Gitea.com'],
['value' => 'codeberg.org', 'label' => 'Codeberg (Forgejo)'],
['value' => 'self-hosted', 'label' => 'Self-hosted Gitea or Forgejo'],
]"
helper="Codeberg runs Forgejo. For your own Gitea or Forgejo instance, select Self-hosted and enter its URL below." />
<div class="grid gap-4 sm:grid-cols-2">
<x-forms.input id="name" label="Name" required />
<x-forms.input id="organization" label="Organization"
@@ -15,11 +23,11 @@
<div x-data="{ showWarning: @entangle('is_system_wide') }">
<div class="max-w-xs">
<x-forms.checkbox id="is_system_wide" label="System wide"
helper="If checked, this Gitea App will be available for everyone in this Coolify instance." />
helper="If checked, this Gitea / Forgejo App will be available for everyone in this Coolify instance." />
</div>
<div x-cloak x-show="showWarning" x-transition class="mt-3">
<x-callout type="warning" title="Shared with every team">
System-wide Gitea Apps are available to every team on this instance. Prefer team-specific apps when you need repository isolation.
System-wide Gitea / Forgejo Apps are available to every team on this instance. Prefer team-specific apps when you need repository isolation.
</x-callout>
</div>
</div>
@@ -27,10 +35,10 @@
<div x-data="{
open: false,
}" class="rounded-lg border border-neutral-200 dark:border-white/[0.08]">
}" x-effect="if ($wire.provider === 'self-hosted') open = true" class="rounded-lg border border-neutral-200 dark:border-white/[0.08]">
<button type="button" @click="open = !open"
class="flex w-full items-center justify-between px-3 py-2.5 text-left text-[12px] font-medium text-neutral-700 transition-colors hover:bg-neutral-50 dark:text-fg-dim dark:hover:bg-white/[0.03]">
Self-hosted Gitea
Self-hosted Gitea or Forgejo
<svg class="size-3.5 transition-transform" :class="{ 'rotate-180': open }" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"></polyline>
@@ -38,10 +46,10 @@
</button>
<div x-cloak x-show="open" x-collapse.duration.200ms class="border-t border-neutral-200 px-3 py-3 dark:border-white/[0.08]">
<div class="grid gap-4 sm:grid-cols-2">
<x-forms.input id="html_url" label="Gitea URL" required
helper="For self-hosted Gitea, enter your instance URL (e.g. https://gitea.example.com)." />
<x-forms.input id="html_url" label="Instance URL" required
helper="For self-hosted Gitea or Forgejo, enter your instance URL (e.g. https://git.example.com)." />
<x-forms.input id="api_url" label="API URL" required
helper="Usually your Gitea URL with /api/v1 appended." />
helper="Usually your instance URL with /api/v1 appended." />
<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>
@@ -54,6 +62,6 @@
</form>
@else
<x-callout type="danger" title="Insufficient permissions">
You don't have permission to create new Gitea Apps. Contact your team administrator.
You don't have permission to create new Gitea / Forgejo Apps. Contact your team administrator.
</x-callout>
@endcan
+4 -4
View File
@@ -44,12 +44,12 @@
</x-slot:content>
<livewire:source.gitlab.create />
</x-modal-input>
<x-modal-input title="New Gitea App" :closeOutside="false">
<x-modal-input title="New Gitea / Forgejo App" :closeOutside="false">
<x-slot:content>
<button type="button" @click="dropdownOpen = false"
class="listbox-option justify-start! gap-2.5!" role="menuitem">
<x-git-icon class="size-3.5 shrink-0 opacity-70" git="App\Models\GiteaApp" />
Gitea App
Gitea / Forgejo App
</button>
</x-slot:content>
<livewire:source.gitea.create />
@@ -71,7 +71,7 @@
'name' => $source->name,
'provider' => match ($source->getMorphClass()) {
'App\\Models\\GithubApp' => 'GitHub',
'App\\Models\\GiteaApp' => 'Gitea',
'App\\Models\\GiteaApp' => 'Gitea / Forgejo',
default => 'GitLab',
},
'organization' => $isGitlab ? $source->group_name : $source->organization,
@@ -143,7 +143,7 @@
</div>
</a>
@elseif ($source->getMorphClass() === 'App\Models\GiteaApp')
<a x-show="matches(@js([$source->name, 'Gitea', $source->organization, $source->isConnected() ? 'Connected' : 'Setup incomplete']))" class="group flex min-h-28 flex-col rounded-xl border border-neutral-200 bg-white p-3 shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:no-underline hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.05] dark:hover:border-white/[0.14]"
<a x-show="matches(@js([$source->name, 'Gitea', 'Forgejo', $source->organization, $source->isConnected() ? 'Connected' : 'Setup incomplete']))" class="group flex min-h-28 flex-col rounded-xl border border-neutral-200 bg-white p-3 shadow-sm transition-all hover:-translate-y-px hover:border-neutral-300 hover:no-underline hover:shadow-md dark:border-white/[0.08] dark:bg-white/[0.05] dark:hover:border-white/[0.14]"
{{ wireNavigate() }}
href="{{ route('source.gitea.show', ['gitea_app_uuid' => data_get($source, 'uuid')]) }}">
<div class="flex items-start gap-3">
+1 -1
View File
@@ -48,7 +48,7 @@ describe('Gitea source setup view', function () {
test('shows incomplete-setup guidance with the credential form', function () {
Livewire::withQueryParams(['gitea_app_uuid' => $this->giteaApp->uuid])
->test(Change::class)
->assertSee('Finish connecting this Gitea App before using it as a source')
->assertSee('Finish connecting this Gitea / Forgejo App before using it as a source')
->assertSee('https://gitea.com/user/settings/applications')
->assertSee('Client ID')
->assertSee('Client secret')
+1 -1
View File
@@ -36,7 +36,7 @@ test('sources page lists gitea apps with a link to their settings', function ()
$this->get(route('source.all'))
->assertOk()
->assertSee('New Gitea App')
->assertSee('New Gitea / Forgejo App')
->assertSee('Team Gitea')
->assertSee('Gitea · acme')
->assertSee(route('source.gitea.show', ['gitea_app_uuid' => $this->giteaApp->uuid]), false);