v4.0.0-beta.420 (#6008)

* chore(version): update coolify-realtime to version 1.0.9 in docker-compose and versions files

* feat(migration): add is_sentinel_enabled column to server_settings with default true

* fix(migration): update default value handling for is_sentinel_enabled column in server_settings

* feat(seeder): dispatch StartProxy action for each server in ProductionSeeder

* feat(seeder): add CheckAndStartSentinelJob dispatch for each server in ProductionSeeder

* fix(seeder): conditionally dispatch CheckAndStartSentinelJob based on server's sentinel status

* feat(seeder): conditionally dispatch StartProxy action based on proxy check result

* refactor(ui): terminal

* refactor(ui): remove terminal header from execute-container-command view

* refactor(ui): remove unnecessary padding from deployment, backup, and logs sections

* fix(service): disable healthcheck logging for Gotenberg (#6005)

* fix(service): Joplin volume name (#5930)

* chore(version): update coolify version to 4.0.0-beta.420 and nightly version to 4.0.0-beta.421

* fix(server): update sentinelUpdatedAt assignment to use server's sentinel_updated_at property

* feat(service): update Changedetection template (#5937)

* chore(service): changedetection remove unused code

* fix(service): audiobookshelf healthcheck command (#5993)

* refactor(service): update Hoarder to their new name karakeep (#5964)

* fix(service): downgrade Evolution API phone version (#5977)

* feat(service): add Miniflux service (#5843)

* refactor(service): karakeep naming and formatting

* refactor(service): improve miniflux

- improve DB url
- add depends_on
- formatting, naming & order

* feat(service): add Pingvin Share service (#5969)

* fix(service): pingvinshare-with-clamav

- add platform to make clamav work
- formatting

* feat(auth): Add Discord OAuth Provider (#5552)

* feat(auth): Add Clerk OAuth Provider (#5553)

* feat(auth): add Zitadel OAuth Provider (#5490)

* Update composer.lock

* fix(ssh): scp requires square brackets for ipv6 (#6001)

* refactor(core): rename API rate limit ENV

* refactor(ui): simplify container selection form in execute-container-command view

* chore(service): Update Evolution API image to the official one (#6031)

* chore(versions): bump coolify versions to v4.0.0-beta.420 and v4.0.0-beta.421

* fix(github): changing github app breaks the webhook. it does not anymore

* feat(service): enhance service status handling and UI updates

* fix(parser): improve FQDN generation and update environment variable handling

* fix(ui):  enhance status refresh buttons with loading indicators

* fix(ui): update confirmation button text for stopping database and service

* fix(routes): update middleware for deploy route to use 'api.ability:deploy'

* fix(ui): refine API token creation form and update helper text for clarity

* fix(ui): adjust layout of deployments section for improved alignment

* chore(dependencies): update composer dependencies to latest versions including resend-laravel to ^0.19.0 and aws-sdk-php to 3.347.0

* refactor(email): streamline SMTP and resend settings logic for improved clarity

* fix(ui): adjust project grid layout and refine server border styling for better visibility

* fix(ui): update border styling for consistency across components and enhance loading indicators

* feat(cleanup): add functionality to delete teams with no members or servers in CleanupStuckedResources command

* refactor(invitation): rename methods for consistency and enhance invitation deletion logic

* refactor(user): streamline user deletion process and enhance team management logic

* fix(ui): add padding to section headers in settings views for improved spacing

* fix(ui): reduce gap between input fields in email settings for better alignment

* fix(docker): conditionally enable gzip compression in Traefik labels based on configuration

* fix(parser): enable gzip compression conditionally for Pocketbase images and streamline service creation logic

* fix(ui): update padding for trademarks policy and enhance spacing in advanced settings section

* feat(ui): add heart icon and enhance popup messaging for sponsorship support

* feat(settings): add sponsorship popup toggle and corresponding database migration

* fix(ui): correct closing tag for sponsorship link in layout popups

* fix(ui): refine wording in sponsorship donation prompt in layout popups

* fix(ui): update navbar icon color and enhance popup layout for sponsorship support

* Update resources/views/livewire/project/shared/health-checks.blade.php

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update app/Livewire/Subscription/Index.php

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(ui): add target="_blank" to sponsorship links in layout popups for improved user experience

* fix(models): refine comment wording in User model for clarity on user deletion criteria

* Update app/Providers/RouteServiceProvider.php

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(models): improve user deletion logic in User model to handle team member roles and prevent deletion if user is alone in root team

* fix(ui): update wording in sponsorship prompt for clarity and engagement

---------

Co-authored-by: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Co-authored-by: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com>
Co-authored-by: Carsten <BanditsBacon@users.noreply.github.com>
Co-authored-by: Alberto Rizzi <48057685+albertorizzi@users.noreply.github.com>
Co-authored-by: Jonas Klesen <deklesen@gmail.com>
Co-authored-by: Stew Night. <22344601+stewnight@users.noreply.github.com>
Co-authored-by: Jeffer Marcelino <jeffersunde72@gmail.com>
Co-authored-by: Lucas Eduardo <lucas59356@gmail.com>
Co-authored-by: CrazyTim71 <118295691+CrazyTim71@users.noreply.github.com>
Co-authored-by: Yassir Elmarissi <yassir.elmarissi@hm.edu>
Co-authored-by: Hauke Schnau <hauke@schnau-lilienthal.de>
Co-authored-by: Darren Sisson <74752850+djsisson@users.noreply.github.com>
Co-authored-by: Alkesh Das <67038642+smad-bro@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
🏔️ Peak
2025-06-26 12:23:08 +02:00
committed by GitHub
parent e0f16e9539
commit d3f85d777c
110 changed files with 1134 additions and 562 deletions

View File

@@ -21,7 +21,9 @@ Coolify implements **defense-in-depth security** with multiple layers of protect
- **Supported Providers**:
- Google OAuth
- Microsoft Azure AD
- Clerk
- Authentik
- Discord
- GitHub (via GitHub Apps)
- GitLab

View File

@@ -90,7 +90,7 @@ alwaysApply: false
- **Purpose**: OAuth provider integration
- **Providers**:
- GitHub, GitLab, Google
- Microsoft Azure, Authentik
- Microsoft Azure, Authentik, Discord, Clerk
- Custom OAuth implementations
## Background Processing

View File

@@ -41,6 +41,6 @@ class StartService
}
}
return remote_process($commands, $service->server, type_uuid: $service->uuid);
return remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
}
}

View File

@@ -20,6 +20,7 @@ use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use App\Models\Team;
use Illuminate\Console\Command;
class CleanupStuckedResources extends Command
@@ -36,6 +37,12 @@ class CleanupStuckedResources extends Command
private function cleanup_stucked_resources()
{
try {
$teams = Team::all()->filter(function ($team) {
return $team->members()->count() === 0 && $team->servers()->count() === 0;
});
foreach ($teams as $team) {
$team->delete();
}
$servers = Server::all()->filter(function ($server) {
return $server->isFunctional();
});

View File

@@ -103,7 +103,11 @@ class SshMultiplexingHelper
}
$scp_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'), isScp: true);
$scp_command .= "{$source} {$server->user}@{$server->ip}:{$dest}";
if ($server->isIpv6()) {
$scp_command .= "{$source} {$server->user}@[{$server->ip}]:{$dest}";
} else {
$scp_command .= "{$source} {$server->user}@{$server->ip}:{$dest}";
}
return $scp_command;
}

View File

@@ -144,7 +144,7 @@ class Controller extends BaseController
}
}
public function revoke_invitation()
public function revokeInvitation()
{
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
$user = User::whereEmail($invitation->email)->firstOrFail();

View File

@@ -1381,8 +1381,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if (is_object($this->source) && $this->source->getMorphClass() === \App\Models\GithubApp::class && $this->source->is_public === false) {
$repository = githubApi($this->source, "repos/{$this->customRepository}");
$data = data_get($repository, 'data');
if (isset($data->id)) {
$repository_project_id = $data->id;
$repository_project_id = data_get($data, 'id');
if (isset($repository_project_id)) {
if (blank($this->application->repository_project_id) || $this->application->repository_project_id !== $repository_project_id) {
$this->application->repository_project_id = $repository_project_id;
$this->application->save();

View File

@@ -254,10 +254,9 @@ class Email extends Component
'smtpEncryption.required' => 'Encryption type is required.',
]);
$this->settings->resend_enabled = false;
$this->settings->use_instance_email_settings = false;
$this->resendEnabled = false;
$this->useInstanceEmailSettings = false;
if ($this->smtpEnabled) {
$this->settings->resend_enabled = $this->resendEnabled = false;
}
$this->settings->smtp_enabled = $this->smtpEnabled;
$this->settings->smtp_from_address = $this->smtpFromAddress;
@@ -293,11 +292,9 @@ class Email extends Component
'smtpFromAddress.email' => 'Please enter a valid email address.',
'smtpFromName.required' => 'From Name is required.',
]);
$this->settings->smtp_enabled = false;
$this->settings->use_instance_email_settings = false;
$this->smtpEnabled = false;
$this->useInstanceEmailSettings = false;
if ($this->resendEnabled) {
$this->settings->smtp_enabled = $this->smtpEnabled = false;
}
$this->settings->resend_enabled = $this->resendEnabled;
$this->settings->resend_api_key = $this->resendApiKey;

View File

@@ -111,8 +111,19 @@ class Source extends Component
$this->application->update([
'source_id' => $sourceId,
'source_type' => $sourceType,
'repository_project_id' => null,
]);
['repository' => $customRepository] = $this->application->customRepository();
$repository = githubApi($this->application->source, "repos/{$customRepository}");
$data = data_get($repository, 'data');
$repository_project_id = data_get($data, 'id');
if (isset($repository_project_id)) {
if ($this->application->repository_project_id !== $repository_project_id) {
$this->application->repository_project_id = $repository_project_id;
$this->application->save();
}
}
$this->application->refresh();
$this->getSources();
$this->dispatch('success', 'Source updated!');

View File

@@ -6,7 +6,6 @@ use App\Actions\Docker\GetContainersStatus;
use App\Actions\Service\StartService;
use App\Actions\Service\StopService;
use App\Enums\ProcessStatus;
use App\Events\ServiceStatusChanged;
use App\Models\Service;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
@@ -96,7 +95,7 @@ class Heading extends Component
public function start()
{
$activity = StartService::run($this->service, pullLatestImages: true);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
$this->dispatch('activityMonitor', $activity->id);
}
public function forceDeploy()
@@ -112,7 +111,7 @@ class Heading extends Component
$activity->save();
}
$activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
$this->dispatch('activityMonitor', $activity->id);
} catch (\Exception $e) {
$this->dispatch('error', $e->getMessage());
}
@@ -136,7 +135,7 @@ class Heading extends Component
return;
}
$activity = StartService::run($this->service, stopBeforeStart: true);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
$this->dispatch('activityMonitor', $activity->id);
}
public function pullAndRestartEvent()
@@ -148,7 +147,7 @@ class Heading extends Component
return;
}
$activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
$this->dispatch('activityMonitor', $activity->id);
}
public function render()

View File

@@ -19,7 +19,15 @@ class Proxy extends Component
public ?string $redirect_url = null;
protected $listeners = ['saveConfiguration' => 'submit'];
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
'saveConfiguration' => 'submit',
"echo-private:team.{$teamId},ProxyStatusChangedUI" => '$refresh',
];
}
protected $rules = [
'server.settings.generate_exact_labels' => 'required|boolean',

View File

@@ -69,6 +69,7 @@ class Patches extends Component
{
if (! $this->packageManager || ! $this->osId) {
$this->dispatch('error', message: 'Run “Check for updates” first.');
return;
}

View File

@@ -176,7 +176,7 @@ class Show extends Component
$this->sentinelCustomUrl = $this->server->settings->sentinel_custom_url;
$this->isSentinelEnabled = $this->server->settings->is_sentinel_enabled;
$this->isSentinelDebugEnabled = $this->server->settings->is_sentinel_debug_enabled;
$this->sentinelUpdatedAt = $this->server->settings->updated_at;
$this->sentinelUpdatedAt = $this->server->sentinel_updated_at;
$this->serverTimezone = $this->server->settings->server_timezone;
}
}

View File

@@ -68,6 +68,9 @@ class Index extends Component
#[Validate('boolean')]
public bool $disable_two_step_confirmation;
#[Validate('boolean')]
public bool $is_sponsorship_popup_enabled;
public function render()
{
return view('livewire.settings.index');
@@ -96,6 +99,7 @@ class Index extends Component
$this->update_check_frequency = $this->settings->update_check_frequency;
$this->instance_timezone = $this->settings->instance_timezone;
$this->disable_two_step_confirmation = $this->settings->disable_two_step_confirmation;
$this->is_sponsorship_popup_enabled = $this->settings->is_sponsorship_popup_enabled;
}
}
@@ -134,6 +138,7 @@ class Index extends Component
$this->settings->update_check_frequency = $this->update_check_frequency;
$this->settings->disable_two_step_confirmation = $this->disable_two_step_confirmation;
$this->settings->instance_timezone = $this->instance_timezone;
$this->settings->is_sponsorship_popup_enabled = $this->is_sponsorship_popup_enabled;
if ($isSave) {
$this->settings->save();
$this->dispatch('success', 'Settings updated!');

View File

@@ -3,7 +3,6 @@
namespace App\Livewire\Team;
use App\Models\InstanceSettings;
use App\Models\Team;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
@@ -53,30 +52,12 @@ class AdminView extends Component
}
}
private function finalizeDeletion(User $user, Team $team)
{
$servers = $team->servers;
foreach ($servers as $server) {
$resources = $server->definedResources();
foreach ($resources as $resource) {
$resource->forceDelete();
}
$server->forceDelete();
}
$projects = $team->projects;
foreach ($projects as $project) {
$project->forceDelete();
}
$team->members()->detach($user->id);
$team->delete();
}
public function delete($id, $password)
{
if (! isInstanceAdmin()) {
return redirect()->route('dashboard');
}
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
@@ -84,52 +65,22 @@ class AdminView extends Component
return;
}
}
if (! auth()->user()->isInstanceAdmin()) {
return $this->dispatch('error', 'You are not authorized to delete users');
}
$user = User::find($id);
$teams = $user->teams;
foreach ($teams as $team) {
$user_alone_in_team = $team->members->count() === 1;
if ($team->id === 0) {
if ($user_alone_in_team) {
return $this->dispatch('error', 'User is alone in the root team, cannot delete');
}
}
if ($user_alone_in_team) {
$this->finalizeDeletion($user, $team);
continue;
}
if ($user->isOwner()) {
$found_other_owner_or_admin = $team->members->filter(function ($member) {
return $member->pivot->role === 'owner' || $member->pivot->role === 'admin';
})->where('id', '!=', $user->id)->first();
if ($found_other_owner_or_admin) {
$team->members()->detach($user->id);
continue;
} else {
$found_other_member_who_is_not_owner = $team->members->filter(function ($member) {
return $member->pivot->role === 'member';
})->first();
if ($found_other_member_who_is_not_owner) {
$found_other_member_who_is_not_owner->pivot->role = 'owner';
$found_other_member_who_is_not_owner->pivot->save();
$team->members()->detach($user->id);
} else {
$this->finalizeDeletion($user, $team);
}
continue;
}
} else {
$team->members()->detach($user->id);
}
if (! $user) {
return $this->dispatch('error', 'User not found');
}
try {
$user->delete();
$this->getUsers();
} catch (\Exception $e) {
return $this->dispatch('error', $e->getMessage());
}
$user->delete();
$this->getUsers();
}
public function render()

