feat(acl): Change views/backend code to able to use proper ACL's later on. Currently it is not enabled.

This commit is contained in:
Andras Bacsai
2025-08-26 10:27:31 +02:00
parent 5a88377a67
commit 63fcc0ebc3
159 changed files with 3610 additions and 1922 deletions

View File

@@ -72,5 +72,7 @@ class Kernel extends HttpKernel
'api.ability' => \App\Http\Middleware\ApiAbility::class,
'api.sensitive' => \App\Http\Middleware\ApiSensitiveData::class,
'can.create.resources' => \App\Http\Middleware\CanCreateResources::class,
'can.update.resource' => \App\Http\Middleware\CanUpdateResource::class,
'can.access.terminal' => \App\Http\Middleware\CanAccessTerminal::class,
];
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CanAccessTerminal
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
return $next($request);
// if (! auth()->check()) {
// abort(401, 'Authentication required');
// }
// // Only admins/owners can access terminal functionality
// if (! auth()->user()->can('canAccessTerminal')) {
// abort(403, 'Access to terminal functionality is restricted to team administrators');
// }
// return $next($request);
}
}

View File

@@ -16,10 +16,11 @@ class CanCreateResources
*/
public function handle(Request $request, Closure $next): Response
{
if (! Gate::allows('createAnyResource')) {
abort(403, 'You do not have permission to create resources.');
}
return $next($request);
// if (! Gate::allows('createAnyResource')) {
// abort(403, 'You do not have permission to create resources.');
// }
// return $next($request);
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace App\Http\Middleware;
use App\Models\Application;
use App\Models\Environment;
use App\Models\Project;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Symfony\Component\HttpFoundation\Response;
class CanUpdateResource
{
public function handle(Request $request, Closure $next): Response
{
return $next($request);
// Get resource from route parameters
// $resource = null;
// if ($request->route('application_uuid')) {
// $resource = Application::where('uuid', $request->route('application_uuid'))->first();
// } elseif ($request->route('service_uuid')) {
// $resource = Service::where('uuid', $request->route('service_uuid'))->first();
// } elseif ($request->route('stack_service_uuid')) {
// // Handle ServiceApplication or ServiceDatabase
// $stack_service_uuid = $request->route('stack_service_uuid');
// $resource = ServiceApplication::where('uuid', $stack_service_uuid)->first() ??
// ServiceDatabase::where('uuid', $stack_service_uuid)->first();
// } elseif ($request->route('database_uuid')) {
// // Try different database types
// $database_uuid = $request->route('database_uuid');
// $resource = StandalonePostgresql::where('uuid', $database_uuid)->first() ??
// StandaloneMysql::where('uuid', $database_uuid)->first() ??
// StandaloneMariadb::where('uuid', $database_uuid)->first() ??
// StandaloneRedis::where('uuid', $database_uuid)->first() ??
// StandaloneKeydb::where('uuid', $database_uuid)->first() ??
// StandaloneDragonfly::where('uuid', $database_uuid)->first() ??
// StandaloneClickhouse::where('uuid', $database_uuid)->first() ??
// StandaloneMongodb::where('uuid', $database_uuid)->first();
// } elseif ($request->route('server_uuid')) {
// // For server routes, check if user can manage servers
// if (! auth()->user()->isAdmin()) {
// abort(403, 'You do not have permission to access this resource.');
// }
// return $next($request);
// } elseif ($request->route('environment_uuid')) {
// $resource = Environment::where('uuid', $request->route('environment_uuid'))->first();
// } elseif ($request->route('project_uuid')) {
// $resource = Project::ownedByCurrentTeam()->where('uuid', $request->route('project_uuid'))->first();
// }
// if (! $resource) {
// abort(404, 'Resource not found.');
// }
// if (! Gate::allows('update', $resource)) {
// abort(403, 'You do not have permission to update this resource.');
// }
// return $next($request);
}
}

View File

@@ -5,12 +5,15 @@ namespace App\Livewire\Destination;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Show extends Component
{
use AuthorizesRequests;
#[Locked]
public $destination;
@@ -63,6 +66,8 @@ class Show extends Component
public function submit()
{
try {
$this->authorize('update', $this->destination);
$this->syncData(true);
$this->dispatch('success', 'Destination saved.');
} catch (\Throwable $e) {
@@ -73,6 +78,8 @@ class Show extends Component
public function delete()
{
try {
$this->authorize('delete', $this->destination);
if ($this->destination->getMorphClass() === \App\Models\StandaloneDocker::class) {
if ($this->destination->attachedTo()) {
return $this->dispatch('error', 'You must delete all resources before deleting this destination.');

View File

@@ -5,11 +5,14 @@ namespace App\Livewire\Notifications;
use App\Models\DiscordNotificationSettings;
use App\Models\Team;
use App\Notifications\Test;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Discord extends Component
{
use AuthorizesRequests;
public Team $team;
public DiscordNotificationSettings $settings;
@@ -67,6 +70,7 @@ class Discord extends Component
try {
$this->team = auth()->user()->currentTeam();
$this->settings = $this->team->discordNotificationSettings;
$this->authorize('view', $this->settings);
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -77,6 +81,7 @@ class Discord extends Component
{
if ($toModel) {
$this->validate();
$this->authorize('update', $this->settings);
$this->settings->discord_enabled = $this->discordEnabled;
$this->settings->discord_webhook_url = $this->discordWebhookUrl;
@@ -182,6 +187,7 @@ class Discord extends Component
public function sendTestNotification()
{
try {
$this->authorize('sendTest', $this->settings);
$this->team->notify(new Test(channel: 'discord'));
$this->dispatch('success', 'Test notification sent.');
} catch (\Throwable $e) {

View File

@@ -5,6 +5,7 @@ namespace App\Livewire\Notifications;
use App\Models\EmailNotificationSettings;
use App\Models\Team;
use App\Notifications\Test;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
@@ -12,6 +13,8 @@ use Livewire\Component;
class Email extends Component
{
use AuthorizesRequests;
protected $listeners = ['refresh' => '$refresh'];
#[Locked]
@@ -110,6 +113,7 @@ class Email extends Component
$this->team = auth()->user()->currentTeam();
$this->emails = auth()->user()->email;
$this->settings = $this->team->emailNotificationSettings;
$this->authorize('view', $this->settings);
$this->syncData();
$this->testEmailAddress = auth()->user()->email;
} catch (\Throwable $e) {
@@ -121,6 +125,7 @@ class Email extends Component
{
if ($toModel) {
$this->validate();
$this->authorize('update', $this->settings);
$this->settings->smtp_enabled = $this->smtpEnabled;
$this->settings->smtp_from_address = $this->smtpFromAddress;
$this->settings->smtp_from_name = $this->smtpFromName;
@@ -311,6 +316,7 @@ class Email extends Component
public function sendTestEmail()
{
try {
$this->authorize('sendTest', $this->settings);
$this->validate([
'testEmailAddress' => 'required|email',
], [
@@ -338,6 +344,7 @@ class Email extends Component
public function copyFromInstanceSettings()
{
$this->authorize('update', $this->settings);
$settings = instanceSettings();
$this->smtpFromAddress = $settings->smtp_from_address;
$this->smtpFromName = $settings->smtp_from_name;

View File

@@ -5,12 +5,15 @@ namespace App\Livewire\Notifications;
use App\Models\PushoverNotificationSettings;
use App\Models\Team;
use App\Notifications\Test;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Pushover extends Component
{
use AuthorizesRequests;
protected $listeners = ['refresh' => '$refresh'];
#[Locked]
@@ -72,6 +75,7 @@ class Pushover extends Component
try {
$this->team = auth()->user()->currentTeam();
$this->settings = $this->team->pushoverNotificationSettings;
$this->authorize('view', $this->settings);
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -82,6 +86,7 @@ class Pushover extends Component
{
if ($toModel) {
$this->validate();
$this->authorize('update', $this->settings);
$this->settings->pushover_enabled = $this->pushoverEnabled;
$this->settings->pushover_user_key = $this->pushoverUserKey;
$this->settings->pushover_api_token = $this->pushoverApiToken;
@@ -175,6 +180,7 @@ class Pushover extends Component
public function sendTestNotification()
{
try {
$this->authorize('sendTest', $this->settings);
$this->team->notify(new Test(channel: 'pushover'));
$this->dispatch('success', 'Test notification sent.');
} catch (\Throwable $e) {

View File

@@ -5,12 +5,15 @@ namespace App\Livewire\Notifications;
use App\Models\SlackNotificationSettings;
use App\Models\Team;
use App\Notifications\Test;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Slack extends Component
{
use AuthorizesRequests;
protected $listeners = ['refresh' => '$refresh'];
#[Locked]
@@ -69,6 +72,7 @@ class Slack extends Component
try {
$this->team = auth()->user()->currentTeam();
$this->settings = $this->team->slackNotificationSettings;
$this->authorize('view', $this->settings);
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -79,6 +83,7 @@ class Slack extends Component
{
if ($toModel) {
$this->validate();
$this->authorize('update', $this->settings);
$this->settings->slack_enabled = $this->slackEnabled;
$this->settings->slack_webhook_url = $this->slackWebhookUrl;
@@ -168,6 +173,7 @@ class Slack extends Component
public function sendTestNotification()
{
try {
$this->authorize('sendTest', $this->settings);
$this->team->notify(new Test(channel: 'slack'));
$this->dispatch('success', 'Test notification sent.');
} catch (\Throwable $e) {

View File

@@ -5,12 +5,15 @@ namespace App\Livewire\Notifications;
use App\Models\Team;
use App\Models\TelegramNotificationSettings;
use App\Notifications\Test;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Telegram extends Component
{
use AuthorizesRequests;
protected $listeners = ['refresh' => '$refresh'];
#[Locked]
@@ -111,6 +114,7 @@ class Telegram extends Component
try {
$this->team = auth()->user()->currentTeam();
$this->settings = $this->team->telegramNotificationSettings;
$this->authorize('view', $this->settings);
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -121,6 +125,7 @@ class Telegram extends Component
{
if ($toModel) {
$this->validate();
$this->authorize('update', $this->settings);
$this->settings->telegram_enabled = $this->telegramEnabled;
$this->settings->telegram_token = $this->telegramToken;
$this->settings->telegram_chat_id = $this->telegramChatId;
@@ -241,6 +246,7 @@ class Telegram extends Component
public function sendTestNotification()
{
try {
$this->authorize('sendTest', $this->settings);
$this->team->notify(new Test(channel: 'telegram'));
$this->dispatch('success', 'Test notification sent.');
} catch (\Throwable $e) {

View File

@@ -3,12 +3,15 @@
namespace App\Livewire\Project\Application\Preview;
use App\Models\Application;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Spatie\Url\Url;
class Form extends Component
{
use AuthorizesRequests;
public Application $application;
#[Validate('required')]
@@ -27,6 +30,7 @@ class Form extends Component
public function submit()
{
try {
$this->authorize('update', $this->application);
$this->resetErrorBag();
$this->validate();
$this->application->preview_url_template = str_replace(' ', '', $this->previewUrlTemplate);
@@ -41,6 +45,7 @@ class Form extends Component
public function resetToDefault()
{
try {
$this->authorize('update', $this->application);
$this->application->preview_url_template = '{{pr_id}}.{{domain}}';
$this->previewUrlTemplate = $this->application->preview_url_template;
$this->application->save();

View File

@@ -38,6 +38,7 @@ class Previews extends Component
public function load_prs()
{
try {
$this->authorize('update', $this->application);
['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/pulls");
$this->rate_limit_remaining = $rate_limit_remaining;
$this->pull_requests = $data->sortBy('number')->values();

View File

@@ -176,6 +176,7 @@ EOD;
return;
}
try {
$this->importRunning = true;
$this->importCommands = [];
if (filled($this->customLocation)) {
$backupFileName = '/tmp/restore_'.$this->resource->uuid;

View File

@@ -20,6 +20,7 @@ class Index extends Component
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get()->map(function ($project) {
$project->settingsRoute = route('project.edit', ['project_uuid' => $project->uuid]);
$project->canUpdate = auth()->user()->can('update', $project);
return $project;
});

View File

@@ -3,11 +3,14 @@
namespace App\Livewire\Project\Service;
use App\Models\Service;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class Configuration extends Component
{
use AuthorizesRequests;
public $currentRoute;
public $project;
@@ -40,6 +43,7 @@ class Configuration extends Component
public function mount()
{
try {
$this->parameters = get_route_parameters();
$this->currentRoute = request()->route()->getName();
$this->query = request()->query();
@@ -54,10 +58,15 @@ class Configuration extends Component
->firstOrFail();
$this->service = $environment->services()->whereUuid(request()->route('service_uuid'))->firstOrFail();
$this->authorize('view', $this->service);
$this->project = $project;
$this->environment = $environment;
$this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function refreshServices()
@@ -70,6 +79,7 @@ class Configuration extends Component
public function restartApplication($id)
{
try {
$this->authorize('update', $this->service);
$application = $this->service->applications->find($id);
if ($application) {
$application->restart();
@@ -83,6 +93,7 @@ class Configuration extends Component
public function restartDatabase($id)
{
try {
$this->authorize('update', $this->service);
$database = $this->service->databases->find($id);
if ($database) {
$database->restart();

View File

@@ -6,6 +6,7 @@ use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\InstanceSettings;
use App\Models\ServiceDatabase;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
@@ -13,6 +14,8 @@ use Livewire\Component;
class Database extends Component
{
use AuthorizesRequests;
public ServiceDatabase $database;
public ?string $db_url_public = null;
@@ -40,15 +43,23 @@ class Database extends Component
public function mount()
{
try {
$this->parameters = get_route_parameters();
$this->authorize('view', $this->database);
if ($this->database->is_public) {
$this->db_url_public = $this->database->getServiceDatabaseUrl();
}
$this->refreshFileStorages();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function delete($password)
{
try {
$this->authorize('delete', $this->database);
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
@@ -57,7 +68,6 @@ class Database extends Component
}
}
try {
$this->database->delete();
$this->dispatch('success', 'Database deleted.');
@@ -69,11 +79,18 @@ class Database extends Component
public function instantSaveExclude()
{
try {
$this->authorize('update', $this->database);
$this->submit();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function instantSaveLogDrain()
{
try {
$this->authorize('update', $this->database);
if (! $this->database->service->destination->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
@@ -82,11 +99,15 @@ class Database extends Component
}
$this->submit();
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function convertToApplication()
{
try {
$this->authorize('update', $this->database);
$service = $this->database->service;
$serviceDatabase = $this->database;
@@ -122,6 +143,8 @@ class Database extends Component
public function instantSave()
{
try {
$this->authorize('update', $this->database);
if ($this->database->is_public && ! $this->database->public_port) {
$this->dispatch('error', 'Public port is required.');
$this->database->is_public = false;
@@ -144,6 +167,9 @@ class Database extends Component
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
$this->submit();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function refreshFileStorages()
@@ -154,11 +180,13 @@ class Database extends Component
public function submit()
{
try {
$this->authorize('update', $this->database);
$this->validate();
$this->database->save();
updateCompose($this->database);
$this->dispatch('success', 'Database saved.');
} catch (\Throwable) {
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->dispatch('generateDockerCompose');
}

View File

@@ -15,12 +15,15 @@ use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Livewire\Component;
class FileStorage extends Component
{
use AuthorizesRequests;
public LocalFileVolume $fileStorage;
public ServiceApplication|StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase|Application $resource;
@@ -54,6 +57,8 @@ class FileStorage extends Component
public function convertToDirectory()
{
try {
$this->authorize('update', $this->resource);
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = true;
$this->fileStorage->content = null;
@@ -70,6 +75,8 @@ class FileStorage extends Component
public function loadStorageOnServer()
{
try {
$this->authorize('update', $this->resource);
$this->fileStorage->loadStorageOnServer();
$this->dispatch('success', 'File storage loaded from server.');
} catch (\Throwable $e) {
@@ -82,6 +89,8 @@ class FileStorage extends Component
public function convertToFile()
{
try {
$this->authorize('update', $this->resource);
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = false;
$this->fileStorage->content = null;
@@ -99,6 +108,8 @@ class FileStorage extends Component
public function delete($password)
{
$this->authorize('update', $this->resource);
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
@@ -127,6 +138,8 @@ class FileStorage extends Component
public function submit()
{
$this->authorize('update', $this->resource);
$original = $this->fileStorage->getOriginal();
try {
$this->validate();

View File

@@ -5,11 +5,14 @@ namespace App\Livewire\Project\Service;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection;
use Livewire\Component;
class Index extends Component
{
use AuthorizesRequests;
public ?Service $service = null;
public ?ServiceApplication $serviceApplication = null;
@@ -36,6 +39,7 @@ class Index extends Component
if (! $this->service) {
return redirect()->route('dashboard');
}
$this->authorize('view', $this->service);
$service = $this->service->applications()->whereUuid($this->parameters['stack_service_uuid'])->first();
if ($service) {
$this->serviceApplication = $service;
@@ -52,7 +56,12 @@ class Index extends Component
public function generateDockerCompose()
{
try {
$this->authorize('update', $this->service);
$this->service->parse();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Service;
use App\Models\InstanceSettings;
use App\Models\ServiceApplication;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
@@ -12,6 +13,8 @@ use Spatie\Url\Url;
class ServiceApplicationView extends Component
{
use AuthorizesRequests;
public ServiceApplication $application;
public $parameters;
@@ -34,11 +37,18 @@ class ServiceApplicationView extends Component
public function instantSave()
{
try {
$this->authorize('update', $this->application);
$this->submit();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function instantSaveAdvanced()
{
try {
$this->authorize('update', $this->application);
if (! $this->application->service->destination->server->isLogDrainEnabled()) {
$this->application->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
@@ -47,10 +57,16 @@ class ServiceApplicationView extends Component
}
$this->application->save();
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function delete($password)
{
try {
$this->authorize('delete', $this->application);
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
@@ -59,7 +75,6 @@ class ServiceApplicationView extends Component
}
}
try {
$this->application->delete();
$this->dispatch('success', 'Application deleted.');
@@ -71,12 +86,18 @@ class ServiceApplicationView extends Component
public function mount()
{
try {
$this->parameters = get_route_parameters();
$this->authorize('view', $this->application);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function convertToDatabase()
{
try {
$this->authorize('update', $this->application);
$service = $this->application->service;
$serviceApplication = $this->application;
@@ -111,6 +132,7 @@ class ServiceApplicationView extends Component
public function submit()
{
try {
$this->authorize('update', $this->application);
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {

View File

@@ -3,10 +3,13 @@
namespace App\Livewire\Project\Service;
use App\Models\LocalPersistentVolume;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Storage extends Component
{
use AuthorizesRequests;
public $resource;
public $fileStorage;
@@ -42,6 +45,8 @@ class Storage extends Component
public function addNewVolume($data)
{
try {
$this->authorize('update', $this->resource);
LocalPersistentVolume::create([
'name' => $data['name'],
'mount_path' => $data['mount_path'],

View File

@@ -2,10 +2,13 @@
namespace App\Livewire\Project\Shared\EnvironmentVariable;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Add extends Component
{
use AuthorizesRequests;
public $parameters;
public bool $shared = false;

View File

@@ -5,11 +5,12 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use App\Models\SharedEnvironmentVariable;
use App\Traits\EnvironmentVariableProtection;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Show extends Component
{
use EnvironmentVariableProtection;
use AuthorizesRequests, EnvironmentVariableProtection;
public $parameters;
@@ -75,6 +76,11 @@ class Show extends Component
}
}
public function getResourceProperty()
{
return $this->env->resourceable ?? $this->env;
}
public function refresh()
{
$this->syncData();
@@ -140,6 +146,8 @@ class Show extends Component
public function lock()
{
$this->authorize('update', $this->env);
$this->env->is_shown_once = true;
if ($this->isSharedVariable) {
unset($this->env->is_required);
@@ -158,6 +166,8 @@ class Show extends Component
public function submit()
{
try {
$this->authorize('update', $this->env);
if (! $this->isSharedVariable && $this->is_required && str($this->value)->isEmpty()) {
$oldValue = $this->env->getOriginal('value');
$this->value = $oldValue;
@@ -179,9 +189,11 @@ class Show extends Component
public function delete()
{
try {
$this->authorize('delete', $this->env);
// Check if the variable is used in Docker Compose
if ($this->type === 'service' || $this->type === 'application' && $this->env->resource()?->docker_compose) {
[$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($this->env->key, $this->env->resource()?->docker_compose);
if ($this->type === 'service' || $this->type === 'application' && $this->env->resourceable?->docker_compose) {
[$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($this->env->key, $this->env->resourceable?->docker_compose);
if ($isUsed) {
$this->dispatch('error', "Cannot delete environment variable '{$this->env->key}' <br><br>Please remove it from the Docker Compose file first.");

View File

@@ -2,10 +2,13 @@
namespace App\Livewire\Project\Shared;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class HealthChecks extends Component
{
use AuthorizesRequests;
public $resource;
protected $rules = [
@@ -27,6 +30,7 @@ class HealthChecks extends Component
public function instantSave()
{
$this->authorize('update', $this->resource);
$this->resource->save();
$this->dispatch('success', 'Health check updated.');
}
@@ -34,6 +38,7 @@ class HealthChecks extends Component
public function submit()
{
try {
$this->authorize('update', $this->resource);
$this->validate();
$this->resource->save();
$this->dispatch('success', 'Health check updated.');

View File

@@ -2,10 +2,13 @@
namespace App\Livewire\Project\Shared;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class ResourceLimits extends Component
{
use AuthorizesRequests;
public $resource;
protected $rules = [
@@ -31,6 +34,7 @@ class ResourceLimits extends Component
public function submit()
{
try {
$this->authorize('update', $this->resource);
if (! $this->resource->limits_memory) {
$this->resource->limits_memory = '0';
}

View File

@@ -3,12 +3,15 @@
namespace App\Livewire\Project\Shared\ScheduledTask;
use App\Models\ScheduledTask;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection;
use Livewire\Attributes\Locked;
use Livewire\Component;
class Add extends Component
{
use AuthorizesRequests;
public $parameters;
#[Locked]
@@ -20,6 +23,9 @@ class Add extends Component
#[Locked]
public Collection $containerNames;
#[Locked]
public $resource;
public string $name;
public string $command;
@@ -45,6 +51,22 @@ class Add extends Component
public function mount()
{
$this->parameters = get_route_parameters();
// Get the resource based on type and id
switch ($this->type) {
case 'application':
$this->resource = \App\Models\Application::findOrFail($this->id);
break;
case 'service':
$this->resource = \App\Models\Service::findOrFail($this->id);
break;
case 'standalone-postgresql':
$this->resource = \App\Models\StandalonePostgresql::findOrFail($this->id);
break;
default:
throw new \Exception('Invalid resource type');
}
if ($this->containerNames->count() > 0) {
$this->container = $this->containerNames->first();
}
@@ -53,6 +75,7 @@ class Add extends Component
public function submit()
{
try {
$this->authorize('update', $this->resource);
$this->validate();
$isValid = validate_cron_expression($this->frequency);
if (! $isValid) {

View File

@@ -6,12 +6,15 @@ use App\Jobs\ScheduledTaskJob;
use App\Models\Application;
use App\Models\ScheduledTask;
use App\Models\Service;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Show extends Component
{
use AuthorizesRequests;
public Application|Service $resource;
public ScheduledTask $task;
@@ -109,6 +112,7 @@ class Show extends Component
public function instantSave()
{
try {
$this->authorize('update', $this->resource);
$this->syncData(true);
$this->dispatch('success', 'Scheduled task updated.');
$this->refreshTasks();
@@ -120,6 +124,7 @@ class Show extends Component
public function submit()
{
try {
$this->authorize('update', $this->resource);
$this->syncData(true);
$this->dispatch('success', 'Scheduled task updated.');
} catch (\Exception $e) {
@@ -139,6 +144,7 @@ class Show extends Component
public function delete()
{
try {
$this->authorize('update', $this->resource);
$this->task->delete();
if ($this->type === 'application') {
@@ -154,6 +160,7 @@ class Show extends Component
public function executeNow()
{
try {
$this->authorize('update', $this->resource);
ScheduledTaskJob::dispatch($this->task);
$this->dispatch('success', 'Scheduled task executed.');
} catch (\Exception $e) {

View File

@@ -4,10 +4,13 @@ namespace App\Livewire\Project\Shared\Storages;
use App\Models\Application;
use App\Models\LocalFileVolume;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Add extends Component
{
use AuthorizesRequests;
public $resource;
public $uuid;
@@ -77,6 +80,8 @@ class Add extends Component
public function submitFileStorage()
{
try {
$this->authorize('update', $this->resource);
$this->validate([
'file_storage_path' => 'string',
'file_storage_content' => 'nullable|string',
@@ -112,6 +117,8 @@ class Add extends Component
public function submitFileStorageDirectory()
{
try {
$this->authorize('update', $this->resource);
$this->validate([
'file_storage_directory_source' => 'string',
'file_storage_directory_destination' => 'string',
@@ -140,6 +147,8 @@ class Add extends Component
public function submitPersistentVolume()
{
try {
$this->authorize('update', $this->resource);
$this->validate([
'name' => 'required|string',
'mount_path' => 'required|string',

View File

@@ -4,14 +4,19 @@ namespace App\Livewire\Project\Shared\Storages;
use App\Models\InstanceSettings;
use App\Models\LocalPersistentVolume;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Livewire\Component;
class Show extends Component
{
use AuthorizesRequests;
public LocalPersistentVolume $storage;
public $resource;
public bool $isReadOnly = false;
public bool $isFirst = true;
@@ -34,6 +39,8 @@ class Show extends Component
public function submit()
{
$this->authorize('update', $this->resource);
$this->validate();
$this->storage->save();
$this->dispatch('success', 'Storage updated successfully');
@@ -41,6 +48,8 @@ class Show extends Component
public function delete($password)
{
$this->authorize('update', $this->resource);
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');

View File

@@ -3,12 +3,15 @@
namespace App\Livewire\Project\Shared;
use App\Models\Tag;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Validate;
use Livewire\Component;
// Refactored ✅
class Tags extends Component
{
use AuthorizesRequests;
public $resource = null;
#[Validate('required|string|min:2')]
@@ -34,6 +37,7 @@ class Tags extends Component
public function submit()
{
try {
$this->authorize('update', $this->resource);
$this->validate();
$tags = str($this->newTags)->trim()->explode(' ');
foreach ($tags as $tag) {
@@ -66,6 +70,7 @@ class Tags extends Component
public function addTag(string $id, string $name)
{
try {
$this->authorize('update', $this->resource);
$name = strip_tags($name);
if ($this->resource->tags()->where('id', $id)->exists()) {
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='dark:text-warning'>$name</span> already added.");
@@ -83,6 +88,7 @@ class Tags extends Component
public function deleteTag(string $id)
{
try {
$this->authorize('update', $this->resource);
$this->resource->tags()->detach($id);
$found_more_tags = Tag::ownedByCurrentTeam()->find($id);
if ($found_more_tags && $found_more_tags->applications()->count() == 0 && $found_more_tags->services()->count() == 0) {

View File

@@ -2,11 +2,14 @@
namespace App\Livewire\Project\Shared;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
// Refactored ✅
class Webhooks extends Component
{
use AuthorizesRequests;
public $resource;
public ?string $deploywebhook;

View File

@@ -3,10 +3,14 @@
namespace App\Livewire\Security;
use App\Models\InstanceSettings;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Laravel\Sanctum\PersonalAccessToken;
use Livewire\Component;
class ApiTokens extends Component
{
use AuthorizesRequests;
public ?string $description = null;
public $tokens = [];
@@ -15,6 +19,10 @@ class ApiTokens extends Component
public $isApiEnabled;
public bool $canUseRootPermissions = false;
public bool $canUseWritePermissions = false;
public function render()
{
return view('livewire.security.api-tokens');
@@ -23,6 +31,8 @@ class ApiTokens extends Component
public function mount()
{
$this->isApiEnabled = InstanceSettings::get()->is_api_enabled;
$this->canUseRootPermissions = auth()->user()->can('useRootPermissions', PersonalAccessToken::class);
$this->canUseWritePermissions = auth()->user()->can('useWritePermissions', PersonalAccessToken::class);
$this->getTokens();
}
@@ -33,6 +43,23 @@ class ApiTokens extends Component
public function updatedPermissions($permissionToUpdate)
{
// Check if user is trying to use restricted permissions
if ($permissionToUpdate == 'root' && ! $this->canUseRootPermissions) {
$this->dispatch('error', 'You do not have permission to use root permissions.');
// Remove root from permissions if it was somehow added
$this->permissions = array_diff($this->permissions, ['root']);
return;
}
if (in_array($permissionToUpdate, ['write', 'write:sensitive']) && ! $this->canUseWritePermissions) {
$this->dispatch('error', 'You do not have permission to use write permissions.');
// Remove write permissions if they were somehow added
$this->permissions = array_diff($this->permissions, ['write', 'write:sensitive']);
return;
}
if ($permissionToUpdate == 'root') {
$this->permissions = ['root'];
} elseif ($permissionToUpdate == 'read:sensitive' && ! in_array('read', $this->permissions)) {
@@ -50,6 +77,17 @@ class ApiTokens extends Component
public function addNewToken()
{
try {
$this->authorize('create', PersonalAccessToken::class);
// Validate permissions based on user role
if (in_array('root', $this->permissions) && ! $this->canUseRootPermissions) {
throw new \Exception('You do not have permission to create tokens with root permissions.');
}
if (array_intersect(['write', 'write:sensitive'], $this->permissions) && ! $this->canUseWritePermissions) {
throw new \Exception('You do not have permission to create tokens with write permissions.');
}
$this->validate([
'description' => 'required|min:3|max:255',
]);
@@ -65,6 +103,7 @@ class ApiTokens extends Component
{
try {
$token = auth()->user()->tokens()->where('id', $id)->firstOrFail();
$this->authorize('delete', $token);
$token->delete();
$this->getTokens();
} catch (\Exception $e) {

View File

@@ -3,10 +3,13 @@
namespace App\Livewire\Security\PrivateKey;
use App\Models\PrivateKey;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Index extends Component
{
use AuthorizesRequests;
public function render()
{
$privateKeys = PrivateKey::ownedByCurrentTeam(['name', 'uuid', 'is_git_related', 'description'])->get();
@@ -18,6 +21,7 @@ class Index extends Component
public function cleanupUnusedKeys()
{
$this->authorize('create', PrivateKey::class);
PrivateKey::cleanupUnusedKeys();
$this->dispatch('success', 'Unused keys have been cleaned up.');
}

View File

@@ -3,12 +3,17 @@
namespace App\Livewire\Server\Proxy;
use App\Models\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class DynamicConfigurationNavbar extends Component
{
use AuthorizesRequests;
public $server_id;
public Server $server;
public $fileName = '';
public $value = '';
@@ -17,18 +22,18 @@ class DynamicConfigurationNavbar extends Component
public function delete(string $fileName)
{
$server = Server::ownedByCurrentTeam()->whereId($this->server_id)->first();
$proxy_path = $server->proxyPath();
$proxy_type = $server->proxyType();
$this->authorize('update', $this->server);
$proxy_path = $this->server->proxyPath();
$proxy_type = $this->server->proxyType();
$file = str_replace('|', '.', $fileName);
if ($proxy_type === 'CADDY' && $file === 'Caddyfile') {
$this->dispatch('error', 'Cannot delete Caddyfile.');
return;
}
instant_remote_process(["rm -f {$proxy_path}/dynamic/{$file}"], $server);
instant_remote_process(["rm -f {$proxy_path}/dynamic/{$file}"], $this->server);
if ($proxy_type === 'CADDY') {
$server->reloadCaddy();
$this->server->reloadCaddy();
}
$this->dispatch('success', 'File deleted.');
$this->dispatch('loadDynamicConfigurations');

View File

@@ -4,10 +4,13 @@ namespace App\Livewire\SharedVariables\Environment;
use App\Models\Application;
use App\Models\Project;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Show extends Component
{
use AuthorizesRequests;
public Project $project;
public Application $application;
@@ -21,6 +24,8 @@ class Show extends Component
public function saveKey($data)
{
try {
$this->authorize('update', $this->environment);
$found = $this->environment->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');

View File

@@ -3,10 +3,13 @@
namespace App\Livewire\SharedVariables\Project;
use App\Models\Project;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Show extends Component
{
use AuthorizesRequests;
public Project $project;
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey', 'environmentVariableDeleted' => '$refresh'];
@@ -14,6 +17,8 @@ class Show extends Component
public function saveKey($data)
{
try {
$this->authorize('update', $this->project);
$found = $this->project->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');

View File

@@ -3,10 +3,13 @@
namespace App\Livewire\SharedVariables\Team;
use App\Models\Team;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Index extends Component
{
use AuthorizesRequests;
public Team $team;
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey', 'environmentVariableDeleted' => '$refresh'];
@@ -14,6 +17,8 @@ class Index extends Component
public function saveKey($data)
{
try {
$this->authorize('update', $this->team);
$found = $this->team->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');

View File

@@ -5,6 +5,7 @@ namespace App\Livewire\Source\Github;
use App\Jobs\GithubAppPermissionJob;
use App\Models\GithubApp;
use App\Models\PrivateKey;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Http;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Key\InMemory;
@@ -13,7 +14,9 @@ use Livewire\Component;
class Change extends Component
{
public string $webhook_endpoint;
use AuthorizesRequests;
public string $webhook_endpoint = '';
public ?string $ipv4 = null;
@@ -69,6 +72,8 @@ class Change extends Component
public function checkPermissions()
{
try {
$this->authorize('view', $this->github_app);
GithubAppPermissionJob::dispatchSync($this->github_app);
$this->github_app->refresh()->makeVisible('client_secret')->makeVisible('webhook_secret');
$this->dispatch('success', 'Github App permissions updated.');
@@ -155,7 +160,7 @@ class Change extends Component
if (isCloud() && ! isDev()) {
$this->webhook_endpoint = config('app.url');
} else {
$this->webhook_endpoint = $this->ipv4;
$this->webhook_endpoint = $this->ipv4 ?? '';
$this->is_system_wide = $this->github_app->is_system_wide;
}
} catch (\Throwable $e) {
@@ -195,6 +200,8 @@ class Change extends Component
public function updateGithubAppName()
{
try {
$this->authorize('update', $this->github_app);
$privateKey = PrivateKey::ownedByCurrentTeam()->find($this->github_app->private_key_id);
if (! $privateKey) {
@@ -237,6 +244,8 @@ class Change extends Component
public function submit()
{
try {
$this->authorize('update', $this->github_app);
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
$this->validate([
'github_app.name' => 'required|string',
@@ -262,6 +271,8 @@ class Change extends Component
public function createGithubAppManually()
{
$this->authorize('update', $this->github_app);
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
$this->github_app->app_id = '1234567890';
$this->github_app->installation_id = '1234567890';
@@ -272,6 +283,8 @@ class Change extends Component
public function instantSave()
{
try {
$this->authorize('update', $this->github_app);
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
$this->github_app->save();
$this->dispatch('success', 'Github App updated.');
@@ -283,6 +296,8 @@ class Change extends Component
public function delete()
{
try {
$this->authorize('delete', $this->github_app);
if ($this->github_app->applications->isNotEmpty()) {
$this->dispatch('error', 'This source is being used by an application. Please delete all applications first.');
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');

View File

@@ -3,10 +3,13 @@
namespace App\Livewire\Source\Github;
use App\Models\GithubApp;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Create extends Component
{
use AuthorizesRequests;
public string $name;
public ?string $organization = null;
@@ -29,6 +32,8 @@ class Create extends Component
public function createGitHubApp()
{
try {
$this->authorize('createAnyResource');
$this->validate([
'name' => 'required|string',
'organization' => 'nullable|string',

View File

@@ -4,11 +4,14 @@ namespace App\Livewire\Storage;
use App\Models\S3Storage;
use App\Support\ValidationPatterns;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Uri;
use Livewire\Component;
class Create extends Component
{
use AuthorizesRequests;
public string $name;
public string $description;
@@ -94,6 +97,8 @@ class Create extends Component
public function submit()
{
try {
$this->authorize('create', S3Storage::class);
$this->validate();
$this->storage = new S3Storage;
$this->storage->name = $this->name;

View File

@@ -4,10 +4,13 @@ namespace App\Livewire\Storage;
use App\Models\S3Storage;
use App\Support\ValidationPatterns;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Form extends Component
{
use AuthorizesRequests;
public S3Storage $storage;
protected function rules(): array
@@ -60,6 +63,8 @@ class Form extends Component
public function testConnection()
{
try {
$this->authorize('validateConnection', $this->storage);
$this->storage->testConnection(shouldSave: true);
return $this->dispatch('success', 'Connection is working.', 'Tested with "ListObjectsV2" action.');
@@ -83,8 +88,10 @@ class Form extends Component
public function submit()
{
$this->validate();
try {
$this->authorize('update', $this->storage);
$this->validate();
$this->testConnection();
} catch (\Throwable $e) {
return handleError($e, $this);

View File

@@ -5,12 +5,15 @@ namespace App\Livewire\Team;
use App\Models\Team;
use App\Models\TeamInvitation;
use App\Support\ValidationPatterns;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
class Index extends Component
{
use AuthorizesRequests;
public $invitations = [];
public Team $team;
@@ -58,6 +61,7 @@ class Index extends Component
{
$this->validate();
try {
$this->authorize('update', $this->team);
$this->team->save();
refreshSession();
$this->dispatch('success', 'Team updated.');
@@ -69,6 +73,7 @@ class Index extends Component
public function delete()
{
$currentTeam = currentTeam();
$this->authorize('delete', $currentTeam);
$currentTeam->delete();
$currentTeam->members->each(function ($user) use ($currentTeam) {

View File

@@ -4,10 +4,13 @@ namespace App\Livewire\Team;
use App\Models\TeamInvitation;
use App\Models\User;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Invitations extends Component
{
use AuthorizesRequests;
public $invitations;
protected $listeners = ['refreshInvitations'];
@@ -15,6 +18,8 @@ class Invitations extends Component
public function deleteInvitation(int $invitation_id)
{
try {
$this->authorize('manageInvitations', currentTeam());
$invitation = TeamInvitation::ownedByCurrentTeam()->findOrFail($invitation_id);
$user = User::whereEmail($invitation->email)->first();
if (filled($user)) {

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Team;
use App\Models\TeamInvitation;
use App\Models\User;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash;
@@ -13,6 +14,8 @@ use Visus\Cuid2\Cuid2;
class InviteLink extends Component
{
use AuthorizesRequests;
public string $email;
public string $role = 'member';
@@ -40,6 +43,7 @@ class InviteLink extends Component
private function generateInviteLink(bool $sendEmail = false)
{
try {
$this->authorize('manageInvitations', currentTeam());
$this->validate();
if (auth()->user()->role() === 'admin' && $this->role === 'owner') {
throw new \Exception('Admins cannot invite owners.');

View File

@@ -4,16 +4,21 @@ namespace App\Livewire\Team;
use App\Enums\Role;
use App\Models\User;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Cache;
use Livewire\Component;
class Member extends Component
{
use AuthorizesRequests;
public User $member;
public function makeAdmin()
{
try {
$this->authorize('manageMembers', currentTeam());
if (Role::from(auth()->user()->role())->lt(Role::ADMIN)
|| Role::from($this->getMemberRole())->gt(auth()->user()->role())) {
throw new \Exception('You are not authorized to perform this action.');
@@ -28,6 +33,8 @@ class Member extends Component
public function makeOwner()
{
try {
$this->authorize('manageMembers', currentTeam());
if (Role::from(auth()->user()->role())->lt(Role::OWNER)
|| Role::from($this->getMemberRole())->gt(auth()->user()->role())) {
throw new \Exception('You are not authorized to perform this action.');
@@ -42,6 +49,8 @@ class Member extends Component
public function makeReadonly()
{
try {
$this->authorize('manageMembers', currentTeam());
if (Role::from(auth()->user()->role())->lt(Role::ADMIN)
|| Role::from($this->getMemberRole())->gt(auth()->user()->role())) {
throw new \Exception('You are not authorized to perform this action.');
@@ -56,6 +65,8 @@ class Member extends Component
public function remove()
{
try {
$this->authorize('manageMembers', currentTeam());
if (Role::from(auth()->user()->role())->lt(Role::ADMIN)
|| Role::from($this->getMemberRole())->gt(auth()->user()->role())) {
throw new \Exception('You are not authorized to perform this action.');

View File

@@ -3,15 +3,19 @@
namespace App\Livewire\Team\Member;
use App\Models\TeamInvitation;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Index extends Component
{
use AuthorizesRequests;
public $invitations = [];
public function mount()
{
if (auth()->user()->isAdminFromSession()) {
// Only load invitations for users who can manage them
if (auth()->user()->can('manageInvitations', currentTeam())) {
$this->invitations = TeamInvitation::whereTeamId(currentTeam()->id)->get();
}
}

View File

@@ -18,7 +18,6 @@ class Index extends Component
public function mount()
{
$this->authorize('useTerminal', Server::class);
$this->servers = Server::isReachable()->get()->filter(function ($server) {
return $server->isTerminalEnabled();
});

View File

@@ -12,4 +12,19 @@ class SharedEnvironmentVariable extends Model
'key' => 'string',
'value' => 'encrypted',
];
public function team()
{
return $this->belongsTo(Team::class);
}
public function project()
{
return $this->belongsTo(Project::class);
}
public function environment()
{
return $this->belongsTo(Environment::class);
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace App\Policies;
use App\Models\User;
use Laravel\Sanctum\PersonalAccessToken;
class ApiTokenPolicy
{
/**
* Determine whether the user can view any API tokens.
*/
public function viewAny(User $user): bool
{
// Authorization temporarily disabled
/*
// Users can view their own API tokens
return true;
*/
return true;
}
/**
* Determine whether the user can view the API token.
*/
public function view(User $user, PersonalAccessToken $token): bool
{
// Authorization temporarily disabled
/*
// Users can only view their own tokens
return $user->id === $token->tokenable_id && $token->tokenable_type === User::class;
*/
return true;
}
/**
* Determine whether the user can create API tokens.
*/
public function create(User $user): bool
{
// Authorization temporarily disabled
/*
// All authenticated users can create their own API tokens
return true;
*/
return true;
}
/**
* Determine whether the user can update the API token.
*/
public function update(User $user, PersonalAccessToken $token): bool
{
// Authorization temporarily disabled
/*
// Users can only update their own tokens
return $user->id === $token->tokenable_id && $token->tokenable_type === User::class;
*/
return true;
}
/**
* Determine whether the user can delete the API token.
*/
public function delete(User $user, PersonalAccessToken $token): bool
{
// Authorization temporarily disabled
/*
// Users can only delete their own tokens
return $user->id === $token->tokenable_id && $token->tokenable_type === User::class;
*/
return true;
}
/**
* Determine whether the user can manage their own API tokens.
*/
public function manage(User $user): bool
{
// Authorization temporarily disabled
/*
// All authenticated users can manage their own API tokens
return true;
*/
return true;
}
/**
* Determine whether the user can use root permissions for API tokens.
*/
public function useRootPermissions(User $user): bool
{
// Only admins and owners can use root permissions
return $user->isAdmin() || $user->isOwner();
}
/**
* Determine whether the user can use write permissions for API tokens.
*/
public function useWritePermissions(User $user): bool
{
// Authorization temporarily disabled
/*
// Only admins and owners can use write permissions
return $user->isAdmin() || $user->isOwner();
*/
return true;
}
}

View File

@@ -13,6 +13,10 @@ class ApplicationPolicy
*/
public function viewAny(User $user): bool
{
// Authorization temporarily disabled
/*
return true;
*/
return true;
}
@@ -21,6 +25,10 @@ class ApplicationPolicy
*/
public function view(User $user, Application $application): bool
{
// Authorization temporarily disabled
/*
return true;
*/
return true;
}
@@ -29,11 +37,15 @@ class ApplicationPolicy
*/
public function create(User $user): bool
{
// Authorization temporarily disabled
/*
if ($user->isAdmin()) {
return true;
}
return false;
*/
return true;
}
/**
@@ -41,11 +53,15 @@ class ApplicationPolicy
*/
public function update(User $user, Application $application): Response
{
// Authorization temporarily disabled
/*
if ($user->isAdmin()) {
return Response::allow();
}
return Response::deny('As a member, you cannot update this application.<br/><br/>You need at least admin or owner permissions.');
*/
return Response::allow();
}
/**
@@ -53,11 +69,15 @@ class ApplicationPolicy
*/
public function delete(User $user, Application $application): bool
{
// Authorization temporarily disabled
/*
if ($user->isAdmin()) {
return true;
}
return false;
*/
return true;
}
/**
@@ -65,6 +85,10 @@ class ApplicationPolicy
*/
public function restore(User $user, Application $application): bool
{
// Authorization temporarily disabled
/*
return true;
*/
return true;
}
@@ -73,7 +97,11 @@ class ApplicationPolicy
*/
public function forceDelete(User $user, Application $application): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $application->team()->first()->id) !== null;
// Authorization temporarily disabled
/*
return $user->isAdmin() && $user->teams->contains('id', $application->team()->first()->id);
*/
return true;
}
/**
@@ -81,7 +109,11 @@ class ApplicationPolicy
*/
public function deploy(User $user, Application $application): bool
{
return $user->teams()->get()->firstWhere('id', $application->team()->first()->id) !== null;
// Authorization temporarily disabled
/*
return $user->teams->contains('id', $application->team()->first()->id);
*/
return true;
}
/**
@@ -89,7 +121,11 @@ class ApplicationPolicy
*/
public function manageDeployments(User $user, Application $application): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $application->team()->first()->id) !== null;
// Authorization temporarily disabled
/*
return $user->isAdmin() && $user->teams->contains('id', $application->team()->first()->id);
*/
return true;
}
/**
@@ -97,7 +133,11 @@ class ApplicationPolicy
*/
public function manageEnvironment(User $user, Application $application): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $application->team()->first()->id) !== null;
// Authorization temporarily disabled
/*
return $user->isAdmin() && $user->teams->contains('id', $application->team()->first()->id);
*/
return true;
}
/**
@@ -105,6 +145,10 @@ class ApplicationPolicy
*/
public function cleanupDeploymentQueue(User $user): bool
{
// Authorization temporarily disabled
/*
return $user->isAdmin();
*/
return true;
}
}

View File

@@ -21,7 +21,8 @@ class ApplicationPreviewPolicy
*/
public function view(User $user, ApplicationPreview $applicationPreview): bool
{
return $user->teams()->get()->firstWhere('id', $applicationPreview->application->team()->first()->id) !== null;
// return $user->teams->contains('id', $applicationPreview->application->team()->first()->id);
return true;
}
/**
@@ -29,7 +30,8 @@ class ApplicationPreviewPolicy
*/
public function create(User $user): bool
{
return $user->isAdmin();
// return $user->isAdmin();
return true;
}
/**
@@ -37,11 +39,12 @@ class ApplicationPreviewPolicy
*/
public function update(User $user, ApplicationPreview $applicationPreview): Response
{
if ($user->isAdmin()) {
return Response::allow();
}
// if ($user->isAdmin()) {
// return Response::allow();
// }
return Response::deny('As a member, you cannot update this preview.<br/><br/>You need at least admin or owner permissions.');
// return Response::deny('As a member, you cannot update this preview.<br/><br/>You need at least admin or owner permissions.');
return true;
}
/**
@@ -49,7 +52,8 @@ class ApplicationPreviewPolicy
*/
public function delete(User $user, ApplicationPreview $applicationPreview): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationPreview->application->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $applicationPreview->application->team()->first()->id);
return true;
}
/**
@@ -57,7 +61,8 @@ class ApplicationPreviewPolicy
*/
public function restore(User $user, ApplicationPreview $applicationPreview): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationPreview->application->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $applicationPreview->application->team()->first()->id);
return true;
}
/**
@@ -65,7 +70,8 @@ class ApplicationPreviewPolicy
*/
public function forceDelete(User $user, ApplicationPreview $applicationPreview): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationPreview->application->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $applicationPreview->application->team()->first()->id);
return true;
}
/**
@@ -73,7 +79,8 @@ class ApplicationPreviewPolicy
*/
public function deploy(User $user, ApplicationPreview $applicationPreview): bool
{
return $user->teams()->get()->firstWhere('id', $applicationPreview->application->team()->first()->id) !== null;
// return $user->teams->contains('id', $applicationPreview->application->team()->first()->id);
return true;
}
/**
@@ -81,6 +88,7 @@ class ApplicationPreviewPolicy
*/
public function manageDeployments(User $user, ApplicationPreview $applicationPreview): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationPreview->application->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $applicationPreview->application->team()->first()->id);
return true;
}
}

View File

@@ -20,7 +20,8 @@ class ApplicationSettingPolicy
*/
public function view(User $user, ApplicationSetting $applicationSetting): bool
{
return $user->teams()->get()->firstWhere('id', $applicationSetting->application->team()->first()->id) !== null;
// return $user->teams->contains('id', $applicationSetting->application->team()->first()->id);
return true;
}
/**
@@ -28,7 +29,8 @@ class ApplicationSettingPolicy
*/
public function create(User $user): bool
{
return $user->isAdmin();
// return $user->isAdmin();
return true;
}
/**
@@ -36,7 +38,8 @@ class ApplicationSettingPolicy
*/
public function update(User $user, ApplicationSetting $applicationSetting): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationSetting->application->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $applicationSetting->application->team()->first()->id);
return true;
}
/**
@@ -44,7 +47,8 @@ class ApplicationSettingPolicy
*/
public function delete(User $user, ApplicationSetting $applicationSetting): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationSetting->application->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $applicationSetting->application->team()->first()->id);
return true;
}
/**
@@ -52,7 +56,8 @@ class ApplicationSettingPolicy
*/
public function restore(User $user, ApplicationSetting $applicationSetting): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationSetting->application->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $applicationSetting->application->team()->first()->id);
return true;
}
/**
@@ -60,6 +65,7 @@ class ApplicationSettingPolicy
*/
public function forceDelete(User $user, ApplicationSetting $applicationSetting): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $applicationSetting->application->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $applicationSetting->application->team()->first()->id);
return true;
}
}

View File

@@ -20,7 +20,8 @@ class DatabasePolicy
*/
public function view(User $user, $database): bool
{
return $user->teams()->get()->firstWhere('id', $database->team()->first()->id) !== null;
// return $user->teams->contains('id', $database->team()->first()->id);
return true;
}
/**
@@ -28,7 +29,8 @@ class DatabasePolicy
*/
public function create(User $user): bool
{
return $user->isAdmin();
// return $user->isAdmin();
return true;
}
/**
@@ -36,11 +38,12 @@ class DatabasePolicy
*/
public function update(User $user, $database): Response
{
if ($user->isAdmin() && $user->teams()->get()->firstWhere('id', $database->team()->first()->id) !== null) {
return Response::allow();
}
// if ($user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id)) {
// return Response::allow();
// }
return Response::deny('As a member, you cannot update this database.<br/><br/>You need at least admin or owner permissions.');
// return Response::deny('As a member, you cannot update this database.<br/><br/>You need at least admin or owner permissions.');
return true;
}
/**
@@ -48,7 +51,8 @@ class DatabasePolicy
*/
public function delete(User $user, $database): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $database->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id);
return true;
}
/**
@@ -56,7 +60,8 @@ class DatabasePolicy
*/
public function restore(User $user, $database): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $database->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id);
return true;
}
/**
@@ -64,7 +69,8 @@ class DatabasePolicy
*/
public function forceDelete(User $user, $database): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $database->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id);
return true;
}
/**
@@ -72,7 +78,8 @@ class DatabasePolicy
*/
public function manage(User $user, $database): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $database->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id);
return true;
}
/**
@@ -80,7 +87,8 @@ class DatabasePolicy
*/
public function manageBackups(User $user, $database): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $database->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id);
return true;
}
/**
@@ -88,6 +96,7 @@ class DatabasePolicy
*/
public function manageEnvironment(User $user, $database): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $database->team()->first()->id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id);
return true;
}
}

View File

@@ -20,7 +20,8 @@ class EnvironmentPolicy
*/
public function view(User $user, Environment $environment): bool
{
return $user->teams()->get()->firstWhere('id', $environment->project->team_id) !== null;
// return $user->teams->contains('id', $environment->project->team_id);
return true;
}
/**
@@ -28,7 +29,8 @@ class EnvironmentPolicy
*/
public function create(User $user): bool
{
return $user->isAdmin();
// return $user->isAdmin();
return true;
}
/**
@@ -36,7 +38,8 @@ class EnvironmentPolicy
*/
public function update(User $user, Environment $environment): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $environment->project->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $environment->project->team_id);
return true;
}
/**
@@ -44,7 +47,8 @@ class EnvironmentPolicy
*/
public function delete(User $user, Environment $environment): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $environment->project->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $environment->project->team_id);
return true;
}
/**
@@ -52,7 +56,8 @@ class EnvironmentPolicy
*/
public function restore(User $user, Environment $environment): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $environment->project->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $environment->project->team_id);
return true;
}
/**
@@ -60,6 +65,7 @@ class EnvironmentPolicy
*/
public function forceDelete(User $user, Environment $environment): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $environment->project->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $environment->project->team_id);
return true;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Policies;
use App\Models\GithubApp;
use App\Models\User;
class GithubAppPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, GithubApp $githubApp): bool
{
// return $user->teams->contains('id', $githubApp->team_id) || $githubApp->is_system_wide;
return true;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
// return $user->isAdmin();
return true;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, GithubApp $githubApp): bool
{
if ($githubApp->is_system_wide) {
// return $user->isAdmin();
return true;
}
// return $user->isAdmin() && $user->teams->contains('id', $githubApp->team_id);
return true;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, GithubApp $githubApp): bool
{
if ($githubApp->is_system_wide) {
// return $user->isAdmin();
return true;
}
// return $user->isAdmin() && $user->teams->contains('id', $githubApp->team_id);
return true;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, GithubApp $githubApp): bool
{
return false;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, GithubApp $githubApp): bool
{
return false;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Policies;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
class NotificationPolicy
{
/**
* Determine whether the user can view the notification settings.
*/
public function view(User $user, Model $notificationSettings): bool
{
// Check if the notification settings belong to the user's current team
if (! $notificationSettings->team) {
return false;
}
// return $user->teams()->where('teams.id', $notificationSettings->team->id)->exists();
return true;
}
/**
* Determine whether the user can update the notification settings.
*/
public function update(User $user, Model $notificationSettings): bool
{
// Check if the notification settings belong to the user's current team
if (! $notificationSettings->team) {
return false;
}
// Only owners and admins can update notification settings
// return $user->isAdmin() || $user->isOwner();
return true;
}
/**
* Determine whether the user can manage (create, update, delete) notification settings.
*/
public function manage(User $user, Model $notificationSettings): bool
{
// return $this->update($user, $notificationSettings);
return true;
}
/**
* Determine whether the user can send test notifications.
*/
public function sendTest(User $user, Model $notificationSettings): bool
{
// return $this->update($user, $notificationSettings);
return true;
}
}

View File

@@ -20,7 +20,8 @@ class PrivateKeyPolicy
*/
public function view(User $user, PrivateKey $privateKey): bool
{
return $user->teams()->get()->firstWhere('id', $privateKey->team_id) !== null;
// return $user->teams->contains('id', $privateKey->team_id);
return true;
}
/**
@@ -28,7 +29,8 @@ class PrivateKeyPolicy
*/
public function create(User $user): bool
{
return $user->isAdmin();
// return $user->isAdmin();
return true;
}
/**
@@ -36,7 +38,8 @@ class PrivateKeyPolicy
*/
public function update(User $user, PrivateKey $privateKey): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $privateKey->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $privateKey->team_id);
return true;
}
/**
@@ -44,7 +47,8 @@ class PrivateKeyPolicy
*/
public function delete(User $user, PrivateKey $privateKey): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $privateKey->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $privateKey->team_id);
return true;
}
/**

View File

@@ -20,7 +20,8 @@ class ProjectPolicy
*/
public function view(User $user, Project $project): bool
{
return $user->teams()->get()->firstWhere('id', $project->team_id) !== null;
// return $user->teams->contains('id', $project->team_id);
return true;
}
/**
@@ -28,7 +29,8 @@ class ProjectPolicy
*/
public function create(User $user): bool
{
return $user->isAdmin();
// return $user->isAdmin();
return true;
}
/**
@@ -36,7 +38,8 @@ class ProjectPolicy
*/
public function update(User $user, Project $project): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $project->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $project->team_id);
return true;
}
/**
@@ -44,7 +47,8 @@ class ProjectPolicy
*/
public function delete(User $user, Project $project): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $project->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $project->team_id);
return true;
}
/**
@@ -52,7 +56,8 @@ class ProjectPolicy
*/
public function restore(User $user, Project $project): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $project->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $project->team_id);
return true;
}
/**
@@ -60,6 +65,7 @@ class ProjectPolicy
*/
public function forceDelete(User $user, Project $project): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $project->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $project->team_id);
return true;
}
}

View File

@@ -30,6 +30,7 @@ class ResourceCreatePolicy
StandaloneClickhouse::class,
Service::class,
Application::class,
GithubApp::class,
];
/**
@@ -37,7 +38,8 @@ class ResourceCreatePolicy
*/
public function createAny(User $user): bool
{
return $user->isAdmin();
// return $user->isAdmin();
return true;
}
/**
@@ -49,7 +51,8 @@ class ResourceCreatePolicy
return false;
}
return $user->isAdmin();
// return $user->isAdmin();
return true;
}
/**

View File

@@ -3,7 +3,6 @@
namespace App\Policies;
use App\Models\S3Storage;
use App\Models\Server;
use App\Models\User;
class S3StoragePolicy
@@ -21,7 +20,7 @@ class S3StoragePolicy
*/
public function view(User $user, S3Storage $storage): bool
{
return $user->teams()->get()->firstWhere('id', $storage->team_id)->exists();
return $user->teams->contains('id', $storage->team_id);
}
/**
@@ -35,9 +34,10 @@ class S3StoragePolicy
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Server $server): bool
public function update(User $user, S3Storage $storage): bool
{
return $user->teams()->get()->firstWhere('id', $server->team_id)->exists() && $user->isAdmin();
// return $user->teams->contains('id', $storage->team_id) && $user->isAdmin();
return $user->teams->contains('id', $storage->team_id);
}
/**
@@ -45,7 +45,8 @@ class S3StoragePolicy
*/
public function delete(User $user, S3Storage $storage): bool
{
return $user->teams()->get()->firstWhere('id', $storage->team_id)->exists() && $user->isAdmin();
// return $user->teams->contains('id', $storage->team_id) && $user->isAdmin();
return $user->teams->contains('id', $storage->team_id);
}
/**
@@ -63,4 +64,12 @@ class S3StoragePolicy
{
return false;
}
/**
* Determine whether the user can validate the connection of the model.
*/
public function validateConnection(User $user, S3Storage $storage): bool
{
return $user->teams->contains('id', $storage->team_id);
}
}

View File

@@ -20,7 +20,7 @@ class ServerPolicy
*/
public function view(User $user, Server $server): bool
{
return $user->teams()->get()->firstWhere('id', $server->team_id) !== null;
return $user->teams->contains('id', $server->team_id);
}
/**
@@ -36,7 +36,7 @@ class ServerPolicy
*/
public function update(User $user, Server $server): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $server->team_id) !== null;
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
}
/**
@@ -44,7 +44,7 @@ class ServerPolicy
*/
public function delete(User $user, Server $server): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $server->team_id) !== null;
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
}
/**
@@ -68,7 +68,7 @@ class ServerPolicy
*/
public function manageProxy(User $user, Server $server): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $server->team_id) !== null;
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
}
/**
@@ -76,7 +76,7 @@ class ServerPolicy
*/
public function manageSentinel(User $user, Server $server): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $server->team_id) !== null;
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
}
/**
@@ -84,7 +84,7 @@ class ServerPolicy
*/
public function manageCaCertificate(User $user, Server $server): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $server->team_id) !== null;
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
}
/**
@@ -92,7 +92,7 @@ class ServerPolicy
*/
public function viewTerminal(User $user, Server $server): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $server->team_id) !== null;
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
}
/**
@@ -100,6 +100,6 @@ class ServerPolicy
*/
public function viewSecurity(User $user, Server $server): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $server->team_id) !== null;
return $user->isAdmin() && $user->teams->contains('id', $server->team_id);
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Policies;
use App\Models\ServiceApplication;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
class ServiceApplicationPolicy
{
/**
* Determine whether the user can view the model.
*/
public function view(User $user, ServiceApplication $serviceApplication): bool
{
return Gate::allows('view', $serviceApplication->service);
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
// return $user->isAdmin();
return true;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, ServiceApplication $serviceApplication): bool
{
// return Gate::allows('update', $serviceApplication->service);
return true;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, ServiceApplication $serviceApplication): bool
{
// return Gate::allows('delete', $serviceApplication->service);
return true;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, ServiceApplication $serviceApplication): bool
{
// return Gate::allows('update', $serviceApplication->service);
return true;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, ServiceApplication $serviceApplication): bool
{
// return Gate::allows('delete', $serviceApplication->service);
return true;
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Policies;
use App\Models\ServiceDatabase;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
class ServiceDatabasePolicy
{
/**
* Determine whether the user can view the model.
*/
public function view(User $user, ServiceDatabase $serviceDatabase): bool
{
return Gate::allows('view', $serviceDatabase->service);
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
// return $user->isAdmin();
return true;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, ServiceDatabase $serviceDatabase): bool
{
// return Gate::allows('update', $serviceDatabase->service);
return true;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, ServiceDatabase $serviceDatabase): bool
{
// return Gate::allows('delete', $serviceDatabase->service);
return true;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, ServiceDatabase $serviceDatabase): bool
{
// return Gate::allows('update', $serviceDatabase->service);
return true;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, ServiceDatabase $serviceDatabase): bool
{
// return Gate::allows('delete', $serviceDatabase->service);
return true;
}
}

View File

@@ -28,7 +28,8 @@ class ServicePolicy
*/
public function create(User $user): bool
{
return $user->isAdmin();
// return $user->isAdmin();
return true;
}
/**
@@ -36,7 +37,13 @@ class ServicePolicy
*/
public function update(User $user, Service $service): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $service->team()->first()->id) !== null;
$team = $service->team();
if (! $team) {
return false;
}
// return $user->isAdmin() && $user->teams->contains('id', $team->id);
return true;
}
/**
@@ -44,11 +51,12 @@ class ServicePolicy
*/
public function delete(User $user, Service $service): bool
{
if ($user->isAdmin()) {
return true;
}
// if ($user->isAdmin()) {
// return true;
// }
return false;
// return false;
return true;
}
/**
@@ -56,6 +64,7 @@ class ServicePolicy
*/
public function restore(User $user, Service $service): bool
{
// return true;
return true;
}
@@ -64,16 +73,23 @@ class ServicePolicy
*/
public function forceDelete(User $user, Service $service): bool
{
if ($user->isAdmin()) {
return true;
}
// if ($user->isAdmin()) {
// return true;
// }
return false;
// return false;
return true;
}
public function stop(User $user, Service $service): bool
{
return $user->teams()->get()->firstWhere('id', $service->team()->first()->id) !== null;
$team = $service->team();
if (! $team) {
return false;
}
// return $user->teams->contains('id', $team->id);
return true;
}
/**
@@ -81,7 +97,13 @@ class ServicePolicy
*/
public function manageEnvironment(User $user, Service $service): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $service->team()->first()->id) !== null;
$team = $service->team();
if (! $team) {
return false;
}
// return $user->isAdmin() && $user->teams->contains('id', $team->id);
return true;
}
/**
@@ -89,6 +111,18 @@ class ServicePolicy
*/
public function deploy(User $user, Service $service): bool
{
return $user->teams()->get()->firstWhere('id', $service->team()->first()->id) !== null;
$team = $service->team();
if (! $team) {
return false;
}
// return $user->teams->contains('id', $team->id);
return true;
}
public function accessTerminal(User $user, Service $service): bool
{
// return $user->isAdmin() || $user->teams->contains('id', $service->team()->id);
return true;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Policies;
use App\Models\SharedEnvironmentVariable;
use App\Models\User;
class SharedEnvironmentVariablePolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool
{
return $user->teams->contains('id', $sharedEnvironmentVariable->team_id);
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
// return $user->isAdmin();
return true;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $sharedEnvironmentVariable->team_id);
return true;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $sharedEnvironmentVariable->team_id);
return true;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $sharedEnvironmentVariable->team_id);
return true;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $sharedEnvironmentVariable->team_id);
return true;
}
/**
* Determine whether the user can manage environment variables.
*/
public function manageEnvironment(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $sharedEnvironmentVariable->team_id);
return true;
}
}

View File

@@ -20,7 +20,7 @@ class StandaloneDockerPolicy
*/
public function view(User $user, StandaloneDocker $standaloneDocker): bool
{
return $user->teams()->get()->firstWhere('id', $standaloneDocker->server->team_id) !== null;
return $user->teams->contains('id', $standaloneDocker->server->team_id);
}
/**
@@ -28,7 +28,8 @@ class StandaloneDockerPolicy
*/
public function create(User $user): bool
{
return $user->isAdmin();
// return $user->isAdmin();
return true;
}
/**
@@ -36,7 +37,8 @@ class StandaloneDockerPolicy
*/
public function update(User $user, StandaloneDocker $standaloneDocker): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $standaloneDocker->server->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $standaloneDocker->server->team_id);
return true;
}
/**
@@ -44,7 +46,8 @@ class StandaloneDockerPolicy
*/
public function delete(User $user, StandaloneDocker $standaloneDocker): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $standaloneDocker->server->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $standaloneDocker->server->team_id);
return true;
}
/**
@@ -52,7 +55,8 @@ class StandaloneDockerPolicy
*/
public function restore(User $user, StandaloneDocker $standaloneDocker): bool
{
return false;
// return false;
return true;
}
/**
@@ -60,6 +64,7 @@ class StandaloneDockerPolicy
*/
public function forceDelete(User $user, StandaloneDocker $standaloneDocker): bool
{
return false;
// return false;
return true;
}
}

View File

@@ -20,7 +20,7 @@ class SwarmDockerPolicy
*/
public function view(User $user, SwarmDocker $swarmDocker): bool
{
return $user->teams()->get()->firstWhere('id', $swarmDocker->server->team_id) !== null;
return $user->teams->contains('id', $swarmDocker->server->team_id);
}
/**
@@ -28,7 +28,8 @@ class SwarmDockerPolicy
*/
public function create(User $user): bool
{
return $user->isAdmin();
// return $user->isAdmin();
return true;
}
/**
@@ -36,7 +37,8 @@ class SwarmDockerPolicy
*/
public function update(User $user, SwarmDocker $swarmDocker): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $swarmDocker->server->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $swarmDocker->server->team_id);
return true;
}
/**
@@ -44,7 +46,8 @@ class SwarmDockerPolicy
*/
public function delete(User $user, SwarmDocker $swarmDocker): bool
{
return $user->isAdmin() && $user->teams()->get()->firstWhere('id', $swarmDocker->server->team_id) !== null;
// return $user->isAdmin() && $user->teams->contains('id', $swarmDocker->server->team_id);
return true;
}
/**
@@ -52,7 +55,8 @@ class SwarmDockerPolicy
*/
public function restore(User $user, SwarmDocker $swarmDocker): bool
{
return false;
// return false;
return true;
}
/**
@@ -60,6 +64,7 @@ class SwarmDockerPolicy
*/
public function forceDelete(User $user, SwarmDocker $swarmDocker): bool
{
return false;
// return false;
return true;
}
}

104
app/Policies/TeamPolicy.php Normal file
View File

@@ -0,0 +1,104 @@
<?php
namespace App\Policies;
use App\Models\Team;
use App\Models\User;
class TeamPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Team $team): bool
{
return $user->teams->contains('id', $team->id);
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
// All authenticated users can create teams
return true;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Team $team): bool
{
// Only admins and owners can update team settings
if (! $user->teams->contains('id', $team->id)) {
return false;
}
// return $user->isAdmin() || $user->isOwner();
return true;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Team $team): bool
{
// Only admins and owners can delete teams
if (! $user->teams->contains('id', $team->id)) {
return false;
}
// return $user->isAdmin() || $user->isOwner();
return true;
}
/**
* Determine whether the user can manage team members.
*/
public function manageMembers(User $user, Team $team): bool
{
// Only admins and owners can manage team members
if (! $user->teams->contains('id', $team->id)) {
return false;
}
// return $user->isAdmin() || $user->isOwner();
return true;
}
/**
* Determine whether the user can view admin panel.
*/
public function viewAdmin(User $user, Team $team): bool
{
// Only admins and owners can view admin panel
if (! $user->teams->contains('id', $team->id)) {
return false;
}
// return $user->isAdmin() || $user->isOwner();
return true;
}
/**
* Determine whether the user can manage invitations.
*/
public function manageInvitations(User $user, Team $team): bool
{
// Only admins and owners can manage invitations
if (! $user->teams->contains('id', $team->id)) {
return false;
}
// return $user->isAdmin() || $user->isOwner();
return true;
}
}

View File

@@ -23,8 +23,11 @@ class AuthServiceProvider extends ServiceProvider
\App\Models\ApplicationPreview::class => \App\Policies\ApplicationPreviewPolicy::class,
\App\Models\ApplicationSetting::class => \App\Policies\ApplicationSettingPolicy::class,
\App\Models\Service::class => \App\Policies\ServicePolicy::class,
\App\Models\ServiceApplication::class => \App\Policies\ServiceApplicationPolicy::class,
\App\Models\ServiceDatabase::class => \App\Policies\ServiceDatabasePolicy::class,
\App\Models\Project::class => \App\Policies\ProjectPolicy::class,
\App\Models\Environment::class => \App\Policies\EnvironmentPolicy::class,
\App\Models\SharedEnvironmentVariable::class => \App\Policies\SharedEnvironmentVariablePolicy::class,
// Database policies - all use the shared DatabasePolicy
\App\Models\StandalonePostgresql::class => \App\Policies\DatabasePolicy::class,
\App\Models\StandaloneMysql::class => \App\Policies\DatabasePolicy::class,
@@ -35,6 +38,22 @@ class AuthServiceProvider extends ServiceProvider
\App\Models\StandaloneDragonfly::class => \App\Policies\DatabasePolicy::class,
\App\Models\StandaloneClickhouse::class => \App\Policies\DatabasePolicy::class,
// Notification policies - all use the shared NotificationPolicy
\App\Models\EmailNotificationSettings::class => \App\Policies\NotificationPolicy::class,
\App\Models\DiscordNotificationSettings::class => \App\Policies\NotificationPolicy::class,
\App\Models\TelegramNotificationSettings::class => \App\Policies\NotificationPolicy::class,
\App\Models\SlackNotificationSettings::class => \App\Policies\NotificationPolicy::class,
\App\Models\PushoverNotificationSettings::class => \App\Policies\NotificationPolicy::class,
// API Token policy
\Laravel\Sanctum\PersonalAccessToken::class => \App\Policies\ApiTokenPolicy::class,
// Team policy
\App\Models\Team::class => \App\Policies\TeamPolicy::class,
// Git source policies
\App\Models\GithubApp::class => \App\Policies\GithubAppPolicy::class,
];
/**
@@ -44,5 +63,11 @@ class AuthServiceProvider extends ServiceProvider
{
// Register gates for resource creation policy
Gate::define('createAnyResource', [ResourceCreatePolicy::class, 'createAny']);
// Register gate for terminal access
Gate::define('canAccessTerminal', function ($user) {
// return $user->isAdmin() || $user->isOwner();
return true;
});
}
}

View File

@@ -4,6 +4,7 @@ namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Gate;
use Illuminate\View\Component;
class Button extends Component
@@ -17,7 +18,19 @@ class Button extends Component
public ?string $modalId = null,
public string $defaultClass = 'button',
public bool $showLoadingIndicator = true,
public ?string $canGate = null,
public mixed $canResource = null,
public bool $autoDisable = true,
) {
// Handle authorization-based disabling
if ($this->canGate && $this->canResource && $this->autoDisable) {
$hasPermission = Gate::allows($this->canGate, $this->canResource);
if (! $hasPermission) {
$this->disabled = true;
}
}
if ($this->noStyle) {
$this->defaultClass = '';
}

View File

@@ -4,6 +4,7 @@ namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Gate;
use Illuminate\View\Component;
class Checkbox extends Component
@@ -22,7 +23,20 @@ class Checkbox extends Component
public string|bool $instantSave = false,
public bool $disabled = false,
public string $defaultClass = 'dark:border-neutral-700 text-coolgray-400 focus:ring-warning dark:bg-coolgray-100 rounded-sm cursor-pointer dark:disabled:bg-base dark:disabled:cursor-not-allowed',
public ?string $canGate = null,
public mixed $canResource = null,
public bool $autoDisable = true,
) {
// Handle authorization-based disabling
if ($this->canGate && $this->canResource && $this->autoDisable) {
$hasPermission = Gate::allows($this->canGate, $this->canResource);
if (! $hasPermission) {
$this->disabled = true;
$this->instantSave = false; // Disable instant save for unauthorized users
}
}
if ($this->disabled) {
$this->defaultClass .= ' opacity-40';
}

View File

@@ -4,6 +4,7 @@ namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Gate;
use Illuminate\View\Component;
use Visus\Cuid2\Cuid2;
@@ -26,7 +27,19 @@ class Input extends Component
public ?int $minlength = null,
public ?int $maxlength = null,
public bool $autofocus = false,
) {}
public ?string $canGate = null,
public mixed $canResource = null,
public bool $autoDisable = true,
) {
// Handle authorization-based disabling
if ($this->canGate && $this->canResource && $this->autoDisable) {
$hasPermission = Gate::allows($this->canGate, $this->canResource);
if (! $hasPermission) {
$this->disabled = true;
}
}
}
public function render(): View|Closure|string
{

View File

@@ -4,6 +4,7 @@ namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Gate;
use Illuminate\View\Component;
use Visus\Cuid2\Cuid2;
@@ -19,9 +20,19 @@ class Select extends Component
public ?string $helper = null,
public bool $required = false,
public bool $disabled = false,
public string $defaultClass = 'select w-full'
public string $defaultClass = 'select w-full',
public ?string $canGate = null,
public mixed $canResource = null,
public bool $autoDisable = true,
) {
//
// Handle authorization-based disabling
if ($this->canGate && $this->canResource && $this->autoDisable) {
$hasPermission = Gate::allows($this->canGate, $this->canResource);
if (! $hasPermission) {
$this->disabled = true;
}
}
}
/**

View File

@@ -4,6 +4,7 @@ namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Gate;
use Illuminate\View\Component;
use Visus\Cuid2\Cuid2;
@@ -33,8 +34,18 @@ class Textarea extends Component
public string $defaultClassInput = 'input',
public ?int $minlength = null,
public ?int $maxlength = null,
public ?string $canGate = null,
public mixed $canResource = null,
public bool $autoDisable = true,
) {
//
// Handle authorization-based disabling
if ($this->canGate && $this->canResource && $this->autoDisable) {
$hasPermission = Gate::allows($this->canGate, $this->canResource);
if (! $hasPermission) {
$this->disabled = true;
}
}
}
/**

View File

@@ -229,6 +229,7 @@
Tags
</a>
</li>
@can('canAccessTerminal')
<li>
<a title="Terminal"
class="{{ request()->is('terminal*') ? 'menu-item-active menu-item' : 'menu-item' }}"
@@ -243,6 +244,7 @@
Terminal
</a>
</li>
@endcan
<li>
<a title="Profile"
class="{{ request()->is('profile*') ? 'menu-item-active menu-item' : 'menu-item' }}"

View File

@@ -5,9 +5,11 @@
<div class="flex items-center gap-2">
<h1>Destinations</h1>
@if ($servers->count() > 0)
@can('createAnyResource')
<x-modal-input buttonTitle="+ Add" title="New Destination">
<livewire:destination.new.docker />
</x-modal-input>
@endcan
@endif
</div>
<div class="subtitle">Network endpoints to deploy your resources.</div>

View File

@@ -1,4 +1,5 @@
<div class="w-full ">
@can('createAnyResource')
<div class="w-full ">
<div class="subtitle">Destinations are used to segregate resources by network.</div>
<form class="flex flex-col gap-4" wire:submit='submit'>
<div class="flex gap-2">
@@ -15,4 +16,10 @@
Continue
</x-forms.button>
</form>
</div>
</div>
@else
<div class="text-gray-500 p-4 text-center">
<p>You don't have permission to create new destinations.</p>
<p class="text-sm">Please contact your team administrator for access.</p>
</div>
@endcan

View File

@@ -2,14 +2,14 @@
<form class="flex flex-col">
<div class="flex items-center gap-2">
<h1>Destination</h1>
<x-forms.button wire:click.prevent='submit' type="submit">
Save
</x-forms.button>
<x-forms.button canGate="update" :canResource="$destination" wire:click.prevent='submit'
type="submit">Save</x-forms.button>
@if ($network !== 'coolify')
<x-modal-confirmation title="Confirm Destination Deletion?" buttonTitle="Delete Destination" isErrorButton
submitAction="delete" :actions="['This will delete the selected destination/network.']" confirmationText="{{ $destination->name }}"
confirmationLabel="Please confirm the execution of the actions by entering the Destination Name below"
shortConfirmationLabel="Destination Name" :confirmWithPassword="false" step2ButtonText="Permanently Delete" />
shortConfirmationLabel="Destination Name" :confirmWithPassword="false" step2ButtonText="Permanently Delete"
canGate="delete" :canResource="$destination" />
@endif
</div>
@@ -19,7 +19,7 @@
<div class="subtitle ">A swarm Docker network. WIP</div>
@endif
<div class="flex gap-2">
<x-forms.input id="name" label="Name" />
<x-forms.input canGate="update" :canResource="$destination" id="name" label="Name" />
<x-forms.input id="serverIp" label="Server IP" readonly />
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
<x-forms.input id="network" label="Docker Network" readonly />

View File

@@ -6,27 +6,27 @@
<form wire:submit='submit' class="flex flex-col gap-4 pb-4">
<div class="flex items-center gap-2">
<h2>Discord</h2>
<x-forms.button type="submit">
<x-forms.button canGate="update" :canResource="$settings" type="submit">
Save
</x-forms.button>
@if ($discordEnabled)
<x-forms.button class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
<x-forms.button canGate="sendTest" :canResource="$settings" class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
wire:click="sendTestNotification">
Send Test Notification
</x-forms.button>
@else
<x-forms.button disabled class="normal-case dark:text-white btn btn-xs no-animation btn-primary">
<x-forms.button canGate="sendTest" :canResource="$settings" disabled class="normal-case dark:text-white btn btn-xs no-animation btn-primary">
Send Test Notification
</x-forms.button>
@endif
</div>
<div class="w-48">
<x-forms.checkbox instantSave="instantSaveDiscordEnabled" id="discordEnabled" label="Enabled" />
<x-forms.checkbox instantSave="instantSaveDiscordPingEnabled" id="discordPingEnabled"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="instantSaveDiscordEnabled" id="discordEnabled" label="Enabled" />
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="instantSaveDiscordPingEnabled" id="discordPingEnabled"
helper="If enabled, a ping (@here) will be sent to the notification when a critical event happens."
label="Ping Enabled" />
</div>
<x-forms.input type="password"
<x-forms.input canGate="update" :canResource="$settings" type="password"
helper="Create a Discord Server and generate a Webhook URL. <br><a class='inline-block underline dark:text-white' href='https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks' target='_blank'>Webhook Documentation</a>"
required id="discordWebhookUrl" label="Webhook" />
</form>
@@ -38,11 +38,11 @@
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentSuccessDiscordNotifications"
label="Deployment Success" />
<x-forms.checkbox instantSave="saveModel" id="deploymentFailureDiscordNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentFailureDiscordNotifications"
label="Deployment Failure" />
<x-forms.checkbox instantSave="saveModel"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
helper="Send a notification when a container status changes. It will notify for Stopped and Restarted events of a container."
id="statusChangeDiscordNotifications" label="Container Status Changes" />
</div>
@@ -50,35 +50,35 @@
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupSuccessDiscordNotifications"
label="Backup Success" />
<x-forms.checkbox instantSave="saveModel" id="backupFailureDiscordNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupFailureDiscordNotifications"
label="Backup Failure" />
</div>
</div>
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskSuccessDiscordNotifications"
label="Scheduled Task Success" />
<x-forms.checkbox instantSave="saveModel" id="scheduledTaskFailureDiscordNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskFailureDiscordNotifications"
label="Scheduled Task Failure" />
</div>
</div>
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupSuccessDiscordNotifications"
label="Docker Cleanup Success" />
<x-forms.checkbox instantSave="saveModel" id="dockerCleanupFailureDiscordNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupFailureDiscordNotifications"
label="Docker Cleanup Failure" />
<x-forms.checkbox instantSave="saveModel" id="serverDiskUsageDiscordNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverDiskUsageDiscordNotifications"
label="Server Disk Usage" />
<x-forms.checkbox instantSave="saveModel" id="serverReachableDiscordNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverReachableDiscordNotifications"
label="Server Reachable" />
<x-forms.checkbox instantSave="saveModel" id="serverUnreachableDiscordNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverUnreachableDiscordNotifications"
label="Server Unreachable" />
<x-forms.checkbox instantSave="saveModel" id="serverPatchDiscordNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverPatchDiscordNotifications"
label="Server Patching" />
</div>
</div>

View File

@@ -6,10 +6,11 @@
<form wire:submit='submit' class="flex flex-col gap-4 pb-4">
<div class="flex items-center gap-2">
<h2>Email</h2>
<x-forms.button type="submit">
<x-forms.button canGate="update" :canResource="$settings" type="submit">
Save
</x-forms.button>
@if (auth()->user()->isAdminFromSession())
@can('sendTest', $settings)
@if ($team->isNotificationEnabled('email'))
<x-modal-input buttonTitle="Send Test Email" title="Send Test Email">
<form wire:submit.prevent="sendTestEmail" class="flex flex-col w-full gap-2">
@@ -25,22 +26,23 @@
Send Test Email
</x-forms.button>
@endif
@endcan
@endif
</div>
@if (!isCloud())
<div class="w-96">
<x-forms.checkbox instantSave="instantSave()" id="useInstanceEmailSettings"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="instantSave()" id="useInstanceEmailSettings"
label="Use system wide (transactional) email settings" />
</div>
@endif
@if (!$useInstanceEmailSettings)
<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."
<x-forms.input canGate="update" :canResource="$settings" required id="smtpFromName" helper="Name used in emails." label="From Name" />
<x-forms.input canGate="update" :canResource="$settings" required id="smtpFromAddress" helper="Email address used in emails."
label="From Address" />
</div>
@if (isInstanceAdmin() && !$useInstanceEmailSettings)
<x-forms.button wire:click='copyFromInstanceSettings'>
<x-forms.button canGate="update" :canResource="$settings" wire:click='copyFromInstanceSettings'>
Copy from Instance Settings
</x-forms.button>
@endif
@@ -48,7 +50,7 @@
</form>
@if (isCloud())
<div class="w-64 py-4">
<x-forms.checkbox instantSave="instantSave()" id="useInstanceEmailSettings"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="instantSave()" id="useInstanceEmailSettings"
label="Use Hosted Email Service" />
</div>
@endif
@@ -58,29 +60,29 @@
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">
<x-forms.button canGate="update" :canResource="$settings" type="submit">
Save
</x-forms.button>
</div>
<div class="w-32">
<x-forms.checkbox wire:model="smtpEnabled" instantSave="instantSave('SMTP')" id="smtpEnabled"
<x-forms.checkbox canGate="update" :canResource="$settings" wire:model="smtpEnabled" instantSave="instantSave('SMTP')" id="smtpEnabled"
label="Enabled" />
</div>
<div class="flex flex-col">
<div class="flex flex-col gap-4">
<div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input required id="smtpHost" placeholder="smtp.mailgun.org" label="Host" />
<x-forms.input required id="smtpPort" placeholder="587" label="Port" />
<x-forms.select required id="smtpEncryption" label="Encryption">
<x-forms.input canGate="update" :canResource="$settings" required id="smtpHost" placeholder="smtp.mailgun.org" label="Host" />
<x-forms.input canGate="update" :canResource="$settings" required id="smtpPort" placeholder="587" label="Port" />
<x-forms.select canGate="update" :canResource="$settings" required id="smtpEncryption" label="Encryption">
<option value="starttls">StartTLS</option>
<option value="tls">TLS/SSL</option>
<option value="none">None</option>
</x-forms.select>
</div>
<div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input id="smtpUsername" label="SMTP Username" />
<x-forms.input id="smtpPassword" type="password" label="SMTP Password" />
<x-forms.input id="smtpTimeout" helper="Timeout value for sending emails."
<x-forms.input canGate="update" :canResource="$settings" id="smtpUsername" label="SMTP Username" />
<x-forms.input canGate="update" :canResource="$settings" id="smtpPassword" type="password" label="SMTP Password" />
<x-forms.input canGate="update" :canResource="$settings" id="smtpTimeout" helper="Timeout value for sending emails."
label="Timeout" />
</div>
</div>
@@ -90,18 +92,18 @@
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">
<x-forms.button canGate="update" :canResource="$settings" type="submit">
Save
</x-forms.button>
</div>
<div class="w-32">
<x-forms.checkbox wire:model="resendEnabled" instantSave="instantSave('Resend')" id="resendEnabled"
<x-forms.checkbox canGate="update" :canResource="$settings" wire:model="resendEnabled" instantSave="instantSave('Resend')" id="resendEnabled"
label="Enabled" />
</div>
<div class="flex flex-col">
<div class="flex flex-col gap-4">
<div class="flex flex-col w-full gap-2 xl:flex-row">
<x-forms.input required type="password" id="resendApiKey" placeholder="API key"
<x-forms.input canGate="update" :canResource="$settings" required type="password" id="resendApiKey" placeholder="API key"
label="API Key" />
</div>
</div>
@@ -117,11 +119,11 @@
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentSuccessEmailNotifications"
label="Deployment Success" />
<x-forms.checkbox instantSave="saveModel" id="deploymentFailureEmailNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentFailureEmailNotifications"
label="Deployment Failure" />
<x-forms.checkbox instantSave="saveModel"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
helper="Send an email when a container status changes. It will send and email for Stopped and Restarted events of a container."
id="statusChangeEmailNotifications" label="Container Status Changes" />
</div>
@@ -129,35 +131,35 @@
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupSuccessEmailNotifications"
label="Backup Success" />
<x-forms.checkbox instantSave="saveModel" id="backupFailureEmailNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupFailureEmailNotifications"
label="Backup Failure" />
</div>
</div>
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskSuccessEmailNotifications"
label="Scheduled Task Success" />
<x-forms.checkbox instantSave="saveModel" id="scheduledTaskFailureEmailNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskFailureEmailNotifications"
label="Scheduled Task Failure" />
</div>
</div>
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupSuccessEmailNotifications"
label="Docker Cleanup Success" />
<x-forms.checkbox instantSave="saveModel" id="dockerCleanupFailureEmailNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupFailureEmailNotifications"
label="Docker Cleanup Failure" />
<x-forms.checkbox instantSave="saveModel" id="serverDiskUsageEmailNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverDiskUsageEmailNotifications"
label="Server Disk Usage" />
<x-forms.checkbox instantSave="saveModel" id="serverReachableEmailNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverReachableEmailNotifications"
label="Server Reachable" />
<x-forms.checkbox instantSave="saveModel" id="serverUnreachableEmailNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverUnreachableEmailNotifications"
label="Server Unreachable" />
<x-forms.checkbox instantSave="saveModel" id="serverPatchEmailNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverPatchEmailNotifications"
label="Server Patching" />
</div>
</div>

View File

@@ -6,28 +6,28 @@
<form wire:submit='submit' class="flex flex-col gap-4 pb-4">
<div class="flex items-center gap-2">
<h2>Pushover</h2>
<x-forms.button type="submit">
<x-forms.button canGate="update" :canResource="$settings" type="submit">
Save
</x-forms.button>
@if ($pushoverEnabled)
<x-forms.button class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
<x-forms.button canGate="sendTest" :canResource="$settings" class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
wire:click="sendTestNotification">
Send Test Notification
</x-forms.button>
@else
<x-forms.button disabled class="normal-case dark:text-white btn btn-xs no-animation btn-primary">
<x-forms.button canGate="sendTest" :canResource="$settings" disabled class="normal-case dark:text-white btn btn-xs no-animation btn-primary">
Send Test Notification
</x-forms.button>
@endif
</div>
<div class="w-32">
<x-forms.checkbox instantSave="instantSavePushoverEnabled" id="pushoverEnabled" label="Enabled" />
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="instantSavePushoverEnabled" id="pushoverEnabled" label="Enabled" />
</div>
<div class="flex gap-2">
<x-forms.input type="password"
<x-forms.input canGate="update" :canResource="$settings" type="password"
helper="Get your User Key in Pushover. You need to be logged in to Pushover to see your user key in the top right corner. <br><a class='inline-block underline dark:text-white' href='https://pushover.net/' target='_blank'>Pushover Dashboard</a>"
required id="pushoverUserKey" label="User Key" />
<x-forms.input type="password"
<x-forms.input canGate="update" :canResource="$settings" type="password"
helper="Generate an API Token/Key in Pushover by creating a new application. <br><a class='inline-block underline dark:text-white' href='https://pushover.net/apps/build' target='_blank'>Create Pushover Application</a>"
required id="pushoverApiToken" label="API Token" />
</div>
@@ -40,11 +40,11 @@
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentSuccessPushoverNotifications"
label="Deployment Success" />
<x-forms.checkbox instantSave="saveModel" id="deploymentFailurePushoverNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentFailurePushoverNotifications"
label="Deployment Failure" />
<x-forms.checkbox instantSave="saveModel"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
helper="Send a notification when a container status changes. It will notify for Stopped and Restarted events of a container."
id="statusChangePushoverNotifications" label="Container Status Changes" />
</div>
@@ -52,35 +52,35 @@
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupSuccessPushoverNotifications"
label="Backup Success" />
<x-forms.checkbox instantSave="saveModel" id="backupFailurePushoverNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupFailurePushoverNotifications"
label="Backup Failure" />
</div>
</div>
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskSuccessPushoverNotifications"
label="Scheduled Task Success" />
<x-forms.checkbox instantSave="saveModel" id="scheduledTaskFailurePushoverNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskFailurePushoverNotifications"
label="Scheduled Task Failure" />
</div>
</div>
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupSuccessPushoverNotifications"
label="Docker Cleanup Success" />
<x-forms.checkbox instantSave="saveModel" id="dockerCleanupFailurePushoverNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupFailurePushoverNotifications"
label="Docker Cleanup Failure" />
<x-forms.checkbox instantSave="saveModel" id="serverDiskUsagePushoverNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverDiskUsagePushoverNotifications"
label="Server Disk Usage" />
<x-forms.checkbox instantSave="saveModel" id="serverReachablePushoverNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverReachablePushoverNotifications"
label="Server Reachable" />
<x-forms.checkbox instantSave="saveModel" id="serverUnreachablePushoverNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverUnreachablePushoverNotifications"
label="Server Unreachable" />
<x-forms.checkbox instantSave="saveModel" id="serverPatchPushoverNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverPatchPushoverNotifications"
label="Server Patching" />
</div>
</div>

View File

@@ -6,24 +6,24 @@
<form wire:submit='submit' class="flex flex-col gap-4 pb-4">
<div class="flex items-center gap-2">
<h2>Slack</h2>
<x-forms.button type="submit">
<x-forms.button canGate="update" :canResource="$settings" type="submit">
Save
</x-forms.button>
@if ($slackEnabled)
<x-forms.button class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
<x-forms.button canGate="sendTest" :canResource="$settings" class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
wire:click="sendTestNotification">
Send Test Notification
</x-forms.button>
@else
<x-forms.button disabled class="normal-case dark:text-white btn btn-xs no-animation btn-primary">
<x-forms.button canGate="sendTest" :canResource="$settings" disabled class="normal-case dark:text-white btn btn-xs no-animation btn-primary">
Send Test Notification
</x-forms.button>
@endif
</div>
<div class="w-32">
<x-forms.checkbox instantSave="instantSaveSlackEnabled" id="slackEnabled" label="Enabled" />
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="instantSaveSlackEnabled" id="slackEnabled" label="Enabled" />
</div>
<x-forms.input type="password"
<x-forms.input canGate="update" :canResource="$settings" type="password"
helper="Create a Slack APP and generate a Incoming Webhook URL. <br><a class='inline-block underline dark:text-white' href='https://api.slack.com/apps' target='_blank'>Create Slack APP</a>"
required id="slackWebhookUrl" label="Webhook" />
</form>
@@ -35,11 +35,11 @@
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentSuccessSlackNotifications"
label="Deployment Success" />
<x-forms.checkbox instantSave="saveModel" id="deploymentFailureSlackNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentFailureSlackNotifications"
label="Deployment Failure" />
<x-forms.checkbox instantSave="saveModel"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel"
helper="Send a notification when a container status changes. It will notify for Stopped and Restarted events of a container."
id="statusChangeSlackNotifications" label="Container Status Changes" />
</div>
@@ -47,33 +47,33 @@
<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" />
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupSuccessSlackNotifications" label="Backup Success" />
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupFailureSlackNotifications" label="Backup Failure" />
</div>
</div>
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskSuccessSlackNotifications"
label="Scheduled Task Success" />
<x-forms.checkbox instantSave="saveModel" id="scheduledTaskFailureSlackNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskFailureSlackNotifications"
label="Scheduled Task Failure" />
</div>
</div>
<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"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupSuccessSlackNotifications"
label="Docker Cleanup Success" />
<x-forms.checkbox instantSave="saveModel" id="dockerCleanupFailureSlackNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupFailureSlackNotifications"
label="Docker Cleanup Failure" />
<x-forms.checkbox instantSave="saveModel" id="serverDiskUsageSlackNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverDiskUsageSlackNotifications"
label="Server Disk Usage" />
<x-forms.checkbox instantSave="saveModel" id="serverReachableSlackNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverReachableSlackNotifications"
label="Server Reachable" />
<x-forms.checkbox instantSave="saveModel" id="serverUnreachableSlackNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverUnreachableSlackNotifications"
label="Server Unreachable" />
<x-forms.checkbox instantSave="saveModel" id="serverPatchSlackNotifications" label="Server Patching" />
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverPatchSlackNotifications" label="Server Patching" />
</div>
</div>
</div>

View File

@@ -6,28 +6,28 @@
<form wire:submit='submit' class="flex flex-col gap-4 pb-4">
<div class="flex items-center gap-2">
<h2>Telegram</h2>
<x-forms.button type="submit">
<x-forms.button canGate="update" :canResource="$settings" type="submit">
Save
</x-forms.button>
@if ($telegramEnabled)
<x-forms.button class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
<x-forms.button canGate="sendTest" :canResource="$settings" class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
wire:click="sendTestNotification">
Send Test Notification
</x-forms.button>
@else
<x-forms.button disabled class="normal-case dark:text-white btn btn-xs no-animation btn-primary">
<x-forms.button canGate="sendTest" :canResource="$settings" disabled class="normal-case dark:text-white btn btn-xs no-animation btn-primary">
Send Test Notification
</x-forms.button>
@endif
</div>
<div class="w-32">
<x-forms.checkbox instantSave="instantSaveTelegramEnabled" id="telegramEnabled" label="Enabled" />
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="instantSaveTelegramEnabled" id="telegramEnabled" label="Enabled" />
</div>
<div class="flex gap-2">
<x-forms.input type="password" autocomplete="new-password"
<x-forms.input canGate="update" :canResource="$settings" type="password" autocomplete="new-password"
helper="Get it from the <a class='inline-block underline dark:text-white' href='https://t.me/botfather' target='_blank'>BotFather Bot</a> on Telegram."
required id="telegramToken" label="Bot API Token" />
<x-forms.input type="password" autocomplete="new-password"
<x-forms.input canGate="update" :canResource="$settings" type="password" autocomplete="new-password"
helper="Add your bot to a group chat and add its Chat ID here." required id="telegramChatId"
label="Chat ID" />
</div>
@@ -42,27 +42,27 @@
<div class="flex flex-col gap-1.5 pl-1">
<div class="pl-1 flex gap-2">
<div class="w-96">
<x-forms.checkbox instantSave="saveModel" id="deploymentSuccessTelegramNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentSuccessTelegramNotifications"
label="Deployment Success" />
</div>
<x-forms.input type="password" placeholder="Custom Telegram Thread ID"
<x-forms.input canGate="update" :canResource="$settings" type="password" placeholder="Custom Telegram Thread ID"
id="telegramNotificationsDeploymentSuccessThreadId" />
</div>
<div class="pl-1 flex gap-2">
<div class="w-96">
<x-forms.checkbox instantSave="saveModel" id="deploymentFailureTelegramNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="deploymentFailureTelegramNotifications"
label="Deployment Failure" />
</div>
<x-forms.input type="password" placeholder="Custom Telegram Thread ID"
<x-forms.input canGate="update" :canResource="$settings" type="password" placeholder="Custom Telegram Thread ID"
id="telegramNotificationsDeploymentFailureThreadId" />
</div>
<div class="pl-1 flex gap-2">
<div class="w-96">
<x-forms.checkbox instantSave="saveModel" id="statusChangeTelegramNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="statusChangeTelegramNotifications"
label="Container Status Changes"
helper="Send a notification when a container status changes. It will send a notification for Stopped and Restarted events of a container." />
</div>
<x-forms.input type="password" id="telegramNotificationsStatusChangeThreadId"
<x-forms.input canGate="update" :canResource="$settings" type="password" id="telegramNotificationsStatusChangeThreadId"
placeholder="Custom Telegram Thread ID" />
</div>
</div>
@@ -72,19 +72,19 @@
<div class="flex flex-col gap-1.5 pl-1">
<div class="pl-1 flex gap-2">
<div class="w-96">
<x-forms.checkbox instantSave="saveModel" id="backupSuccessTelegramNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupSuccessTelegramNotifications"
label="Backup Success" />
</div>
<x-forms.input type="password" placeholder="Custom Telegram Thread ID"
<x-forms.input canGate="update" :canResource="$settings" type="password" placeholder="Custom Telegram Thread ID"
id="telegramNotificationsBackupSuccessThreadId" />
</div>
<div class="pl-1 flex gap-2">
<div class="w-96">
<x-forms.checkbox instantSave="saveModel" id="backupFailureTelegramNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="backupFailureTelegramNotifications"
label="Backup Failure" />
</div>
<x-forms.input type="password" placeholder="Custom Telegram Thread ID"
<x-forms.input canGate="update" :canResource="$settings" type="password" placeholder="Custom Telegram Thread ID"
id="telegramNotificationsBackupFailureThreadId" />
</div>
</div>
@@ -95,19 +95,19 @@
<div class="flex flex-col gap-1.5 pl-1">
<div class="pl-1 flex gap-2">
<div class="w-96">
<x-forms.checkbox instantSave="saveModel" id="scheduledTaskSuccessTelegramNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskSuccessTelegramNotifications"
label="Scheduled Task Success" />
</div>
<x-forms.input type="password" placeholder="Custom Telegram Thread ID"
<x-forms.input canGate="update" :canResource="$settings" type="password" placeholder="Custom Telegram Thread ID"
id="telegramNotificationsScheduledTaskSuccessThreadId" />
</div>
<div class="pl-1 flex gap-2">
<div class="w-96">
<x-forms.checkbox instantSave="saveModel" id="scheduledTaskFailureTelegramNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="scheduledTaskFailureTelegramNotifications"
label="Scheduled Task Failure" />
</div>
<x-forms.input type="password" placeholder="Custom Telegram Thread ID"
<x-forms.input canGate="update" :canResource="$settings" type="password" placeholder="Custom Telegram Thread ID"
id="telegramNotificationsScheduledTaskFailureThreadId" />
</div>
</div>
@@ -118,55 +118,55 @@
<div class="flex flex-col gap-1.5 pl-1">
<div class="pl-1 flex gap-2">
<div class="w-96">
<x-forms.checkbox instantSave="saveModel" id="dockerCleanupSuccessTelegramNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupSuccessTelegramNotifications"
label="Docker Cleanup Success" />
</div>
<x-forms.input type="password" placeholder="Custom Telegram Thread ID"
<x-forms.input canGate="update" :canResource="$settings" type="password" placeholder="Custom Telegram Thread ID"
id="telegramNotificationsDockerCleanupSuccessThreadId" />
</div>
<div class="pl-1 flex gap-2">
<div class="w-96">
<x-forms.checkbox instantSave="saveModel" id="dockerCleanupFailureTelegramNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="dockerCleanupFailureTelegramNotifications"
label="Docker Cleanup Failure" />
</div>
<x-forms.input type="password" placeholder="Custom Telegram Thread ID"
<x-forms.input canGate="update" :canResource="$settings" type="password" placeholder="Custom Telegram Thread ID"
id="telegramNotificationsDockerCleanupFailureThreadId" />
</div>
<div class="pl-1 flex gap-2">
<div class="w-96">
<x-forms.checkbox instantSave="saveModel" id="serverDiskUsageTelegramNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverDiskUsageTelegramNotifications"
label="Server Disk Usage" />
</div>
<x-forms.input type="password" placeholder="Custom Telegram Thread ID"
<x-forms.input canGate="update" :canResource="$settings" type="password" placeholder="Custom Telegram Thread ID"
id="telegramNotificationsServerDiskUsageThreadId" />
</div>
<div class="pl-1 flex gap-2">
<div class="w-96">
<x-forms.checkbox instantSave="saveModel" id="serverReachableTelegramNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverReachableTelegramNotifications"
label="Server Reachable" />
</div>
<x-forms.input type="password" placeholder="Custom Telegram Thread ID"
<x-forms.input canGate="update" :canResource="$settings" type="password" placeholder="Custom Telegram Thread ID"
id="telegramNotificationsServerReachableThreadId" />
</div>
<div class="pl-1 flex gap-2">
<div class="w-96">
<x-forms.checkbox instantSave="saveModel" id="serverUnreachableTelegramNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverUnreachableTelegramNotifications"
label="Server Unreachable" />
</div>
<x-forms.input type="password" placeholder="Custom Telegram Thread ID"
<x-forms.input canGate="update" :canResource="$settings" type="password" placeholder="Custom Telegram Thread ID"
id="telegramNotificationsServerUnreachableThreadId" />
</div>
<div class="pl-1 flex gap-2">
<div class="w-96">
<x-forms.checkbox instantSave="saveModel" id="serverPatchTelegramNotifications"
<x-forms.checkbox canGate="update" :canResource="$settings" instantSave="saveModel" id="serverPatchTelegramNotifications"
label="Server Patching" />
</div>
<x-forms.input type="password" placeholder="Custom Telegram Thread ID"
<x-forms.input canGate="update" :canResource="$settings" type="password" placeholder="Custom Telegram Thread ID"
id="telegramNotificationsServerPatchThreadId" />
</div>
</div>

View File

@@ -8,76 +8,73 @@
<h3>General</h3>
@if ($application->git_based())
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
id="isAutoDeployEnabled" label="Auto Deploy" />
id="isAutoDeployEnabled" label="Auto Deploy" canGate="update" :canResource="$application" />
<x-forms.checkbox
helper="Allow to automatically deploy Preview Deployments for all opened PR's.<br><br>Closing a PR will delete Preview Deployments."
instantSave id="isPreviewDeploymentsEnabled" label="Preview Deployments" />
instantSave id="isPreviewDeploymentsEnabled" label="Preview Deployments" canGate="update"
:canResource="$application" />
@endif
<x-forms.checkbox helper="Disable Docker build cache on every deployment." instantSave id="disableBuildCache"
label="Disable Build Cache" />
<x-forms.checkbox helper="Disable Docker build cache on every deployment." instantSave
id="disableBuildCache" label="Disable Build Cache" canGate="update" :canResource="$application" />
@if ($application->settings->is_container_label_readonly_enabled)
<x-forms.checkbox
helper="Your application will be available only on https if your domain starts with https://..."
instantSave id="isForceHttpsEnabled" label="Force Https" />
instantSave id="isForceHttpsEnabled" label="Force Https" canGate="update" :canResource="$application" />
<x-forms.checkbox 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."
instantSave id="isGzipEnabled" />
instantSave id="isGzipEnabled" canGate="update" :canResource="$application" />
<x-forms.checkbox helper="Strip Prefix is used to remove prefixes from paths. Like /api/ to /api."
instantSave id="isStripprefixEnabled" label="Strip Prefixes" />
instantSave id="isStripprefixEnabled" label="Strip Prefixes" canGate="update" :canResource="$application" />
@else
<x-forms.checkbox disabled
helper="Readonly labels are disabled. You need to set the labels in the labels section." instantSave
id="isForceHttpsEnabled" label="Force Https" />
id="isForceHttpsEnabled" label="Force Https" canGate="update" :canResource="$application" />
<x-forms.checkbox label="Enable Gzip Compression" disabled
helper="Readonly labels are disabled. You need to set the labels in the labels section." instantSave
id="isGzipEnabled" />
id="isGzipEnabled" canGate="update" :canResource="$application" />
<x-forms.checkbox
helper="Readonly labels are disabled. You need to set the labels in the labels section." disabled
instantSave id="isStripprefixEnabled" label="Strip Prefixes" />
instantSave id="isStripprefixEnabled" label="Strip Prefixes" canGate="update" :canResource="$application" />
@endif
@if ($application->build_pack === 'dockercompose')
<h3>Docker Compose</h3>
<x-forms.checkbox instantSave id="isRawComposeDeploymentEnabled" label="Raw Compose Deployment"
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>" />
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>"
canGate="update" :canResource="$application" />
@endif
<h3 class="pt-4">Container Names</h3>
<x-forms.checkbox
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
instantSave id="isConsistentContainerNameEnabled" label="Consistent Container Names" />
instantSave id="isConsistentContainerNameEnabled" label="Consistent Container Names" canGate="update"
:canResource="$application" />
@if ($isConsistentContainerNameEnabled === false)
<form class="flex items-end gap-2 " wire:submit.prevent='saveCustomName'>
<x-forms.input
helper="You can add a custom name for your container.<br><br>The name will be converted to slug format when you save it. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
instantSave id="customInternalName" label="Custom Container Name" />
@can('update', $application)
<x-forms.button type="submit">
Save
</x-forms.button>
@else
<x-forms.button type="submit" disabled
title="You don't have permission to update this application. Contact your team administrator for access.">
Save
</x-forms.button>
@endcan
instantSave id="customInternalName" label="Custom Container Name" canGate="update"
:canResource="$application" />
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
</form>
@endif
@if ($application->build_pack === 'dockercompose')
<h3 class="pt-4">Network</h3>
<x-forms.checkbox instantSave id="isConnectToDockerNetworkEnabled" label="Connect To Predefined Network"
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>." />
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>."
canGate="update" :canResource="$application" />
@endif
<h3 class="pt-4">Logs</h3>
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave id="isLogDrainEnabled" label="Drain Logs" />
instantSave id="isLogDrainEnabled" label="Drain Logs" canGate="update" :canResource="$application" />
@if ($application->git_based())
<h3>Git</h3>
<x-forms.checkbox instantSave id="isGitSubmodulesEnabled" label="Submodules"
helper="Allow Git Submodules during build process." />
helper="Allow Git Submodules during build process." canGate="update" :canResource="$application" />
<x-forms.checkbox instantSave id="isGitLfsEnabled" label="LFS"
helper="Allow Git LFS during build process." />
helper="Allow Git LFS during build process." canGate="update" :canResource="$application" />
<x-forms.checkbox instantSave id="isGitShallowCloneEnabled" label="Shallow Clone"
helper="Use shallow cloning (--depth=1) to speed up deployments by only fetching the latest commit history. This reduces clone time and resource usage, especially for large repositories." />
helper="Use shallow cloning (--depth=1) to speed up deployments by only fetching the latest commit history. This reduces clone time and resource usage, especially for large repositories."
canGate="update" :canResource="$application" />
@endif
</div>
@@ -87,16 +84,7 @@
<div class="flex gap-2 items-end pt-4">
<h3>GPU</h3>
@if ($isGpuEnabled)
@can('update', $application)
<x-forms.button type="submit">
Save
</x-forms.button>
@else
<x-forms.button type="submit" disabled
title="You don't have permission to update this application. Contact your team administrator for access.">
Save
</x-forms.button>
@endcan
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
@endif
</div>
@endif
@@ -104,21 +92,23 @@
<div class="md:w-96 pb-4">
<x-forms.checkbox
helper="Enable GPU usage for this application. More info <a href='https://docs.docker.com/compose/gpu-support/' class='underline dark:text-white' target='_blank'>here</a>."
instantSave id="isGpuEnabled" label="Enable GPU" />
instantSave id="isGpuEnabled" label="Enable GPU" canGate="update" :canResource="$application" />
</div>
@endif
@if ($isGpuEnabled)
<div class="flex flex-col w-full gap-2 ">
<div class="flex gap-2 items-end">
<x-forms.input label="GPU Driver" id="gpuDriver"> </x-forms.input>
<x-forms.input label="GPU Count" placeholder="empty means use all GPUs" id="gpuCount">
<x-forms.input label="GPU Driver" id="gpuDriver" canGate="update" :canResource="$application">
</x-forms.input>
<x-forms.input label="GPU Count" placeholder="empty means use all GPUs" id="gpuCount"
canGate="update" :canResource="$application">
</x-forms.input>
</div>
<x-forms.input label="GPU Device Ids" placeholder="0,2"
helper="Comma separated list of device ids. More info <a href='https://docs.docker.com/compose/gpu-support/#access-specific-devices' class='underline dark:text-white' target='_blank'>here</a>."
id="gpuDeviceIds"> </x-forms.input>
<x-forms.textarea rows="10" label="GPU Options" id="gpuOptions"> </x-forms.textarea>
id="gpuDeviceIds" canGate="update" :canResource="$application"> </x-forms.input>
<x-forms.textarea rows="10" label="GPU Options" id="gpuOptions" canGate="update"
:canResource="$application"> </x-forms.textarea>
</div>
@endif
</form>

View File

@@ -1,56 +1,35 @@
<div x-data="{ initLoadingCompose: $wire.entangle('initLoadingCompose') }">
<div x-data="{
initLoadingCompose: $wire.entangle('initLoadingCompose'),
canUpdate: @js(auth()->user()->can('update', $application)),
shouldDisable() {
return this.initLoadingCompose || !this.canUpdate;
}
}">
<form wire:submit='submit' class="flex flex-col pb-32">
<div class="flex items-center gap-2">
<h2>General</h2>
@can('update', $application)
<x-forms.button type="submit">
Save
</x-forms.button>
@else
<x-forms.button type="submit" disabled
title="You don't have permission to update this application. Contact your team administrator for access.">
Save
</x-forms.button>
@endcan
{{-- <x-forms.button wire:click="downloadConfig">
Download Config
</x-forms.button> --}}
{{-- <x-modal-input buttonTitle="Upload Config" title="Upload Config" :closeOutside="false">
<livewire:project.shared.upload-config :applicationId="$application->id" />
</x-modal-input> --}}
<x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
</div>
<div>General configuration for your application.</div>
<div class="flex flex-col gap-2 py-4">
<div class="flex flex-col items-end gap-2 xl:flex-row">
<x-forms.input x-bind:disabled="initLoadingCompose" id="application.name" label="Name" required />
<x-forms.input x-bind:disabled="initLoadingCompose" id="application.description" label="Description" />
<x-forms.input x-bind:disabled="shouldDisable()" id="application.name" label="Name" required />
<x-forms.input x-bind:disabled="shouldDisable()" id="application.description" label="Description" />
</div>
@if (!$application->dockerfile && $application->build_pack !== 'dockerimage')
<div class="flex flex-col gap-2">
<div class="flex gap-2">
@can('update', $application)
<x-forms.select x-bind:disabled="initLoadingCompose" wire:model.live="application.build_pack"
<x-forms.select x-bind:disabled="shouldDisable()" wire:model.live="application.build_pack"
label="Build Pack" required>
<option value="nixpacks">Nixpacks</option>
<option value="static">Static</option>
<option value="dockerfile">Dockerfile</option>
<option value="dockercompose">Docker Compose</option>
</x-forms.select>
@else
<x-forms.select disabled label="Build Pack" required>
<option value="nixpacks" @if ($application->build_pack === 'nixpacks') selected @endif>Nixpacks</option>
<option value="static" @if ($application->build_pack === 'static') selected @endif>Static</option>
<option value="dockerfile" @if ($application->build_pack === 'dockerfile') selected @endif>Dockerfile
</option>
<option value="dockercompose" @if ($application->build_pack === 'dockercompose') selected @endif>Docker
Compose</option>
</x-forms.select>
@endcan
@if ($application->settings->is_static || $application->build_pack === 'static')
<x-forms.select id="application.static_image" label="Static Image" required>
<x-forms.select x-bind:disabled="!canUpdate" id="application.static_image"
label="Static Image" required>
<option value="nginx:alpine">nginx:alpine</option>
<option disabled value="apache:alpine">apache:alpine</option>
</x-forms.select>
@@ -69,7 +48,8 @@
<x-forms.input
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
label="Domains for {{ str($serviceName)->headline() }}"
id="parsedServiceDomains.{{ str($serviceName)->slug('_') }}.domain"></x-forms.input>
id="parsedServiceDomains.{{ str($serviceName)->slug('_') }}.domain"
x-bind:disabled="shouldDisable()"></x-forms.input>
@can('update', $application)
<x-forms.button wire:click="generateDomain('{{ $serviceName }}')">Generate
Domain</x-forms.button>
@@ -85,27 +65,23 @@
@if ($application->settings->is_static || $application->build_pack === 'static')
<x-forms.textarea id="application.custom_nginx_configuration"
placeholder="Empty means default configuration will be used." label="Custom Nginx Configuration"
helper="You can add custom Nginx configuration here." />
helper="You can add custom Nginx configuration here." x-bind:disabled="!canUpdate" />
@can('update', $application)
<x-forms.button wire:click="generateNginxConfiguration">
Generate Default Nginx Configuration
</x-forms.button>
@else
<x-forms.button wire:click="generateNginxConfiguration" disabled
title="You don't have permission to update this application. Contact your team administrator for access.">
Generate Default Nginx Configuration
</x-forms.button>
@endcan
@endif
<div class="w-96 pb-6">
@if ($application->could_set_build_commands())
<x-forms.checkbox instantSave id="application.settings.is_static" label="Is it a static site?"
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
helper="If your application is a static site or the final build assets should be served as a static site, enable this."
x-bind:disabled="!canUpdate" />
@endif
@if ($application->settings->is_static && $application->build_pack !== 'static')
<x-forms.checkbox label="Is it a SPA (Single Page Application)?"
helper="If your application is a SPA, enable this." id="application.settings.is_spa"
instantSave></x-forms.checkbox>
helper="If your application is a SPA, enable this." id="application.settings.is_spa" instantSave
x-bind:disabled="!canUpdate"></x-forms.checkbox>
@endif
</div>
@if ($application->build_pack !== 'dockercompose')
@@ -113,11 +89,13 @@
@if ($application->settings->is_container_label_readonly_enabled == false)
<x-forms.input placeholder="https://coolify.io" wire:model.blur-sm="application.fqdn"
label="Domains" readonly
helper="Readonly labels are disabled. You can set the domains in the labels section." />
helper="Readonly labels are disabled. You can set the domains in the labels section."
x-bind:disabled="!canUpdate" />
@else
<x-forms.input placeholder="https://coolify.io" wire:model.blur-sm="application.fqdn"
label="Domains"
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " />
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
x-bind:disabled="!canUpdate" />
@can('update', $application)
<x-forms.button wire:click="getWildcardDomain">Generate Domain
</x-forms.button>
@@ -128,22 +106,27 @@
@if ($application->settings->is_container_label_readonly_enabled == false)
@if ($application->redirect === 'both')
<x-forms.input label="Direction" value="Allow www & non-www." readonly
helper="Readonly labels are disabled. You can set the direction in the labels section." />
helper="Readonly labels are disabled. You can set the direction in the labels section."
x-bind:disabled="!canUpdate" />
@elseif ($application->redirect === 'www')
<x-forms.input label="Direction" value="Redirect to www." readonly
helper="Readonly labels are disabled. You can set the direction in the labels section." />
helper="Readonly labels are disabled. You can set the direction in the labels section."
x-bind:disabled="!canUpdate" />
@elseif ($application->redirect === 'non-www')
<x-forms.input label="Direction" value="Redirect to non-www." readonly
helper="Readonly labels are disabled. You can set the direction in the labels section." />
helper="Readonly labels are disabled. You can set the direction in the labels section."
x-bind:disabled="!canUpdate" />
@endif
@else
<x-forms.select label="Direction" id="application.redirect" required
helper="You must need to add www and non-www as an A DNS record. Make sure the www domain is added under Domains.">
helper="You must need to add www and non-www as an A DNS record. Make sure the www domain is added under Domains."
x-bind:disabled="!canUpdate">
<option value="both">Allow www & non-www.</option>
<option value="www">Redirect to www.</option>
<option value="non-www">Redirect to non-www.</option>
</x-forms.select>
@if ($application->settings->is_container_label_readonly_enabled)
@can('update', $application)
<x-modal-confirmation title="Confirm Redirection Setting?" buttonTitle="Set Direction"
submitAction="setRedirect" :actions="['All traffic will be redirected to the selected direction.']"
confirmationText="{{ $application->fqdn . '/' }}"
@@ -154,6 +137,7 @@
<div class="w-[7.2rem]">Set Direction</div>
</x-slot:customButton>
</x-modal-confirmation>
@endcan
@endif
@endif
</div>
@@ -177,11 +161,15 @@
<div class="flex flex-col gap-2 xl:flex-row">
@if ($application->build_pack === 'dockerimage')
@if ($application->destination->server->isSwarm())
<x-forms.input required id="application.docker_registry_image_name" label="Docker Image" />
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" />
<x-forms.input required id="application.docker_registry_image_name" label="Docker Image"
x-bind:disabled="!canUpdate" />
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag"
x-bind:disabled="!canUpdate" />
@else
<x-forms.input id="application.docker_registry_image_name" label="Docker Image" />
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" />
<x-forms.input id="application.docker_registry_image_name" label="Docker Image"
x-bind:disabled="!canUpdate" />
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag"
x-bind:disabled="!canUpdate" />
@endif
@else
@if (
@@ -189,19 +177,20 @@
$application->additional_servers->count() > 0 ||
$application->settings->is_build_server_enabled)
<x-forms.input id="application.docker_registry_image_name" required label="Docker Image"
placeholder="Required!" />
placeholder="Required!" x-bind:disabled="!canUpdate" />
<x-forms.input id="application.docker_registry_image_tag"
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
placeholder="Empty means latest will be used." label="Docker Image Tag" />
placeholder="Empty means latest will be used." label="Docker Image Tag"
x-bind:disabled="!canUpdate" />
@else
<x-forms.input id="application.docker_registry_image_name"
helper="Empty means it won't push the image to a docker registry."
placeholder="Empty means it won't push the image to a docker registry."
label="Docker Image" />
label="Docker Image" x-bind:disabled="!canUpdate" />
<x-forms.input id="application.docker_registry_image_tag"
placeholder="Empty means only push commit sha tag."
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
label="Docker Image Tag" />
label="Docker Image Tag" x-bind:disabled="!canUpdate" />
@endif
@endif
</div>
@@ -212,17 +201,21 @@
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k --hostname=myapp"
id="application.custom_docker_run_options" label="Custom Docker Options" />
id="application.custom_docker_run_options" label="Custom Docker Options"
x-bind:disabled="!canUpdate" />
@else
@if ($application->could_set_build_commands())
@if ($application->build_pack === 'nixpacks')
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
id="application.install_command" label="Install Command" />
id="application.install_command" label="Install Command"
x-bind:disabled="!canUpdate" />
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
id="application.build_command" label="Build Command" />
id="application.build_command" label="Build Command"
x-bind:disabled="!canUpdate" />
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
id="application.start_command" label="Start Command" />
id="application.start_command" label="Start Command"
x-bind:disabled="!canUpdate" />
</div>
<div class="pt-1 text-xs">Nixpacks will detect the required configuration
automatically.
@@ -240,10 +233,10 @@
<div class="flex flex-col gap-2">
@endcan
<div class="flex gap-2">
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="/"
<x-forms.input x-bind:disabled="shouldDisable()" placeholder="/"
id="application.base_directory" label="Base Directory"
helper="Directory to use as root. Useful for monorepos." />
<x-forms.input x-bind:disabled="initLoadingCompose"
<x-forms.input x-bind:disabled="shouldDisable()"
placeholder="/docker-compose.yaml"
id="application.docker_compose_location" label="Docker Compose Location"
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }}</span>" />
@@ -252,7 +245,8 @@
<x-forms.checkbox instantSave
id="application.settings.is_preserve_repository_enabled"
label="Preserve Repository During Deployment"
helper="Git repository (based on the base directory settings) will be copied to the deployment directory." />
helper="Git repository (based on the base directory settings) will be copied to the deployment directory."
x-bind:disabled="shouldDisable()" />
</div>
<div class="pt-4">The following commands are for advanced use cases.
Only
@@ -260,13 +254,13 @@
know what are
you doing.</div>
<div class="flex gap-2">
<x-forms.input placeholder="docker compose build"
x-bind:disabled="initLoadingCompose"
<x-forms.input x-bind:disabled="shouldDisable()"
placeholder="docker compose build"
id="application.docker_compose_custom_build_command"
helper="If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} build</span>"
label="Custom Build Command" />
<x-forms.input placeholder="docker compose up -d"
x-bind:disabled="initLoadingCompose"
<x-forms.input x-bind:disabled="shouldDisable()"
placeholder="docker compose up -d"
id="application.docker_compose_custom_start_command"
helper="If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} up -d</span>"
label="Custom Start Command" />
@@ -276,25 +270,28 @@
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input placeholder="/" id="application.base_directory"
label="Base Directory"
helper="Directory to use as root. Useful for monorepos." />
helper="Directory to use as root. Useful for monorepos."
x-bind:disabled="!canUpdate" />
@if ($application->build_pack === 'dockerfile' && !$application->dockerfile)
<x-forms.input placeholder="/Dockerfile" id="application.dockerfile_location"
label="Dockerfile Location"
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->dockerfile_location, '/') }}</span>" />
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->dockerfile_location, '/') }}</span>"
x-bind:disabled="!canUpdate" />
@endif
@if ($application->build_pack === 'dockerfile')
<x-forms.input id="application.dockerfile_target_build"
label="Docker Build Stage Target"
helper="Useful if you have multi-staged dockerfile." />
helper="Useful if you have multi-staged dockerfile."
x-bind:disabled="!canUpdate" />
@endif
@if ($application->could_set_build_commands())
@if ($application->settings->is_static)
<x-forms.input placeholder="/dist" id="application.publish_directory"
label="Publish Directory" required />
label="Publish Directory" required x-bind:disabled="!canUpdate" />
@else
<x-forms.input placeholder="/" id="application.publish_directory"
label="Publish Directory" />
label="Publish Directory" x-bind:disabled="!canUpdate" />
@endif
@endif
@@ -304,20 +301,21 @@
<x-forms.textarea
helper="Gitignore-style rules to filter Git based webhook deployments."
placeholder="src/pages/**" id="application.watch_paths"
label="Watch Paths" />
label="Watch Paths" x-bind:disabled="!canUpdate" />
</div>
@endif
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k --hostname=myapp"
id="application.custom_docker_run_options" label="Custom Docker Options" />
id="application.custom_docker_run_options" label="Custom Docker Options"
x-bind:disabled="!canUpdate" />
@if ($application->build_pack !== 'dockercompose')
<div class="pt-2 w-96">
<x-forms.checkbox
helper="Use a build server to build your application. You can configure your build server in the Server settings. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>."
instantSave id="application.settings.is_build_server_enabled"
label="Use a Build Server?" />
label="Use a Build Server?" x-bind:disabled="!canUpdate" />
</div>
@endif
@endif
@@ -327,8 +325,10 @@
@if ($application->build_pack === 'dockercompose')
<div class="flex items-center gap-2 pb-4">
<h3>Docker Compose</h3>
@can('update', $application)
<x-forms.button wire:target='initLoadingCompose'
x-on:click="$wire.dispatch('loadCompose', false)">Reload Compose File</x-forms.button>
@endcan
</div>
@if ($application->settings->is_raw_compose_deployment_enabled)
<x-forms.textarea rows="10" readonly id="application.docker_compose_raw"
@@ -350,7 +350,8 @@
<div class="w-96">
<x-forms.checkbox label="Escape special characters in labels?"
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
id="application.settings.is_container_label_escape_enabled" instantSave
x-bind:disabled="!canUpdate"></x-forms.checkbox>
{{-- <x-forms.checkbox label="Readonly labels"
helper="Labels are readonly by default. Readonly means that edits you do to the labels could be lost and Coolify will autogenerate the labels for you. If you want to edit the labels directly, disable this option. <br><br>Be careful, it could break the proxy configuration after you restart the container as Coolify will now NOT autogenerate the labels for you (ofc you can always reset the labels to the coolify defaults manually)."
id="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox> --}}
@@ -358,32 +359,36 @@
@endif
@if ($application->dockerfile)
<x-forms.textarea label="Dockerfile" id="application.dockerfile" monacoEditorLanguage="dockerfile"
useMonacoEditor rows="6"> </x-forms.textarea>
useMonacoEditor rows="6" x-bind:disabled="!canUpdate"> </x-forms.textarea>
@endif
@if ($application->build_pack !== 'dockercompose')
<h3 class="pt-8">Network</h3>
<div class="flex flex-col gap-2 xl:flex-row">
@if ($application->settings->is_static || $application->build_pack === 'static')
<x-forms.input id="application.ports_exposes" label="Ports Exposes" readonly />
<x-forms.input id="application.ports_exposes" label="Ports Exposes" readonly
x-bind:disabled="!canUpdate" />
@else
@if ($application->settings->is_container_label_readonly_enabled === false)
<x-forms.input placeholder="3000,3001" id="application.ports_exposes"
label="Ports Exposes" readonly
helper="Readonly labels are disabled. You can set the ports manually in the labels section." />
helper="Readonly labels are disabled. You can set the ports manually in the labels section."
x-bind:disabled="!canUpdate" />
@else
<x-forms.input placeholder="3000,3001" id="application.ports_exposes"
label="Ports Exposes" required
helper="A comma separated list of ports your application uses. The first port will be used as default healthcheck port if nothing defined in the Healthcheck menu. Be sure to set this correctly." />
helper="A comma separated list of ports your application uses. The first port will be used as default healthcheck port if nothing defined in the Healthcheck menu. Be sure to set this correctly."
x-bind:disabled="!canUpdate" />
@endif
@endif
@if (!$application->destination->server->isSwarm())
<x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." />
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host."
x-bind:disabled="!canUpdate" />
@endif
@if (!$application->destination->server->isSwarm())
<x-forms.input id="application.custom_network_aliases" label="Network Aliases"
helper="A comma separated list of custom network aliases you would like to add for container in Docker network.<br><br><span class='inline-block font-bold dark:text-warning'>Example:</span><br>api.internal,api.local"
wire:model="application.custom_network_aliases" />
wire:model="application.custom_network_aliases" x-bind:disabled="!canUpdate" />
@endif
</div>
@@ -391,32 +396,37 @@
<div>
<div class="w-96">
<x-forms.checkbox helper="This will add the proper proxy labels to the container." instantSave
label="Enable" id="application.is_http_basic_auth_enabled" />
label="Enable" id="application.is_http_basic_auth_enabled"
x-bind:disabled="!canUpdate" />
</div>
@if ($application->is_http_basic_auth_enabled)
<div class="flex gap-2 py-2">
<x-forms.input id="application.http_basic_auth_username" label="Username" required />
<x-forms.input id="application.http_basic_auth_username" label="Username" required
x-bind:disabled="!canUpdate" />
<x-forms.input id="application.http_basic_auth_password" type="password" label="Password"
required />
required x-bind:disabled="!canUpdate" />
</div>
@endif
</div>
@if ($application->settings->is_container_label_readonly_enabled)
<x-forms.textarea readonly disabled label="Container Labels" rows="15" id="customLabels"
monacoEditorLanguage="ini" useMonacoEditor></x-forms.textarea>
monacoEditorLanguage="ini" useMonacoEditor x-bind:disabled="!canUpdate"></x-forms.textarea>
@else
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"
monacoEditorLanguage="ini" useMonacoEditor></x-forms.textarea>
monacoEditorLanguage="ini" useMonacoEditor x-bind:disabled="!canUpdate"></x-forms.textarea>
@endif
<div class="w-96">
<x-forms.checkbox label="Readonly labels"
helper="Labels are readonly by default. Readonly means that edits you do to the labels could be lost and Coolify will autogenerate the labels for you. If you want to edit the labels directly, disable this option. <br><br>Be careful, it could break the proxy configuration after you restart the container as Coolify will now NOT autogenerate the labels for you (ofc you can always reset the labels to the coolify defaults manually)."
id="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox>
id="application.settings.is_container_label_readonly_enabled" instantSave
x-bind:disabled="!canUpdate"></x-forms.checkbox>
<x-forms.checkbox label="Escape special characters in labels?"
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
id="application.settings.is_container_label_escape_enabled" instantSave
x-bind:disabled="!canUpdate"></x-forms.checkbox>
</div>
@can('update', $application)
<x-modal-confirmation title="Confirm Labels Reset to Coolify Defaults?"
buttonTitle="Reset Labels to Defaults" buttonFullWidth submitAction="resetDefaultLabels(true)"
:actions="[
@@ -426,25 +436,26 @@
confirmationLabel="Please confirm the execution of the actions by entering the Application URL below"
shortConfirmationLabel="Application URL" :confirmWithPassword="false"
step2ButtonText="Permanently Reset Labels" />
@endcan
@endif
<h3 class="pt-8">Pre/Post Deployment Commands</h3>
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="php artisan migrate"
<x-forms.input x-bind:disabled="shouldDisable()" placeholder="php artisan migrate"
id="application.pre_deployment_command" label="Pre-deployment "
helper="An optional script or command to execute in the existing container before the deployment begins.<br>It is always executed with 'sh -c', so you do not need add it manually." />
@if ($application->build_pack === 'dockercompose')
<x-forms.input x-bind:disabled="initLoadingCompose"
id="application.pre_deployment_command_container" label="Container Name"
<x-forms.input x-bind:disabled="shouldDisable()" id="application.pre_deployment_command_container"
label="Container Name"
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
@endif
</div>
<div class="flex flex-col gap-2 xl:flex-row">
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="php artisan migrate"
<x-forms.input x-bind:disabled="shouldDisable()" placeholder="php artisan migrate"
id="application.post_deployment_command" label="Post-deployment "
helper="An optional script or command to execute in the newly built container after the deployment completes.<br>It is always executed with 'sh -c', so you do not need add it manually." />
@if ($application->build_pack === 'dockercompose')
<x-forms.input x-bind:disabled="initLoadingCompose"
<x-forms.input x-bind:disabled="shouldDisable()"
id="application.post_deployment_command_container" label="Container Name"
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
@endif

View File

@@ -15,10 +15,12 @@
Logs
</a>
@if (!$application->destination->server->isSwarm())
@can('canAccessTerminal')
<a class="{{ request()->routeIs('project.application.command') ? 'dark:text-white' : '' }}"
href="{{ route('project.application.command', $parameters) }}">
Terminal
</a>
@endcan
@endif
<x-applications.links :application="$application" />
</nav>

View File

@@ -1,13 +1,15 @@
<form wire:submit='submit'>
<div class="flex items-center gap-2">
<h2>Preview Deployments</h2>
@can('update', $application)
<x-forms.button type="submit">Save</x-forms.button>
<x-forms.button isHighlighted wire:click="resetToDefault">Reset template to default</x-forms.button>
@endcan
</div>
<div class="pb-4 ">Preview Deployments based on pull requests are here.</div>
<div class="flex flex-col gap-2 pb-4">
<x-forms.input id="previewUrlTemplate" label="Preview URL Template"
helper="Templates:<br/><span class='text-helper'>@@{{ random }}</span> to generate random sub-domain each time a PR is deployed<br/><span class='text-helper'>@@{{ pr_id }}</span> to use pull request ID as sub-domain or <span class='text-helper'>@@{{ domain }}</span> to replace the domain name with the application's domain name." />
helper="Templates:<br/><span class='text-helper'>@@{{ random }}</span> to generate random sub-domain each time a PR is deployed<br/><span class='text-helper'>@@{{ pr_id }}</span> to use pull request ID as sub-domain or <span class='text-helper'>@@{{ domain }}</span> to replace the domain name with the application's domain name." canGate="update" :canResource="$application" />
@if ($previewUrlTemplate)
<div class="">Domain Preview: {{ $previewUrlTemplate }}</div>
@endif

View File

@@ -1,6 +1,6 @@
<form wire:submit="save" class="flex items-end gap-2">
<x-forms.input helper="One domain per preview." label="Domains for {{ str($serviceName)->headline() }}"
id="service.domain"></x-forms.input>
id="service.domain" canGate="update" :canResource="$preview->application"></x-forms.input>
<x-forms.button type="submit">Save</x-forms.button>
<x-forms.button wire:click="generate">Generate
Domain</x-forms.button>

View File

@@ -7,9 +7,11 @@
<div>
@if ($application->is_github_based())
<div class="flex items-center gap-2">
@can('update', $application)
<h3>Pull Requests on Git</h3>
<x-forms.button wire:click="load_prs">Load Pull Requests
</x-forms.button>
@endcan
</div>
@endif
@isset($rate_limit_remaining)
@@ -40,10 +42,13 @@
</a>
</td>
<td class="flex flex-col gap-1 md:flex-row">
@can('update', $application)
<x-forms.button
wire:click="add('{{ data_get($pull_request, 'number') }}', '{{ data_get($pull_request, 'html_url') }}')">
Configure
</x-forms.button>
@endcan
@can('deploy', $application)
<x-forms.button
wire:click="add_and_deploy('{{ data_get($pull_request, 'number') }}', '{{ data_get($pull_request, 'html_url') }}')">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning"
@@ -53,6 +58,7 @@
<path d="M7 4v16l13 -8z" />
</svg>Deploy
</x-forms.button>
@endcan
</td>
</tr>
@endforeach
@@ -106,10 +112,12 @@
<form wire:submit="save_preview('{{ $preview->id }}')"
class="flex items-end gap-2 pt-4">
<x-forms.input label="Domain" helper="One domain per preview."
id="application.previews.{{ $previewName }}.fqdn"></x-forms.input>
id="application.previews.{{ $previewName }}.fqdn" canGate="update" :canResource="$application"></x-forms.input>
@can('update', $application)
<x-forms.button type="submit">Save</x-forms.button>
<x-forms.button wire:click="generate_preview('{{ $preview->id }}')">Generate
Domain</x-forms.button>
@endcan
</form>
@else
@foreach (collect(json_decode($preview->docker_compose_domains)) as $serviceName => $service)
@@ -122,14 +130,17 @@
@else
<form wire:submit="save_preview('{{ $preview->id }}')" class="flex items-end gap-2 pt-4">
<x-forms.input label="Domain" helper="One domain per preview."
id="application.previews.{{ $previewName }}.fqdn"></x-forms.input>
id="application.previews.{{ $previewName }}.fqdn" canGate="update" :canResource="$application"></x-forms.input>
@can('update', $application)
<x-forms.button type="submit">Save</x-forms.button>
<x-forms.button wire:click="generate_preview('{{ $preview->id }}')">Generate
Domain</x-forms.button>
@endcan
</form>
@endif
<div class="flex flex-col xl:flex-row xl:items-center gap-2 pt-6">
<div class="flex-1"></div>
@can('deploy', $application)
<x-forms.button
wire:click="force_deploy_without_cache({{ data_get($preview, 'pull_request_id') }})">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24"
@@ -167,7 +178,9 @@
</svg> Redeploy
@endif
</x-forms.button>
@endcan
@if (data_get($preview, 'status') !== 'exited')
@can('deploy', $application)
<x-modal-confirmation title="Confirm Preview Deployment Stopping?" buttonTitle="Stop"
submitAction="stop({{ data_get($preview, 'pull_request_id') }})" :actions="[
'This preview deployment will be stopped.',
@@ -190,7 +203,9 @@
Stop
</x-slot:customButton>
</x-modal-confirmation>
@endcan
@endif
@can('delete', $application)
<x-modal-confirmation title="Confirm Preview Deployment Deletion?" buttonTitle="Delete"
isErrorButton submitAction="delete({{ data_get($preview, 'pull_request_id') }})"
:actions="[
@@ -198,6 +213,7 @@
]" confirmationText="{{ data_get($preview, 'fqdn') . '/' }}"
confirmationLabel="Please confirm the execution of the actions by entering the Preview Deployment name below"
shortConfirmationLabel="Preview Deployment Name" :confirmWithPassword="false" />
@endcan
</div>
</div>
@endforeach

View File

@@ -1,7 +1,9 @@
<div x-init="$wire.loadImages">
<div class="flex items-center gap-2">
<h2>Rollback</h2>
@can('view', $application)
<x-forms.button wire:click='loadImages(true)'>Reload Available Images</x-forms.button>
@endcan
</div>
<div class="pb-4 ">You can easily rollback to a previously built (local) images
quickly.</div>
@@ -26,6 +28,7 @@
<div class="text-xs">{{ $date }}</div>
</div>
<div class="flex justify-end p-2">
@can('deploy', $application)
@if (data_get($image, 'is_current'))
<x-forms.button disabled tooltip="This image is currently running.">
Rollback
@@ -36,6 +39,7 @@
Rollback
</x-forms.button>
@endif
@endcan
</div>
</div>
</div>

View File

@@ -2,7 +2,9 @@
<form wire:submit='submit' class="flex flex-col">
<div class="flex items-center gap-2">
<h2>Source</h2>
@can('update', $application)
<x-forms.button type="submit">Save</x-forms.button>
@endcan
<a target="_blank" class="hover:no-underline" href="{{ $application?->gitBranchLocation }}">
<x-forms.button>
Open Repository
@@ -32,11 +34,11 @@
</div>
@endif
<div class="flex gap-2">
<x-forms.input placeholder="coollabsio/coolify-example" id="gitRepository" label="Repository" />
<x-forms.input placeholder="main" id="gitBranch" label="Branch" />
<x-forms.input placeholder="coollabsio/coolify-example" id="gitRepository" label="Repository" canGate="update" :canResource="$application" />
<x-forms.input placeholder="main" id="gitBranch" label="Branch" canGate="update" :canResource="$application" />
</div>
<div class="flex items-end gap-2">
<x-forms.input placeholder="HEAD" id="gitCommitSha" placeholder="HEAD" label="Commit SHA" />
<x-forms.input placeholder="HEAD" id="gitCommitSha" placeholder="HEAD" label="Commit SHA" canGate="update" :canResource="$application" />
</div>
</div>
@@ -46,6 +48,7 @@
class="dark:text-warning">{{ $privateKeyName }}</span>
</div>
@can('update', $application)
<h4 class="py-2 ">Select another Private Key</h4>
<div class="flex flex-wrap gap-2">
@foreach ($privateKeys as $key)
@@ -53,7 +56,9 @@
</x-forms.button>
@endforeach
</div>
@endcan
@else
@can('update', $application)
<div class="pt-4">
<h3 class="pb-2">Change Git Source</h3>
<div class="grid grid-cols-1 gap-2">
@@ -85,6 +90,7 @@
@endforelse
</div>
</div>
@endcan
@endif
</form>
</div>

View File

@@ -15,14 +15,14 @@
</div>
<div class="flex flex-col gap-2 py-4">
<div class="flex flex-col items-end gap-2 xl:flex-row">
<x-forms.input id="swarmReplicas" label="Replicas" required />
<x-forms.input id="swarmReplicas" label="Replicas" required canGate="update" :canResource="$application" />
<x-forms.checkbox instantSave helper="If turned off, this resource will start on manager nodes too."
id="isSwarmOnlyWorkerNodes" label="Only Start on Worker nodes" />
id="isSwarmOnlyWorkerNodes" label="Only Start on Worker nodes" canGate="update" :canResource="$application" />
</div>
<x-forms.textarea id="swarmPlacementConstraints" rows="7" label="Custom Placement Constraints"
placeholder="placement:
constraints:
- 'node.role == worker'" />
- 'node.role == worker'" canGate="update" :canResource="$application" />
</div>
</form>

View File

@@ -8,9 +8,11 @@
<div>
<div class="flex gap-2">
<h2 class="pb-4">Scheduled Backups</h2>
@can('update', $database)
<x-modal-input buttonTitle="+ Add" title="New Scheduled Backup">
<livewire:project.database.create-scheduled-backup :database="$database" />
</x-modal-input>
@endcan
</div>
<livewire:project.database.scheduled-backups :database="$database" />
</div>

View File

@@ -2,54 +2,56 @@
<form wire:submit="submit" class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<h2>General</h2>
<x-forms.button type="submit">
<x-forms.button type="submit" canGate="update" :canResource="$database">
Save
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="name" />
<x-forms.input label="Description" id="description" />
<x-forms.input label="Image" id="image" required
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="image" required canGate="update" :canResource="$database"
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/r/clickhouse/clickhouse-server/'>https://hub.docker.com/r/clickhouse/clickhouse-server/</a>" />
</div>
@if ($database->started_at)
<div class="flex gap-2">
<x-forms.input label="Initial Username" id="clickhouseAdminUser" placeholder="If empty: clickhouse"
readonly helper="You can only change this in the database." />
readonly helper="You can only change this in the database." canGate="update" :canResource="$database" />
<x-forms.input label="Initial Password" id="clickhouseAdminPassword" type="password" required readonly
helper="You can only change this in the database." />
helper="You can only change this in the database." canGate="update" :canResource="$database" />
</div>
@else
<div class=" dark:text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2">
<x-forms.input label="Username" id="clickhouseAdminUser" required />
<x-forms.input label="Password" id="clickhouseAdminPassword" type="password" required />
<x-forms.input label="Username" id="clickhouseAdminUser" required canGate="update" :canResource="$database" />
<x-forms.input label="Password" id="clickhouseAdminPassword" type="password" required canGate="update"
:canResource="$database" />
</div>
@endif
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="customDockerRunOptions" label="Custom Docker Options" />
id="customDockerRunOptions" label="Custom Docker Options" canGate="update" :canResource="$database" />
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="3000:5432" id="portsMappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433" />
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433"
canGate="update" :canResource="$database" />
</div>
<x-forms.input label="Clickhouse URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="dbUrl" />
type="password" readonly wire:model="dbUrl" canGate="update" :canResource="$database" />
@if ($dbUrlPublic)
<x-forms.input label="Clickhouse URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="dbUrlPublic" />
type="password" readonly wire:model="dbUrlPublic" canGate="update" :canResource="$database" />
@else
<x-forms.input label="Clickhouse URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
readonly value="Starting the database will generate this." />
readonly value="Starting the database will generate this." canGate="update" :canResource="$database" />
@endif
</div>
<div>
@@ -71,14 +73,17 @@
</x-slide-over>
@endif
</div>
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" />
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" canGate="update"
:canResource="$database" />
</div>
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port" />
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port"
canGate="update" :canResource="$database" />
</div>
</form>
<h3 class="pt-4">Advanced</h3>
<div class="w-64">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs" />
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs" canGate="update"
:canResource="$database" />
</div>
</div>

View File

@@ -17,9 +17,11 @@
<a class='menu-item' wire:current.exact="menu-item-active"
href="{{ route('project.database.persistent-storage', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Persistent
Storage</a>
@can('update', $database)
<a class='menu-item' wire:current.exact="menu-item-active"
href="{{ route('project.database.import-backups', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Import
Backups</a>
@endcan
<a class='menu-item' wire:current.exact="menu-item-active"
href="{{ route('project.database.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Webhooks</a>
<a class="menu-item" wire:current.exact="menu-item-active"

View File

@@ -2,51 +2,53 @@
<form wire:submit="submit" class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<h2>General</h2>
<x-forms.button type="submit">
<x-forms.button type="submit" canGate="update" :canResource="$database">
Save
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="name" />
<x-forms.input label="Description" id="description" />
<x-forms.input label="Image" id="image" required />
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="image" required canGate="update" :canResource="$database" />
</div>
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="customDockerRunOptions" label="Custom Docker Options" />
id="customDockerRunOptions" label="Custom Docker Options" canGate="update" :canResource="$database" />
@if ($database->started_at)
<div class="flex gap-2">
<x-forms.input label="Initial Password" id="dragonflyPassword" type="password" required readonly
helper="You can only change this in the database." />
helper="You can only change this in the database." canGate="update" :canResource="$database" />
</div>
@else
<div class=" dark:text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2">
<x-forms.input label="Password" id="dragonflyPassword" type="password" required />
<x-forms.input label="Password" id="dragonflyPassword" type="password" required canGate="update"
:canResource="$database" />
</div>
@endif
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="3000:5432" id="portsMappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433" />
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433"
canGate="update" :canResource="$database" />
</div>
<x-forms.input label="Dragonfly URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="dbUrl" />
type="password" readonly wire:model="dbUrl" canGate="update" :canResource="$database" />
@if ($dbUrlPublic)
<x-forms.input label="Dragonfly URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="dbUrlPublic" />
type="password" readonly wire:model="dbUrlPublic" canGate="update" :canResource="$database" />
@else
<x-forms.input label="Dragonfly URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
readonly value="Starting the database will generate this." />
readonly value="Starting the database will generate this." canGate="update" :canResource="$database" />
@endif
</div>
<div class="flex flex-col gap-2">
@@ -79,11 +81,12 @@
<div class="w-64">
@if (str($database->status)->contains('exited'))
<x-forms.checkbox id="enable_ssl" label="Enable SSL" wire:model.live="enable_ssl"
instantSave="instantSaveSSL" />
instantSave="instantSaveSSL" canGate="update" :canResource="$database" />
@else
<x-forms.checkbox id="enable_ssl" label="Enable SSL" wire:model.live="enable_ssl"
instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." />
helper="Database should be stopped to change this settings." canGate="update"
:canResource="$database" />
@endif
</div>
</div>
@@ -107,14 +110,17 @@
</x-slide-over>
@endif
</div>
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" />
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" canGate="update"
:canResource="$database" />
</div>
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port" />
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port"
canGate="update" :canResource="$database" />
</div>
</form>
<h3 class="pt-4">Advanced</h3>
<div class="w-64">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs" />
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs" canGate="update"
:canResource="$database" />
</div>
</div>

View File

@@ -18,10 +18,12 @@
href="{{ route('project.database.logs', $parameters) }}">
Logs
</a>
@can('canAccessTerminal')
<a class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.command', $parameters) }}">
Terminal
</a>
@endcan
@if (
$database->getMorphClass() === 'App\Models\StandalonePostgresql' ||
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||

View File

@@ -108,8 +108,8 @@
<div>Location: <span x-text="filename ?? 'N/A'"></span> <span x-text="filesize">/ </span></div>
<x-forms.button class="w-full my-4" wire:click='runImport'>Restore Backup</x-forms.button>
</div>
<div class="container w-full mx-auto">
<livewire:activity-monitor header="Database Restore Output" />
<div class="container w-full mx-auto" x-show="$wire.importRunning">
<livewire:activity-monitor header="Database Restore Output" :showWaiting="false" />
</div>
@else
<div>Database must be running to restore a backup.</div>

View File

@@ -2,51 +2,53 @@
<form wire:submit="submit" class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<h2>General</h2>
<x-forms.button type="submit">
<x-forms.button type="submit" canGate="update" :canResource="$database">
Save
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="name" />
<x-forms.input label="Description" id="description" />
<x-forms.input label="Image" id="image" required
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="image" required canGate="update" :canResource="$database"
helper="For all available images, check here:<br><br><a target='_blank' href=https://hub.docker.com/r/eqalpha/keydb'>https://hub.docker.com/r/eqalpha/keydb</a>" />
</div>
@if ($database->started_at)
<div class="flex gap-2">
<x-forms.input label="Initial Password" id="keydbPassword" type="password" required readonly
helper="You can only change this in the database." />
helper="You can only change this in the database." canGate="update" :canResource="$database" />
</div>
@else
<div class=" dark:text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database.
</div>
<div class="flex gap-2">
<x-forms.input label="Password" id="keydbPassword" type="password" required />
<x-forms.input label="Password" id="keydbPassword" type="password" required canGate="update"
:canResource="$database" />
</div>
@endif
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="customDockerRunOptions" label="Custom Docker Options" />
id="customDockerRunOptions" label="Custom Docker Options" canGate="update" :canResource="$database" />
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="3000:5432" id="portsMappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433" />
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433"
canGate="update" :canResource="$database" />
</div>
<x-forms.input label="KeyDB URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="dbUrl" />
type="password" readonly wire:model="dbUrl" canGate="update" :canResource="$database" />
@if ($dbUrlPublic)
<x-forms.input label="KeyDB URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="dbUrlPublic" />
type="password" readonly wire:model="dbUrlPublic" canGate="update" :canResource="$database" />
@else
<x-forms.input label="KeyDB URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
readonly value="Starting the database will generate this." />
readonly value="Starting the database will generate this." canGate="update" :canResource="$database" />
@endif
</div>
<div class="flex flex-col gap-2">
@@ -79,11 +81,12 @@
<div class="w-64">
@if (str($database->status)->contains('exited'))
<x-forms.checkbox id="enable_ssl" label="Enable SSL" wire:model.live="enable_ssl"
instantSave="instantSaveSSL" />
instantSave="instantSaveSSL" canGate="update" :canResource="$database" />
@else
<x-forms.checkbox id="enable_ssl" label="Enable SSL" wire:model.live="enable_ssl"
instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." />
helper="Database should be stopped to change this settings." canGate="update"
:canResource="$database" />
@endif
</div>
</div>
@@ -107,17 +110,20 @@
</x-slide-over>
@endif
</div>
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" />
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" canGate="update"
:canResource="$database" />
</div>
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port" />
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}" id="publicPort" label="Public Port"
canGate="update" :canResource="$database" />
</div>
<x-forms.textarea
helper="<a target='_blank' class='underline dark:text-white' href='https://raw.githubusercontent.com/Snapchat/KeyDB/unstable/keydb.conf'>KeyDB Default Configuration</a>"
label="Custom KeyDB Configuration" rows="10" id="keydbConf" />
label="Custom KeyDB Configuration" rows="10" id="keydbConf" canGate="update" :canResource="$database" />
</form>
<h3 class="pt-4">Advanced</h3>
<div class="w-64">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs" />
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs" canGate="update"
:canResource="$database" />
</div>
</div>

View File

@@ -2,14 +2,14 @@
<form wire:submit="submit" class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<h2>General</h2>
<x-forms.button type="submit">
<x-forms.button type="submit" canGate="update" :canResource="$database">
Save
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" />
<x-forms.input label="Description" id="database.description" />
<x-forms.input label="Image" id="database.image" required
<x-forms.input label="Name" id="database.name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="database.description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="database.image" required canGate="update" :canResource="$database"
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/mariadb'>https://hub.docker.com/_/mariadb</a>" />
</div>
<div class="pt-2 dark:text-warning">If you change the values in the database, please sync it here, otherwise
@@ -18,11 +18,14 @@
@if ($database->started_at)
<div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work."
canGate="update" :canResource="$database" />
<x-forms.input label="Normal User" id="database.mariadb_user" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work."
canGate="update" :canResource="$database" />
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work."
canGate="update" :canResource="$database" />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mariadb_database"
@@ -32,37 +35,39 @@
@else
<div class="flex xl:flex-row flex-col gap-2 pb-2">
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password"
helper="You can only change this in the database." />
helper="You can only change this in the database." canGate="update" :canResource="$database" />
<x-forms.input label="Normal User" id="database.mariadb_user" required
helper="You can only change this in the database." />
helper="You can only change this in the database." canGate="update" :canResource="$database" />
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
helper="You can only change this in the database." />
helper="You can only change this in the database." canGate="update" :canResource="$database" />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mariadb_database"
placeholder="If empty, it will be the same as Username."
helper="You can only change this in the database." />
helper="You can only change this in the database." canGate="update" :canResource="$database" />
</div>
@endif
<div class="pt-2">
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" />
id="database.custom_docker_run_options" label="Custom Docker Options" canGate="update"
:canResource="$database" />
</div>
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433" />
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433"
canGate="update" :canResource="$database" />
</div>
<x-forms.input label="MariaDB URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url" />
type="password" readonly wire:model="db_url" canGate="update" :canResource="$database" />
@if ($db_url_public)
<x-forms.input label="MariaDB URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url_public" />
type="password" readonly wire:model="db_url_public" canGate="update" :canResource="$database" />
@endif
</div>
@@ -98,11 +103,13 @@
<div class="w-64">
@if (str($database->status)->contains('exited'))
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" />
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" canGate="update"
:canResource="$database" />
@else
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." />
helper="Database should be stopped to change this settings." canGate="update"
:canResource="$database" />
@endif
</div>
</div>
@@ -127,16 +134,19 @@
</x-slide-over>
@endif
</div>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available"
canGate="update" :canResource="$database" />
</div>
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
id="database.public_port" label="Public Port" canGate="update" :canResource="$database" />
</div>
<x-forms.textarea label="Custom MariaDB Configuration" rows="10" id="database.mariadb_conf" />
<x-forms.textarea label="Custom MariaDB Configuration" rows="10" id="database.mariadb_conf"
canGate="update" :canResource="$database" />
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs" />
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs"
canGate="update" :canResource="$database" />
</div>
</form>
</div>

View File

@@ -2,14 +2,14 @@
<form wire:submit="submit" class="flex flex-col gap-2">
<div class="flex items-center gap-2">
<h2>General</h2>
<x-forms.button type="submit">
<x-forms.button type="submit" canGate="update" :canResource="$database">
Save
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" />
<x-forms.input label="Description" id="database.description" />
<x-forms.input label="Image" id="database.image" required
<x-forms.input label="Name" id="database.name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="database.description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="database.image" required canGate="update" :canResource="$database"
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/mongo'>https://hub.docker.com/_/mongo</a>" />
</div>
<div class="pt-2 dark:text-warning">If you change the values in the database, please sync it here, otherwise
@@ -19,40 +19,44 @@
<div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Initial Username" id="database.mongo_initdb_root_username"
placeholder="If empty: postgres"
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work."
canGate="update" :canResource="$database" />
<x-forms.input label="Initial Password" id="database.mongo_initdb_root_password" type="password"
required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work."
canGate="update" :canResource="$database" />
<x-forms.input label="Initial Database" id="database.mongo_initdb_database"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." />
helper="You can only change this in the database." canGate="update" :canResource="$database" />
</div>
@else
<div class="flex xl:flex-row flex-col gap-2 pb-2">
<x-forms.input required label="Username" id="database.mongo_initdb_root_username"
placeholder="If empty: postgres" />
<x-forms.input label="Password" id="database.mongo_initdb_root_password" type="password" required />
placeholder="If empty: postgres" canGate="update" :canResource="$database" />
<x-forms.input label="Password" id="database.mongo_initdb_root_password" type="password" required
canGate="update" :canResource="$database" />
<x-forms.input required label="Database" id="database.mongo_initdb_database"
placeholder="If empty, it will be the same as Username." />
placeholder="If empty, it will be the same as Username." canGate="update" :canResource="$database" />
</div>
@endif
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" />
id="database.custom_docker_run_options" label="Custom Docker Options" canGate="update" :canResource="$database" />
<div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3>
<div class="flex items-end gap-2">
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings"
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433" />
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433"
canGate="update" :canResource="$database" />
</div>
<x-forms.input label="Mongo URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url" />
type="password" readonly wire:model="db_url" canGate="update" :canResource="$database" />
@if ($db_url_public)
<x-forms.input label="Mongo URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url_public" />
type="password" readonly wire:model="db_url_public" canGate="update" :canResource="$database" />
@endif
</div>
@@ -88,11 +92,13 @@
<div class="w-64">
@if (str($database->status)->contains('exited'))
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" />
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" canGate="update"
:canResource="$database" />
@else
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." />
helper="Database should be stopped to change this settings." canGate="update"
:canResource="$database" />
@endif
</div>
@if ($database->enable_ssl)
@@ -100,7 +106,8 @@
@if (str($database->status)->contains('exited'))
<x-forms.select id="database.ssl_mode" label="SSL Mode" wire:model.live="database.ssl_mode"
instantSave="instantSaveSSL"
helper="Choose the SSL verification mode for MongoDB connections">
helper="Choose the SSL verification mode for MongoDB connections" canGate="update"
:canResource="$database">
<option value="allow" title="Allow insecure connections">allow (insecure)</option>
<option value="prefer" title="Prefer secure connections">prefer (secure)</option>
<option value="require" title="Require secure connections">require (secure)</option>
@@ -109,7 +116,8 @@
</x-forms.select>
@else
<x-forms.select id="database.ssl_mode" label="SSL Mode" instantSave="instantSaveSSL"
disabled helper="Database should be stopped to change this settings.">
disabled helper="Database should be stopped to change this settings." canGate="update"
:canResource="$database">
<option value="allow" title="Allow insecure connections">allow (insecure)</option>
<option value="prefer" title="Prefer secure connections">prefer (secure)</option>
<option value="require" title="Require secure connections">require (secure)</option>
@@ -140,16 +148,20 @@
</x-slide-over>
@endif
</div>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available"
canGate="update" :canResource="$database" />
</div>
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" />
id="database.public_port" label="Public Port" canGate="update" :canResource="$database" />
</div>
<x-forms.textarea label="Custom MongoDB Configuration" rows="10" id="database.mongo_conf" />
<x-forms.textarea label="Custom MongoDB Configuration" rows="10" id="database.mongo_conf"
canGate="update" :canResource="$database" />
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs" />
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs"
canGate="update" :canResource="$database" />
</div>
</div>
</form>
</div>

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