View File

@@ -3,6 +3,7 @@
namespace App\Livewire\Team;
use App\Models\TeamInvitation;
use App\Models\User;
use Livewire\Component;
class Invitations extends Component
@@ -14,8 +15,13 @@ class Invitations extends Component
public function deleteInvitation(int $invitation_id)
{
try {
$initiation_found = TeamInvitation::ownedByCurrentTeam()->findOrFail($invitation_id);
$initiation_found->delete();
$invitation = TeamInvitation::ownedByCurrentTeam()->findOrFail($invitation_id);
$user = User::whereEmail($invitation->email)->first();
if (filled($user)) {
$user->deleteIfNotVerifiedAndForcePasswordReset();
}
$invitation->delete();
$this->refreshInvitations();
$this->dispatch('success', 'Invitation revoked.');
} catch (\Exception) {

View File

@@ -29,15 +29,15 @@ class InviteLink extends Component
public function viaEmail()
{
$this->generate_invite_link(sendEmail: true);
$this->generateInviteLink(sendEmail: true);
}
public function viaLink()
{
$this->generate_invite_link(sendEmail: false);
$this->generateInviteLink(sendEmail: false);
}
private function generate_invite_link(bool $sendEmail = false)
private function generateInviteLink(bool $sendEmail = false)
{
try {
$this->validate();

View File

@@ -27,6 +27,7 @@ class OauthSetting extends Model
case 'azure':
return filled($this->client_id) && filled($this->client_secret) && filled($this->tenant);
case 'authentik':
case 'clerk':
return filled($this->client_id) && filled($this->client_secret) && filled($this->base_url);
default:
return filled($this->client_id) && filled($this->client_secret);

View File

@@ -2,6 +2,7 @@
namespace App\Models;
use App\Enums\ProcessStatus;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -9,6 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use OpenApi\Attributes as OA;
use Spatie\Activitylog\Models\Activity;
use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
@@ -116,6 +118,18 @@ class Service extends BaseModel
return (bool) str($this->status)->contains('exited');
}
public function isStarting(): bool
{
try {
$activity = Activity::where('properties->type_uuid', $this->uuid)->latest()->first();
$status = data_get($activity, 'properties.status');
return $status === ProcessStatus::QUEUED->value || $status === ProcessStatus::IN_PROGRESS->value;
} catch (\Throwable) {
return false;
}
}
public function type()
{
return 'service';
@@ -159,6 +173,10 @@ class Service extends BaseModel
public function getStatusAttribute()
{
if ($this->isStarting()) {
return 'starting:unhealthy';
}
$applications = $this->applications;
$databases = $this->databases;

View File

@@ -33,6 +33,10 @@ class TeamInvitation extends Model
return true;
} else {
$this->delete();
$user = User::whereEmail($this->email)->first();
if (filled($user)) {
$user->deleteIfNotVerifiedAndForcePasswordReset();
}
return false;
}

View File

@@ -72,6 +72,93 @@ class User extends Authenticatable implements SendsEmail
$new_team = Team::create($team);
$user->teams()->attach($new_team, ['role' => 'owner']);
});
static::deleting(function (User $user) {
\DB::transaction(function () use ($user) {
$teams = $user->teams;
foreach ($teams as $team) {
$user_alone_in_team = $team->members->count() === 1;
// Prevent deletion if user is alone in root team
if ($team->id === 0 && $user_alone_in_team) {
throw new \Exception('User is alone in the root team, cannot delete');
}
if ($user_alone_in_team) {
static::finalizeTeamDeletion($user, $team);
// Delete any pending team invitations for this user
TeamInvitation::whereEmail($user->email)->delete();
continue;
}
// Load the user's role for this team
$userRole = $team->members->where('id', $user->id)->first()?->pivot?->role;
if ($userRole === 'owner') {
$found_other_owner_or_admin = $team->members->filter(function ($member) use ($user) {
return ($member->pivot->role === 'owner' || $member->pivot->role === 'admin') && $member->id !== $user->id;
})->first();
if ($found_other_owner_or_admin) {
$team->members()->detach($user->id);
continue;
} else {
$found_other_member_who_is_not_owner = $team->members->filter(function ($member) {
return $member->pivot->role === 'member';
})->first();
if ($found_other_member_who_is_not_owner) {
$found_other_member_who_is_not_owner->pivot->role = 'owner';
$found_other_member_who_is_not_owner->pivot->save();
$team->members()->detach($user->id);
} else {
static::finalizeTeamDeletion($user, $team);
}
continue;
}
} else {
$team->members()->detach($user->id);
}
}
});
});
}
/**
* Finalize team deletion by cleaning up all associated resources
*/
private static function finalizeTeamDeletion(User $user, Team $team)
{
$servers = $team->servers;
foreach ($servers as $server) {
$resources = $server->definedResources();
foreach ($resources as $resource) {
$resource->forceDelete();
}
$server->forceDelete();
}
$projects = $team->projects;
foreach ($projects as $project) {
$project->forceDelete();
}
$team->members()->detach($user->id);
$team->delete();
}
/**
* Delete the user if they are not verified and have a force password reset.
* This is used to clean up users that have been invited, did not accept the invitation (and did not verify their email and have a force password reset).
*/
public function deleteIfNotVerifiedAndForcePasswordReset()
{
if ($this->hasVerifiedEmail() === false && $this->force_password_reset === true) {
$this->delete();
}
}
public function recreate_personal_team()

View File

@@ -9,9 +9,12 @@ use Illuminate\Foundation\Events\MaintenanceModeEnabled;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use SocialiteProviders\Authentik\AuthentikExtendSocialite;
use SocialiteProviders\Azure\AzureExtendSocialite;
use SocialiteProviders\Clerk\ClerkExtendSocialite;
use SocialiteProviders\Discord\DiscordExtendSocialite;
use SocialiteProviders\Google\GoogleExtendSocialite;
use SocialiteProviders\Infomaniak\InfomaniakExtendSocialite;
use SocialiteProviders\Manager\SocialiteWasCalled;
use SocialiteProviders\Zitadel\ZitadelExtendSocialite;
class EventServiceProvider extends ServiceProvider
{
@@ -25,8 +28,11 @@ class EventServiceProvider extends ServiceProvider
SocialiteWasCalled::class => [
AzureExtendSocialite::class.'@handle',
AuthentikExtendSocialite::class.'@handle',
ClerkExtendSocialite::class.'@handle',
DiscordExtendSocialite::class.'@handle',
GoogleExtendSocialite::class.'@handle',
InfomaniakExtendSocialite::class.'@handle',
ZitadelExtendSocialite::class.'@handle',
],
];

View File

@@ -49,7 +49,7 @@ class RouteServiceProvider extends ServiceProvider
return Limit::perMinute(1000)->by($request->user()?->id ?: $request->ip());
}
return Limit::perMinute(config('api.throttle'))->by($request->user()?->id ?: $request->ip());
return Limit::perMinute((int) config('api.rate_limit'))->by($request->user()?->id ?: $request->ip());
});
RateLimiter::for('5', function (Request $request) {
return Limit::perMinute(5)->by($request->user()?->id ?: $request->ip());

View File

@@ -359,7 +359,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
{
$labels = collect([]);
$labels->push('traefik.enable=true');
$labels->push('traefik.http.middlewares.gzip.compress=true');
if ($is_gzip_enabled) {
$labels->push('traefik.http.middlewares.gzip.compress=true');
}
$labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https');
$is_http_basic_auth_enabled = $is_http_basic_auth_enabled && $http_basic_auth_username !== null && $http_basic_auth_password !== null;

View File

@@ -2991,12 +2991,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first();
if ($applicationFound) {
$savedService = $applicationFound;
// $savedService = ServiceDatabase::firstOrCreate([
// 'name' => $applicationFound->name,
// 'image' => $applicationFound->image,
// 'service_id' => $applicationFound->service_id,
// ]);
// $applicationFound->delete();
} else {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
@@ -3007,15 +3001,23 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$savedService = ServiceApplication::firstOrCreate([
'name' => $serviceName,
'service_id' => $resource->id,
], [
'is_gzip_enabled' => true,
]);
}
// Check if image changed
if ($savedService->image !== $image) {
$savedService->image = $image;
$savedService->save();
}
}
// Pocketbase does not need gzip for SSE.
if (str($savedService->image)->contains('pocketbase') && $savedService->is_gzip_enabled) {
$savedService->is_gzip_enabled = false;
$savedService->save();
}
$environment = collect(data_get($service, 'environment', []));
$buildArgs = collect(data_get($service, 'build.args', []));
$environment = $environment->merge($buildArgs);
@@ -3060,12 +3062,18 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$port = null;
}
if ($isApplication) {
$fqdn = generateFqdn($server, "$uuid");
$fqdn = $resource->fqdn;
if (blank($resource->fqdn)) {
$fqdn = generateFqdn($server, "$uuid");
}
} elseif ($isService) {
if ($fqdnFor) {
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
} else {
$fqdn = generateFqdn($server, "{$savedService->name}-$uuid");
$fqdn = $savedService->fqdn;
if (blank($savedService->fqdn)) {
if ($fqdnFor) {
$fqdn = generateFqdn($server, "$fqdnFor-$uuid");
} else {
$fqdn = generateFqdn($server, "{$savedService->name}-$uuid");
}
}
}
@@ -3090,7 +3098,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
}
if (substr_count(str($key)->value(), '_') === 2) {
$resource->environment_variables()->firstOrCreate([
$resource->environment_variables()->updateOrCreate([
'key' => $key->value(),
'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,
@@ -3102,7 +3110,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
}
if (substr_count(str($key)->value(), '_') === 3) {
$newKey = str($key)->beforeLast('_');
$resource->environment_variables()->firstOrCreate([
$resource->environment_variables()->updateOrCreate([
'key' => $newKey->value(),
'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id,

View File

@@ -22,15 +22,26 @@ function get_socialite_provider(string $provider)
return Socialite::driver('azure')->setConfig($azure_config);
}
if ($provider == 'authentik') {
$authentik_config = new \SocialiteProviders\Manager\Config(
if ($provider == 'authentik' || $provider == 'clerk') {
$authentik_clerk_config = new \SocialiteProviders\Manager\Config(
$oauth_setting->client_id,
$oauth_setting->client_secret,
$oauth_setting->redirect_uri,
['base_url' => $oauth_setting->base_url],
);
return Socialite::driver('authentik')->setConfig($authentik_config);
return Socialite::driver($provider)->setConfig($authentik_clerk_config);
}
if ($provider == 'zitadel') {
$zitadel_config = new \SocialiteProviders\Manager\Config(
$oauth_setting->client_id,
$oauth_setting->client_secret,
$oauth_setting->redirect_uri,
['base_url' => $oauth_setting->base_url],
);
return Socialite::driver('zitadel')->setConfig($zitadel_config);
}
if ($provider == 'google') {
@@ -53,6 +64,7 @@ function get_socialite_provider(string $provider)
$provider_class_map = [
'bitbucket' => \Laravel\Socialite\Two\BitbucketProvider::class,
'discord' => \SocialiteProviders\Discord\Provider::class,
'github' => \Laravel\Socialite\Two\GithubProvider::class,
'gitlab' => \Laravel\Socialite\Two\GitlabProvider::class,
'infomaniak' => \SocialiteProviders\Infomaniak\Provider::class,

View File

@@ -36,12 +36,15 @@
"poliander/cron": "^3.2.1",
"purplepixie/phpdns": "^2.2",
"pusher/pusher-php-server": "^7.2.7",
"resend/resend-laravel": "^0.17.0",
"resend/resend-laravel": "^0.19.0",
"sentry/sentry-laravel": "^4.13",
"socialiteproviders/authentik": "^5.2",
"socialiteproviders/clerk": "^5.0",
"socialiteproviders/discord": "^4.2",
"socialiteproviders/google": "^4.1",
"socialiteproviders/infomaniak": "^4.0",
"socialiteproviders/microsoft-azure": "^5.2",
"socialiteproviders/zitadel": "^4.1",
"spatie/laravel-activitylog": "^4.10.1",
"spatie/laravel-data": "^4.13.1",
"spatie/laravel-ray": "^1.39.1",

585
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<?php
return [
'throttle' => env('API_THROTTLE', 200),
'rate_limit' => env('API_RATE_LIMIT', 200),
];

View File

@@ -2,7 +2,7 @@
return [
'coolify' => [
'version' => '4.0.0-beta.419',
'version' => '4.0.0-beta.420',
'helper_version' => '1.0.8',
'realtime_version' => '1.0.9',
'self_hosted' => env('SELF_HOSTED', true),

View File

@@ -46,6 +46,13 @@ return [
'redirect' => env('AUTHENTIK_REDIRECT_URI'),
],
'clerk' => [
'client_id' => env('CLERK_CLIENT_ID'),
'client_secret' => env('CLERK_CLIENT_SECRET'),
'redirect' => env('CLERK_REDIRECT_URI'),
'base_url' => env('CLERK_BASE_URL'),
],
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
@@ -53,4 +60,11 @@ return [
'tenant' => env('GOOGLE_TENANT'),
],
'zitadel' => [
'client_id' => env('ZITADEL_CLIENT_ID'),
'client_secret' => env('ZITADEL_CLIENT_SECRET'),
'redirect' => env('ZITADEL_REDIRECT_URI'),
'base_url' => env('ZITADEL_BASE_URL'),
]
];

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('instance_settings', function (Blueprint $table) {
$table->boolean('is_sponsorship_popup_enabled')->default(true);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('instance_settings', function (Blueprint $table) {
$table->dropColumn('is_sponsorship_popup_enabled');
});
}
};

View File

@@ -17,11 +17,14 @@ class OauthSettingSeeder extends Seeder
$providers = collect([
'azure',
'bitbucket',
'clerk',
'discord',
'github',
'gitlab',
'google',
'authentik',
'infomaniak',
'zitadel',
]);
$isOauthSeeded = OauthSetting::count() > 0;

View File

@@ -3,6 +3,8 @@
"auth.login.authentik": "تسجيل الدخول باستخدام Authentik",
"auth.login.azure": "تسجيل الدخول باستخدام Microsoft",
"auth.login.bitbucket": "تسجيل الدخول باستخدام Bitbucket",
"auth.login.clerk": "تسجيل الدخول باستخدام Clerk",
"auth.login.discord": "تسجيل الدخول باستخدام Discord",
"auth.login.github": "تسجيل الدخول باستخدام GitHub",
"auth.login.gitlab": "تسجيل الدخول باستخدام Gitlab",
"auth.login.google": "تسجيل الدخول باستخدام Google",

View File

@@ -3,6 +3,8 @@
"auth.login.authentik": "Authentik ilə daxil ol",
"auth.login.azure": "Azure ilə daxil ol",
"auth.login.bitbucket": "Bitbucket ilə daxil ol",
"auth.login.clerk": "Clerk ilə daxil ol",
"auth.login.discord": "Discord ilə daxil ol",
"auth.login.github": "Github ilə daxil ol",
"auth.login.gitlab": "GitLab ilə daxil ol",
"auth.login.google": "Google ilə daxil ol",

View File

@@ -2,6 +2,8 @@
"auth.login": "Přihlásit se",
"auth.login.azure": "Přihlásit se pomocí Microsoftu",
"auth.login.bitbucket": "Přihlásit se pomocí Bitbucketu",
"auth.login.clerk": "Přihlásit se pomocí Clerk",
"auth.login.discord": "Přihlásit se pomocí Discordu",
"auth.login.github": "Přihlásit se pomocí GitHubu",
"auth.login.gitlab": "Přihlásit se pomocí Gitlabu",
"auth.login.google": "Přihlásit se pomocí Google",

View File

@@ -2,10 +2,13 @@
"auth.login": "Anmelden",
"auth.login.azure": "Mit Microsoft anmelden",
"auth.login.bitbucket": "Mit Bitbucket anmelden",
"auth.login.clerk": "Mit Clerk anmelden",
"auth.login.discord": "Mit Discord anmelden",
"auth.login.github": "Mit GitHub anmelden",
"auth.login.gitlab": "Mit GitLab anmelden",
"auth.login.google": "Mit Google anmelden",
"auth.login.infomaniak": "Mit Infomaniak anmelden",
"auth.login.zitadel": "Mit Zitadel anmelden",
"auth.already_registered": "Bereits registriert?",
"auth.confirm_password": "Passwort bestätigen",
"auth.forgot_password": "Passwort vergessen",

View File

@@ -3,10 +3,13 @@
"auth.login.authentik": "Login with Authentik",
"auth.login.azure": "Login with Microsoft",
"auth.login.bitbucket": "Login with Bitbucket",
"auth.login.clerk": "Login with Clerk",
"auth.login.discord": "Login with Discord",
"auth.login.github": "Login with GitHub",
"auth.login.gitlab": "Login with Gitlab",
"auth.login.google": "Login with Google",
"auth.login.infomaniak": "Login with Infomaniak",
"auth.login.zitadel": "Login with Zitadel",
"auth.already_registered": "Already registered?",
"auth.confirm_password": "Confirm password",
"auth.forgot_password": "Forgot password",

View File

@@ -2,6 +2,8 @@
"auth.login": "Iniciar Sesión",
"auth.login.azure": "Acceder con Microsoft",
"auth.login.bitbucket": "Acceder con Bitbucket",
"auth.login.clerk": "Acceder con Clerk",
"auth.login.discord": "Acceder con Discord",
"auth.login.github": "Acceder con GitHub",
"auth.login.gitlab": "Acceder con Gitlab",
"auth.login.google": "Acceder con Google",

View File

@@ -2,6 +2,8 @@
"auth.login": "ورود",
"auth.login.azure": "ورود با مایکروسافت",
"auth.login.bitbucket": "ورود با Bitbucket",
"auth.login.clerk": "ورود با Clerk",
"auth.login.discord": "ورود با Discord",
"auth.login.github": "ورود با گیت هاب",
"auth.login.gitlab": "ورود با گیت لب",
"auth.login.google": "ورود با گوگل",

View File

@@ -3,6 +3,8 @@
"auth.login.authentik": "Connexion avec Authentik",
"auth.login.azure": "Connexion avec Microsoft",
"auth.login.bitbucket": "Connexion avec Bitbucket",
"auth.login.clerk": "Connexion avec Clerk",
"auth.login.discord": "Connexion avec Discord",
"auth.login.github": "Connexion avec GitHub",
"auth.login.gitlab": "Connexion avec Gitlab",
"auth.login.google": "Connexion avec Google",

View File

@@ -3,6 +3,8 @@
"auth.login.authentik": "Masuk dengan Authentik",
"auth.login.azure": "Masuk dengan Microsoft",
"auth.login.bitbucket": "Masuk dengan Bitbucket",
"auth.login.clerk": "Masuk dengan Clerk",
"auth.login.discord": "Masuk dengan Discord",
"auth.login.github": "Masuk dengan GitHub",
"auth.login.gitlab": "Masuk dengan Gitlab",
"auth.login.google": "Masuk dengan Google",

View File

@@ -3,6 +3,8 @@
"auth.login.authentik": "Accedi con Authentik",
"auth.login.azure": "Accedi con Microsoft",
"auth.login.bitbucket": "Accedi con Bitbucket",
"auth.login.clerk": "Accedi con Clerk",
"auth.login.discord": "Accedi con Discord",
"auth.login.github": "Accedi con GitHub",
"auth.login.gitlab": "Accedi con Gitlab",
"auth.login.google": "Accedi con Google",

View File

@@ -2,6 +2,8 @@
"auth.login": "ログイン",
"auth.login.azure": "Microsoftでログイン",
"auth.login.bitbucket": "Bitbucketでログイン",
"auth.login.clerk": "Clerkでログイン",
"auth.login.discord": "Discordでログイン",
"auth.login.github": "GitHubでログイン",
"auth.login.gitlab": "Gitlabでログイン",
"auth.login.google": "Googleでログイン",

View File

@@ -3,6 +3,8 @@
"auth.login.authentik": "Logg inn med Authentik",
"auth.login.azure": "Logg inn med Microsoft",
"auth.login.bitbucket": "Logg inn med Bitbucket",
"auth.login.clerk": "Logg inn med Clerk",
"auth.login.discord": "Logg inn med Discord",
"auth.login.github": "Logg inn med GitHub",
"auth.login.gitlab": "Logg inn med Gitlab",
"auth.login.google": "Logg inn med Google",

View File

@@ -3,6 +3,8 @@
"auth.login.authentik": "Entrar com Authentik",
"auth.login.azure": "Entrar com Microsoft",
"auth.login.bitbucket": "Entrar com Bitbucket",
"auth.login.clerk": "Entrar com Clerk",
"auth.login.discord": "Entrar com Discord",
"auth.login.github": "Entrar com GitHub",
"auth.login.gitlab": "Entrar com Gitlab",
"auth.login.google": "Entrar com Google",

View File

@@ -2,6 +2,8 @@
"auth.login": "Entrar",
"auth.login.azure": "Entrar com Microsoft",
"auth.login.bitbucket": "Entrar com Bitbucket",
"auth.login.clerk": "Entrar com Clerk",
"auth.login.discord": "Entrar com Discord",
"auth.login.github": "Entrar com GitHub",
"auth.login.gitlab": "Entrar com Gitlab",
"auth.login.google": "Entrar com Google",

View File

@@ -2,6 +2,8 @@
"auth.login": "Autentificare",
"auth.login.azure": "Autentificare prin Microsoft",
"auth.login.bitbucket": "Autentificare prin Bitbucket",
"auth.login.clerk": "Autentificare prin Clerk",
"auth.login.discord": "Autentificare prin Discord",
"auth.login.github": "Autentificare prin GitHub",
"auth.login.gitlab": "Autentificare prin Gitlab",
"auth.login.google": "Autentificare prin Google",

View File

@@ -2,6 +2,8 @@
"auth.login": "Giriş",
"auth.login.azure": "Microsoft ile Giriş Yap",
"auth.login.bitbucket": "Bitbucket ile Giriş Yap",
"auth.login.clerk": "Clerk ile Giriş Yap",
"auth.login.discord": "Discord ile Giriş Yap",
"auth.login.github": "GitHub ile Giriş Yap",
"auth.login.gitlab": "GitLab ile Giriş Yap",
"auth.login.google": "Google ile Giriş Yap",

View File

@@ -2,6 +2,8 @@
"auth.login": "Đăng Nhập",
"auth.login.azure": "Đăng Nhập Bằng Microsoft",
"auth.login.bitbucket": "Đăng Nhập Bằng Bitbucket",
"auth.login.clerk": "Đăng Nhập Bằng Clerk",
"auth.login.discord": "Đăng Nhập Bằng Discord",
"auth.login.github": "Đăng Nhập Bằng GitHub",
"auth.login.gitlab": "Đăng Nhập Bằng Gitlab",
"auth.login.google": "Đăng Nhập Bằng Google",

View File

@@ -2,6 +2,8 @@
"auth.login": "登录",
"auth.login.azure": "使用 Microsoft 登录",
"auth.login.bitbucket": "使用 Bitbucket 登录",
"auth.login.clerk": "使用 Clerk 登录",
"auth.login.discord": "使用 Discord 登录",
"auth.login.github": "使用 GitHub 登录",
"auth.login.gitlab": "使用 Gitlab 登录",
"auth.login.google": "使用 Google 登录",

View File

@@ -2,6 +2,8 @@
"auth.login": "登入",
"auth.login.azure": "使用 Microsoft 登入",
"auth.login.bitbucket": "使用 Bitbucket 登入",
"auth.login.clerk": "使用 Clerk 登入",
"auth.login.discord": "使用 Discord 登入",
"auth.login.github": "使用 GitHub 登入",
"auth.login.gitlab": "使用 Gitlab 登入",
"auth.login.google": "使用 Google 登入",

View File

@@ -1,10 +1,10 @@
{
"coolify": {
"v4": {
"version": "4.0.0-beta.419"
"version": "4.0.0-beta.420"
},
"nightly": {
"version": "4.0.0-beta.420"
"version": "4.0.0-beta.421"
},
"helper": {
"version": "1.0.8"

BIN
public/heart.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 842 B

After

Width:  |  Height:  |  Size: 842 B

1
public/svgs/miniflux.svg Normal file
View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 431.27 298.29"><title>icon</title><path d="M178.24,117a95.75,95.75,0,0,1,45.48-11q50.36,0,64.16,43.8a139.1,139.1,0,0,1,37.57-31.07q22.13-12.73,49.34-12.73,37.77,0,54,23.08T445,198.38v174c0,4.35.61,7.3,1.83,8.88s3.86,3,7.92,4.14L469,390.14v10.65H384.53q-11,0-15.83-8.29t-4.88-24.86V187.73q0-26.64-5.89-37.88T338.24,138.6q-21.93,0-46.7,26A210,210,0,0,1,294,198.38v174c0,4.35.61,7.3,1.83,8.88s3.86,3,7.92,4.14l14.21,4.74v10.65H233.47q-11,0-15.84-8.29t-4.88-24.86V187.73q0-26.64-5.88-37.88t-19.7-11.25q-21.53,0-44.26,23.68v210.1c0,4.35.6,7.4,1.82,9.17s3.72,3.26,7.52,4.44l13.8,4.15v10.65H37.73V390.14l14.21-4.74q6.09-1.77,7.92-4.14c1.22-1.58,1.83-4.53,1.83-8.88V148.67q0-6.51-1.83-8.88t-7.92-4.15l-14.21-4.73V120.26l97.46-17.76h6.9v41.43A142.53,142.53,0,0,1,178.24,117Z" transform="translate(-37.73 -102.5)"/></svg>

After

Width:  |  Height:  |  Size: 897 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 943.11 911.62"><ellipse cx="471.56" cy="454.28" rx="471.56" ry="454.28" fill="#46509e"/><ellipse cx="471.56" cy="390.28" rx="233.66" ry="207" fill="#37474f"/><path d="M705.22,849c-36.69,21.14-123.09,64.32-240.64,62.57A469.81,469.81,0,0,1,237.89,849V394.76H705.22Z" fill="#37474f"/><path d="M658.81,397.7V873.49a478.12,478.12,0,0,1-374.19,0V397.7c0-95.55,83.78-173,187.1-173S658.81,302.15,658.81,397.7Z" fill="#fff"/><polygon points="565.02 431.68 471.56 514.49 378.09 431.68 565.02 431.68" fill="#46509e"/><ellipse cx="378.09" cy="369.58" rx="23.37" ry="20.7" fill="#37474f"/><ellipse cx="565.02" cy="369.58" rx="23.37" ry="20.7" fill="#37474f"/><path d="M658.49,400.63c0-40-36.6-72.45-81.79-72.45s-81.78,32.41-81.78,72.45a64.79,64.79,0,0,0,7.9,31.05H440.29a64.79,64.79,0,0,0,7.9-31.05c0-40-36.59-72.45-81.78-72.45s-81.79,32.41-81.79,72.45l-46.73-10.35c0-114.31,104.64-207,233.67-207s233.66,92.69,233.66,207Z" fill="#37474f"/></svg>

After

Width:  |  Height:  |  Size: 1018 B

View File

@@ -122,7 +122,7 @@
}
@utility navbar-main {
@apply flex flex-col gap-4 justify-items-start pb-2 border-b-2 border-solid h-fit md:flex-row sm:justify-between dark:border-coolgray-200 md:items-center;
@apply flex flex-col gap-4 justify-items-start pb-2 border-b-2 border-solid h-fit md:flex-row sm:justify-between dark:border-coolgray-200 border-neutral-200 md:items-center;
}
@utility loading {

View File

@@ -3,7 +3,7 @@
@if (isset($text))
<div>{{ $text }}</div>
@endif
<svg class="w-4 h-4 mx-1 ml-3 text-warning animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none"
<svg class="w-4 h-4 mx-1 ml-3 dark:text-warning animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor"

View File

@@ -344,7 +344,7 @@
@if (isInstanceAdmin() || session('impersonating'))
<li>
<a title="Admin" class="menu-item" href="/admin">
<svg class="text-pink-600 icon" viewBox="0 0 256 256"
<svg class="text-pink-500 icon" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M177.62 159.6a52 52 0 0 1-34 34a12.2 12.2 0 0 1-3.6.55a12 12 0 0 1-3.6-23.45a28 28 0 0 0 18.32-18.32a12 12 0 0 1 22.9 7.2ZM220 144a92 92 0 0 1-184 0c0-28.81 11.27-58.18 33.48-87.28a12 12 0 0 1 17.9-1.33l19.69 19.11L127 19.89a12 12 0 0 1 18.94-5.12C168.2 33.25 220 82.85 220 144m-24 0c0-41.71-30.61-78.39-52.52-99.29l-20.21 55.4a12 12 0 0 1-19.63 4.5L80.71 82.36C67 103.38 60 124.06 60 144a68 68 0 0 0 136 0" />
@@ -362,7 +362,7 @@
</li>
@endpersist
@endif
<li>
{{-- <li>
<a title="Onboarding"
class="{{ request()->is('onboarding*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('onboarding') }}">
@@ -372,7 +372,7 @@
</svg>
Onboarding
</a>
</li>
</li> --}}
<li>
<a title="Sponsor us" class="menu-item" href="https://coolify.io/sponsorships"
target="_blank">
@@ -410,7 +410,7 @@
<form action="/logout" method="POST">
@csrf
<button title="Logout" type="submit" class="gap-2 mb-6 menu-item">
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<svg class="icon mr-1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2a9.985 9.985 0 0 1 8 4h-2.71a8 8 0 1 0 .001 12h2.71A9.985 9.985 0 0 1 12 22m7-6v-3h-8v-2h8V8l5 4z" />
</svg>

View File

@@ -6,29 +6,35 @@
x-transition:enter-start="translate-y-full" x-transition:enter-end="translate-y-0"
x-transition:leave="transition ease-in duration-300" x-transition:leave-start="translate-y-0"
x-transition:leave-end="translate-y-full" x-init="setTimeout(() => { bannerVisible = true }, bannerVisibleAfter);"
class="fixed bottom-0 right-0 w-full h-auto duration-300 ease-out sm:px-5 sm:pb-5 w-full z-999"
x-cloak>
<div
class="flex items-center flex-col justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100 lg:p-8 lg:flex-row sm:rounded-sm">
class="fixed bottom-0 right-0 w-full h-auto duration-300 ease-out sm:px-5 sm:pb-5 w-full z-999" x-cloak>
@isset($customActions)
{{ $customActions }}
@else
<div
class="flex flex-col items-start h-full pb-6 text-xs lg:items-center lg:flex-row lg:pb-0 lg:pr-6 lg:space-x-5 dark:text-neutral-300">
@if (isset($icon))
{{ $icon }}
@endif
class="flex lg:items-center flex-col justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 border-neutral-200 dark:bg-coolgray-100 lg:p-8 lg:flex-row sm:rounded-sm">
<div
class="flex flex-col items-start h-full pb-6 text-xs lg:items-center lg:flex-row lg:pb-0 lg:pr-6 lg:space-x-5 dark:text-neutral-300">
@if (isset($icon))
<div class="hidden lg:block">
{{ $icon }}
</div>
@endif
<div class="pt-6 lg:pt-0">
<h4 class="w-full mb-1 text-xl font-bold leading-none -translate-y-1 text-neutral-900 dark:text-white">
{{ $title }}
</h4>
<p class="">{{ $description }}</span></p>
<div class="pt-6 lg:pt-0">
<h4 class="w-full mb-1 text-xl font-bold leading-none -translate-y-1 text-neutral-900 dark:text-white">
{{ $title }}
</h4>
<p>{{ $description }}</span></p>
</div>
</div>
</div>
<button
@if ($buttonText->attributes->whereStartsWith('@click')->first()) @click="bannerVisible=false;{{ $buttonText->attributes->get('@click') }}"
@else
@click="bannerVisible=false;" @endif
@else
@click="bannerVisible=false;" @endif
class="w-full px-4 py-2 text-sm font-medium tracking-wide transition-colors duration-200 rounded-md bg-neutral-100 hover:bg-neutral-200 dark:bg-coolgray-200 lg:w-auto dark:text-neutral-200 dark:hover:bg-coolgray-300 focus:shadow-outline focus:outline-hidden">
{{ $buttonText }}
</button>
</div>
</div>
@endisset
</div>

View File

@@ -13,11 +13,18 @@
<x-status.stopped :status="$resource->status" />
@endif
@if (!str($resource->status)->contains('exited') && $showRefreshButton)
<button title="Refresh Status" wire:click='checkStatus'
<button wire:loading.remove title="Refresh Status" wire:click='checkStatus'
class="mx-1 dark:hover:fill-white fill-black dark:fill-warning">
<svg class="w-4 h-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />
</svg>
</button>
<button wire:loading title="Refreshing Status" wire:click='checkStatus'
class="mx-1 dark:hover:fill-white fill-black dark:fill-warning">
<svg class="w-4 h-4 animate-spin" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />
</svg>
</button>
@endif

View File

@@ -1,5 +1,7 @@
@if (str($complexStatus)->contains('running'))
<x-status.running :status="$complexStatus" />
@elseif(str($complexStatus)->contains('starting'))
<x-status.restarting :status="$complexStatus" />
@elseif(str($complexStatus)->contains('restarting'))
<x-status.restarting :status="$complexStatus" />
@elseif(str($complexStatus)->contains('degraded'))
@@ -8,11 +10,18 @@
<x-status.stopped :status="$complexStatus" />
@endif
@if (!str($complexStatus)->contains('exited') && $showRefreshButton)
<button title="Refresh Status" wire:click='checkStatus'
<button wire:loading.remove title="Refresh Status" wire:click='checkStatus'
class="mx-1 dark:hover:fill-white fill-black dark:fill-warning">
<svg class="w-4 h-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />
</svg>
</button>
<button wire:loading title="Refreshing Status" wire:click='checkStatus'
class="mx-1 dark:hover:fill-white fill-black dark:fill-warning">
<svg class="w-4 h-4 animate-spin" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 2a10.016 10.016 0 0 0-7 2.877V3a1 1 0 1 0-2 0v4.5a1 1 0 0 0 1 1h4.5a1 1 0 0 0 0-2H6.218A7.98 7.98 0 0 1 20 12a1 1 0 0 0 2 0A10.012 10.012 0 0 0 12 2zm7.989 13.5h-4.5a1 1 0 0 0 0 2h2.293A7.98 7.98 0 0 1 4 12a1 1 0 0 0-2 0a9.986 9.986 0 0 0 16.989 7.133V21a1 1 0 0 0 2 0v-4.5a1 1 0 0 0-1-1z" />
</svg>
</button>
@endif

View File

@@ -4,7 +4,7 @@
</x-slot>
<section class="flex flex-col h-full lg:items-center lg:justify-center">
<div
class="flex flex-col items-center justify-center p-10 mx-2 mt-10 bg-white border rounded-lg shadow-sm lg:p-20 dark:bg-transparent dark:border-none max-w-7xl ">
class="flex flex-col items-center justify-center p-10 mx-2 mt-10 bg-white border rounded-lg shadow-sm lg:p-20 dark:bg-transparent dark:border-none max-w-7xl border-neutral-200">
@if ($currentState === 'welcome')
<h1 class="text-3xl font-bold lg:text-5xl">Welcome to Coolify</h1>
<div class="py-6 text-center lg:text-xl">Let me help you set up the basics.</div>
@@ -21,8 +21,8 @@
Git
integrations, deploy databases and services, monitor these resources with notifications and
alerts
without vendor lock-in. <br/>
<a href="https://coolify.io" class="dark:text-white hover:underline">Coolify Home</a>.
without vendor lock-in. <br />
<a href="https://coolify.io" class="dark:text-white hover:underline">Coolify Home</a>.
<br><br>
<span class="text-xl">
<x-highlighted text="Self-hosting with superpowers!" /></span>
@@ -277,8 +277,7 @@
<x-forms.input required placeholder="Choose a name for your Server. Could be anything."
label="Name" id="remoteServerName" wire:model="remoteServerName" />
<x-forms.input placeholder="Description, so others will know more about this."
label="Description" id="remoteServerDescription"
wire:model="remoteServerDescription" />
label="Description" id="remoteServerDescription" wire:model="remoteServerDescription" />
<x-forms.input required placeholder="127.0.0.1" label="IP Address" id="remoteServerHost"
wire:model="remoteServerHost" />
<div x-data="{ showAdvanced: false }" class="flex flex-col gap-2">
@@ -323,7 +322,8 @@
</x-slot:actions>
<x-slot:explanation>
<p>This will install the latest Docker Engine on your server, configure a few things to be able
to run optimal.<br><br>Minimum Docker Engine version is: {{ $minDockerVersion }}<br><br>To manually install
to run optimal.<br><br>Minimum Docker Engine version is: {{ $minDockerVersion }}<br><br>To
manually install
Docker
Engine, check <a target="_blank" class="underline dark:text-warning"
href="https://docs.docker.com/engine/install/#server">this

View File

@@ -17,9 +17,9 @@
<section>
<h3 class="pb-2">Projects</h3>
@if ($projects->count() > 0)
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
@foreach ($projects as $project)
<div class="gap-2 border border-transparent cursor-pointer box group"
<div class="gap-2 border cursor-pointer box group"
wire:click="navigateToProject('{{ $project->uuid }}')">
<div class="flex flex-1 mx-6">
<div class="flex flex-col justify-center flex-1">
@@ -68,7 +68,6 @@
<a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}"
@class([
'gap-2 border cursor-pointer box group',
'border-transparent' => $server->settings->is_reachable,
'border-red-500' => !$server->settings->is_reachable,
])>
<div class="flex flex-col justify-center mx-6">
@@ -124,7 +123,7 @@
@if ($servers->count() > 0 && $projects->count() > 0)
<section>
<div class="flex items-center gap-2">
<div class="flex items-start gap-2">
<h3 class="pb-2">Deployments</h3>
@if (count($deploymentsPerServer) > 0)
<x-loading />

View File

@@ -4,9 +4,10 @@
notification: true,
realtime: false,
},
isDevelopment: {{ isDev() ? 'true' : 'false' }},
init() {
this.popups.sponsorship = localStorage.getItem('popupSponsorship') !== 'false';
this.popups.notification = localStorage.getItem('popupNotification') !== 'false';
this.popups.sponsorship = this.shouldShowMonthlyPopup('popupSponsorship');
this.popups.notification = this.shouldShowMonthlyPopup('popupNotification');
this.popups.realtime = localStorage.getItem('popupRealtime');
let checkNumber = 1;
@@ -31,6 +32,35 @@
}
}, 2000);
}
},
shouldShowMonthlyPopup(storageKey) {
const disabledTimestamp = localStorage.getItem(storageKey);
// If never disabled, show the popup
if (!disabledTimestamp || disabledTimestamp === 'false') {
return true;
}
// If disabled timestamp is not a valid number, show the popup
const disabledTime = parseInt(disabledTimestamp);
if (isNaN(disabledTime)) {
return true;
}
const now = new Date();
const disabledDate = new Date(disabledTime);
{{-- if (this.isDevelopment) {
// In development: check if 10 seconds have passed
const timeDifference = now.getTime() - disabledDate.getTime();
const tenSecondsInMs = 10 * 1000;
return timeDifference >= tenSecondsInMs;
} else { --}}
// In production: check if we're in a different month or year
const isDifferentMonth = now.getMonth() !== disabledDate.getMonth() ||
now.getFullYear() !== disabledDate.getFullYear();
return isDifferentMonth;
{{-- } --}}
}
}">
@auth
@@ -56,35 +86,60 @@
@endif
</span>
@endauth
<span x-show="popups.sponsorship">
<x-popup>
<x-slot:title>
Love Coolify as we do?
</x-slot:title>
<x-slot:icon>
<img src="https://cdn-icons-png.flaticon.com/512/8236/8236748.png"
class="w-8 h-8 sm:w-12 sm:h-12 lg:w-16 lg:h-16">
</x-slot:icon>
<x-slot:description>
<span>Please
consider donating on <a href="https://github.com/sponsors/coollabsio"
class="text-xs underline dark:text-white">GitHub</a> or <a
href="https://opencollective.com/coollabsio"
class="text-xs underline dark:text-white">OpenCollective</a>.<br><br></span>
<span>It enables us to keep creating features without paywalls, ensuring our work remains free and
open.</span>
</x-slot:description>
<x-slot:button-text @click="disableSponsorship()">
Disable This Popup
</x-slot:button-text>
</x-popup>
</span>
@if (instanceSettings()->is_sponsorship_popup_enabled && !isCloud())
<span x-show="popups.sponsorship">
<x-popup>
<x-slot:customActions>
<div
class="flex md:flex-row flex-col max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 border-neutral-200 dark:bg-coolgray-100 lg:p-8 lg:pb-4 sm:rounded-sm gap-2">
<div class="md:block hidden">
<img src="{{ asset('heart.png') }}" class="w-20 h-20">
</div>
<div class="flex flex-col gap-2 lg:px-10 px-1">
<div class="lg:text-xl text-md dark:text-white font-bold">Love Coolify? Support our work.
</div>
<div class="lg:text-sm text-xs dark:text-white">
We are already profitable thanks to <span class="font-bold text-pink-500">YOU</span>
but...<br />We
would
like to
make
more cool features.
</div>
<div class="lg:text-sm text-xs dark:text-white pt-2 ">
For this we need your help to support our work financially.
</div>
</div>
<div class="flex flex-col gap-2 text-center md:mx-auto lg:py-0 pt-2">
<x-forms.button isHighlighted class="md:w-36 w-full"><a target="_blank"
href="https://github.com/sponsors/coollabsio"
class="font-bold dark:text-white">GitHub
Sponsors</a></x-forms.button>
<x-forms.button isHighlighted class="md:w-36 w-full"><a target="_blank"
href="https://opencollective.com/coollabsio/donate?interval=month&amount=10&name=&legalName=&email="
class="font-bold dark:text-white">Open
Collective</a></x-forms.button>
<x-forms.button isHighlighted class="md:w-36 w-full"><a
href="https://donate.stripe.com/8x2bJ104ifmB9kB45u38402" target="_blank"
class="font-bold dark:text-white">Stripe</a></x-forms.button>
<div class="pt-4 dark:text-white hover:underline cursor-pointer lg:text-base text-xs"
@click="bannerVisible=false;disableSponsorship()">
Maybe next time
</div>
</div>
</div>
</x-slot:customActions>
</x-popup>
</span>
@endif
@if (currentTeam()->subscriptionPastOverDue())
<x-banner :closable=false>
<div><span class="font-bold text-red-500">WARNING:</span> Your subscription is in over-due. If your latest
<div><span class="font-bold text-red-500">WARNING:</span> Your subscription is in over-due. If your
latest
payment is not paid within a week, all automations <span class="font-bold text-red-500">will
be deactivated</span>. Visit <a href="{{ route('subscription.show') }}"
class="underline dark:text-white">/subscription</a> to check your subscription status or pay your
class="underline dark:text-white">/subscription</a> to check your subscription status or pay
your
invoice (or check your email for the invoice).
</div>
</x-banner>
@@ -128,11 +183,13 @@
@endif
<script>
function disableSponsorship() {
localStorage.setItem('popupSponsorship', false);
// Store current timestamp instead of just 'false'
localStorage.setItem('popupSponsorship', Date.now().toString());
}
function disableNotification() {
localStorage.setItem('popupNotification', false);
// Store current timestamp instead of just 'false'
localStorage.setItem('popupNotification', Date.now().toString());
}
function disableRealtime() {

View File

@@ -35,7 +35,7 @@
Select events for which you would like to receive Discord notifications.
</p>
<div class="flex flex-col gap-4 max-w-2xl">
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Deployments</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="deploymentSuccessDiscordNotifications"
@@ -47,7 +47,7 @@
id="statusChangeDiscordNotifications" label="Container Status Changes" />
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Backups</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="backupSuccessDiscordNotifications"
@@ -56,7 +56,7 @@
label="Backup Failure" />
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Scheduled Tasks</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="scheduledTaskSuccessDiscordNotifications"
@@ -65,7 +65,7 @@
label="Scheduled Task Failure" />
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Server</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="dockerCleanupSuccessDiscordNotifications"

View File

@@ -34,7 +34,7 @@
</div>
@endif
@if (!$useInstanceEmailSettings)
<div class="flex gap-4">
<div class="flex gap-2">
<x-forms.input required id="smtpFromName" helper="Name used in emails." label="From Name" />
<x-forms.input required id="smtpFromAddress" helper="Email address used in emails."
label="From Address" />
@@ -54,7 +54,8 @@
@endif
@if (!$useInstanceEmailSettings)
<div class="flex flex-col gap-4">
<form wire:submit='submitSmtp' class="p-4 border dark:border-coolgray-300 flex flex-col gap-2">
<form wire:submit='submitSmtp'
class="p-4 border dark:border-coolgray-300 border-neutral-200 flex flex-col gap-2">
<div class="flex items-center gap-2">
<h3>SMTP Server</h3>
<x-forms.button type="submit">
@@ -85,7 +86,8 @@
</div>
</div>
</form>
<form wire:submit='submitResend' class="p-4 border dark:border-coolgray-300 flex flex-col gap-2">
<form wire:submit='submitResend'
class="p-4 border dark:border-coolgray-300 border-neutral-200 flex flex-col gap-2">
<div class="flex items-center gap-2">
<h3>Resend</h3>
<x-forms.button type="submit">
@@ -112,7 +114,7 @@
Select events for which you would like to receive email notifications.
</p>
<div class="flex flex-col gap-4 max-w-2xl">
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Deployments</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="deploymentSuccessEmailNotifications"
@@ -124,7 +126,7 @@
id="statusChangeEmailNotifications" label="Container Status Changes" />
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Backups</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="backupSuccessEmailNotifications"
@@ -133,7 +135,7 @@
label="Backup Failure" />
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Scheduled Tasks</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="scheduledTaskSuccessEmailNotifications"
@@ -142,7 +144,7 @@
label="Scheduled Task Failure" />
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Server</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="dockerCleanupSuccessEmailNotifications"

View File

@@ -37,7 +37,7 @@
Select events for which you would like to receive Pushover notifications.
</p>
<div class="flex flex-col gap-4 max-w-2xl">
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Deployments</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="deploymentSuccessPushoverNotifications"
@@ -49,7 +49,7 @@
id="statusChangePushoverNotifications" label="Container Status Changes" />
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Backups</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="backupSuccessPushoverNotifications"
@@ -58,7 +58,7 @@
label="Backup Failure" />
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Scheduled Tasks</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="scheduledTaskSuccessPushoverNotifications"
@@ -67,7 +67,7 @@
label="Scheduled Task Failure" />
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Server</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="dockerCleanupSuccessPushoverNotifications"

View File

@@ -32,7 +32,7 @@
Select events for which you would like to receive Slack notifications.
</p>
<div class="flex flex-col gap-4 max-w-2xl">
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Deployments</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="deploymentSuccessSlackNotifications"
@@ -44,14 +44,14 @@
id="statusChangeSlackNotifications" label="Container Status Changes" />
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Backups</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="backupSuccessSlackNotifications" label="Backup Success" />
<x-forms.checkbox instantSave="saveModel" id="backupFailureSlackNotifications" label="Backup Failure" />
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Scheduled Tasks</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="scheduledTaskSuccessSlackNotifications"
@@ -60,7 +60,7 @@
label="Scheduled Task Failure" />
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="font-medium mb-3">Server</h3>
<div class="flex flex-col gap-1.5 pl-1">
<x-forms.checkbox instantSave="saveModel" id="dockerCleanupSuccessSlackNotifications"

View File

@@ -37,7 +37,7 @@
Select events for which you would like to receive Telegram notifications.
</p>
<div class="flex flex-col gap-4 ">
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="text-lg font-medium mb-3">Deployments</h3>
<div class="flex flex-col gap-1.5 pl-1">
<div class="pl-1 flex gap-2">
@@ -67,7 +67,7 @@
</div>
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="text-lg font-medium mb-3">Backups</h3>
<div class="flex flex-col gap-1.5 pl-1">
<div class="pl-1 flex gap-2">
@@ -90,7 +90,7 @@
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="text-lg font-medium mb-3">Scheduled Tasks</h3>
<div class="flex flex-col gap-1.5 pl-1">
<div class="pl-1 flex gap-2">
@@ -113,7 +113,7 @@
</div>
</div>
<div class="border dark:border-coolgray-300 p-4 rounded-lg">
<div class="border dark:border-coolgray-300 border-neutral-200 p-4 rounded-lg">
<h3 class="text-lg font-medium mb-3">Server</h3>
<div class="flex flex-col gap-1.5 pl-1">
<div class="pl-1 flex gap-2">

View File

@@ -19,7 +19,7 @@
<h2>Change Password</h2>
<x-forms.button type="submit" label="Save">Save</x-forms.button>
</div>
<div class="text-xs font-bold text-warning pb-2">Resetting the password will logout all sessions.</div>
<div class="text-xs font-bold dark:text-warning pb-2">Resetting the password will logout all sessions.</div>
<div class="flex flex-col gap-2">
<x-forms.input id="current_password" label="Current Password" required type="password" />
<div class="flex gap-2">

View File

@@ -8,16 +8,16 @@
</a>
<a class="{{ request()->routeIs('project.application.deployment.index') ? 'dark:text-white' : '' }}"
href="{{ route('project.application.deployment.index', $parameters) }}">
<button>Deployments</button>
Deployments
</a>
<a class="{{ request()->routeIs('project.application.logs') ? 'dark:text-white' : '' }}"
href="{{ route('project.application.logs', $parameters) }}">
<button>Logs</button>
Logs
</a>
@if (!$application->destination->server->isSwarm())
<a class="{{ request()->routeIs('project.application.command') ? 'dark:text-white' : '' }}"
href="{{ route('project.application.command', $parameters) }}">
<button>Terminal</button>
Terminal
</a>
@endif
<x-applications.links :application="$application" />

View File

@@ -5,11 +5,11 @@
</div>
<div class="pb-4 ">You can easily rollback to a previously built (local) images
quickly.</div>
<div wire:target='loadImages'>
<div wire:target='loadImages' wire:loading.remove>
<div class="flex flex-wrap">
@forelse ($images as $image)
<div class="w-2/4 p-2">
<div class="bg-white border rounded-sm dark:border-black dark:bg-coolgray-100">
<div class="bg-white border rounded-sm dark:border-black dark:bg-coolgray-100 border-neutral-200">
<div class="p-2">
<div class="">
@if (data_get($image, 'is_current'))
@@ -31,7 +31,7 @@
Rollback
</x-forms.button>
@else
<x-forms.button class="bg-coolgray-100"
<x-forms.button class="dark:bg-coolgray-100"
wire:click="rollbackImage('{{ data_get($image, 'tag') }}')">
Rollback
</x-forms.button>
@@ -44,4 +44,5 @@
@endforelse
</div>
</div>
<div wire:target='loadImages' wire:loading>Loading available docker images...</div>
</div>

View File

@@ -5,7 +5,7 @@
<h1>Backups</h1>
<livewire:project.shared.configuration-checker :resource="$database" />
<livewire:project.database.heading :database="$database" />
<div class="pt-6">
<div>
<livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" />
<livewire:project.database.backup-executions :backup="$backup" />
</div>

View File

@@ -11,16 +11,16 @@
class="flex overflow-x-scroll shrink-0 gap-6 items-center whitespace-nowrap sm:overflow-x-hidden scrollbar min-h-10">
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.configuration', $parameters) }}">
<button>Configuration</button>
Configuration
</a>
<a class="{{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.logs', $parameters) }}">
<button>Logs</button>
Logs
</a>
<a class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.command', $parameters) }}">
<button>Terminal</button>
Terminal
</a>
@if (
$database->getMorphClass() === 'App\Models\StandalonePostgresql' ||
@@ -29,7 +29,7 @@
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
<a class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.backup.index', $parameters) }}">
<button>Backups</button>
Backups
</a>
@endif
</nav>
@@ -60,7 +60,7 @@
'If the database is currently in use data could be lost.',
'All non-persistent data of this database (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the database again).',
]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Stop Database">
step1ButtonText="Continue" step2ButtonText="Confirm">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"

View File

@@ -78,7 +78,7 @@
<h2>Services</h2>
<x-forms.button x-on:click="loadResources">Reload List</x-forms.button>
</div>
<div class="pb-4 text-xs">Trademarks Policy: The respective trademarks mentioned here are owned by
<div class="py-4 text-xs">Trademarks Policy: The respective trademarks mentioned here are owned by
the
respective
companies, and use of them does not imply any affiliation or endorsement.<br>Find more services

View File

@@ -72,6 +72,9 @@
<template x-if="item.status.startsWith('exited')">
<div title="exited" class="bg-error badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('starting')">
<div title="starting" class="bg-warning badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="bg-warning badge-dashboard"></div>
</template>
@@ -118,6 +121,9 @@
<template x-if="item.status.startsWith('exited')">
<div title="exited" class="bg-error badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('starting')">
<div title="starting" class="bg-warning badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="bg-warning badge-dashboard"></div>
</template>
@@ -164,6 +170,9 @@
<template x-if="item.status.startsWith('exited')">
<div title="exited" class="bg-error badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('starting')">
<div title="starting" class="bg-warning badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="bg-warning badge-dashboard"></div>
</template>

View File

@@ -40,7 +40,7 @@
</x-forms.button>
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Stop Service">
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -68,7 +68,7 @@
</x-forms.button>
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Stop Service">
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -96,7 +96,7 @@
@else
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Stop Service">
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -136,7 +136,7 @@
<script>
$wire.$on('stopEvent', () => {
$wire.$dispatch('info',
'Gracefully stopping service.<br/>It could take a while depending on the service.');
'Gracefully stopping service.<br/><br/>It could take a while depending on the service.');
$wire.$call('stop');
});
$wire.$on('startEvent', async () => {
@@ -163,7 +163,7 @@
return;
}
$wire.$dispatch('info',
'Gracefully stopping service.<br/>It could take a while depending on the service.');
'Gracefully stopping service.<br/><br/>It could take a while depending on the service.');
window.dispatchEvent(new CustomEvent('startservice'));
$wire.$call('restart');
});

View File

@@ -39,10 +39,15 @@
label="Image" id="application.image"></x-forms.input>
</div>
</div>
<h3 class="pt-2">Advanced</h3>
<div class="w-96">
<x-forms.checkbox instantSave id="application.is_gzip_enabled" label="Enable gzip compression"
helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this." />
<h3 class="py-2 pt-4">Advanced</h3>
<div class="w-96 flex flex-col gap-1">
@if (str($application->image)->contains('pocketbase'))
<x-forms.checkbox instantSave id="application.is_gzip_enabled" label="Enable Gzip Compression"
helper="Pocketbase does not need gzip compression, otherwise SSE will not work." disabled />
@else
<x-forms.checkbox instantSave id="application.is_gzip_enabled" label="Enable Gzip Compression"
helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this." />
@endif
<x-forms.checkbox instantSave id="application.is_stripprefix_enabled" label="Strip Prefixes"
helper="Strip Prefix is used to remove prefixes from paths. Like /api/ to /api." />
<x-forms.checkbox instantSave label="Exclude from service status"

View File

@@ -2,7 +2,7 @@
<form wire:submit='submit' @class([
'flex flex-col items-center gap-4 p-4 bg-white border lg:items-start dark:bg-base',
'border-error' => $is_really_required,
'dark:border-coolgray-300' => !$is_really_required,
'dark:border-coolgray-300 border-neutral-200' => !$is_really_required,
])>
@if ($isLocked)
<div class="flex flex-1 w-full gap-2">

View File

@@ -20,23 +20,21 @@
@if (count($containers) === 0)
<div>No containers are running or terminal access is disabled on this server.</div>
@else
@foreach ($containers as $container)
<form class="w-full flex gap-2 items-end" wire:submit="$dispatchSelf('connectToContainer')">
<x-forms.select label="Container" id="container" required wire:model="selected_container">
@foreach ($containers as $container)
@if ($loop->first)
<option disabled value="default">Select a container</option>
@endif
<option value="{{ data_get($container, 'container.Names') }}">
{{ data_get($container, 'container.Names') }}
({{ data_get($container, 'server.name') }})
</option>
@endforeach
</x-forms.select>
<x-forms.button :disabled="$isConnecting"
type="submit">{{ $isConnecting ? 'Connecting...' : 'Connect' }}</x-forms.button>
</form>
@endforeach
<form class="w-full flex gap-2 items-end" wire:submit="$dispatchSelf('connectToContainer')">
<x-forms.select label="Container" id="container" required wire:model="selected_container">
@foreach ($containers as $container)
@if ($loop->first)
<option disabled value="default">Select a container</option>
@endif
<option value="{{ data_get($container, 'container.Names') }}">
{{ data_get($container, 'container.Names') }}
({{ data_get($container, 'server.name') }})
</option>
@endforeach
</x-forms.select>
<x-forms.button :disabled="$isConnecting"
type="submit">{{ $isConnecting ? 'Connecting...' : 'Connect' }}</x-forms.button>
</form>
<div class="mx-auto w-full">
<livewire:project.shared.terminal />
</div>

View File

@@ -1,4 +1,4 @@
<div class="p-4 my-4 border dark:border-coolgray-200">
<div class="p-4 my-4 border dark:border-coolgray-200 border-neutral-200">
<div x-init="$wire.getLogs" id="screen" x-data="{
fullscreen: false,
alwaysScroll: false,
@@ -56,7 +56,7 @@
<x-forms.checkbox instantSave label="Include Timestamps" id="showTimeStamps"></x-forms.checkbox>
</form>
<div :class="fullscreen ? 'fullscreen' : 'relative w-full py-4 mx-auto'">
<div class="flex overflow-y-auto flex-col-reverse px-4 py-2 w-full bg-white dark:text-white dark:bg-coolgray-100 scrollbar dark:border-coolgray-300"
<div class="flex overflow-y-auto flex-col-reverse px-4 py-2 w-full bg-white dark:text-white dark:bg-coolgray-100 scrollbar dark:border-coolgray-300 border-neutral-200"
:class="fullscreen ? '' : 'max-h-96 border border-solid rounded-sm'">
<div :class="fullscreen ? 'fixed top-4 right-4' : 'absolute top-6 right-0'">
<div class="flex justify-end gap-4" :class="fullscreen ? 'fixed' : ''"

View File

@@ -6,7 +6,8 @@
<div class="pb-4">Define how your resource's health should be checked.</div>
<div class="flex flex-col gap-4">
@if ($resource->custom_healthcheck_found)
<div class="text-warning">A custom health check has been found and will be used until you enable this.</div>
<div class="dark:text-warning">A custom health check has been found and will be used until you enable this.
</div>
@endif
<div class="w-32">
<x-forms.checkbox instantSave id="resource.health_check_enabled" label="Enabled" />
@@ -25,8 +26,8 @@
<x-forms.input id="resource.health_check_response_text" placeholder="OK" label="Response Text" />
</div>
<div class="flex gap-2">
<x-forms.input min=1 type="number" id="resource.health_check_interval" placeholder="30" label="Interval (s)"
required />
<x-forms.input min="1" type="number" id="resource.health_check_interval" placeholder="30"
label="Interval (s)" required />
<x-forms.input type="number" id="resource.health_check_timeout" placeholder="30" label="Timeout (s)"
required />
<x-forms.input type="number" id="resource.health_check_retries" placeholder="3" label="Retries" required />

View File

@@ -9,7 +9,7 @@
<div>
<h2>Logs</h2>
@if (str($status)->contains('exited'))
<div class="pt-2">The resource is not running.</div>
<div class="pt-4">The resource is not running.</div>
@else
<div class="pt-2" wire:loading wire:target="loadAllContainers">
Loading containers...
@@ -44,7 +44,7 @@
<div>
<h2>Logs</h2>
@if (str($status)->contains('exited'))
<div class="pt-2">The resource is not running.</div>
<div class="pt-4">The resource is not running.</div>
@else
<div class="pt-2" wire:loading wire:target="loadAllContainers">
Loading containers...
@@ -68,7 +68,7 @@
<div>
<h2>Logs</h2>
@if (str($status)->contains('exited'))
<div class="pt-2">The resource is not running.</div>
<div class="pt-4">The resource is not running.</div>
@else
<div class="pt-2" wire:loading wire:target="loadAllContainers">
Loading containers...

View File

@@ -9,14 +9,13 @@
<div>API is disabled. If you want to use the API, please enable it in the <a
href="{{ route('settings.index') }}" class="underline dark:text-white">Settings</a> menu.</div>
@else
<div>Tokens are created with the current team as scope. You will only have access to this team's resources.
</div>
<div>Tokens are created with the current team as scope.</div>
</div>
<h3>New Token</h3>
<form class="flex flex-col gap-2 pt-4" wire:submit='addNewToken'>
<div class="flex gap-2 items-end">
<form class="flex flex-col gap-2" wire:submit='addNewToken'>
<div class="flex gap-2 items-end w-96">
<x-forms.input required id="description" label="Description" />
<x-forms.button type="submit">Create New Token</x-forms.button>
<x-forms.button type="submit">Create</x-forms.button>
</div>
<div class="flex">
Permissions
@@ -37,18 +36,18 @@
helper="Root access, be careful!" :checked="in_array('root', $permissions)"></x-forms.checkbox>
@if (!in_array('root', $permissions))
<x-forms.checkbox label="write" wire:model.live="permissions" domValue="write"
helper="Write access to all resources" :checked="in_array('write', $permissions)"></x-forms.checkbox>
helper="Write access to all resources." :checked="in_array('write', $permissions)"></x-forms.checkbox>
<x-forms.checkbox label="deploy" wire:model.live="permissions" domValue="deploy"
helper="Can trigger deploy webhooks" :checked="in_array('deploy', $permissions)"></x-forms.checkbox>
helper="Can trigger deploy webhooks." :checked="in_array('deploy', $permissions)"></x-forms.checkbox>
<x-forms.checkbox label="read" domValue="read" wire:model.live="permissions" domValue="read"
:checked="in_array('read', $permissions)"></x-forms.checkbox>
<x-forms.checkbox label="read:sensitive" wire:model.live="permissions" domValue="read:sensitive"
helper="Responses will include secrets, logs, passwords, and compose file contents"
helper="Responses will include secrets, logs, passwords, and compose file contents."
:checked="in_array('read:sensitive', $permissions)"></x-forms.checkbox>
@endif
</div>
@if (in_array('root', $permissions))
<div class="font-bold text-warning">Root access, be careful!</div>
<div class="font-bold dark:text-warning">Root access, be careful!</div>
@endif
</form>
@if (session()->has('token'))

View File

@@ -13,7 +13,7 @@
</div>
<div>Sends service logs to 3rd party tools.</div>
<div class="flex flex-col gap-4 pt-4">
<div class="p-4 border dark:border-coolgray-300">
<div class="p-4 border dark:border-coolgray-300 border-neutral-200">
<form wire:submit='submit("newrelic")' class="flex flex-col">
<h3>New Relic</h3>
<div class="w-32">

View File

@@ -29,7 +29,7 @@
@endif
<div wire:loading wire:target="checkProxy" class="badge badge-warning"></div>
<div wire:loading wire:target="checkProxy"
class="pl-2 pr-1 text-xs font-bold tracking-wider text-warning">
class="pl-2 pr-1 text-xs font-bold tracking-wider dark:text-warning">
Checking Ports Availability...
</div>
@if ($proxyStatus !== 'exited')
@@ -60,7 +60,7 @@
href="{{ route('server.show', [
'server_uuid' => data_get($server, 'uuid'),
]) }}">
<button>Configuration</button>
Configuration
</a>
@if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
@@ -68,26 +68,26 @@
href="{{ route('server.proxy', [
'server_uuid' => data_get($server, 'uuid'),
]) }}">
<button>Proxy</button>
Proxy
</a>
@endif
<a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}"
href="{{ route('server.resources', [
'server_uuid' => data_get($server, 'uuid'),
]) }}">
<button>Resources</button>
Resources
</a>
<a class="{{ request()->routeIs('server.command') ? 'dark:text-white' : '' }}"
href="{{ route('server.command', [
'server_uuid' => data_get($server, 'uuid'),
]) }}">
<button>Terminal</button>
Terminal
</a>
<a class="{{ request()->routeIs('server.security.patches') ? 'dark:text-white' : '' }}"
href="{{ route('server.security.patches', [
'server_uuid' => data_get($server, 'uuid'),
]) }}">
<button>Security</button>
Security
</a>
</nav>
<div class="order-first sm:order-last">

View File

@@ -27,7 +27,9 @@
@endforeach
</x-forms.select>
<div class="">
<x-forms.checkbox instantSave type="checkbox" id="is_build_server" label="Use it as a build server?" />
<x-forms.checkbox instantSave type="checkbox" id="is_build_server"
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>

View File

@@ -13,8 +13,8 @@
<div>Here you can find all resources that are managed by Coolify.</div>
<div class="flex flex-row gap-4 py-10">
<div @class([
'box-without-bg cursor-pointer bg-coolgray-100 text-white w-full text-center items-center justify-center',
'bg-coollabs' => $activeTab === 'managed',
'box-without-bg cursor-pointer dark:bg-coolgray-100 dark:text-white w-full text-center items-center justify-center',
'dark:bg-coollabs bg-coollabs text-white' => $activeTab === 'managed',
]) wire:click="loadManagedContainers">
Managed
<div class="flex flex-col items-center justify-center">
@@ -22,8 +22,8 @@
</div>
</div>
<div @class([
'box-without-bg cursor-pointer bg-coolgray-100 text-white w-full text-center items-center justify-center',
'bg-coollabs' => $activeTab === 'unmanaged',
'box-without-bg cursor-pointer dark:bg-coolgray-100 dark:text-white w-full text-center items-center justify-center',
'dark:bg-coollabs bg-coollabs text-white' => $activeTab === 'unmanaged',
]) wire:click="loadUnmanagedContainers">
Unmanaged
<div class="flex flex-col items-center justify-center">

View File

@@ -4,7 +4,7 @@
</x-slot>
<x-settings.navbar />
<div class="flex flex-col">
<div class="flex items-center gap-2">
<div class="flex items-center gap-2 pb-2">
<h2>Backup</h2>
@if (isset($database) && $server->isFunctional())
<x-forms.button type="submit" wire:click="submit">

View File

@@ -22,13 +22,13 @@
@endif
</div>
<div class="pb-4">Instance wide email settings for password resets, invitations, etc.</div>
<div class="flex gap-4">
<div class="flex gap-2">
<x-forms.input required id="smtpFromName" helper="Name used in emails." label="From Name" />
<x-forms.input required id="smtpFromAddress" helper="Email address used in emails." label="From Address" />
</div>
</form>
<div class="flex flex-col gap-4">
<div class="p-4 border dark:border-coolgray-300">
<div class="p-4 border dark:border-coolgray-300 border-neutral-200">
<form wire:submit.prevent="submitSmtp" class="flex flex-col">
<div class="flex gap-2">
<h3>SMTP Server</h3>
@@ -58,7 +58,7 @@
</div>
</form>
</div>
<div class="p-4 border dark:border-coolgray-300">
<div class="p-4 border dark:border-coolgray-300 border-neutral-200">
<form wire:submit.prevent="submitResend" class="flex flex-col">
<div class="flex gap-2">
<h3>Resend</h3>

View File

@@ -5,7 +5,7 @@
<x-settings.navbar />
<form wire:submit='submit' class="flex flex-col">
<div class="flex flex-col">
<div class="flex items-center gap-2">
<div class="flex items-center gap-2 pb-2">
<h2>Authentication</h2>
<x-forms.button type="submit">
Save
@@ -15,7 +15,7 @@
</div>
<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">
<div class="p-4 border dark:border-coolgray-300 border-neutral-200">
<h3>{{ ucfirst($oauth_setting->provider) }}</h3>
<div class="w-32">
<x-forms.checkbox instantSave="instantSave('{{ $oauth_setting->provider }}')"
@@ -26,8 +26,8 @@
label="Client ID" />
<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" />
<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" />
@@ -37,7 +37,10 @@
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')
@if (
$oauth_setting->provider == 'authentik' ||
$oauth_setting->provider == 'clerk' ||
$oauth_setting->provider == 'zitadel')
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.base_url"
label="Base URL" />
@endif

View File

@@ -4,7 +4,7 @@
</x-slot>
<x-settings.navbar />
<form wire:submit='submit' class="flex flex-col">
<div class="flex items-center gap-2">
<div class="flex items-center gap-2 pb-2">
<h2>Configuration</h2>
<x-forms.button type="submit">
Save
@@ -97,7 +97,7 @@
helper="Allowed IP lists for the API. A comma separated list of IPs. Empty means you allow from everywhere."
placeholder="1.1.1.1,8.8.8.8" />
<h4 class="pt-6">Update</h4>
<div class="text-right md:w-96">
<div class="text-right md:w-96 pb-4">
@if (!is_null(config('constants.coolify.autoupdate', null)))
<div class="text-right md:w-96">
<x-forms.checkbox instantSave helper="AUTOUPDATE is set in .env file, you need to modify it there."
@@ -128,6 +128,10 @@
</div>
<h4 class="py-4">Confirmation Settings</h4>
<div class="md:w-96 ">
<x-forms.checkbox instantSave id="is_sponsorship_popup_enabled" label="Show Sponsorship Popup"
helper="When enabled, sponsorship popups will be shown monthly to users. When disabled, the sponsorship popup will be permanently hidden for all users." />
</div>
@if ($disable_two_step_confirmation)
<div class="md:w-96 pb-4" wire:key="two-step-confirmation-enabled">

View File

@@ -56,7 +56,7 @@
</span>
</p>
<div class="flex items-center pt-6">
<svg xmlns="http://www.w3.org/2000/svg" class="flex-none w-8 h-8 mr-3 text-warning"
<svg xmlns="http://www.w3.org/2000/svg" class="flex-none w-8 h-8 mr-3 dark:text-warning"
fill="currentColor" viewBox="0 0 256 256">
<path
d="M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z">
@@ -89,8 +89,8 @@
</div>
<ul role="list" class="mt-8 space-y-3 text-sm leading-6 dark:text-neutral-400">
<li class="flex">
<svg class="flex-none w-5 h-6 mr-3 text-warning" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<svg class="flex-none w-5 h-6 mr-3 dark:text-warning" viewBox="0 0 20 20"
fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" />
@@ -99,8 +99,8 @@
<span class="px-1 font-bold dark:text-white">unlimited</span> servers
</li>
<li class="flex">
<svg class="flex-none w-5 h-6 mr-3 text-warning" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<svg class="flex-none w-5 h-6 mr-3 dark:text-warning" viewBox="0 0 20 20"
fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" />
@@ -109,7 +109,7 @@
<span class="px-1 font-bold dark:text-white">unlimited</span> applications per server
</li>
<li class="flex gap-x-3">
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
<svg class="flex-none w-5 h-6 dark:text-warning" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
@@ -118,7 +118,7 @@
Free email notifications
</li>
<li class="flex gap-x-3">
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
<svg class="flex-none w-5 h-6 dark:text-warning" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"

View File

@@ -1,7 +1,7 @@
<div>
<div class="flex items-start gap-2 pb-10">
<div>
<h1>Tags</h1>
<h1 class="pb-2">Tags</h1>
<div>Tags help you to perform actions on multiple resources.</div>
</div>
</div>

View File

@@ -18,8 +18,8 @@
<div class="flex items-center justify-center gap-2 mx-4 text-xs font-bold ">
<x-modal-confirmation title="Confirm User Deletion?" buttonTitle="Delete" isErrorButton
submitAction="delete({{ $user->id }})" :actions="[
'The selected user will be permanently deleted from Coolify and the database.',
'All resources (application, databases, services, configurations, servers, private keys, tags, etc.) related to this user will be deleted from Coolify and from the server (if the server is reachable).',
'The selected user will be permanently deleted from Coolify\'s database.',
'All resources (application, databases, services, configurations, servers, private keys, tags, etc.) related to this user\'s default team will be deleted from Coolify\'s database.',
]"
confirmationText="{{ $user->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the User Name below"

View File

@@ -27,8 +27,7 @@
</button>
<template x-teleport="body">
<div x-show="modalOpen"
class="fixed top-0 lg:pt-10 left-0 z-99 flex items-start justify-center w-screen h-screen"
x-cloak>
class="fixed top-0 lg:pt-10 left-0 z-99 flex items-start justify-center w-screen h-screen" x-cloak>
<div x-show="modalOpen" x-transition:enter="ease-out duration-100"
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-100" x-transition:leave-start="opacity-100"
@@ -55,8 +54,11 @@
<p>Are you sure you would like to upgrade your instance to {{ $latestVersion }}?</p>
<br />
<div class="p-4 mb-4 text-yellow-800 border border-yellow-300 rounded-lg bg-yellow-50 dark:bg-yellow-900/30 dark:text-yellow-300 dark:border-yellow-800">
<p class="font-medium">Warning: Any deployments running during the update process will fail. Please ensure no deployments are in progress on any server before continuing.</p>
<div
class="p-4 mb-4 text-yellow-800 border border-yellow-300 rounded-lg bg-yellow-50 dark:bg-yellow-900/30 dark:text-yellow-300 dark:border-yellow-800">
<p class="font-medium">Warning: Any deployments running during the update process will
fail. Please ensure no deployments are in progress on any server before continuing.
</p>
</div>
<p>You can review the changelogs <a class="font-bold underline dark:text-white"
href="https://github.com/coollabsio/coolify/releases" target="_blank">here</a>.</p>

View File

@@ -58,7 +58,7 @@ Route::group([
Route::patch('/security/keys/{uuid}', [SecurityController::class, 'update_key'])->middleware(['api.ability:write']);
Route::delete('/security/keys/{uuid}', [SecurityController::class, 'delete_key'])->middleware(['api.ability:write']);
Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy'])->middleware(['api.ability:write,deploy']);
Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy'])->middleware(['api.ability:deploy']);
Route::get('/deployments', [DeployController::class, 'deployments'])->middleware(['api.ability:read']);
Route::get('/deployments/{uuid}', [DeployController::class, 'deployment_by_uuid'])->middleware(['api.ability:read']);
Route::get('/deployments/applications/{uuid}', [DeployController::class, 'get_application_deployments'])->middleware(['api.ability:read']);

View File

@@ -164,7 +164,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
Route::prefix('invitations')->group(function () {
Route::get('/{uuid}', [Controller::class, 'acceptInvitation'])->name('team.invitation.accept');
Route::get('/{uuid}/revoke', [Controller::class, 'revoke_invitation'])->name('team.invitation.revoke');
Route::get('/{uuid}/revoke', [Controller::class, 'revokeInvitation'])->name('team.invitation.revoke');
});
Route::get('/projects', ProjectIndex::class)->name('project.index');

Some files were not shown because too many files have changed in this diff Show More