v4.0.0-beta.456 (#7673)
This commit is contained in:
commit
b6d0d303c8
124 changed files with 2688 additions and 716 deletions
36
CLAUDE.md
36
CLAUDE.md
|
|
@ -10,6 +10,42 @@ ## Project Overview
|
||||||
|
|
||||||
Coolify is an open-source, self-hostable platform for deploying applications and managing servers - an alternative to Heroku/Netlify/Vercel. It's built with Laravel (PHP) and uses Docker for containerization.
|
Coolify is an open-source, self-hostable platform for deploying applications and managing servers - an alternative to Heroku/Netlify/Vercel. It's built with Laravel (PHP) and uses Docker for containerization.
|
||||||
|
|
||||||
|
## Git Worktree Shared Dependencies
|
||||||
|
|
||||||
|
This repository uses git worktrees for parallel development with **automatic shared dependency setup** via Conductor.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
The `conductor.json` setup script (`scripts/conductor-setup.sh`) automatically:
|
||||||
|
1. Creates symlinks from worktree's `node_modules` and `vendor` to the main repository's directories
|
||||||
|
2. All worktrees share the same dependencies from the main repository
|
||||||
|
3. This happens automatically when Conductor creates a new worktree
|
||||||
|
|
||||||
|
### Benefits
|
||||||
|
|
||||||
|
- **Save disk space**: Only one copy of dependencies across all worktrees
|
||||||
|
- **Faster setup**: No need to run `npm install` or `composer install` for each worktree
|
||||||
|
- **Consistent versions**: All worktrees use the same dependency versions
|
||||||
|
- **Auto-configured**: Handled by Conductor's setup script
|
||||||
|
- **Simple**: Uses the main repo's existing directories, no extra folders
|
||||||
|
|
||||||
|
### Manual Setup (If Needed)
|
||||||
|
|
||||||
|
If you need to set up symlinks manually or for non-Conductor worktrees:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From the worktree directory
|
||||||
|
rm -rf node_modules vendor
|
||||||
|
ln -sf ../../node_modules node_modules
|
||||||
|
ln -sf ../../vendor vendor
|
||||||
|
```
|
||||||
|
|
||||||
|
### Important Notes
|
||||||
|
|
||||||
|
- Dependencies are shared from the main repository (`$CONDUCTOR_ROOT_PATH`)
|
||||||
|
- Run `npm install` or `composer install` from the main repo or any worktree to update all
|
||||||
|
- If different branches need different dependency versions, this won't work - remove symlinks and use separate directories
|
||||||
|
|
||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
### Frontend Development
|
### Frontend Development
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ public function handle(StandaloneClickhouse $database)
|
||||||
],
|
],
|
||||||
'labels' => defaultDatabaseLabels($this->database)->toArray(),
|
'labels' => defaultDatabaseLabels($this->database)->toArray(),
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => "clickhouse-client --password {$this->database->clickhouse_admin_password} --query 'SELECT 1'",
|
'test' => "clickhouse-client --user {$this->database->clickhouse_admin_user} --password {$this->database->clickhouse_admin_password} --query 'SELECT 1'",
|
||||||
'interval' => '5s',
|
'interval' => '5s',
|
||||||
'timeout' => '5s',
|
'timeout' => '5s',
|
||||||
'retries' => 10,
|
'retries' => 10,
|
||||||
|
|
@ -152,12 +152,16 @@ private function generate_environment_variables()
|
||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("CLICKHOUSE_ADMIN_USER={$this->database->clickhouse_admin_user}");
|
$environment_variables->push("CLICKHOUSE_USER={$this->database->clickhouse_admin_user}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
|
$environment_variables->push("CLICKHOUSE_PASSWORD={$this->database->clickhouse_admin_password}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_DB'))->isEmpty()) {
|
||||||
|
$environment_variables->push("CLICKHOUSE_DB={$this->database->clickhouse_db}");
|
||||||
}
|
}
|
||||||
|
|
||||||
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
|
|
|
||||||
|
|
@ -199,12 +199,26 @@ public function handle(Server $server, ?Collection $containers = null, ?Collecti
|
||||||
$isPublic = data_get($database, 'is_public');
|
$isPublic = data_get($database, 'is_public');
|
||||||
$foundDatabases[] = $database->id;
|
$foundDatabases[] = $database->id;
|
||||||
$statusFromDb = $database->status;
|
$statusFromDb = $database->status;
|
||||||
|
|
||||||
|
// Track restart count for databases (single-container)
|
||||||
|
$restartCount = data_get($container, 'RestartCount', 0);
|
||||||
|
$previousRestartCount = $database->restart_count ?? 0;
|
||||||
|
|
||||||
if ($statusFromDb !== $containerStatus) {
|
if ($statusFromDb !== $containerStatus) {
|
||||||
$database->update(['status' => $containerStatus]);
|
$updateData = ['status' => $containerStatus];
|
||||||
} else {
|
} else {
|
||||||
$database->update(['last_online_at' => now()]);
|
$updateData = ['last_online_at' => now()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update restart tracking if restart count increased
|
||||||
|
if ($restartCount > $previousRestartCount) {
|
||||||
|
$updateData['restart_count'] = $restartCount;
|
||||||
|
$updateData['last_restart_at'] = now();
|
||||||
|
$updateData['last_restart_type'] = 'crash';
|
||||||
|
}
|
||||||
|
|
||||||
|
$database->update($updateData);
|
||||||
|
|
||||||
if ($isPublic) {
|
if ($isPublic) {
|
||||||
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
|
|
@ -365,7 +379,13 @@ public function handle(Server $server, ?Collection $containers = null, ?Collecti
|
||||||
if (str($database->status)->startsWith('exited')) {
|
if (str($database->status)->startsWith('exited')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$database->update(['status' => 'exited']);
|
// Reset restart tracking when database exits completely
|
||||||
|
$database->update([
|
||||||
|
'status' => 'exited',
|
||||||
|
'restart_count' => 0,
|
||||||
|
'last_restart_at' => null,
|
||||||
|
'last_restart_type' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
$name = data_get($database, 'name');
|
$name = data_get($database, 'name');
|
||||||
$fqdn = data_get($database, 'fqdn');
|
$fqdn = data_get($database, 'fqdn');
|
||||||
|
|
|
||||||
|
|
@ -237,8 +237,9 @@ public function handle()
|
||||||
$this->foundProxy = true;
|
$this->foundProxy = true;
|
||||||
} elseif ($type === 'service' && $this->isRunning($containerStatus)) {
|
} elseif ($type === 'service' && $this->isRunning($containerStatus)) {
|
||||||
} else {
|
} else {
|
||||||
if ($this->allDatabaseUuids->contains($uuid) && $this->isRunning($containerStatus)) {
|
if ($this->allDatabaseUuids->contains($uuid) && $this->isActiveOrTransient($containerStatus)) {
|
||||||
$this->foundDatabaseUuids->push($uuid);
|
$this->foundDatabaseUuids->push($uuid);
|
||||||
|
// TCP proxy should only be started/managed when database is actually running
|
||||||
if ($this->allTcpProxyUuids->contains($uuid) && $this->isRunning($containerStatus)) {
|
if ($this->allTcpProxyUuids->contains($uuid) && $this->isRunning($containerStatus)) {
|
||||||
$this->updateDatabaseStatus($uuid, $containerStatus, tcpProxy: true);
|
$this->updateDatabaseStatus($uuid, $containerStatus, tcpProxy: true);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -503,20 +504,28 @@ private function updateDatabaseStatus(string $databaseUuid, string $containerSta
|
||||||
private function updateNotFoundDatabaseStatus()
|
private function updateNotFoundDatabaseStatus()
|
||||||
{
|
{
|
||||||
$notFoundDatabaseUuids = $this->allDatabaseUuids->diff($this->foundDatabaseUuids);
|
$notFoundDatabaseUuids = $this->allDatabaseUuids->diff($this->foundDatabaseUuids);
|
||||||
if ($notFoundDatabaseUuids->isNotEmpty()) {
|
if ($notFoundDatabaseUuids->isEmpty()) {
|
||||||
$notFoundDatabaseUuids->each(function ($databaseUuid) {
|
return;
|
||||||
$database = $this->databases->where('uuid', $databaseUuid)->first();
|
|
||||||
if ($database) {
|
|
||||||
if ($database->status !== 'exited') {
|
|
||||||
$database->status = 'exited';
|
|
||||||
$database->save();
|
|
||||||
}
|
|
||||||
if ($database->is_public) {
|
|
||||||
StopDatabaseProxy::dispatch($database);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only protection: Verify we received any container data at all
|
||||||
|
// If containers collection is completely empty, Sentinel might have failed
|
||||||
|
if ($this->containers->isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$notFoundDatabaseUuids->each(function ($databaseUuid) {
|
||||||
|
$database = $this->databases->where('uuid', $databaseUuid)->first();
|
||||||
|
if ($database) {
|
||||||
|
if (! str($database->status)->startsWith('exited')) {
|
||||||
|
$database->status = 'exited';
|
||||||
|
$database->save();
|
||||||
|
}
|
||||||
|
if ($database->is_public) {
|
||||||
|
StopDatabaseProxy::dispatch($database);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateServiceSubStatus(string $serviceId, string $subType, string $subId, string $containerStatus)
|
private function updateServiceSubStatus(string $serviceId, string $subType, string $subId, string $containerStatus)
|
||||||
|
|
@ -576,6 +585,23 @@ private function isRunning(string $containerStatus)
|
||||||
return str($containerStatus)->contains('running');
|
return str($containerStatus)->contains('running');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if container is in an active or transient state.
|
||||||
|
* Active states: running
|
||||||
|
* Transient states: restarting, starting, created, paused
|
||||||
|
*
|
||||||
|
* These states indicate the container exists and should be tracked.
|
||||||
|
* Terminal states (exited, dead, removing) should NOT be tracked.
|
||||||
|
*/
|
||||||
|
private function isActiveOrTransient(string $containerStatus): bool
|
||||||
|
{
|
||||||
|
return str($containerStatus)->contains('running') ||
|
||||||
|
str($containerStatus)->contains('restarting') ||
|
||||||
|
str($containerStatus)->contains('starting') ||
|
||||||
|
str($containerStatus)->contains('created') ||
|
||||||
|
str($containerStatus)->contains('paused');
|
||||||
|
}
|
||||||
|
|
||||||
private function checkLogDrainContainer()
|
private function checkLogDrainContainer()
|
||||||
{
|
{
|
||||||
if ($this->server->isLogDrainEnabled() && $this->foundLogDrainContainer === false) {
|
if ($this->server->isLogDrainEnabled() && $this->foundLogDrainContainer === false) {
|
||||||
|
|
|
||||||
|
|
@ -1314,11 +1314,6 @@ private function completeResourceCreation()
|
||||||
'server_id' => $this->selectedServerId,
|
'server_id' => $this->selectedServerId,
|
||||||
];
|
];
|
||||||
|
|
||||||
// PostgreSQL requires a database_image parameter
|
|
||||||
if ($this->selectedResourceType === 'postgresql') {
|
|
||||||
$queryParams['database_image'] = 'postgres:16-alpine';
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->redirect(route('project.resource.create', [
|
$this->redirect(route('project.resource.create', [
|
||||||
'project_uuid' => $this->selectedProjectUuid,
|
'project_uuid' => $this->selectedProjectUuid,
|
||||||
'environment_uuid' => $this->selectedEnvironmentUuid,
|
'environment_uuid' => $this->selectedEnvironmentUuid,
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,19 @@ public function getLogLinesProperty()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function copyLogs(): string
|
||||||
|
{
|
||||||
|
$logs = decode_remote_command_output($this->application_deployment_queue)
|
||||||
|
->map(function ($line) {
|
||||||
|
return $line['timestamp'].' '.
|
||||||
|
(isset($line['command']) && $line['command'] ? '[CMD]: ' : '').
|
||||||
|
trim($line['line']);
|
||||||
|
})
|
||||||
|
->join("\n");
|
||||||
|
|
||||||
|
return sanitizeLogsForExport($logs);
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.application.deployment.show');
|
return view('livewire.project.application.deployment.show');
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ class Select extends Component
|
||||||
|
|
||||||
protected $queryString = [
|
protected $queryString = [
|
||||||
'server_id',
|
'server_id',
|
||||||
|
'type' => ['except' => ''],
|
||||||
|
'destination_uuid' => ['except' => '', 'as' => 'destination'],
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
|
|
@ -66,6 +68,20 @@ public function mount()
|
||||||
$project = Project::whereUuid($projectUuid)->firstOrFail();
|
$project = Project::whereUuid($projectUuid)->firstOrFail();
|
||||||
$this->environments = $project->environments;
|
$this->environments = $project->environments;
|
||||||
$this->selectedEnvironment = $this->environments->where('uuid', data_get($this->parameters, 'environment_uuid'))->firstOrFail()->name;
|
$this->selectedEnvironment = $this->environments->where('uuid', data_get($this->parameters, 'environment_uuid'))->firstOrFail()->name;
|
||||||
|
|
||||||
|
// Check if we have all required params for PostgreSQL type selection
|
||||||
|
// This handles navigation from global search
|
||||||
|
$queryType = request()->query('type');
|
||||||
|
$queryServerId = request()->query('server_id');
|
||||||
|
$queryDestination = request()->query('destination');
|
||||||
|
|
||||||
|
if ($queryType === 'postgresql' && $queryServerId !== null && $queryDestination) {
|
||||||
|
$this->type = $queryType;
|
||||||
|
$this->server_id = $queryServerId;
|
||||||
|
$this->destination_uuid = $queryDestination;
|
||||||
|
$this->server = Server::find($queryServerId);
|
||||||
|
$this->current_step = 'select-postgresql-type';
|
||||||
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,13 @@ public function mount()
|
||||||
|
|
||||||
if (in_array($type, DATABASE_TYPES)) {
|
if (in_array($type, DATABASE_TYPES)) {
|
||||||
if ($type->value() === 'postgresql') {
|
if ($type->value() === 'postgresql') {
|
||||||
|
// PostgreSQL requires database_image to be explicitly set
|
||||||
|
// If not provided, fall through to Select component for version selection
|
||||||
|
if (! $database_image) {
|
||||||
|
$this->type = $type->value();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
$database = create_standalone_postgresql(
|
$database = create_standalone_postgresql(
|
||||||
environmentId: $environment->id,
|
environmentId: $environment->id,
|
||||||
destinationUuid: $destination_uuid,
|
destinationUuid: $destination_uuid,
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,6 @@ public function mount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function doSomethingWithThisChunkOfOutput($output)
|
|
||||||
{
|
|
||||||
$this->outputs .= removeAnsiColors($output);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
if (! is_null($this->resource)) {
|
if (! is_null($this->resource)) {
|
||||||
|
|
@ -162,23 +157,32 @@ public function getLogs($refresh = false)
|
||||||
$sshCommand = SshMultiplexingHelper::generateSshCommand($this->server, $command);
|
$sshCommand = SshMultiplexingHelper::generateSshCommand($this->server, $command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($refresh) {
|
// Collect new logs into temporary variable first to prevent flickering
|
||||||
$this->outputs = '';
|
// (avoids clearing output before new data is ready)
|
||||||
}
|
$newOutputs = '';
|
||||||
Process::run($sshCommand, function (string $type, string $output) {
|
Process::run($sshCommand, function (string $type, string $output) use (&$newOutputs) {
|
||||||
$this->doSomethingWithThisChunkOfOutput($output);
|
$newOutputs .= removeAnsiColors($output);
|
||||||
});
|
});
|
||||||
|
|
||||||
if ($this->showTimeStamps) {
|
if ($this->showTimeStamps) {
|
||||||
$this->outputs = str($this->outputs)->split('/\n/')->sort(function ($a, $b) {
|
$newOutputs = str($newOutputs)->split('/\n/')->sort(function ($a, $b) {
|
||||||
$a = explode(' ', $a);
|
$a = explode(' ', $a);
|
||||||
$b = explode(' ', $b);
|
$b = explode(' ', $b);
|
||||||
|
|
||||||
return $a[0] <=> $b[0];
|
return $a[0] <=> $b[0];
|
||||||
})->join("\n");
|
})->join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only update outputs after new data is ready (atomic update prevents flicker)
|
||||||
|
$this->outputs = $newOutputs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function copyLogs(): string
|
||||||
|
{
|
||||||
|
return sanitizeLogsForExport($this->outputs);
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.shared.get-logs');
|
return view('livewire.project.shared.get-logs');
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Resources extends Component
|
class Resources extends Component
|
||||||
|
|
@ -15,7 +14,7 @@ class Resources extends Component
|
||||||
|
|
||||||
public $parameters = [];
|
public $parameters = [];
|
||||||
|
|
||||||
public Collection $containers;
|
public array $unmanagedContainers = [];
|
||||||
|
|
||||||
public $activeTab = 'managed';
|
public $activeTab = 'managed';
|
||||||
|
|
||||||
|
|
@ -64,7 +63,7 @@ public function loadManagedContainers()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->activeTab = 'managed';
|
$this->activeTab = 'managed';
|
||||||
$this->containers = $this->server->refresh()->definedResources();
|
$this->server->refresh();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +73,7 @@ public function loadUnmanagedContainers()
|
||||||
{
|
{
|
||||||
$this->activeTab = 'unmanaged';
|
$this->activeTab = 'unmanaged';
|
||||||
try {
|
try {
|
||||||
$this->containers = $this->server->loadUnmanagedContainers();
|
$this->unmanagedContainers = $this->server->loadUnmanagedContainers()->toArray();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|
@ -82,14 +81,12 @@ public function loadUnmanagedContainers()
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->containers = collect();
|
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
try {
|
try {
|
||||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||||
if (is_null($this->server)) {
|
if (is_null($this->server)) {
|
||||||
return redirect()->route('server.index');
|
return redirect()->route('server.index');
|
||||||
}
|
}
|
||||||
$this->loadManagedContainers();
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,9 @@ class Advanced extends Component
|
||||||
#[Validate('boolean')]
|
#[Validate('boolean')]
|
||||||
public bool $disable_two_step_confirmation;
|
public bool $disable_two_step_confirmation;
|
||||||
|
|
||||||
|
#[Validate('boolean')]
|
||||||
|
public bool $is_wire_navigate_enabled;
|
||||||
|
|
||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|
@ -50,6 +53,7 @@ public function rules()
|
||||||
'allowed_ips' => ['nullable', 'string', new ValidIpOrCidr],
|
'allowed_ips' => ['nullable', 'string', new ValidIpOrCidr],
|
||||||
'is_sponsorship_popup_enabled' => 'boolean',
|
'is_sponsorship_popup_enabled' => 'boolean',
|
||||||
'disable_two_step_confirmation' => 'boolean',
|
'disable_two_step_confirmation' => 'boolean',
|
||||||
|
'is_wire_navigate_enabled' => 'boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,6 +72,7 @@ public function mount()
|
||||||
$this->is_api_enabled = $this->settings->is_api_enabled;
|
$this->is_api_enabled = $this->settings->is_api_enabled;
|
||||||
$this->disable_two_step_confirmation = $this->settings->disable_two_step_confirmation;
|
$this->disable_two_step_confirmation = $this->settings->disable_two_step_confirmation;
|
||||||
$this->is_sponsorship_popup_enabled = $this->settings->is_sponsorship_popup_enabled;
|
$this->is_sponsorship_popup_enabled = $this->settings->is_sponsorship_popup_enabled;
|
||||||
|
$this->is_wire_navigate_enabled = $this->settings->is_wire_navigate_enabled ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
|
|
@ -146,6 +151,7 @@ public function instantSave()
|
||||||
$this->settings->allowed_ips = $this->allowed_ips;
|
$this->settings->allowed_ips = $this->allowed_ips;
|
||||||
$this->settings->is_sponsorship_popup_enabled = $this->is_sponsorship_popup_enabled;
|
$this->settings->is_sponsorship_popup_enabled = $this->is_sponsorship_popup_enabled;
|
||||||
$this->settings->disable_two_step_confirmation = $this->disable_two_step_confirmation;
|
$this->settings->disable_two_step_confirmation = $this->disable_two_step_confirmation;
|
||||||
|
$this->settings->is_wire_navigate_enabled = $this->is_wire_navigate_enabled;
|
||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
$this->dispatch('success', 'Settings updated!');
|
$this->dispatch('success', 'Settings updated!');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,14 @@ class Upgrade extends Component
|
||||||
|
|
||||||
public string $currentVersion = '';
|
public string $currentVersion = '';
|
||||||
|
|
||||||
|
public bool $devMode = false;
|
||||||
|
|
||||||
protected $listeners = ['updateAvailable' => 'checkUpdate'];
|
protected $listeners = ['updateAvailable' => 'checkUpdate'];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->currentVersion = config('constants.coolify.version');
|
$this->currentVersion = config('constants.coolify.version');
|
||||||
|
$this->devMode = isDev();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkUpdate()
|
public function checkUpdate()
|
||||||
|
|
|
||||||
|
|
@ -77,21 +77,21 @@ public function generate_preview_fqdn()
|
||||||
if ($this->application->fqdn) {
|
if ($this->application->fqdn) {
|
||||||
if (str($this->application->fqdn)->contains(',')) {
|
if (str($this->application->fqdn)->contains(',')) {
|
||||||
$url = Url::fromString(str($this->application->fqdn)->explode(',')[0]);
|
$url = Url::fromString(str($this->application->fqdn)->explode(',')[0]);
|
||||||
$preview_fqdn = getFqdnWithoutPort(str($this->application->fqdn)->explode(',')[0]);
|
|
||||||
} else {
|
} else {
|
||||||
$url = Url::fromString($this->application->fqdn);
|
$url = Url::fromString($this->application->fqdn);
|
||||||
if ($this->fqdn) {
|
|
||||||
$preview_fqdn = getFqdnWithoutPort($this->fqdn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$template = $this->application->preview_url_template;
|
$template = $this->application->preview_url_template;
|
||||||
$host = $url->getHost();
|
$host = $url->getHost();
|
||||||
$schema = $url->getScheme();
|
$schema = $url->getScheme();
|
||||||
|
$portInt = $url->getPort();
|
||||||
|
$port = $portInt !== null ? ':'.$portInt : '';
|
||||||
|
$urlPath = $url->getPath();
|
||||||
|
$path = ($urlPath !== '' && $urlPath !== '/') ? $urlPath : '';
|
||||||
$random = new Cuid2;
|
$random = new Cuid2;
|
||||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||||
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
||||||
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
|
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
|
||||||
$preview_fqdn = "$schema://$preview_fqdn";
|
$preview_fqdn = "$schema://$preview_fqdn{$port}{$path}";
|
||||||
$this->fqdn = $preview_fqdn;
|
$this->fqdn = $preview_fqdn;
|
||||||
$this->save();
|
$this->save();
|
||||||
}
|
}
|
||||||
|
|
@ -147,11 +147,13 @@ public function generate_preview_fqdn_compose()
|
||||||
$schema = $url->getScheme();
|
$schema = $url->getScheme();
|
||||||
$portInt = $url->getPort();
|
$portInt = $url->getPort();
|
||||||
$port = $portInt !== null ? ':'.$portInt : '';
|
$port = $portInt !== null ? ':'.$portInt : '';
|
||||||
|
$urlPath = $url->getPath();
|
||||||
|
$path = ($urlPath !== '' && $urlPath !== '/') ? $urlPath : '';
|
||||||
$random = new Cuid2;
|
$random = new Cuid2;
|
||||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||||
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
||||||
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
|
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
|
||||||
$preview_fqdn = "$schema://$preview_fqdn{$port}";
|
$preview_fqdn = "$schema://$preview_fqdn{$port}{$path}";
|
||||||
$preview_domains[] = $preview_fqdn;
|
$preview_domains[] = $preview_fqdn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ class InstanceSettings extends Model
|
||||||
'auto_update_frequency' => 'string',
|
'auto_update_frequency' => 'string',
|
||||||
'update_check_frequency' => 'string',
|
'update_check_frequency' => 'string',
|
||||||
'sentinel_token' => 'encrypted',
|
'sentinel_token' => 'encrypted',
|
||||||
|
'is_wire_navigate_enabled' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected static function booted(): void
|
protected static function booted(): void
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ class StandaloneClickhouse extends BaseModel
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'clickhouse_password' => 'encrypted',
|
'clickhouse_password' => 'encrypted',
|
||||||
|
'restart_count' => 'integer',
|
||||||
|
'last_restart_at' => 'datetime',
|
||||||
|
'last_restart_type' => 'string',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
|
|
@ -25,7 +28,7 @@ protected static function booted()
|
||||||
static::created(function ($database) {
|
static::created(function ($database) {
|
||||||
LocalPersistentVolume::create([
|
LocalPersistentVolume::create([
|
||||||
'name' => 'clickhouse-data-'.$database->uuid,
|
'name' => 'clickhouse-data-'.$database->uuid,
|
||||||
'mount_path' => '/bitnami/clickhouse',
|
'mount_path' => '/var/lib/clickhouse',
|
||||||
'host_path' => null,
|
'host_path' => null,
|
||||||
'resource_id' => $database->id,
|
'resource_id' => $database->id,
|
||||||
'resource_type' => $database->getMorphClass(),
|
'resource_type' => $database->getMorphClass(),
|
||||||
|
|
@ -246,8 +249,9 @@ protected function internalDbUrl(): Attribute
|
||||||
get: function () {
|
get: function () {
|
||||||
$encodedUser = rawurlencode($this->clickhouse_admin_user);
|
$encodedUser = rawurlencode($this->clickhouse_admin_user);
|
||||||
$encodedPass = rawurlencode($this->clickhouse_admin_password);
|
$encodedPass = rawurlencode($this->clickhouse_admin_password);
|
||||||
|
$database = $this->clickhouse_db ?? 'default';
|
||||||
|
|
||||||
return "clickhouse://{$encodedUser}:{$encodedPass}@{$this->uuid}:9000/{$this->clickhouse_db}";
|
return "clickhouse://{$encodedUser}:{$encodedPass}@{$this->uuid}:9000/{$database}";
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -263,8 +267,9 @@ protected function externalDbUrl(): Attribute
|
||||||
}
|
}
|
||||||
$encodedUser = rawurlencode($this->clickhouse_admin_user);
|
$encodedUser = rawurlencode($this->clickhouse_admin_user);
|
||||||
$encodedPass = rawurlencode($this->clickhouse_admin_password);
|
$encodedPass = rawurlencode($this->clickhouse_admin_password);
|
||||||
|
$database = $this->clickhouse_db ?? 'default';
|
||||||
|
|
||||||
return "clickhouse://{$encodedUser}:{$encodedPass}@{$serverIp}:{$this->public_port}/{$this->clickhouse_db}";
|
return "clickhouse://{$encodedUser}:{$encodedPass}@{$serverIp}:{$this->public_port}/{$database}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ class StandaloneDragonfly extends BaseModel
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'dragonfly_password' => 'encrypted',
|
'dragonfly_password' => 'encrypted',
|
||||||
|
'restart_count' => 'integer',
|
||||||
|
'last_restart_at' => 'datetime',
|
||||||
|
'last_restart_type' => 'string',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ class StandaloneKeydb extends BaseModel
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'keydb_password' => 'encrypted',
|
'keydb_password' => 'encrypted',
|
||||||
|
'restart_count' => 'integer',
|
||||||
|
'last_restart_at' => 'datetime',
|
||||||
|
'last_restart_type' => 'string',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ class StandaloneMariadb extends BaseModel
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'mariadb_password' => 'encrypted',
|
'mariadb_password' => 'encrypted',
|
||||||
|
'restart_count' => 'integer',
|
||||||
|
'last_restart_at' => 'datetime',
|
||||||
|
'last_restart_type' => 'string',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,12 @@ class StandaloneMongodb extends BaseModel
|
||||||
|
|
||||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'restart_count' => 'integer',
|
||||||
|
'last_restart_at' => 'datetime',
|
||||||
|
'last_restart_type' => 'string',
|
||||||
|
];
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::created(function ($database) {
|
static::created(function ($database) {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ class StandaloneMysql extends BaseModel
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'mysql_password' => 'encrypted',
|
'mysql_password' => 'encrypted',
|
||||||
'mysql_root_password' => 'encrypted',
|
'mysql_root_password' => 'encrypted',
|
||||||
|
'restart_count' => 'integer',
|
||||||
|
'last_restart_at' => 'datetime',
|
||||||
|
'last_restart_type' => 'string',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ class StandalonePostgresql extends BaseModel
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'init_scripts' => 'array',
|
'init_scripts' => 'array',
|
||||||
'postgres_password' => 'encrypted',
|
'postgres_password' => 'encrypted',
|
||||||
|
'restart_count' => 'integer',
|
||||||
|
'last_restart_at' => 'datetime',
|
||||||
|
'last_restart_type' => 'string',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,12 @@ class StandaloneRedis extends BaseModel
|
||||||
|
|
||||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'restart_count' => 'integer',
|
||||||
|
'last_restart_at' => 'datetime',
|
||||||
|
'last_restart_type' => 'string',
|
||||||
|
];
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::created(function ($database) {
|
static::created(function ($database) {
|
||||||
|
|
|
||||||
|
|
@ -269,9 +269,41 @@ function remove_iip($text)
|
||||||
// Ensure the input is valid UTF-8 before processing
|
// Ensure the input is valid UTF-8 before processing
|
||||||
$text = sanitize_utf8_text($text);
|
$text = sanitize_utf8_text($text);
|
||||||
|
|
||||||
|
// Git access tokens
|
||||||
$text = preg_replace('/x-access-token:.*?(?=@)/', 'x-access-token:'.REDACTED, $text);
|
$text = preg_replace('/x-access-token:.*?(?=@)/', 'x-access-token:'.REDACTED, $text);
|
||||||
|
|
||||||
return preg_replace('/\x1b\[[0-9;]*m/', '', $text);
|
// ANSI color codes
|
||||||
|
$text = preg_replace('/\x1b\[[0-9;]*m/', '', $text);
|
||||||
|
|
||||||
|
// Generic URLs with passwords (covers database URLs, ftp, amqp, ssh, etc.)
|
||||||
|
// (protocol://user:password@host → protocol://user:<REDACTED>@host)
|
||||||
|
$text = preg_replace('/((?:postgres|mysql|mongodb|rediss?|mariadb|ftp|sftp|ssh|amqp|amqps|ldap|ldaps|s3):\/\/[^:]+:)[^@]+(@)/i', '$1'.REDACTED.'$2', $text);
|
||||||
|
|
||||||
|
// Email addresses
|
||||||
|
$text = preg_replace('/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/', REDACTED, $text);
|
||||||
|
|
||||||
|
// Bearer/JWT tokens
|
||||||
|
$text = preg_replace('/Bearer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/i', 'Bearer '.REDACTED, $text);
|
||||||
|
|
||||||
|
// GitHub tokens (ghp_ = personal, gho_ = OAuth, ghu_ = user-to-server, ghs_ = server-to-server, ghr_ = refresh)
|
||||||
|
$text = preg_replace('/\b(gh[pousr]_[A-Za-z0-9_]{36,})\b/', REDACTED, $text);
|
||||||
|
|
||||||
|
// GitLab tokens (glpat- = personal access token, glcbt- = CI build token, glrt- = runner token)
|
||||||
|
$text = preg_replace('/\b(gl(?:pat|cbt|rt)-[A-Za-z0-9\-_]{20,})\b/', REDACTED, $text);
|
||||||
|
|
||||||
|
// AWS credentials (Access Key ID starts with AKIA, ABIA, ACCA, ASIA)
|
||||||
|
$text = preg_replace('/\b(A(?:KIA|BIA|CCA|SIA)[A-Z0-9]{16})\b/', REDACTED, $text);
|
||||||
|
|
||||||
|
// AWS Secret Access Key (40 character base64-ish string, typically follows access key)
|
||||||
|
$text = preg_replace('/(aws_secret_access_key|AWS_SECRET_ACCESS_KEY)[=:]\s*[\'"]?([A-Za-z0-9\/+=]{40})[\'"]?/i', '$1='.REDACTED, $text);
|
||||||
|
|
||||||
|
// API keys (common patterns)
|
||||||
|
$text = preg_replace('/(api[_-]?key|apikey|api[_-]?secret|secret[_-]?key)[=:]\s*[\'"]?[A-Za-z0-9\-_]{16,}[\'"]?/i', '$1='.REDACTED, $text);
|
||||||
|
|
||||||
|
// Private key blocks
|
||||||
|
$text = preg_replace('/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/', REDACTED, $text);
|
||||||
|
|
||||||
|
return $text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -672,6 +672,12 @@ function removeAnsiColors($text)
|
||||||
return preg_replace('/\e[[][A-Za-z0-9];?[0-9]*m?/', '', $text);
|
return preg_replace('/\e[[][A-Za-z0-9];?[0-9]*m?/', '', $text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizeLogsForExport(string $text): string
|
||||||
|
{
|
||||||
|
// All sanitization is now handled by remove_iip()
|
||||||
|
return remove_iip($text);
|
||||||
|
}
|
||||||
|
|
||||||
function getTopLevelNetworks(Service|Application $resource)
|
function getTopLevelNetworks(Service|Application $resource)
|
||||||
{
|
{
|
||||||
if ($resource->getMorphClass() === \App\Models\Service::class) {
|
if ($resource->getMorphClass() === \App\Models\Service::class) {
|
||||||
|
|
@ -2916,6 +2922,18 @@ function instanceSettings()
|
||||||
return InstanceSettings::get();
|
return InstanceSettings::get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wireNavigate(): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$settings = instanceSettings();
|
||||||
|
|
||||||
|
// Return wire:navigate.hover for SPA navigation with prefetching, or empty string if disabled
|
||||||
|
return ($settings->is_wire_navigate_enabled ?? true) ? 'wire:navigate.hover' : '';
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return 'wire:navigate.hover';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getHelperVersion(): string
|
function getHelperVersion(): string
|
||||||
{
|
{
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'coolify' => [
|
'coolify' => [
|
||||||
'version' => '4.0.0-beta.455',
|
'version' => '4.0.0-beta.456',
|
||||||
'helper_version' => '1.0.12',
|
'helper_version' => '1.0.12',
|
||||||
'realtime_version' => '1.0.10',
|
'realtime_version' => '1.0.10',
|
||||||
'self_hosted' => env('SELF_HOSTED', true),
|
'self_hosted' => env('SELF_HOSTED', true),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\LocalPersistentVolume;
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* Migrates existing ClickHouse instances from Bitnami/BinamiLegacy images
|
||||||
|
* to the official clickhouse/clickhouse-server image.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// Add clickhouse_db column if it doesn't exist
|
||||||
|
if (! Schema::hasColumn('standalone_clickhouses', 'clickhouse_db')) {
|
||||||
|
Schema::table('standalone_clickhouses', function (Blueprint $table) {
|
||||||
|
$table->string('clickhouse_db')
|
||||||
|
->default('default')
|
||||||
|
->after('clickhouse_admin_password');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the default value for the 'image' column to the official image
|
||||||
|
Schema::table('standalone_clickhouses', function (Blueprint $table) {
|
||||||
|
$table->string('image')->default('clickhouse/clickhouse-server:25.11')->change();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update existing ClickHouse instances from Bitnami images to official image
|
||||||
|
StandaloneClickhouse::where(function ($query) {
|
||||||
|
$query->where('image', 'like', '%bitnami/clickhouse%')
|
||||||
|
->orWhere('image', 'like', '%bitnamilegacy/clickhouse%');
|
||||||
|
})
|
||||||
|
->update([
|
||||||
|
'image' => 'clickhouse/clickhouse-server:25.11',
|
||||||
|
'clickhouse_db' => DB::raw("COALESCE(clickhouse_db, 'default')"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update volume mount paths from Bitnami to official image paths
|
||||||
|
LocalPersistentVolume::where('resource_type', StandaloneClickhouse::class)
|
||||||
|
->where('mount_path', '/bitnami/clickhouse')
|
||||||
|
->update(['mount_path' => '/var/lib/clickhouse']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
// Revert the default value for the 'image' column
|
||||||
|
Schema::table('standalone_clickhouses', function (Blueprint $table) {
|
||||||
|
$table->string('image')->default('bitnamilegacy/clickhouse')->change();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Revert existing ClickHouse instances back to Bitnami image
|
||||||
|
StandaloneClickhouse::where('image', 'clickhouse/clickhouse-server:25.11')
|
||||||
|
->update(['image' => 'bitnamilegacy/clickhouse']);
|
||||||
|
|
||||||
|
// Revert volume mount paths
|
||||||
|
LocalPersistentVolume::where('resource_type', StandaloneClickhouse::class)
|
||||||
|
->where('mount_path', '/var/lib/clickhouse')
|
||||||
|
->update(['mount_path' => '/bitnami/clickhouse']);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('instance_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_wire_navigate_enabled')->default(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('instance_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_wire_navigate_enabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The standalone database tables to add restart tracking columns to.
|
||||||
|
*/
|
||||||
|
private array $tables = [
|
||||||
|
'standalone_postgresqls',
|
||||||
|
'standalone_mysqls',
|
||||||
|
'standalone_mariadbs',
|
||||||
|
'standalone_redis',
|
||||||
|
'standalone_mongodbs',
|
||||||
|
'standalone_keydbs',
|
||||||
|
'standalone_dragonflies',
|
||||||
|
'standalone_clickhouses',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
foreach ($this->tables as $table) {
|
||||||
|
if (! Schema::hasColumn($table, 'restart_count')) {
|
||||||
|
Schema::table($table, function (Blueprint $blueprint) {
|
||||||
|
$blueprint->integer('restart_count')->default(0)->after('status');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! Schema::hasColumn($table, 'last_restart_at')) {
|
||||||
|
Schema::table($table, function (Blueprint $blueprint) {
|
||||||
|
$blueprint->timestamp('last_restart_at')->nullable()->after('restart_count');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! Schema::hasColumn($table, 'last_restart_type')) {
|
||||||
|
Schema::table($table, function (Blueprint $blueprint) {
|
||||||
|
$blueprint->string('last_restart_type', 10)->nullable()->after('last_restart_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$columns = ['restart_count', 'last_restart_at', 'last_restart_type'];
|
||||||
|
|
||||||
|
foreach ($this->tables as $table) {
|
||||||
|
foreach ($columns as $column) {
|
||||||
|
if (Schema::hasColumn($table, $column)) {
|
||||||
|
Schema::table($table, function (Blueprint $blueprint) use ($column) {
|
||||||
|
$blueprint->dropColumn($column);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -29,9 +29,14 @@ if [ $EUID != 0 ]; then
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "Welcome to Coolify Installer!"
|
echo ""
|
||||||
echo -e "This script will install everything for you. Sit back and relax."
|
echo "=========================================="
|
||||||
echo -e "Source code: https://github.com/coollabsio/coolify/blob/v4.x/scripts/install.sh"
|
echo " Coolify Installation - ${DATE}"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "Welcome to Coolify Installer!"
|
||||||
|
echo "This script will install everything for you. Sit back and relax."
|
||||||
|
echo "Source code: https://github.com/coollabsio/coolify/blob/v4.x/scripts/install.sh"
|
||||||
|
|
||||||
# Predefined root user
|
# Predefined root user
|
||||||
ROOT_USERNAME=${ROOT_USERNAME:-}
|
ROOT_USERNAME=${ROOT_USERNAME:-}
|
||||||
|
|
@ -242,6 +247,29 @@ getAJoke() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Helper function to log with timestamp
|
||||||
|
log() {
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to log section headers
|
||||||
|
log_section() {
|
||||||
|
echo ""
|
||||||
|
echo "============================================================"
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||||
|
echo "============================================================"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to check if all required packages are installed
|
||||||
|
all_packages_installed() {
|
||||||
|
for pkg in curl wget git jq openssl; do
|
||||||
|
if ! command -v "$pkg" >/dev/null 2>&1; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# Check if the OS is manjaro, if so, change it to arch
|
# Check if the OS is manjaro, if so, change it to arch
|
||||||
if [ "$OS_TYPE" = "manjaro" ] || [ "$OS_TYPE" = "manjaro-arm" ]; then
|
if [ "$OS_TYPE" = "manjaro" ] || [ "$OS_TYPE" = "manjaro-arm" ]; then
|
||||||
OS_TYPE="arch"
|
OS_TYPE="arch"
|
||||||
|
|
@ -288,9 +316,11 @@ if [ "$OS_TYPE" = 'amzn' ]; then
|
||||||
dnf install -y findutils >/dev/null
|
dnf install -y findutils >/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
LATEST_VERSION=$(curl -L --silent $CDN/versions.json | grep -i version | xargs | awk '{print $2}' | tr -d ',')
|
# Fetch versions.json once and parse all values from it
|
||||||
LATEST_HELPER_VERSION=$(curl -L --silent $CDN/versions.json | grep -i version | xargs | awk '{print $6}' | tr -d ',')
|
VERSIONS_JSON=$(curl -L --silent $CDN/versions.json)
|
||||||
LATEST_REALTIME_VERSION=$(curl -L --silent $CDN/versions.json | grep -i version | xargs | awk '{print $8}' | tr -d ',')
|
LATEST_VERSION=$(echo "$VERSIONS_JSON" | grep -i version | xargs | awk '{print $2}' | tr -d ',')
|
||||||
|
LATEST_HELPER_VERSION=$(echo "$VERSIONS_JSON" | grep -i version | xargs | awk '{print $6}' | tr -d ',')
|
||||||
|
LATEST_REALTIME_VERSION=$(echo "$VERSIONS_JSON" | grep -i version | xargs | awk '{print $8}' | tr -d ',')
|
||||||
|
|
||||||
if [ -z "$LATEST_HELPER_VERSION" ]; then
|
if [ -z "$LATEST_HELPER_VERSION" ]; then
|
||||||
LATEST_HELPER_VERSION=latest
|
LATEST_HELPER_VERSION=latest
|
||||||
|
|
@ -315,7 +345,7 @@ if [ "$1" != "" ]; then
|
||||||
LATEST_VERSION="${LATEST_VERSION#v}"
|
LATEST_VERSION="${LATEST_VERSION#v}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "---------------------------------------------"
|
echo "---------------------------------------------"
|
||||||
echo "| Operating System | $OS_TYPE $OS_VERSION"
|
echo "| Operating System | $OS_TYPE $OS_VERSION"
|
||||||
echo "| Docker | $DOCKER_VERSION"
|
echo "| Docker | $DOCKER_VERSION"
|
||||||
echo "| Coolify | $LATEST_VERSION"
|
echo "| Coolify | $LATEST_VERSION"
|
||||||
|
|
@ -323,46 +353,61 @@ echo "| Helper | $LATEST_HELPER_VERSION"
|
||||||
echo "| Realtime | $LATEST_REALTIME_VERSION"
|
echo "| Realtime | $LATEST_REALTIME_VERSION"
|
||||||
echo "| Docker Pool | $DOCKER_ADDRESS_POOL_BASE (size $DOCKER_ADDRESS_POOL_SIZE)"
|
echo "| Docker Pool | $DOCKER_ADDRESS_POOL_BASE (size $DOCKER_ADDRESS_POOL_SIZE)"
|
||||||
echo "| Registry URL | $REGISTRY_URL"
|
echo "| Registry URL | $REGISTRY_URL"
|
||||||
echo -e "---------------------------------------------\n"
|
echo "---------------------------------------------"
|
||||||
echo -e "1. Installing required packages (curl, wget, git, jq, openssl). "
|
echo ""
|
||||||
|
|
||||||
case "$OS_TYPE" in
|
log_section "Step 1/9: Installing required packages"
|
||||||
arch)
|
echo "1/9 Installing required packages (curl, wget, git, jq, openssl)..."
|
||||||
pacman -Sy --noconfirm --needed curl wget git jq openssl >/dev/null || true
|
|
||||||
;;
|
|
||||||
alpine)
|
|
||||||
sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories
|
|
||||||
apk update >/dev/null
|
|
||||||
apk add curl wget git jq openssl >/dev/null
|
|
||||||
;;
|
|
||||||
ubuntu | debian | raspbian)
|
|
||||||
apt-get update -y >/dev/null
|
|
||||||
apt-get install -y curl wget git jq openssl >/dev/null
|
|
||||||
;;
|
|
||||||
centos | fedora | rhel | ol | rocky | almalinux | amzn)
|
|
||||||
if [ "$OS_TYPE" = "amzn" ]; then
|
|
||||||
dnf install -y wget git jq openssl >/dev/null
|
|
||||||
else
|
|
||||||
if ! command -v dnf >/dev/null; then
|
|
||||||
yum install -y dnf >/dev/null
|
|
||||||
fi
|
|
||||||
if ! command -v curl >/dev/null; then
|
|
||||||
dnf install -y curl >/dev/null
|
|
||||||
fi
|
|
||||||
dnf install -y wget git jq openssl >/dev/null
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
sles | opensuse-leap | opensuse-tumbleweed)
|
|
||||||
zypper refresh >/dev/null
|
|
||||||
zypper install -y curl wget git jq openssl >/dev/null
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now."
|
|
||||||
exit
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo -e "2. Check OpenSSH server configuration. "
|
# Track if apt-get update was run to avoid redundant calls later
|
||||||
|
APT_UPDATED=false
|
||||||
|
|
||||||
|
if all_packages_installed; then
|
||||||
|
log "All required packages already installed, skipping installation"
|
||||||
|
echo " - All required packages already installed."
|
||||||
|
else
|
||||||
|
case "$OS_TYPE" in
|
||||||
|
arch)
|
||||||
|
pacman -Sy --noconfirm --needed curl wget git jq openssl >/dev/null || true
|
||||||
|
;;
|
||||||
|
alpine)
|
||||||
|
sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories
|
||||||
|
apk update >/dev/null
|
||||||
|
apk add curl wget git jq openssl >/dev/null
|
||||||
|
;;
|
||||||
|
ubuntu | debian | raspbian)
|
||||||
|
apt-get update -y >/dev/null
|
||||||
|
APT_UPDATED=true
|
||||||
|
apt-get install -y curl wget git jq openssl >/dev/null
|
||||||
|
;;
|
||||||
|
centos | fedora | rhel | ol | rocky | almalinux | amzn)
|
||||||
|
if [ "$OS_TYPE" = "amzn" ]; then
|
||||||
|
dnf install -y wget git jq openssl >/dev/null
|
||||||
|
else
|
||||||
|
if ! command -v dnf >/dev/null; then
|
||||||
|
yum install -y dnf >/dev/null
|
||||||
|
fi
|
||||||
|
if ! command -v curl >/dev/null; then
|
||||||
|
dnf install -y curl >/dev/null
|
||||||
|
fi
|
||||||
|
dnf install -y wget git jq openssl >/dev/null
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
sles | opensuse-leap | opensuse-tumbleweed)
|
||||||
|
zypper refresh >/dev/null
|
||||||
|
zypper install -y curl wget git jq openssl >/dev/null
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now."
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
log "Required packages installed successfully"
|
||||||
|
fi
|
||||||
|
echo " Done."
|
||||||
|
|
||||||
|
log_section "Step 2/9: Checking OpenSSH server configuration"
|
||||||
|
echo "2/9 Checking OpenSSH server configuration..."
|
||||||
|
|
||||||
# Detect OpenSSH server
|
# Detect OpenSSH server
|
||||||
SSH_DETECTED=false
|
SSH_DETECTED=false
|
||||||
|
|
@ -398,7 +443,10 @@ if [ "$SSH_DETECTED" = "false" ]; then
|
||||||
service sshd start >/dev/null 2>&1
|
service sshd start >/dev/null 2>&1
|
||||||
;;
|
;;
|
||||||
ubuntu | debian | raspbian)
|
ubuntu | debian | raspbian)
|
||||||
apt-get update -y >/dev/null
|
if [ "$APT_UPDATED" = false ]; then
|
||||||
|
apt-get update -y >/dev/null
|
||||||
|
APT_UPDATED=true
|
||||||
|
fi
|
||||||
apt-get install -y openssh-server >/dev/null
|
apt-get install -y openssh-server >/dev/null
|
||||||
systemctl enable ssh >/dev/null 2>&1
|
systemctl enable ssh >/dev/null 2>&1
|
||||||
systemctl start ssh >/dev/null 2>&1
|
systemctl start ssh >/dev/null 2>&1
|
||||||
|
|
@ -465,7 +513,10 @@ install_docker() {
|
||||||
install_docker_manually() {
|
install_docker_manually() {
|
||||||
case "$OS_TYPE" in
|
case "$OS_TYPE" in
|
||||||
"ubuntu" | "debian" | "raspbian")
|
"ubuntu" | "debian" | "raspbian")
|
||||||
apt-get update
|
if [ "$APT_UPDATED" = false ]; then
|
||||||
|
apt-get update
|
||||||
|
APT_UPDATED=true
|
||||||
|
fi
|
||||||
apt-get install -y ca-certificates curl
|
apt-get install -y ca-certificates curl
|
||||||
install -m 0755 -d /etc/apt/keyrings
|
install -m 0755 -d /etc/apt/keyrings
|
||||||
curl -fsSL https://download.docker.com/linux/$OS_TYPE/gpg -o /etc/apt/keyrings/docker.asc
|
curl -fsSL https://download.docker.com/linux/$OS_TYPE/gpg -o /etc/apt/keyrings/docker.asc
|
||||||
|
|
@ -491,7 +542,8 @@ install_docker_manually() {
|
||||||
echo "Docker installed successfully."
|
echo "Docker installed successfully."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
echo -e "3. Check Docker Installation. "
|
log_section "Step 3/9: Checking Docker installation"
|
||||||
|
echo "3/9 Checking Docker installation..."
|
||||||
if ! [ -x "$(command -v docker)" ]; then
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
echo " - Docker is not installed. Installing Docker. It may take a while."
|
echo " - Docker is not installed. Installing Docker. It may take a while."
|
||||||
getAJoke
|
getAJoke
|
||||||
|
|
@ -575,7 +627,8 @@ else
|
||||||
echo " - Docker is installed."
|
echo " - Docker is installed."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "4. Check Docker Configuration. "
|
log_section "Step 4/9: Checking Docker configuration"
|
||||||
|
echo "4/9 Checking Docker configuration..."
|
||||||
|
|
||||||
echo " - Network pool configuration: ${DOCKER_ADDRESS_POOL_BASE}/${DOCKER_ADDRESS_POOL_SIZE}"
|
echo " - Network pool configuration: ${DOCKER_ADDRESS_POOL_BASE}/${DOCKER_ADDRESS_POOL_SIZE}"
|
||||||
echo " - To override existing configuration: DOCKER_POOL_FORCE_OVERRIDE=true"
|
echo " - To override existing configuration: DOCKER_POOL_FORCE_OVERRIDE=true"
|
||||||
|
|
@ -704,13 +757,38 @@ else
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "5. Download required files from CDN. "
|
log_section "Step 5/9: Downloading required files from CDN"
|
||||||
curl -fsSL -L $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
|
echo "5/9 Downloading required files from CDN..."
|
||||||
curl -fsSL -L $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
|
log "Downloading configuration files in parallel..."
|
||||||
curl -fsSL -L $CDN/.env.production -o /data/coolify/source/.env.production
|
|
||||||
curl -fsSL -L $CDN/upgrade.sh -o /data/coolify/source/upgrade.sh
|
|
||||||
|
|
||||||
echo -e "6. Setting up environment variable file"
|
# Download files in parallel for faster installation
|
||||||
|
curl -fsSL -L $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml &
|
||||||
|
PID1=$!
|
||||||
|
curl -fsSL -L $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml &
|
||||||
|
PID2=$!
|
||||||
|
curl -fsSL -L $CDN/.env.production -o /data/coolify/source/.env.production &
|
||||||
|
PID3=$!
|
||||||
|
curl -fsSL -L $CDN/upgrade.sh -o /data/coolify/source/upgrade.sh &
|
||||||
|
PID4=$!
|
||||||
|
|
||||||
|
# Wait for all downloads to complete and check for errors
|
||||||
|
DOWNLOAD_FAILED=false
|
||||||
|
for PID in $PID1 $PID2 $PID3 $PID4; do
|
||||||
|
if ! wait $PID; then
|
||||||
|
DOWNLOAD_FAILED=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$DOWNLOAD_FAILED" = true ]; then
|
||||||
|
echo " - ERROR: One or more downloads failed. Please check your network connection."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "All configuration files downloaded successfully"
|
||||||
|
echo " Done."
|
||||||
|
|
||||||
|
log_section "Step 6/9: Setting up environment variable file"
|
||||||
|
echo "6/9 Setting up environment variable file..."
|
||||||
|
|
||||||
if [ -f "$ENV_FILE" ]; then
|
if [ -f "$ENV_FILE" ]; then
|
||||||
# If .env exists, create backup
|
# If .env exists, create backup
|
||||||
|
|
@ -725,8 +803,11 @@ else
|
||||||
echo " - No .env file found, copying .env.production to .env"
|
echo " - No .env file found, copying .env.production to .env"
|
||||||
cp "/data/coolify/source/.env.production" "$ENV_FILE"
|
cp "/data/coolify/source/.env.production" "$ENV_FILE"
|
||||||
fi
|
fi
|
||||||
|
log "Environment file setup completed"
|
||||||
|
echo " Done."
|
||||||
|
|
||||||
echo -e "7. Checking and updating environment variables if necessary..."
|
log_section "Step 7/9: Checking and updating environment variables"
|
||||||
|
echo "7/9 Checking and updating environment variables..."
|
||||||
|
|
||||||
update_env_var() {
|
update_env_var() {
|
||||||
local key="$1"
|
local key="$1"
|
||||||
|
|
@ -786,8 +867,11 @@ else
|
||||||
update_env_var "DOCKER_ADDRESS_POOL_SIZE" "$DOCKER_ADDRESS_POOL_SIZE"
|
update_env_var "DOCKER_ADDRESS_POOL_SIZE" "$DOCKER_ADDRESS_POOL_SIZE"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
log "Environment variables check completed"
|
||||||
|
echo " Done."
|
||||||
|
|
||||||
echo -e "8. Checking for SSH key for localhost access."
|
log_section "Step 8/9: Checking SSH key for localhost access"
|
||||||
|
echo "8/9 Checking SSH key for localhost access..."
|
||||||
if [ ! -f ~/.ssh/authorized_keys ]; then
|
if [ ! -f ~/.ssh/authorized_keys ]; then
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
chmod 700 ~/.ssh
|
chmod 700 ~/.ssh
|
||||||
|
|
@ -812,8 +896,11 @@ fi
|
||||||
|
|
||||||
chown -R 9999:root /data/coolify
|
chown -R 9999:root /data/coolify
|
||||||
chmod -R 700 /data/coolify
|
chmod -R 700 /data/coolify
|
||||||
|
log "SSH key check completed"
|
||||||
|
echo " Done."
|
||||||
|
|
||||||
echo -e "9. Installing Coolify ($LATEST_VERSION)"
|
log_section "Step 9/9: Installing Coolify"
|
||||||
|
echo "9/9 Installing Coolify ($LATEST_VERSION)..."
|
||||||
echo -e " - It could take a while based on your server's performance, network speed, stars, etc."
|
echo -e " - It could take a while based on your server's performance, network speed, stars, etc."
|
||||||
echo -e " - Please wait."
|
echo -e " - Please wait."
|
||||||
getAJoke
|
getAJoke
|
||||||
|
|
@ -824,11 +911,85 @@ else
|
||||||
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" "${REGISTRY_URL:-ghcr.io}" "true"
|
bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" "${REGISTRY_URL:-ghcr.io}" "true"
|
||||||
fi
|
fi
|
||||||
echo " - Coolify installed successfully."
|
echo " - Coolify installed successfully."
|
||||||
|
echo " - Waiting for Coolify to be ready..."
|
||||||
|
|
||||||
echo " - Waiting 20 seconds for Coolify database migrations to complete."
|
# Wait for upgrade.sh background process to complete
|
||||||
getAJoke
|
# upgrade.sh writes status to /data/coolify/source/.upgrade-status
|
||||||
|
# Status file format: step|message|timestamp
|
||||||
|
# Step 6 = "Upgrade complete", file deleted 10 seconds after
|
||||||
|
UPGRADE_STATUS_FILE="/data/coolify/source/.upgrade-status"
|
||||||
|
MAX_WAIT=180
|
||||||
|
WAITED=0
|
||||||
|
SEEN_STATUS_FILE=false
|
||||||
|
|
||||||
sleep 20
|
while [ $WAITED -lt $MAX_WAIT ]; do
|
||||||
|
if [ -f "$UPGRADE_STATUS_FILE" ]; then
|
||||||
|
SEEN_STATUS_FILE=true
|
||||||
|
STATUS=$(cat "$UPGRADE_STATUS_FILE" 2>/dev/null | cut -d'|' -f1)
|
||||||
|
MESSAGE=$(cat "$UPGRADE_STATUS_FILE" 2>/dev/null | cut -d'|' -f2)
|
||||||
|
if [ "$STATUS" = "6" ]; then
|
||||||
|
log "Upgrade completed: $MESSAGE"
|
||||||
|
echo " - Upgrade complete!"
|
||||||
|
break
|
||||||
|
elif [ "$STATUS" = "error" ]; then
|
||||||
|
echo " - ERROR: Upgrade failed: $MESSAGE"
|
||||||
|
echo " - Please check the upgrade logs: /data/coolify/source/upgrade-*.log"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
if [ $((WAITED % 10)) -eq 0 ]; then
|
||||||
|
echo " - Upgrade in progress: $MESSAGE (${WAITED}s)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Status file doesn't exist
|
||||||
|
if [ "$SEEN_STATUS_FILE" = true ]; then
|
||||||
|
# We saw the file before, now it's gone = upgrade completed and cleaned up
|
||||||
|
log "Upgrade status file cleaned up - upgrade complete"
|
||||||
|
echo " - Upgrade complete!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
# Haven't seen status file yet - either very early or upgrade.sh hasn't started
|
||||||
|
if [ $((WAITED % 10)) -eq 0 ] && [ $WAITED -gt 0 ]; then
|
||||||
|
echo " - Waiting for upgrade process to start... (${WAITED}s)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
WAITED=$((WAITED + 2))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $WAITED -ge $MAX_WAIT ]; then
|
||||||
|
if [ "$SEEN_STATUS_FILE" = false ]; then
|
||||||
|
# Never saw status file - fallback to old behavior (wait 20s + health check)
|
||||||
|
log "Status file not found, using fallback wait"
|
||||||
|
echo " - Status file not found, waiting 20 seconds..."
|
||||||
|
sleep 20
|
||||||
|
else
|
||||||
|
echo " - ERROR: Upgrade timed out after ${MAX_WAIT}s"
|
||||||
|
echo " - Please check the upgrade logs: /data/coolify/source/upgrade-*.log"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Final health verification - wait for container to be healthy
|
||||||
|
echo " - Verifying Coolify is healthy..."
|
||||||
|
HEALTH_WAIT=60
|
||||||
|
HEALTH_WAITED=0
|
||||||
|
while [ $HEALTH_WAITED -lt $HEALTH_WAIT ]; do
|
||||||
|
HEALTH=$(docker inspect --format='{{.State.Health.Status}}' coolify 2>/dev/null || echo "unknown")
|
||||||
|
if [ "$HEALTH" = "healthy" ]; then
|
||||||
|
log "Coolify container is healthy"
|
||||||
|
echo " - Coolify is ready!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
HEALTH_WAITED=$((HEALTH_WAITED + 2))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$HEALTH" != "healthy" ]; then
|
||||||
|
echo " - ERROR: Coolify container is not healthy after ${HEALTH_WAIT}s. Status: $HEALTH"
|
||||||
|
echo " - Please check: docker logs coolify"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
echo -e "\033[0;35m
|
echo -e "\033[0;35m
|
||||||
____ _ _ _ _ _
|
____ _ _ _ _ _
|
||||||
/ ___|___ _ __ __ _ _ __ __ _| |_ _ _| | __ _| |_(_) ___ _ __ ___| |
|
/ ___|___ _ __ __ _ _ __ __ _| |_ _ _| | __ _| |_(_) ___ _ __ ___| |
|
||||||
|
|
@ -838,8 +999,18 @@ echo -e "\033[0;35m
|
||||||
|___/
|
|___/
|
||||||
\033[0m"
|
\033[0m"
|
||||||
|
|
||||||
IPV4_PUBLIC_IP=$(curl -4s https://ifconfig.io || true)
|
# Fetch public IPs in parallel for faster completion
|
||||||
IPV6_PUBLIC_IP=$(curl -6s https://ifconfig.io || true)
|
IPV4_TMP=$(mktemp)
|
||||||
|
IPV6_TMP=$(mktemp)
|
||||||
|
curl -4s --max-time 5 https://ifconfig.io > "$IPV4_TMP" 2>/dev/null &
|
||||||
|
IPV4_PID=$!
|
||||||
|
curl -6s --max-time 5 https://ifconfig.io > "$IPV6_TMP" 2>/dev/null &
|
||||||
|
IPV6_PID=$!
|
||||||
|
wait $IPV4_PID 2>/dev/null || true
|
||||||
|
wait $IPV6_PID 2>/dev/null || true
|
||||||
|
IPV4_PUBLIC_IP=$(cat "$IPV4_TMP" 2>/dev/null || true)
|
||||||
|
IPV6_PUBLIC_IP=$(cat "$IPV6_TMP" 2>/dev/null || true)
|
||||||
|
rm -f "$IPV4_TMP" "$IPV6_TMP"
|
||||||
|
|
||||||
echo -e "\nYour instance is ready to use!\n"
|
echo -e "\nYour instance is ready to use!\n"
|
||||||
if [ -n "$IPV4_PUBLIC_IP" ]; then
|
if [ -n "$IPV4_PUBLIC_IP" ]; then
|
||||||
|
|
@ -864,3 +1035,8 @@ if [ -n "$PRIVATE_IPS" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\nWARNING: It is highly recommended to backup your Environment variables file (/data/coolify/source/.env) to a safe location, outside of this server (e.g. into a Password Manager).\n"
|
echo -e "\nWARNING: It is highly recommended to backup your Environment variables file (/data/coolify/source/.env) to a safe location, outside of this server (e.g. into a Password Manager).\n"
|
||||||
|
|
||||||
|
log_section "Installation Complete"
|
||||||
|
log "Coolify installation completed successfully"
|
||||||
|
log "Version: ${LATEST_VERSION}"
|
||||||
|
log "Log file: ${INSTALLATION_LOG_WITH_DATE}"
|
||||||
|
|
|
||||||
|
|
@ -7,27 +7,101 @@ LATEST_HELPER_VERSION=${2:-latest}
|
||||||
REGISTRY_URL=${3:-ghcr.io}
|
REGISTRY_URL=${3:-ghcr.io}
|
||||||
SKIP_BACKUP=${4:-false}
|
SKIP_BACKUP=${4:-false}
|
||||||
ENV_FILE="/data/coolify/source/.env"
|
ENV_FILE="/data/coolify/source/.env"
|
||||||
|
STATUS_FILE="/data/coolify/source/.upgrade-status"
|
||||||
|
|
||||||
DATE=$(date +%Y-%m-%d-%H-%M-%S)
|
DATE=$(date +%Y-%m-%d-%H-%M-%S)
|
||||||
LOGFILE="/data/coolify/source/upgrade-${DATE}.log"
|
LOGFILE="/data/coolify/source/upgrade-${DATE}.log"
|
||||||
|
|
||||||
curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
|
# Helper function to log with timestamp
|
||||||
curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
|
log() {
|
||||||
curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >>"$LOGFILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to log section headers
|
||||||
|
log_section() {
|
||||||
|
echo "" >>"$LOGFILE"
|
||||||
|
echo "============================================================" >>"$LOGFILE"
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >>"$LOGFILE"
|
||||||
|
echo "============================================================" >>"$LOGFILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to write upgrade status for API polling
|
||||||
|
write_status() {
|
||||||
|
local step="$1"
|
||||||
|
local message="$2"
|
||||||
|
echo "${step}|${message}|$(date -Iseconds)" > "$STATUS_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Coolify Upgrade - ${DATE}"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Initialize log file with header
|
||||||
|
echo "============================================================" >>"$LOGFILE"
|
||||||
|
echo "Coolify Upgrade Log" >>"$LOGFILE"
|
||||||
|
echo "Started: $(date '+%Y-%m-%d %H:%M:%S')" >>"$LOGFILE"
|
||||||
|
echo "Target Version: ${LATEST_IMAGE}" >>"$LOGFILE"
|
||||||
|
echo "Helper Version: ${LATEST_HELPER_VERSION}" >>"$LOGFILE"
|
||||||
|
echo "Registry URL: ${REGISTRY_URL}" >>"$LOGFILE"
|
||||||
|
echo "============================================================" >>"$LOGFILE"
|
||||||
|
|
||||||
|
log_section "Step 1/6: Downloading configuration files"
|
||||||
|
write_status "1" "Downloading configuration files"
|
||||||
|
echo "1/6 Downloading latest configuration files..."
|
||||||
|
log "Downloading docker-compose.yml from ${CDN}/docker-compose.yml"
|
||||||
|
curl -fsSL -L $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
|
||||||
|
log "Downloading docker-compose.prod.yml from ${CDN}/docker-compose.prod.yml"
|
||||||
|
curl -fsSL -L $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml
|
||||||
|
log "Downloading .env.production from ${CDN}/.env.production"
|
||||||
|
curl -fsSL -L $CDN/.env.production -o /data/coolify/source/.env.production
|
||||||
|
log "Configuration files downloaded successfully"
|
||||||
|
echo " Done."
|
||||||
|
|
||||||
|
# Extract all images from docker-compose configuration
|
||||||
|
log "Extracting all images from docker-compose configuration..."
|
||||||
|
COMPOSE_FILES="-f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml"
|
||||||
|
|
||||||
|
# Check if custom compose file exists
|
||||||
|
if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
|
||||||
|
COMPOSE_FILES="$COMPOSE_FILES -f /data/coolify/source/docker-compose.custom.yml"
|
||||||
|
log "Including custom docker-compose.yml in image extraction"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get all unique images from docker compose config
|
||||||
|
# LATEST_IMAGE env var is needed for image substitution in compose files
|
||||||
|
IMAGES=$(LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file "$ENV_FILE" $COMPOSE_FILES config --images 2>/dev/null | sort -u)
|
||||||
|
|
||||||
|
if [ -z "$IMAGES" ]; then
|
||||||
|
log "ERROR: Failed to extract images from docker-compose files"
|
||||||
|
write_status "error" "Failed to parse docker-compose configuration"
|
||||||
|
echo " ERROR: Failed to parse docker-compose configuration. Aborting upgrade."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Images to pull:"
|
||||||
|
echo "$IMAGES" | while read img; do log " - $img"; done
|
||||||
|
|
||||||
# Backup existing .env file before making any changes
|
# Backup existing .env file before making any changes
|
||||||
if [ "$SKIP_BACKUP" != "true" ]; then
|
if [ "$SKIP_BACKUP" != "true" ]; then
|
||||||
if [ -f "$ENV_FILE" ]; then
|
if [ -f "$ENV_FILE" ]; then
|
||||||
echo "Creating backup of existing .env file to .env-$DATE" >>"$LOGFILE"
|
echo " Creating backup of .env file..."
|
||||||
|
log "Creating backup of .env file to .env-$DATE"
|
||||||
cp "$ENV_FILE" "$ENV_FILE-$DATE"
|
cp "$ENV_FILE" "$ENV_FILE-$DATE"
|
||||||
|
log "Backup created: ${ENV_FILE}-${DATE}"
|
||||||
else
|
else
|
||||||
echo "No existing .env file found to backup" >>"$LOGFILE"
|
log "WARNING: No existing .env file found to backup"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Merging .env.production values into .env" >>"$LOGFILE"
|
log_section "Step 2/6: Updating environment configuration"
|
||||||
|
write_status "2" "Updating environment configuration"
|
||||||
|
echo ""
|
||||||
|
echo "2/6 Updating environment configuration..."
|
||||||
|
log "Merging .env.production values into .env"
|
||||||
awk -F '=' '!seen[$1]++' "$ENV_FILE" /data/coolify/source/.env.production > "$ENV_FILE.tmp" && mv "$ENV_FILE.tmp" "$ENV_FILE"
|
awk -F '=' '!seen[$1]++' "$ENV_FILE" /data/coolify/source/.env.production > "$ENV_FILE.tmp" && mv "$ENV_FILE.tmp" "$ENV_FILE"
|
||||||
echo ".env file merged successfully" >>"$LOGFILE"
|
log "Environment file merged successfully"
|
||||||
|
|
||||||
update_env_var() {
|
update_env_var() {
|
||||||
local key="$1"
|
local key="$1"
|
||||||
|
|
@ -36,73 +110,173 @@ update_env_var() {
|
||||||
# If variable "key=" exists but has no value, update the value of the existing line
|
# If variable "key=" exists but has no value, update the value of the existing line
|
||||||
if grep -q "^${key}=$" "$ENV_FILE"; then
|
if grep -q "^${key}=$" "$ENV_FILE"; then
|
||||||
sed -i "s|^${key}=$|${key}=${value}|" "$ENV_FILE"
|
sed -i "s|^${key}=$|${key}=${value}|" "$ENV_FILE"
|
||||||
echo " - Updated value of ${key} as the current value was empty" >>"$LOGFILE"
|
log "Updated ${key} (was empty)"
|
||||||
# If variable "key=" doesn't exist, append it to the file with value
|
# If variable "key=" doesn't exist, append it to the file with value
|
||||||
elif ! grep -q "^${key}=" "$ENV_FILE"; then
|
elif ! grep -q "^${key}=" "$ENV_FILE"; then
|
||||||
printf '%s=%s\n' "$key" "$value" >>"$ENV_FILE"
|
printf '%s=%s\n' "$key" "$value" >>"$ENV_FILE"
|
||||||
echo " - Added ${key} with default value as the variable was missing" >>"$LOGFILE"
|
log "Added ${key} (was missing)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "Checking and updating environment variables if necessary..." >>"$LOGFILE"
|
log "Checking environment variables..."
|
||||||
update_env_var "PUSHER_APP_ID" "$(openssl rand -hex 32)"
|
update_env_var "PUSHER_APP_ID" "$(openssl rand -hex 32)"
|
||||||
update_env_var "PUSHER_APP_KEY" "$(openssl rand -hex 32)"
|
update_env_var "PUSHER_APP_KEY" "$(openssl rand -hex 32)"
|
||||||
update_env_var "PUSHER_APP_SECRET" "$(openssl rand -hex 32)"
|
update_env_var "PUSHER_APP_SECRET" "$(openssl rand -hex 32)"
|
||||||
|
log "Environment variables check complete"
|
||||||
|
echo " Done."
|
||||||
|
|
||||||
# Make sure coolify network exists
|
# Make sure coolify network exists
|
||||||
# It is created when starting Coolify with docker compose
|
# It is created when starting Coolify with docker compose
|
||||||
|
log "Checking Docker network 'coolify'..."
|
||||||
if ! docker network inspect coolify >/dev/null 2>&1; then
|
if ! docker network inspect coolify >/dev/null 2>&1; then
|
||||||
|
log "Network 'coolify' does not exist, creating..."
|
||||||
if ! docker network create --attachable --ipv6 coolify 2>/dev/null; then
|
if ! docker network create --attachable --ipv6 coolify 2>/dev/null; then
|
||||||
echo "Failed to create coolify network with ipv6. Trying without ipv6..."
|
log "Failed to create network with IPv6, trying without IPv6..."
|
||||||
docker network create --attachable coolify 2>/dev/null
|
docker network create --attachable coolify 2>/dev/null
|
||||||
|
log "Network 'coolify' created without IPv6"
|
||||||
|
else
|
||||||
|
log "Network 'coolify' created with IPv6 support"
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
log "Network 'coolify' already exists"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if Docker config file exists
|
# Check if Docker config file exists
|
||||||
DOCKER_CONFIG_MOUNT=""
|
DOCKER_CONFIG_MOUNT=""
|
||||||
if [ -f /root/.docker/config.json ]; then
|
if [ -f /root/.docker/config.json ]; then
|
||||||
DOCKER_CONFIG_MOUNT="-v /root/.docker/config.json:/root/.docker/config.json"
|
DOCKER_CONFIG_MOUNT="-v /root/.docker/config.json:/root/.docker/config.json"
|
||||||
|
log "Docker config mount enabled: /root/.docker/config.json"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Pull all required images before stopping containers
|
log_section "Step 3/6: Pulling Docker images"
|
||||||
# This ensures we don't take down the system if image pull fails (rate limits, network issues, etc.)
|
write_status "3" "Pulling Docker images"
|
||||||
echo "Pulling required Docker images..." >>"$LOGFILE"
|
echo ""
|
||||||
docker pull "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify:${LATEST_IMAGE}" >>"$LOGFILE" 2>&1 || { echo "Failed to pull Coolify image. Aborting upgrade." >>"$LOGFILE"; exit 1; }
|
echo "3/6 Pulling Docker images..."
|
||||||
docker pull "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION}" >>"$LOGFILE" 2>&1 || { echo "Failed to pull Coolify helper image. Aborting upgrade." >>"$LOGFILE"; exit 1; }
|
echo " This may take a few minutes depending on your connection."
|
||||||
docker pull postgres:15-alpine >>"$LOGFILE" 2>&1 || { echo "Failed to pull PostgreSQL image. Aborting upgrade." >>"$LOGFILE"; exit 1; }
|
|
||||||
docker pull redis:7-alpine >>"$LOGFILE" 2>&1 || { echo "Failed to pull Redis image. Aborting upgrade." >>"$LOGFILE"; exit 1; }
|
|
||||||
# Pull realtime image - version is hardcoded in docker-compose.prod.yml, extract it or use a known version
|
|
||||||
docker pull "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.10" >>"$LOGFILE" 2>&1 || { echo "Failed to pull Coolify realtime image. Aborting upgrade." >>"$LOGFILE"; exit 1; }
|
|
||||||
echo "All images pulled successfully." >>"$LOGFILE"
|
|
||||||
|
|
||||||
# Stop and remove existing Coolify containers to prevent conflicts
|
# Also pull the helper image (not in compose files but needed for upgrade)
|
||||||
# This handles both old installations (project "source") and new ones (project "coolify")
|
HELPER_IMAGE="${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION}"
|
||||||
# Use nohup to ensure the script continues even if SSH connection is lost
|
echo " - Pulling $HELPER_IMAGE..."
|
||||||
echo "Starting container restart sequence (detached)..." >>"$LOGFILE"
|
log "Pulling image: $HELPER_IMAGE"
|
||||||
|
if docker pull "$HELPER_IMAGE" >>"$LOGFILE" 2>&1; then
|
||||||
|
log "Successfully pulled $HELPER_IMAGE"
|
||||||
|
else
|
||||||
|
log "ERROR: Failed to pull $HELPER_IMAGE"
|
||||||
|
write_status "error" "Failed to pull $HELPER_IMAGE"
|
||||||
|
echo " ERROR: Failed to pull $HELPER_IMAGE. Aborting upgrade."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pull all images from compose config
|
||||||
|
# Using a for loop to avoid subshell issues with exit
|
||||||
|
for IMAGE in $IMAGES; do
|
||||||
|
if [ -n "$IMAGE" ]; then
|
||||||
|
echo " - Pulling $IMAGE..."
|
||||||
|
log "Pulling image: $IMAGE"
|
||||||
|
if docker pull "$IMAGE" >>"$LOGFILE" 2>&1; then
|
||||||
|
log "Successfully pulled $IMAGE"
|
||||||
|
else
|
||||||
|
log "ERROR: Failed to pull $IMAGE"
|
||||||
|
write_status "error" "Failed to pull $IMAGE"
|
||||||
|
echo " ERROR: Failed to pull $IMAGE. Aborting upgrade."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
log "All images pulled successfully"
|
||||||
|
echo " All images pulled successfully."
|
||||||
|
|
||||||
|
log_section "Step 4/6: Stopping and restarting containers"
|
||||||
|
write_status "4" "Stopping containers"
|
||||||
|
echo ""
|
||||||
|
echo "4/6 Stopping containers and starting new ones..."
|
||||||
|
echo " This step will restart all Coolify containers."
|
||||||
|
echo " Check the log file for details: ${LOGFILE}"
|
||||||
|
|
||||||
|
# From this point forward, we need to ensure the script continues even if
|
||||||
|
# the SSH connection is lost (which happens when coolify container stops)
|
||||||
|
# We use a subshell with nohup to ensure completion
|
||||||
|
log "Starting container restart sequence (detached)..."
|
||||||
|
|
||||||
nohup bash -c "
|
nohup bash -c "
|
||||||
LOGFILE='$LOGFILE'
|
LOGFILE='$LOGFILE'
|
||||||
|
STATUS_FILE='$STATUS_FILE'
|
||||||
DOCKER_CONFIG_MOUNT='$DOCKER_CONFIG_MOUNT'
|
DOCKER_CONFIG_MOUNT='$DOCKER_CONFIG_MOUNT'
|
||||||
REGISTRY_URL='$REGISTRY_URL'
|
REGISTRY_URL='$REGISTRY_URL'
|
||||||
LATEST_HELPER_VERSION='$LATEST_HELPER_VERSION'
|
LATEST_HELPER_VERSION='$LATEST_HELPER_VERSION'
|
||||||
LATEST_IMAGE='$LATEST_IMAGE'
|
LATEST_IMAGE='$LATEST_IMAGE'
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo \"[\$(date '+%Y-%m-%d %H:%M:%S')] \$1\" >>\"\$LOGFILE\"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_status() {
|
||||||
|
echo \"\$1|\$2|\$(date -Iseconds)\" > \"\$STATUS_FILE\"
|
||||||
|
}
|
||||||
|
|
||||||
# Stop and remove containers
|
# Stop and remove containers
|
||||||
echo 'Stopping existing Coolify containers...' >>\"\$LOGFILE\"
|
|
||||||
for container in coolify coolify-db coolify-redis coolify-realtime; do
|
for container in coolify coolify-db coolify-redis coolify-realtime; do
|
||||||
if docker ps -a --format '{{.Names}}' | grep -q \"^\${container}\$\"; then
|
if docker ps -a --format '{{.Names}}' | grep -q \"^\${container}\$\"; then
|
||||||
|
log \"Stopping container: \${container}\"
|
||||||
docker stop \"\$container\" >>\"\$LOGFILE\" 2>&1 || true
|
docker stop \"\$container\" >>\"\$LOGFILE\" 2>&1 || true
|
||||||
|
log \"Removing container: \${container}\"
|
||||||
docker rm \"\$container\" >>\"\$LOGFILE\" 2>&1 || true
|
docker rm \"\$container\" >>\"\$LOGFILE\" 2>&1 || true
|
||||||
echo \" - Removed container: \$container\" >>\"\$LOGFILE\"
|
log \"Container \${container} stopped and removed\"
|
||||||
|
else
|
||||||
|
log \"Container \${container} not found (skipping)\"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
log \"Container cleanup complete\"
|
||||||
|
|
||||||
# Start new containers
|
# Start new containers
|
||||||
|
echo '' >>\"\$LOGFILE\"
|
||||||
|
echo '============================================================' >>\"\$LOGFILE\"
|
||||||
|
log 'Step 5/6: Starting new containers'
|
||||||
|
echo '============================================================' >>\"\$LOGFILE\"
|
||||||
|
write_status '5' 'Starting new containers'
|
||||||
|
|
||||||
if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
|
if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
|
||||||
echo 'docker-compose.custom.yml detected.' >>\"\$LOGFILE\"
|
log 'Using custom docker-compose.yml'
|
||||||
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock \${DOCKER_CONFIG_MOUNT} --rm \${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:\${LATEST_HELPER_VERSION} bash -c \"LATEST_IMAGE=\${LATEST_IMAGE} docker compose --project-name coolify --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --wait --wait-timeout 60\" >>\"\$LOGFILE\" 2>&1
|
log 'Running docker compose up with custom configuration...'
|
||||||
|
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock \${DOCKER_CONFIG_MOUNT} --rm \${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:\${LATEST_HELPER_VERSION} bash -c \"LATEST_IMAGE=\${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --wait --wait-timeout 60\" >>\"\$LOGFILE\" 2>&1
|
||||||
else
|
else
|
||||||
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock \${DOCKER_CONFIG_MOUNT} --rm \${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:\${LATEST_HELPER_VERSION} bash -c \"LATEST_IMAGE=\${LATEST_IMAGE} docker compose --project-name coolify --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --wait --wait-timeout 60\" >>\"\$LOGFILE\" 2>&1
|
log 'Using standard docker-compose configuration'
|
||||||
|
log 'Running docker compose up...'
|
||||||
|
docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock \${DOCKER_CONFIG_MOUNT} --rm \${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:\${LATEST_HELPER_VERSION} bash -c \"LATEST_IMAGE=\${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --wait --wait-timeout 60\" >>\"\$LOGFILE\" 2>&1
|
||||||
fi
|
fi
|
||||||
echo 'Upgrade completed.' >>\"\$LOGFILE\"
|
log 'Docker compose up completed'
|
||||||
|
|
||||||
|
# Final log entry
|
||||||
|
echo '' >>\"\$LOGFILE\"
|
||||||
|
echo '============================================================' >>\"\$LOGFILE\"
|
||||||
|
log 'Step 6/6: Upgrade complete'
|
||||||
|
echo '============================================================' >>\"\$LOGFILE\"
|
||||||
|
write_status '6' 'Upgrade complete'
|
||||||
|
log 'Coolify upgrade completed successfully'
|
||||||
|
log \"Version: \${LATEST_IMAGE}\"
|
||||||
|
echo '' >>\"\$LOGFILE\"
|
||||||
|
echo '============================================================' >>\"\$LOGFILE\"
|
||||||
|
echo \"Upgrade completed: \$(date '+%Y-%m-%d %H:%M:%S')\" >>\"\$LOGFILE\"
|
||||||
|
echo '============================================================' >>\"\$LOGFILE\"
|
||||||
|
|
||||||
|
# Clean up status file after a short delay to allow frontend to read completion
|
||||||
|
sleep 10
|
||||||
|
rm -f \"\$STATUS_FILE\"
|
||||||
|
log 'Status file cleaned up'
|
||||||
" >>"$LOGFILE" 2>&1 &
|
" >>"$LOGFILE" 2>&1 &
|
||||||
|
|
||||||
|
# Give the background process a moment to start
|
||||||
|
sleep 2
|
||||||
|
log "Container restart sequence started in background (PID: $!)"
|
||||||
|
echo ""
|
||||||
|
echo "5/6 Containers are being restarted in the background..."
|
||||||
|
echo "6/6 Upgrade process initiated!"
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Coolify upgrade to ${LATEST_IMAGE} in progress"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo " The upgrade will continue in the background."
|
||||||
|
echo " Coolify will be available again shortly."
|
||||||
|
echo " Log file: ${LOGFILE}"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.455"
|
"version": "4.0.0-beta.456"
|
||||||
},
|
},
|
||||||
"nightly": {
|
"nightly": {
|
||||||
"version": "4.0.0-beta.456"
|
"version": "4.0.0-beta.457"
|
||||||
},
|
},
|
||||||
"helper": {
|
"helper": {
|
||||||
"version": "1.0.12"
|
"version": "1.0.12"
|
||||||
|
|
@ -17,13 +17,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"traefik": {
|
"traefik": {
|
||||||
"v3.6": "3.6.1",
|
"v3.6": "3.6.5",
|
||||||
"v3.5": "3.5.6",
|
"v3.5": "3.5.6",
|
||||||
"v3.4": "3.4.5",
|
"v3.4": "3.4.5",
|
||||||
"v3.3": "3.3.7",
|
"v3.3": "3.3.7",
|
||||||
"v3.2": "3.2.5",
|
"v3.2": "3.2.5",
|
||||||
"v3.1": "3.1.7",
|
"v3.1": "3.1.7",
|
||||||
"v3.0": "3.0.4",
|
"v3.0": "3.0.4",
|
||||||
"v2.11": "2.11.31"
|
"v2.11": "2.11.32"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
public/svgs/appflowy.svg
Normal file
12
public/svgs/appflowy.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<svg width="352" height="351" viewBox="0 0 352 351" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M351.694 211.848C341.593 266.47 297.512 314.204 246.499 341.01C240.117 344.355 237.564 346.269 230.367 346.732H333.912C336.243 346.805 338.566 346.41 340.741 345.57C342.917 344.73 344.902 343.462 346.579 341.84C348.255 340.219 349.589 338.278 350.502 336.132C351.415 333.985 351.888 331.678 351.892 329.346V211.848H351.694Z" fill="#F7931E" />
|
||||||
|
<path d="M238.823 343.613C236.728 343.767 234.625 343.767 232.529 343.613H238.823Z" fill="#FFCE00" />
|
||||||
|
<path d="M135.699 106.437C134.136 107.713 132.574 108.902 130.967 110.024C104.756 128.466 24.7373 188.283 6.62514 162.534C-11.1349 137.269 7.57146 65.591 53.6331 31.1053C54.5134 30.401 55.3937 29.7848 56.296 29.1466C106.319 -5.99935 143.732 -1.1357 161.822 24.6351C178.812 48.7993 159.797 86.6741 135.699 106.437Z" fill="#8427E0" />
|
||||||
|
<path d="M331.277 161.796C306.519 179.182 267.455 158.891 248.111 134.023C247.319 133.011 246.57 131.976 245.91 130.964C227.49 104.731 167.674 24.7118 193.422 6.62164C219.171 -11.4685 293.094 8.29421 326.81 56.2925C327.558 57.3489 328.284 58.4933 329.01 59.4396C361.691 108.01 356.542 144.08 331.277 161.796Z" fill="#00B5FF" />
|
||||||
|
<path d="M298.158 319.655C297.278 320.337 296.42 320.975 295.561 321.569C245.538 356.781 208.126 351.83 190.036 326.103C173.068 301.895 192.038 264.086 216.136 244.301C217.677 243.003 219.284 241.792 220.89 240.67C247.101 222.25 327.12 162.455 345.21 188.182C362.97 213.447 344.264 285.169 298.158 319.655Z" fill="#FFBD00" />
|
||||||
|
<path d="M158.493 344.09C132.745 362.158 58.8216 342.418 25.1062 294.419C24.402 293.451 23.7638 292.483 23.1036 291.514C-9.79756 242.922 -4.6038 206.653 20.6167 188.915C45.3531 171.529 84.4383 191.798 103.761 216.689C104.553 217.701 105.301 218.714 105.962 219.748C124.47 245.981 184.264 326 158.493 344.09Z" fill="#E3006D" />
|
||||||
|
<path d="M135.695 106.437C99.9326 122.15 24.0948 154.853 12.695 128.642C3.14372 106.635 20.8377 61.5636 53.6509 31.1053C54.5312 30.401 55.4115 29.7848 56.3138 29.1466C106.315 -5.99935 143.727 -1.1357 161.818 24.6351C178.807 48.7993 159.793 86.6742 135.695 106.437Z" fill="#9327FF" />
|
||||||
|
<path d="M331.29 161.778C306.531 179.164 267.468 158.873 248.124 134.004C232.036 97.3179 201.402 24.7152 227.04 13.6014C250.148 3.58802 298.543 23.5048 328.935 59.421C361.704 107.992 356.554 144.062 331.29 161.778Z" fill="#00C8FF" />
|
||||||
|
<path d="M298.158 319.642C297.278 320.324 296.42 320.963 295.561 321.557C245.538 356.769 208.126 351.817 190.036 326.09C173.068 301.882 192.038 264.073 216.136 244.289C251.877 228.553 327.758 195.872 339.158 222.061C348.709 244.09 331.015 289.162 298.158 319.642Z" fill="#FFCE00" />
|
||||||
|
<path d="M124.838 337.114C101.818 347.105 53.556 327.276 23.1417 291.514C-9.80345 242.922 -4.60969 206.653 20.6108 188.915C45.3472 171.53 84.4324 191.798 103.755 216.689C119.864 253.397 150.477 325.978 124.838 337.114Z" fill="#FB006D" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -292,3 +292,31 @@ @utility dz-button {
|
||||||
@utility xterm {
|
@utility xterm {
|
||||||
@apply p-2;
|
@apply p-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Log line optimization - uses content-visibility for lazy rendering of off-screen log lines */
|
||||||
|
@utility log-line {
|
||||||
|
content-visibility: auto;
|
||||||
|
contain-intrinsic-size: auto 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search highlight styling for logs */
|
||||||
|
@utility log-highlight {
|
||||||
|
@apply bg-warning/40 dark:bg-warning/30;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Log level color classes */
|
||||||
|
@utility log-error {
|
||||||
|
@apply bg-red-500/10 dark:bg-red-500/15;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility log-warning {
|
||||||
|
@apply bg-yellow-500/10 dark:bg-yellow-500/15;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility log-debug {
|
||||||
|
@apply bg-purple-500/10 dark:bg-purple-500/15;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility log-info {
|
||||||
|
@apply bg-blue-500/10 dark:bg-blue-500/15;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="flex flex-col items-center justify-center h-32">
|
<div class="flex flex-col items-center justify-center h-32">
|
||||||
<span class="text-xl font-bold dark:text-white">You have reached the limit of {{ $name }} you can create.</span>
|
<span class="text-xl font-bold dark:text-white">You have reached the limit of {{ $name }} you can create.</span>
|
||||||
<span>Please <a class="dark:text-white underline "href="{{ route('subscription.show') }}">upgrade your
|
<span>Please <a class="dark:text-white underline" {{ wireNavigate() }} href="{{ route('subscription.show') }}">upgrade your
|
||||||
subscription</a> to create more
|
subscription</a> to create more
|
||||||
{{ $name }}.</span>
|
{{ $name }}.</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
}">
|
}">
|
||||||
<div class="flex lg:pt-6 pt-4 pb-4 pl-2">
|
<div class="flex lg:pt-6 pt-4 pb-4 pl-2">
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<a href="/" class="text-2xl font-bold tracking-wide dark:text-white hover:opacity-80 transition-opacity">Coolify</a>
|
<a href="/" {{ wireNavigate() }} class="text-2xl font-bold tracking-wide dark:text-white hover:opacity-80 transition-opacity">Coolify</a>
|
||||||
<x-version />
|
<x-version />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -105,7 +105,7 @@ class="px-1 py-0.5 text-xs font-semibold text-neutral-500 dark:text-neutral-400
|
||||||
<ul role="list" class="flex flex-col h-full space-y-1.5">
|
<ul role="list" class="flex flex-col h-full space-y-1.5">
|
||||||
@if (isSubscribed() || !isCloud())
|
@if (isSubscribed() || !isCloud())
|
||||||
<li>
|
<li>
|
||||||
<a title="Dashboard" href="/"
|
<a title="Dashboard" href="/" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('/') ? 'menu-item-active menu-item' : 'menu-item' }}">
|
class="{{ request()->is('/') ? 'menu-item-active menu-item' : 'menu-item' }}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24"
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24"
|
||||||
stroke="currentColor">
|
stroke="currentColor">
|
||||||
|
|
@ -116,7 +116,7 @@ class="{{ request()->is('/') ? 'menu-item-active menu-item' : 'menu-item' }}">
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a title="Projects"
|
<a title="Projects" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('project/*') || request()->is('projects') ? 'menu-item menu-item-active' : 'menu-item' }}"
|
class="{{ request()->is('project/*') || request()->is('projects') ? 'menu-item menu-item-active' : 'menu-item' }}"
|
||||||
href="/projects">
|
href="/projects">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"
|
||||||
|
|
@ -131,7 +131,7 @@ class="{{ request()->is('project/*') || request()->is('projects') ? 'menu-item m
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a title="Servers"
|
<a title="Servers" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('server/*') || request()->is('servers') ? 'menu-item menu-item-active' : 'menu-item' }}"
|
class="{{ request()->is('server/*') || request()->is('servers') ? 'menu-item menu-item-active' : 'menu-item' }}"
|
||||||
href="/servers">
|
href="/servers">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"
|
||||||
|
|
@ -150,7 +150,7 @@ class="{{ request()->is('server/*') || request()->is('servers') ? 'menu-item men
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a title="Sources"
|
<a title="Sources" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('source*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
class="{{ request()->is('source*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
||||||
href="{{ route('source.all') }}">
|
href="{{ route('source.all') }}">
|
||||||
<svg class="icon" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
@ -161,7 +161,7 @@ class="{{ request()->is('source*') ? 'menu-item-active menu-item' : 'menu-item'
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a title="Destinations"
|
<a title="Destinations" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('destination*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
class="{{ request()->is('destination*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
||||||
href="{{ route('destination.index') }}">
|
href="{{ route('destination.index') }}">
|
||||||
|
|
||||||
|
|
@ -174,7 +174,7 @@ class="{{ request()->is('destination*') ? 'menu-item-active menu-item' : 'menu-i
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a title="S3 Storages"
|
<a title="S3 Storages" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('storages*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
class="{{ request()->is('storages*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
||||||
href="{{ route('storage.index') }}">
|
href="{{ route('storage.index') }}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
|
||||||
|
|
@ -189,7 +189,7 @@ class="{{ request()->is('storages*') ? 'menu-item-active menu-item' : 'menu-item
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a title="Shared variables"
|
<a title="Shared variables" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('shared-variables*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
class="{{ request()->is('shared-variables*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
||||||
href="{{ route('shared-variables.index') }}">
|
href="{{ route('shared-variables.index') }}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
|
||||||
|
|
@ -204,7 +204,7 @@ class="{{ request()->is('shared-variables*') ? 'menu-item-active menu-item' : 'm
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a title="Notifications"
|
<a title="Notifications" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('notifications*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
class="{{ request()->is('notifications*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
||||||
href="{{ route('notifications.email') }}">
|
href="{{ route('notifications.email') }}">
|
||||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
|
@ -216,7 +216,7 @@ class="{{ request()->is('notifications*') ? 'menu-item-active menu-item' : 'menu
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a title="Keys & Tokens"
|
<a title="Keys & Tokens" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('security*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
class="{{ request()->is('security*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
||||||
href="{{ route('security.private-key.index') }}">
|
href="{{ route('security.private-key.index') }}">
|
||||||
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
@ -228,7 +228,7 @@ class="{{ request()->is('security*') ? 'menu-item-active menu-item' : 'menu-item
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a title="Tags"
|
<a title="Tags" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('tags*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
class="{{ request()->is('tags*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
||||||
href="{{ route('tags.show') }}">
|
href="{{ route('tags.show') }}">
|
||||||
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
@ -259,7 +259,7 @@ class="{{ request()->is('terminal*') ? 'menu-item-active menu-item' : 'menu-item
|
||||||
</li>
|
</li>
|
||||||
@endcan
|
@endcan
|
||||||
<li>
|
<li>
|
||||||
<a title="Profile"
|
<a title="Profile" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('profile*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
class="{{ request()->is('profile*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
||||||
href="{{ route('profile') }}">
|
href="{{ route('profile') }}">
|
||||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
||||||
|
|
@ -274,7 +274,7 @@ class="{{ request()->is('profile*') ? 'menu-item-active menu-item' : 'menu-item'
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a title="Teams"
|
<a title="Teams" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('team*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
class="{{ request()->is('team*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
||||||
href="{{ route('team.index') }}">
|
href="{{ route('team.index') }}">
|
||||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
||||||
|
|
@ -293,7 +293,7 @@ class="{{ request()->is('team*') ? 'menu-item-active menu-item' : 'menu-item' }}
|
||||||
</li>
|
</li>
|
||||||
@if (isCloud() && auth()->user()->isAdmin())
|
@if (isCloud() && auth()->user()->isAdmin())
|
||||||
<li>
|
<li>
|
||||||
<a title="Subscription"
|
<a title="Subscription" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('subscription*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
class="{{ request()->is('subscription*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
||||||
href="{{ route('subscription.show') }}">
|
href="{{ route('subscription.show') }}">
|
||||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
|
@ -308,7 +308,7 @@ class="{{ request()->is('subscription*') ? 'menu-item-active menu-item' : 'menu-
|
||||||
@if (isInstanceAdmin())
|
@if (isInstanceAdmin())
|
||||||
<li>
|
<li>
|
||||||
|
|
||||||
<a title="Settings"
|
<a title="Settings" {{ wireNavigate() }}
|
||||||
class="{{ request()->is('settings*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
class="{{ request()->is('settings*') ? 'menu-item-active menu-item' : 'menu-item' }}"
|
||||||
href="/settings">
|
href="/settings">
|
||||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
|
||||||
|
|
@ -327,7 +327,7 @@ class="{{ request()->is('settings*') ? 'menu-item-active menu-item' : 'menu-item
|
||||||
@if (isCloud() || isDev())
|
@if (isCloud() || isDev())
|
||||||
@if (isInstanceAdmin() || session('impersonating'))
|
@if (isInstanceAdmin() || session('impersonating'))
|
||||||
<li>
|
<li>
|
||||||
<a title="Admin" class="menu-item" href="/admin">
|
<a title="Admin" class="menu-item" href="/admin" {{ wireNavigate() }}>
|
||||||
<svg class="text-pink-500 icon" viewBox="0 0 256 256"
|
<svg class="text-pink-500 icon" viewBox="0 0 256 256"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill="currentColor"
|
<path fill="currentColor"
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,27 @@
|
||||||
<div class="subtitle">Get notified about your infrastructure.</div>
|
<div class="subtitle">Get notified about your infrastructure.</div>
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
<nav class="flex items-center gap-6 min-h-10">
|
<nav class="flex items-center gap-6 min-h-10">
|
||||||
<a class="{{ request()->routeIs('notifications.email') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('notifications.email') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('notifications.email') }}">
|
href="{{ route('notifications.email') }}">
|
||||||
<button>Email</button>
|
<button>Email</button>
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('notifications.discord') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('notifications.discord') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('notifications.discord') }}">
|
href="{{ route('notifications.discord') }}">
|
||||||
<button>Discord</button>
|
<button>Discord</button>
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('notifications.telegram') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('notifications.telegram') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('notifications.telegram') }}">
|
href="{{ route('notifications.telegram') }}">
|
||||||
<button>Telegram</button>
|
<button>Telegram</button>
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('notifications.slack') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('notifications.slack') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('notifications.slack') }}">
|
href="{{ route('notifications.slack') }}">
|
||||||
<button>Slack</button>
|
<button>Slack</button>
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('notifications.pushover') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('notifications.pushover') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('notifications.pushover') }}">
|
href="{{ route('notifications.pushover') }}">
|
||||||
<button>Pushover</button>
|
<button>Pushover</button>
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('notifications.webhook') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('notifications.webhook') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('notifications.webhook') }}">
|
href="{{ route('notifications.webhook') }}">
|
||||||
<button>Webhook</button>
|
<button>Webhook</button>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
<!-- Project Level -->
|
<!-- Project Level -->
|
||||||
<li class="inline-flex items-center" x-data="{ projectOpen: false, closeTimeout: null, toggle() { this.projectOpen = !this.projectOpen }, open() { clearTimeout(this.closeTimeout); this.projectOpen = true }, close() { this.closeTimeout = setTimeout(() => { this.projectOpen = false }, 100) } }">
|
<li class="inline-flex items-center" x-data="{ projectOpen: false, closeTimeout: null, toggle() { this.projectOpen = !this.projectOpen }, open() { clearTimeout(this.closeTimeout); this.projectOpen = true }, close() { this.closeTimeout = setTimeout(() => { this.projectOpen = false }, 100) } }">
|
||||||
<div class="flex items-center relative" @mouseenter="open()" @mouseleave="close()">
|
<div class="flex items-center relative" @mouseenter="open()" @mouseleave="close()">
|
||||||
<a class="text-xs truncate lg:text-sm hover:text-warning"
|
<a class="text-xs truncate lg:text-sm hover:text-warning" {{ wireNavigate() }}
|
||||||
href="{{ route('project.show', ['project_uuid' => $currentProjectUuid]) }}">
|
href="{{ route('project.show', ['project_uuid' => $currentProjectUuid]) }}">
|
||||||
{{ data_get($resource, 'environment.project.name', 'Undefined Name') }}
|
{{ data_get($resource, 'environment.project.name', 'Undefined Name') }}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
x-transition:leave-end="opacity-0 scale-95"
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
class="absolute z-20 top-full mt-1 w-56 -ml-2 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
|
class="absolute z-20 top-full mt-1 w-56 -ml-2 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
|
||||||
@foreach ($projects as $project)
|
@foreach ($projects as $project)
|
||||||
<a href="{{ route('project.show', ['project_uuid' => $project->uuid]) }}"
|
<a href="{{ route('project.show', ['project_uuid' => $project->uuid]) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 {{ $project->uuid === $currentProjectUuid ? 'dark:text-warning font-semibold' : '' }}"
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 {{ $project->uuid === $currentProjectUuid ? 'dark:text-warning font-semibold' : '' }}"
|
||||||
title="{{ $project->name }}">
|
title="{{ $project->name }}">
|
||||||
{{ $project->name }}
|
{{ $project->name }}
|
||||||
|
|
@ -50,7 +50,7 @@ class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolg
|
||||||
<li class="inline-flex items-center" x-data="{ envOpen: false, activeEnv: null, envPositions: {}, activeRes: null, resPositions: {}, activeMenuEnv: null, menuPositions: {}, closeTimeout: null, envTimeout: null, resTimeout: null, menuTimeout: null, toggle() { this.envOpen = !this.envOpen; if (!this.envOpen) { this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; } }, open() { clearTimeout(this.closeTimeout); this.envOpen = true }, close() { this.closeTimeout = setTimeout(() => { this.envOpen = false; this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openEnv(id) { clearTimeout(this.closeTimeout); clearTimeout(this.envTimeout); this.activeEnv = id }, closeEnv() { this.envTimeout = setTimeout(() => { this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openRes(id) { clearTimeout(this.envTimeout); clearTimeout(this.resTimeout); this.activeRes = id }, closeRes() { this.resTimeout = setTimeout(() => { this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openMenu(id) { clearTimeout(this.resTimeout); clearTimeout(this.menuTimeout); this.activeMenuEnv = id }, closeMenu() { this.menuTimeout = setTimeout(() => { this.activeMenuEnv = null; }, 100) } }">
|
<li class="inline-flex items-center" x-data="{ envOpen: false, activeEnv: null, envPositions: {}, activeRes: null, resPositions: {}, activeMenuEnv: null, menuPositions: {}, closeTimeout: null, envTimeout: null, resTimeout: null, menuTimeout: null, toggle() { this.envOpen = !this.envOpen; if (!this.envOpen) { this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; } }, open() { clearTimeout(this.closeTimeout); this.envOpen = true }, close() { this.closeTimeout = setTimeout(() => { this.envOpen = false; this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openEnv(id) { clearTimeout(this.closeTimeout); clearTimeout(this.envTimeout); this.activeEnv = id }, closeEnv() { this.envTimeout = setTimeout(() => { this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openRes(id) { clearTimeout(this.envTimeout); clearTimeout(this.resTimeout); this.activeRes = id }, closeRes() { this.resTimeout = setTimeout(() => { this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openMenu(id) { clearTimeout(this.resTimeout); clearTimeout(this.menuTimeout); this.activeMenuEnv = id }, closeMenu() { this.menuTimeout = setTimeout(() => { this.activeMenuEnv = null; }, 100) } }">
|
||||||
<div class="flex items-center relative" @mouseenter="open()"
|
<div class="flex items-center relative" @mouseenter="open()"
|
||||||
@mouseleave="close()">
|
@mouseleave="close()">
|
||||||
<a class="text-xs truncate lg:text-sm hover:text-warning"
|
<a class="text-xs truncate lg:text-sm hover:text-warning" {{ wireNavigate() }}
|
||||||
href="{{ route('project.resource.index', [
|
href="{{ route('project.resource.index', [
|
||||||
'environment_uuid' => $currentEnvironmentUuid,
|
'environment_uuid' => $currentEnvironmentUuid,
|
||||||
'project_uuid' => $currentProjectUuid,
|
'project_uuid' => $currentProjectUuid,
|
||||||
|
|
@ -96,7 +96,7 @@ class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 bor
|
||||||
<a href="{{ route('project.resource.index', [
|
<a href="{{ route('project.resource.index', [
|
||||||
'environment_uuid' => $environment->uuid,
|
'environment_uuid' => $environment->uuid,
|
||||||
'project_uuid' => $currentProjectUuid,
|
'project_uuid' => $currentProjectUuid,
|
||||||
]) }}"
|
]) }}" {{ wireNavigate() }}
|
||||||
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200 {{ $environment->uuid === $currentEnvironmentUuid ? 'dark:text-warning font-semibold' : '' }}"
|
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200 {{ $environment->uuid === $currentEnvironmentUuid ? 'dark:text-warning font-semibold' : '' }}"
|
||||||
title="{{ $environment->name }}">
|
title="{{ $environment->name }}">
|
||||||
<span class="truncate">{{ $environment->name }}</span>
|
<span class="truncate">{{ $environment->name }}</span>
|
||||||
|
|
@ -111,7 +111,7 @@ class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutra
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
<div class="border-t border-neutral-200 dark:border-coolgray-200 mt-1 pt-1">
|
<div class="border-t border-neutral-200 dark:border-coolgray-200 mt-1 pt-1">
|
||||||
<a href="{{ route('project.show', ['project_uuid' => $currentProjectUuid]) }}"
|
<a href="{{ route('project.show', ['project_uuid' => $currentProjectUuid]) }}" {{ wireNavigate() }}
|
||||||
class="flex items-center gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
class="flex items-center gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
|
@ -178,7 +178,7 @@ class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 bor
|
||||||
@endphp
|
@endphp
|
||||||
<div @mouseenter="openRes('{{ $environment->uuid }}-{{ $res->uuid }}'); resPositions['{{ $environment->uuid }}-{{ $res->uuid }}'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
|
<div @mouseenter="openRes('{{ $environment->uuid }}-{{ $res->uuid }}'); resPositions['{{ $environment->uuid }}-{{ $res->uuid }}'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
|
||||||
@mouseleave="closeRes()">
|
@mouseleave="closeRes()">
|
||||||
<a href="{{ $resRoute }}"
|
<a href="{{ $resRoute }}" {{ wireNavigate() }}
|
||||||
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200 {{ $isCurrentResource ? 'dark:text-warning font-semibold' : '' }}"
|
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200 {{ $isCurrentResource ? 'dark:text-warning font-semibold' : '' }}"
|
||||||
title="{{ $res->name }}{{ $resServerName ? ' ('.$resServerName.')' : '' }}">
|
title="{{ $res->name }}{{ $resServerName ? ' ('.$resServerName.')' : '' }}">
|
||||||
<span class="truncate">{{ $res->name }}@if($resServerName) <span class="text-xs text-neutral-400">({{ $resServerName }})</span>@endif</span>
|
<span class="truncate">{{ $res->name }}@if($resServerName) <span class="text-xs text-neutral-400">({{ $resServerName }})</span>@endif</span>
|
||||||
|
|
@ -223,7 +223,7 @@ class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 bor
|
||||||
@if ($resType === 'application')
|
@if ($resType === 'application')
|
||||||
<div @mouseenter="openMenu('{{ $resKey }}-config'); menuPositions['{{ $resKey }}-config'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
|
<div @mouseenter="openMenu('{{ $resKey }}-config'); menuPositions['{{ $resKey }}-config'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
|
||||||
@mouseleave="closeMenu()">
|
@mouseleave="closeMenu()">
|
||||||
<a href="{{ route('project.application.configuration', $resParams) }}"
|
<a href="{{ route('project.application.configuration', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
<span>Configuration</span>
|
<span>Configuration</span>
|
||||||
<svg class="w-3 h-3 shrink-0" fill="none"
|
<svg class="w-3 h-3 shrink-0" fill="none"
|
||||||
|
|
@ -233,9 +233,9 @@ class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutra
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('project.application.deployment.index', $resParams) }}"
|
<a href="{{ route('project.application.deployment.index', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Deployments</a>
|
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Deployments</a>
|
||||||
<a href="{{ route('project.application.logs', $resParams) }}"
|
<a href="{{ route('project.application.logs', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Logs</a>
|
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Logs</a>
|
||||||
@can('canAccessTerminal')
|
@can('canAccessTerminal')
|
||||||
<a href="{{ route('project.application.command', $resParams) }}"
|
<a href="{{ route('project.application.command', $resParams) }}"
|
||||||
|
|
@ -244,7 +244,7 @@ class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
@elseif ($resType === 'service')
|
@elseif ($resType === 'service')
|
||||||
<div @mouseenter="openMenu('{{ $resKey }}-config'); menuPositions['{{ $resKey }}-config'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
|
<div @mouseenter="openMenu('{{ $resKey }}-config'); menuPositions['{{ $resKey }}-config'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
|
||||||
@mouseleave="closeMenu()">
|
@mouseleave="closeMenu()">
|
||||||
<a href="{{ route('project.service.configuration', $resParams) }}"
|
<a href="{{ route('project.service.configuration', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
<span>Configuration</span>
|
<span>Configuration</span>
|
||||||
<svg class="w-3 h-3 shrink-0" fill="none"
|
<svg class="w-3 h-3 shrink-0" fill="none"
|
||||||
|
|
@ -254,7 +254,7 @@ class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutra
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('project.service.logs', $resParams) }}"
|
<a href="{{ route('project.service.logs', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Logs</a>
|
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Logs</a>
|
||||||
@can('canAccessTerminal')
|
@can('canAccessTerminal')
|
||||||
<a href="{{ route('project.service.command', $resParams) }}"
|
<a href="{{ route('project.service.command', $resParams) }}"
|
||||||
|
|
@ -263,7 +263,7 @@ class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
@else
|
@else
|
||||||
<div @mouseenter="openMenu('{{ $resKey }}-config'); menuPositions['{{ $resKey }}-config'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
|
<div @mouseenter="openMenu('{{ $resKey }}-config'); menuPositions['{{ $resKey }}-config'] = $el.offsetTop - ($el.closest('.overflow-y-auto')?.scrollTop || 0)"
|
||||||
@mouseleave="closeMenu()">
|
@mouseleave="closeMenu()">
|
||||||
<a href="{{ route('project.database.configuration', $resParams) }}"
|
<a href="{{ route('project.database.configuration', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
<span>Configuration</span>
|
<span>Configuration</span>
|
||||||
<svg class="w-3 h-3 shrink-0" fill="none"
|
<svg class="w-3 h-3 shrink-0" fill="none"
|
||||||
|
|
@ -273,7 +273,7 @@ class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutra
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('project.database.logs', $resParams) }}"
|
<a href="{{ route('project.database.logs', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Logs</a>
|
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Logs</a>
|
||||||
@can('canAccessTerminal')
|
@can('canAccessTerminal')
|
||||||
<a href="{{ route('project.database.command', $resParams) }}"
|
<a href="{{ route('project.database.command', $resParams) }}"
|
||||||
|
|
@ -284,7 +284,7 @@ class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
$res->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
$res->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||||
$res->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
$res->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||||
$res->getMorphClass() === 'App\Models\StandaloneMariadb')
|
$res->getMorphClass() === 'App\Models\StandaloneMariadb')
|
||||||
<a href="{{ route('project.database.backup.index', $resParams) }}"
|
<a href="{{ route('project.database.backup.index', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Backups</a>
|
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">Backups</a>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|
@ -300,90 +300,90 @@ class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
class="pl-1">
|
class="pl-1">
|
||||||
<div class="w-52 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
|
<div class="w-52 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
|
||||||
@if ($resType === 'application')
|
@if ($resType === 'application')
|
||||||
<a href="{{ route('project.application.configuration', $resParams) }}"
|
<a href="{{ route('project.application.configuration', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
||||||
<a href="{{ route('project.application.environment-variables', $resParams) }}"
|
<a href="{{ route('project.application.environment-variables', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
||||||
Variables</a>
|
Variables</a>
|
||||||
<a href="{{ route('project.application.persistent-storage', $resParams) }}"
|
<a href="{{ route('project.application.persistent-storage', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Persistent
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Persistent
|
||||||
Storage</a>
|
Storage</a>
|
||||||
<a href="{{ route('project.application.source', $resParams) }}"
|
<a href="{{ route('project.application.source', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Source</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Source</a>
|
||||||
<a href="{{ route('project.application.servers', $resParams) }}"
|
<a href="{{ route('project.application.servers', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Servers</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Servers</a>
|
||||||
<a href="{{ route('project.application.scheduled-tasks.show', $resParams) }}"
|
<a href="{{ route('project.application.scheduled-tasks.show', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Scheduled
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Scheduled
|
||||||
Tasks</a>
|
Tasks</a>
|
||||||
<a href="{{ route('project.application.webhooks', $resParams) }}"
|
<a href="{{ route('project.application.webhooks', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
||||||
<a href="{{ route('project.application.preview-deployments', $resParams) }}"
|
<a href="{{ route('project.application.preview-deployments', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Preview
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Preview
|
||||||
Deployments</a>
|
Deployments</a>
|
||||||
<a href="{{ route('project.application.healthcheck', $resParams) }}"
|
<a href="{{ route('project.application.healthcheck', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Healthcheck</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Healthcheck</a>
|
||||||
<a href="{{ route('project.application.rollback', $resParams) }}"
|
<a href="{{ route('project.application.rollback', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Rollback</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Rollback</a>
|
||||||
<a href="{{ route('project.application.resource-limits', $resParams) }}"
|
<a href="{{ route('project.application.resource-limits', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||||
Limits</a>
|
Limits</a>
|
||||||
<a href="{{ route('project.application.resource-operations', $resParams) }}"
|
<a href="{{ route('project.application.resource-operations', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||||
Operations</a>
|
Operations</a>
|
||||||
<a href="{{ route('project.application.metrics', $resParams) }}"
|
<a href="{{ route('project.application.metrics', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Metrics</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Metrics</a>
|
||||||
<a href="{{ route('project.application.tags', $resParams) }}"
|
<a href="{{ route('project.application.tags', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
||||||
<a href="{{ route('project.application.advanced', $resParams) }}"
|
<a href="{{ route('project.application.advanced', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Advanced</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Advanced</a>
|
||||||
<a href="{{ route('project.application.danger', $resParams) }}"
|
<a href="{{ route('project.application.danger', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
||||||
Zone</a>
|
Zone</a>
|
||||||
@elseif ($resType === 'service')
|
@elseif ($resType === 'service')
|
||||||
<a href="{{ route('project.service.configuration', $resParams) }}"
|
<a href="{{ route('project.service.configuration', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
||||||
<a href="{{ route('project.service.environment-variables', $resParams) }}"
|
<a href="{{ route('project.service.environment-variables', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
||||||
Variables</a>
|
Variables</a>
|
||||||
<a href="{{ route('project.service.storages', $resParams) }}"
|
<a href="{{ route('project.service.storages', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Storages</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Storages</a>
|
||||||
<a href="{{ route('project.service.scheduled-tasks.show', $resParams) }}"
|
<a href="{{ route('project.service.scheduled-tasks.show', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Scheduled
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Scheduled
|
||||||
Tasks</a>
|
Tasks</a>
|
||||||
<a href="{{ route('project.service.webhooks', $resParams) }}"
|
<a href="{{ route('project.service.webhooks', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
||||||
<a href="{{ route('project.service.resource-operations', $resParams) }}"
|
<a href="{{ route('project.service.resource-operations', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||||
Operations</a>
|
Operations</a>
|
||||||
<a href="{{ route('project.service.tags', $resParams) }}"
|
<a href="{{ route('project.service.tags', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
||||||
<a href="{{ route('project.service.danger', $resParams) }}"
|
<a href="{{ route('project.service.danger', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
||||||
Zone</a>
|
Zone</a>
|
||||||
@else
|
@else
|
||||||
<a href="{{ route('project.database.configuration', $resParams) }}"
|
<a href="{{ route('project.database.configuration', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
||||||
<a href="{{ route('project.database.environment-variables', $resParams) }}"
|
<a href="{{ route('project.database.environment-variables', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
||||||
Variables</a>
|
Variables</a>
|
||||||
<a href="{{ route('project.database.servers', $resParams) }}"
|
<a href="{{ route('project.database.servers', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Servers</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Servers</a>
|
||||||
<a href="{{ route('project.database.persistent-storage', $resParams) }}"
|
<a href="{{ route('project.database.persistent-storage', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Persistent
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Persistent
|
||||||
Storage</a>
|
Storage</a>
|
||||||
<a href="{{ route('project.database.webhooks', $resParams) }}"
|
<a href="{{ route('project.database.webhooks', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
||||||
<a href="{{ route('project.database.resource-limits', $resParams) }}"
|
<a href="{{ route('project.database.resource-limits', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||||
Limits</a>
|
Limits</a>
|
||||||
<a href="{{ route('project.database.resource-operations', $resParams) }}"
|
<a href="{{ route('project.database.resource-operations', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||||
Operations</a>
|
Operations</a>
|
||||||
<a href="{{ route('project.database.metrics', $resParams) }}"
|
<a href="{{ route('project.database.metrics', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Metrics</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Metrics</a>
|
||||||
<a href="{{ route('project.database.tags', $resParams) }}"
|
<a href="{{ route('project.database.tags', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
||||||
<a href="{{ route('project.database.danger', $resParams) }}"
|
<a href="{{ route('project.database.danger', $resParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
||||||
Zone</a>
|
Zone</a>
|
||||||
@endif
|
@endif
|
||||||
|
|
@ -422,7 +422,7 @@ class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolg
|
||||||
<li class="inline-flex items-center" x-data="{ resourceOpen: false, activeMenu: null, menuPosition: 0, closeTimeout: null, menuTimeout: null, toggle() { this.resourceOpen = !this.resourceOpen; if (!this.resourceOpen) { this.activeMenu = null; } }, open() { clearTimeout(this.closeTimeout); this.resourceOpen = true }, close() { this.closeTimeout = setTimeout(() => { this.resourceOpen = false; this.activeMenu = null; }, 100) }, openMenu(id) { clearTimeout(this.closeTimeout); clearTimeout(this.menuTimeout); this.activeMenu = id }, closeMenu() { this.menuTimeout = setTimeout(() => { this.activeMenu = null; }, 100) } }">
|
<li class="inline-flex items-center" x-data="{ resourceOpen: false, activeMenu: null, menuPosition: 0, closeTimeout: null, menuTimeout: null, toggle() { this.resourceOpen = !this.resourceOpen; if (!this.resourceOpen) { this.activeMenu = null; } }, open() { clearTimeout(this.closeTimeout); this.resourceOpen = true }, close() { this.closeTimeout = setTimeout(() => { this.resourceOpen = false; this.activeMenu = null; }, 100) }, openMenu(id) { clearTimeout(this.closeTimeout); clearTimeout(this.menuTimeout); this.activeMenu = id }, closeMenu() { this.menuTimeout = setTimeout(() => { this.activeMenu = null; }, 100) } }">
|
||||||
<div class="flex items-center relative" @mouseenter="open()"
|
<div class="flex items-center relative" @mouseenter="open()"
|
||||||
@mouseleave="close()">
|
@mouseleave="close()">
|
||||||
<a class="text-xs truncate lg:text-sm hover:text-warning"
|
<a class="text-xs truncate lg:text-sm hover:text-warning" {{ wireNavigate() }}
|
||||||
href="{{ $isApplication
|
href="{{ $isApplication
|
||||||
? route('project.application.configuration', $routeParams)
|
? route('project.application.configuration', $routeParams)
|
||||||
: ($isService
|
: ($isService
|
||||||
|
|
@ -451,7 +451,7 @@ class="relative w-48 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 bor
|
||||||
@if ($isApplication)
|
@if ($isApplication)
|
||||||
<!-- Application Main Menus -->
|
<!-- Application Main Menus -->
|
||||||
<div @mouseenter="openMenu('config'); menuPosition = $el.offsetTop" @mouseleave="closeMenu()">
|
<div @mouseenter="openMenu('config'); menuPosition = $el.offsetTop" @mouseleave="closeMenu()">
|
||||||
<a href="{{ route('project.application.configuration', $routeParams) }}"
|
<a href="{{ route('project.application.configuration', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
<span>Configuration</span>
|
<span>Configuration</span>
|
||||||
<svg class="w-3 h-3 shrink-0" fill="none" stroke="currentColor"
|
<svg class="w-3 h-3 shrink-0" fill="none" stroke="currentColor"
|
||||||
|
|
@ -461,11 +461,11 @@ class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutra
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('project.application.deployment.index', $routeParams) }}"
|
<a href="{{ route('project.application.deployment.index', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
Deployments
|
Deployments
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('project.application.logs', $routeParams) }}"
|
<a href="{{ route('project.application.logs', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
Logs
|
Logs
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -478,7 +478,7 @@ class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
@elseif ($isService)
|
@elseif ($isService)
|
||||||
<!-- Service Main Menus -->
|
<!-- Service Main Menus -->
|
||||||
<div @mouseenter="openMenu('config'); menuPosition = $el.offsetTop" @mouseleave="closeMenu()">
|
<div @mouseenter="openMenu('config'); menuPosition = $el.offsetTop" @mouseleave="closeMenu()">
|
||||||
<a href="{{ route('project.service.configuration', $routeParams) }}"
|
<a href="{{ route('project.service.configuration', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
<span>Configuration</span>
|
<span>Configuration</span>
|
||||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor"
|
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor"
|
||||||
|
|
@ -488,7 +488,7 @@ class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutra
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('project.service.logs', $routeParams) }}"
|
<a href="{{ route('project.service.logs', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
Logs
|
Logs
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -501,7 +501,7 @@ class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
@else
|
@else
|
||||||
<!-- Database Main Menus -->
|
<!-- Database Main Menus -->
|
||||||
<div @mouseenter="openMenu('config'); menuPosition = $el.offsetTop" @mouseleave="closeMenu()">
|
<div @mouseenter="openMenu('config'); menuPosition = $el.offsetTop" @mouseleave="closeMenu()">
|
||||||
<a href="{{ route('project.database.configuration', $routeParams) }}"
|
<a href="{{ route('project.database.configuration', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
<span>Configuration</span>
|
<span>Configuration</span>
|
||||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor"
|
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor"
|
||||||
|
|
@ -511,7 +511,7 @@ class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutra
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('project.database.logs', $routeParams) }}"
|
<a href="{{ route('project.database.logs', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
Logs
|
Logs
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -526,7 +526,7 @@ class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
$resourceType === 'App\Models\StandaloneMongodb' ||
|
$resourceType === 'App\Models\StandaloneMongodb' ||
|
||||||
$resourceType === 'App\Models\StandaloneMysql' ||
|
$resourceType === 'App\Models\StandaloneMysql' ||
|
||||||
$resourceType === 'App\Models\StandaloneMariadb')
|
$resourceType === 'App\Models\StandaloneMariadb')
|
||||||
<a href="{{ route('project.database.backup.index', $routeParams) }}"
|
<a href="{{ route('project.database.backup.index', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
Backups
|
Backups
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -543,90 +543,90 @@ class="block px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
class="pl-1">
|
class="pl-1">
|
||||||
<div class="w-52 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
|
<div class="w-52 bg-white dark:bg-coolgray-100 rounded-md shadow-lg py-1 border border-neutral-200 dark:border-coolgray-200 max-h-96 overflow-y-auto scrollbar">
|
||||||
@if ($isApplication)
|
@if ($isApplication)
|
||||||
<a href="{{ route('project.application.configuration', $routeParams) }}"
|
<a href="{{ route('project.application.configuration', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
||||||
<a href="{{ route('project.application.environment-variables', $routeParams) }}"
|
<a href="{{ route('project.application.environment-variables', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
||||||
Variables</a>
|
Variables</a>
|
||||||
<a href="{{ route('project.application.persistent-storage', $routeParams) }}"
|
<a href="{{ route('project.application.persistent-storage', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Persistent
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Persistent
|
||||||
Storage</a>
|
Storage</a>
|
||||||
<a href="{{ route('project.application.source', $routeParams) }}"
|
<a href="{{ route('project.application.source', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Source</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Source</a>
|
||||||
<a href="{{ route('project.application.servers', $routeParams) }}"
|
<a href="{{ route('project.application.servers', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Servers</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Servers</a>
|
||||||
<a href="{{ route('project.application.scheduled-tasks.show', $routeParams) }}"
|
<a href="{{ route('project.application.scheduled-tasks.show', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Scheduled
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Scheduled
|
||||||
Tasks</a>
|
Tasks</a>
|
||||||
<a href="{{ route('project.application.webhooks', $routeParams) }}"
|
<a href="{{ route('project.application.webhooks', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
||||||
<a href="{{ route('project.application.preview-deployments', $routeParams) }}"
|
<a href="{{ route('project.application.preview-deployments', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Preview
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Preview
|
||||||
Deployments</a>
|
Deployments</a>
|
||||||
<a href="{{ route('project.application.healthcheck', $routeParams) }}"
|
<a href="{{ route('project.application.healthcheck', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Healthcheck</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Healthcheck</a>
|
||||||
<a href="{{ route('project.application.rollback', $routeParams) }}"
|
<a href="{{ route('project.application.rollback', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Rollback</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Rollback</a>
|
||||||
<a href="{{ route('project.application.resource-limits', $routeParams) }}"
|
<a href="{{ route('project.application.resource-limits', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||||
Limits</a>
|
Limits</a>
|
||||||
<a href="{{ route('project.application.resource-operations', $routeParams) }}"
|
<a href="{{ route('project.application.resource-operations', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||||
Operations</a>
|
Operations</a>
|
||||||
<a href="{{ route('project.application.metrics', $routeParams) }}"
|
<a href="{{ route('project.application.metrics', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Metrics</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Metrics</a>
|
||||||
<a href="{{ route('project.application.tags', $routeParams) }}"
|
<a href="{{ route('project.application.tags', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
||||||
<a href="{{ route('project.application.advanced', $routeParams) }}"
|
<a href="{{ route('project.application.advanced', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Advanced</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Advanced</a>
|
||||||
<a href="{{ route('project.application.danger', $routeParams) }}"
|
<a href="{{ route('project.application.danger', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
||||||
Zone</a>
|
Zone</a>
|
||||||
@elseif ($isService)
|
@elseif ($isService)
|
||||||
<a href="{{ route('project.service.configuration', $routeParams) }}"
|
<a href="{{ route('project.service.configuration', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
||||||
<a href="{{ route('project.service.environment-variables', $routeParams) }}"
|
<a href="{{ route('project.service.environment-variables', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
||||||
Variables</a>
|
Variables</a>
|
||||||
<a href="{{ route('project.service.storages', $routeParams) }}"
|
<a href="{{ route('project.service.storages', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Storages</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Storages</a>
|
||||||
<a href="{{ route('project.service.scheduled-tasks.show', $routeParams) }}"
|
<a href="{{ route('project.service.scheduled-tasks.show', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Scheduled
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Scheduled
|
||||||
Tasks</a>
|
Tasks</a>
|
||||||
<a href="{{ route('project.service.webhooks', $routeParams) }}"
|
<a href="{{ route('project.service.webhooks', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
||||||
<a href="{{ route('project.service.resource-operations', $routeParams) }}"
|
<a href="{{ route('project.service.resource-operations', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||||
Operations</a>
|
Operations</a>
|
||||||
<a href="{{ route('project.service.tags', $routeParams) }}"
|
<a href="{{ route('project.service.tags', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
||||||
<a href="{{ route('project.service.danger', $routeParams) }}"
|
<a href="{{ route('project.service.danger', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
||||||
Zone</a>
|
Zone</a>
|
||||||
@else
|
@else
|
||||||
<a href="{{ route('project.database.configuration', $routeParams) }}"
|
<a href="{{ route('project.database.configuration', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">General</a>
|
||||||
<a href="{{ route('project.database.environment-variables', $routeParams) }}"
|
<a href="{{ route('project.database.environment-variables', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Environment
|
||||||
Variables</a>
|
Variables</a>
|
||||||
<a href="{{ route('project.database.servers', $routeParams) }}"
|
<a href="{{ route('project.database.servers', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Servers</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Servers</a>
|
||||||
<a href="{{ route('project.database.persistent-storage', $routeParams) }}"
|
<a href="{{ route('project.database.persistent-storage', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Persistent
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Persistent
|
||||||
Storage</a>
|
Storage</a>
|
||||||
<a href="{{ route('project.database.webhooks', $routeParams) }}"
|
<a href="{{ route('project.database.webhooks', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Webhooks</a>
|
||||||
<a href="{{ route('project.database.resource-limits', $routeParams) }}"
|
<a href="{{ route('project.database.resource-limits', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||||
Limits</a>
|
Limits</a>
|
||||||
<a href="{{ route('project.database.resource-operations', $routeParams) }}"
|
<a href="{{ route('project.database.resource-operations', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Resource
|
||||||
Operations</a>
|
Operations</a>
|
||||||
<a href="{{ route('project.database.metrics', $routeParams) }}"
|
<a href="{{ route('project.database.metrics', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Metrics</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Metrics</a>
|
||||||
<a href="{{ route('project.database.tags', $routeParams) }}"
|
<a href="{{ route('project.database.tags', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200">Tags</a>
|
||||||
<a href="{{ route('project.database.danger', $routeParams) }}"
|
<a href="{{ route('project.database.danger', $routeParams) }}" {{ wireNavigate() }}
|
||||||
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolgray-200 text-red-500">Danger
|
||||||
Zone</a>
|
Zone</a>
|
||||||
@endif
|
@endif
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,20 @@
|
||||||
<div class="subtitle">Security related settings.</div>
|
<div class="subtitle">Security related settings.</div>
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
<nav class="flex items-center gap-6 scrollbar min-h-10">
|
<nav class="flex items-center gap-6 scrollbar min-h-10">
|
||||||
<a href="{{ route('security.private-key.index') }}">
|
<a href="{{ route('security.private-key.index') }}" {{ wireNavigate() }}>
|
||||||
<button>Private Keys</button>
|
<button>Private Keys</button>
|
||||||
</a>
|
</a>
|
||||||
@can('viewAny', App\Models\CloudProviderToken::class)
|
@can('viewAny', App\Models\CloudProviderToken::class)
|
||||||
<a href="{{ route('security.cloud-tokens') }}">
|
<a href="{{ route('security.cloud-tokens') }}" {{ wireNavigate() }}>
|
||||||
<button>Cloud Tokens</button>
|
<button>Cloud Tokens</button>
|
||||||
</a>
|
</a>
|
||||||
@endcan
|
@endcan
|
||||||
@can('viewAny', App\Models\CloudInitScript::class)
|
@can('viewAny', App\Models\CloudInitScript::class)
|
||||||
<a href="{{ route('security.cloud-init-scripts') }}">
|
<a href="{{ route('security.cloud-init-scripts') }}" {{ wireNavigate() }}>
|
||||||
<button>Cloud-Init Scripts</button>
|
<button>Cloud-Init Scripts</button>
|
||||||
</a>
|
</a>
|
||||||
@endcan
|
@endcan
|
||||||
<a href="{{ route('security.api-tokens') }}">
|
<a href="{{ route('security.api-tokens') }}" {{ wireNavigate() }}>
|
||||||
<button>API Tokens</button>
|
<button>API Tokens</button>
|
||||||
</a>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<div class="flex flex-col items-start gap-2 min-w-fit">
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<a class="{{ request()->routeIs('server.proxy') ? 'menu-item menu-item-active' : 'menu-item' }}"
|
<a class="{{ request()->routeIs('server.proxy') ? 'menu-item menu-item-active' : 'menu-item' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.proxy', $parameters) }}">
|
href="{{ route('server.proxy', $parameters) }}">
|
||||||
<button>Configuration</button>
|
<button>Configuration</button>
|
||||||
</a>
|
</a>
|
||||||
@if ($server->proxySet())
|
@if ($server->proxySet())
|
||||||
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'menu-item menu-item-active' : 'menu-item' }}"
|
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'menu-item menu-item-active' : 'menu-item' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
|
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
|
||||||
<button>Dynamic Configurations</button>
|
<button>Dynamic Configurations</button>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="flex flex-col items-start gap-2 min-w-fit">
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<a class="{{ request()->routeIs('server.security.patches') ? 'menu-item menu-item-active' : 'menu-item' }}"
|
<a class="{{ request()->routeIs('server.security.patches') ? 'menu-item menu-item-active' : 'menu-item' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.security.patches', $parameters) }}">
|
href="{{ route('server.security.patches', $parameters) }}">
|
||||||
Server Patching
|
Server Patching
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,42 @@
|
||||||
<div class="flex flex-col items-start gap-2 min-w-fit">
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<a class="menu-item {{ $activeMenu === 'general' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'general' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.show', ['server_uuid' => $server->uuid]) }}">General</a>
|
href="{{ route('server.show', ['server_uuid' => $server->uuid]) }}">General</a>
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
<a class="menu-item {{ $activeMenu === 'advanced' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'advanced' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.advanced', ['server_uuid' => $server->uuid]) }}">Advanced
|
href="{{ route('server.advanced', ['server_uuid' => $server->uuid]) }}">Advanced
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
<a class="menu-item {{ $activeMenu === 'private-key' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'private-key' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}">Private Key
|
href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}">Private Key
|
||||||
</a>
|
</a>
|
||||||
@if ($server->hetzner_server_id)
|
@if ($server->hetzner_server_id)
|
||||||
<a class="menu-item {{ $activeMenu === 'cloud-provider-token' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'cloud-provider-token' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.cloud-provider-token', ['server_uuid' => $server->uuid]) }}">Hetzner Token
|
href="{{ route('server.cloud-provider-token', ['server_uuid' => $server->uuid]) }}">Hetzner Token
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
<a class="menu-item {{ $activeMenu === 'ca-certificate' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'ca-certificate' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.ca-certificate', ['server_uuid' => $server->uuid]) }}">CA Certificate
|
href="{{ route('server.ca-certificate', ['server_uuid' => $server->uuid]) }}">CA Certificate
|
||||||
</a>
|
</a>
|
||||||
@if (!$server->isLocalhost())
|
@if (!$server->isLocalhost())
|
||||||
<a class="menu-item {{ $activeMenu === 'cloudflare-tunnel' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'cloudflare-tunnel' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.cloudflare-tunnel', ['server_uuid' => $server->uuid]) }}">Cloudflare
|
href="{{ route('server.cloudflare-tunnel', ['server_uuid' => $server->uuid]) }}">Cloudflare
|
||||||
Tunnel</a>
|
Tunnel</a>
|
||||||
@endif
|
@endif
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
<a class="menu-item {{ $activeMenu === 'docker-cleanup' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'docker-cleanup' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.docker-cleanup', ['server_uuid' => $server->uuid]) }}">Docker Cleanup
|
href="{{ route('server.docker-cleanup', ['server_uuid' => $server->uuid]) }}">Docker Cleanup
|
||||||
</a>
|
</a>
|
||||||
<a class="menu-item {{ $activeMenu === 'destinations' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'destinations' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.destinations', ['server_uuid' => $server->uuid]) }}">Destinations
|
href="{{ route('server.destinations', ['server_uuid' => $server->uuid]) }}">Destinations
|
||||||
</a>
|
</a>
|
||||||
<a class="menu-item {{ $activeMenu === 'log-drains' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'log-drains' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.log-drains', ['server_uuid' => $server->uuid]) }}">Log
|
href="{{ route('server.log-drains', ['server_uuid' => $server->uuid]) }}">Log
|
||||||
Drains</a>
|
Drains</a>
|
||||||
<a class="menu-item {{ $activeMenu === 'metrics' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'metrics' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.charts', ['server_uuid' => $server->uuid]) }}">Metrics</a>
|
href="{{ route('server.charts', ['server_uuid' => $server->uuid]) }}">Metrics</a>
|
||||||
@endif
|
@endif
|
||||||
@if (!$server->isLocalhost())
|
@if (!$server->isLocalhost())
|
||||||
<a class="menu-item {{ $activeMenu === 'danger' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'danger' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.delete', ['server_uuid' => $server->uuid]) }}">Danger</a>
|
href="{{ route('server.delete', ['server_uuid' => $server->uuid]) }}">Danger</a>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,19 @@
|
||||||
<div class="subtitle">Instance wide settings for Coolify.</div>
|
<div class="subtitle">Instance wide settings for Coolify.</div>
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
<nav class="flex items-center gap-6 min-h-10 whitespace-nowrap">
|
<nav class="flex items-center gap-6 min-h-10 whitespace-nowrap">
|
||||||
<a class="{{ request()->routeIs('settings.index') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('settings.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('settings.index') }}">
|
href="{{ route('settings.index') }}">
|
||||||
Configuration
|
Configuration
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('settings.backup') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('settings.backup') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('settings.backup') }}">
|
href="{{ route('settings.backup') }}">
|
||||||
Backup
|
Backup
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('settings.email') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('settings.email') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('settings.email') }}">
|
href="{{ route('settings.email') }}">
|
||||||
Transactional Email
|
Transactional Email
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('settings.oauth') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('settings.oauth') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('settings.oauth') }}">
|
href="{{ route('settings.oauth') }}">
|
||||||
OAuth
|
OAuth
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<div class="flex flex-col items-start gap-2 min-w-fit">
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<a class="menu-item {{ $activeMenu === 'general' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'general' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('settings.index') }}">General</a>
|
href="{{ route('settings.index') }}">General</a>
|
||||||
<a class="menu-item {{ $activeMenu === 'advanced' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'advanced' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('settings.advanced') }}">Advanced</a>
|
href="{{ route('settings.advanced') }}">Advanced</a>
|
||||||
<a class="menu-item {{ $activeMenu === 'updates' ? 'menu-item-active' : '' }}"
|
<a class="menu-item {{ $activeMenu === 'updates' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('settings.updates') }}">Updates</a>
|
href="{{ route('settings.updates') }}">Updates</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,16 @@
|
||||||
<div class="subtitle">Team wide configurations.</div>
|
<div class="subtitle">Team wide configurations.</div>
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
<nav class="flex items-center gap-6 min-h-10">
|
<nav class="flex items-center gap-6 min-h-10">
|
||||||
<a class="{{ request()->routeIs('team.index') ? 'dark:text-white' : '' }}" href="{{ route('team.index') }}">
|
<a class="{{ request()->routeIs('team.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
|
href="{{ route('team.index') }}">
|
||||||
General
|
General
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('team.member.index') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('team.member.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('team.member.index') }}">
|
href="{{ route('team.member.index') }}">
|
||||||
Members
|
Members
|
||||||
</a>
|
</a>
|
||||||
@if (isInstanceAdmin())
|
@if (isInstanceAdmin())
|
||||||
<a class="{{ request()->routeIs('team.admin-view') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('team.admin-view') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('team.admin-view') }}">
|
href="{{ route('team.admin-view') }}">
|
||||||
Admin View
|
Admin View
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
The currentStep variable is inherited from parent Alpine component (upgradeModal).
|
The currentStep variable is inherited from parent Alpine component (upgradeModal).
|
||||||
--}}
|
--}}
|
||||||
<div class="w-full max-w-md mx-auto" x-data="{ activeStep: {{ $step }} }" x-effect="activeStep = $el.closest('[x-data]')?.__x?.$data?.currentStep ?? {{ $step }}">
|
<div class="w-full" x-data="{ activeStep: {{ $step }} }" x-effect="activeStep = $el.closest('[x-data]')?.__x?.$data?.currentStep ?? {{ $step }}">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
{{-- Step 1: Preparing --}}
|
{{-- Step 1: Preparing --}}
|
||||||
<div class="flex items-center flex-1">
|
<div class="flex items-center flex-1">
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
<a href="{{ url()->previous() }}">
|
<a href="{{ url()->previous() }}">
|
||||||
<x-forms.button>Go back</x-forms.button>
|
<x-forms.button>Go back</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('dashboard') }}">
|
<a href="{{ route('dashboard') }}" {{ wireNavigate() }}>
|
||||||
<x-forms.button>Dashboard</x-forms.button>
|
<x-forms.button>Dashboard</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<a href="{{ url()->previous() }}">
|
<a href="{{ url()->previous() }}">
|
||||||
<x-forms.button>Go back</x-forms.button>
|
<x-forms.button>Go back</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('dashboard') }}">
|
<a href="{{ route('dashboard') }}" {{ wireNavigate() }}>
|
||||||
<x-forms.button>Dashboard</x-forms.button>
|
<x-forms.button>Dashboard</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<a href="{{ url()->previous() }}">
|
<a href="{{ url()->previous() }}">
|
||||||
<x-forms.button>Go back</x-forms.button>
|
<x-forms.button>Go back</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('dashboard') }}">
|
<a href="{{ route('dashboard') }}" {{ wireNavigate() }}>
|
||||||
<x-forms.button>Dashboard</x-forms.button>
|
<x-forms.button>Dashboard</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<a href="{{ url()->previous() }}">
|
<a href="{{ url()->previous() }}">
|
||||||
<x-forms.button>Go back</x-forms.button>
|
<x-forms.button>Go back</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('dashboard') }}">
|
<a href="{{ route('dashboard') }}" {{ wireNavigate() }}>
|
||||||
<x-forms.button>Dashboard</x-forms.button>
|
<x-forms.button>Dashboard</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@
|
||||||
<div>
|
<div>
|
||||||
<p class="font-mono font-semibold text-7xl dark:text-warning">404</p>
|
<p class="font-mono font-semibold text-7xl dark:text-warning">404</p>
|
||||||
<h1 class="mt-4 font-bold tracking-tight dark:text-white">How did you get here?</h1>
|
<h1 class="mt-4 font-bold tracking-tight dark:text-white">How did you get here?</h1>
|
||||||
<p class="text-base leading-7 dark:text-neutral-400 text-black">Sorry, we couldn’t find the page you’re looking
|
<p class="text-base leading-7 dark:text-neutral-400 text-black">Sorry, we couldn't find the page you're looking
|
||||||
for.
|
for.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex items-center mt-10 gap-x-2">
|
<div class="flex items-center mt-10 gap-x-2">
|
||||||
<a href="{{ url()->previous() }}">
|
<a href="{{ url()->previous() }}">
|
||||||
<x-forms.button>Go back</x-forms.button>
|
<x-forms.button>Go back</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('dashboard') }}">
|
<a href="{{ route('dashboard') }}" {{ wireNavigate() }}>
|
||||||
<x-forms.button>Dashboard</x-forms.button>
|
<x-forms.button>Dashboard</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,14 @@
|
||||||
<div>
|
<div>
|
||||||
<p class="font-mono font-semibold text-7xl dark:text-warning">419</p>
|
<p class="font-mono font-semibold text-7xl dark:text-warning">419</p>
|
||||||
<h1 class="mt-4 font-bold tracking-tight dark:text-white">This page is definitely old, not like you!</h1>
|
<h1 class="mt-4 font-bold tracking-tight dark:text-white">This page is definitely old, not like you!</h1>
|
||||||
<p class="text-base leading-7 dark:text-neutral-300 text-black">Sorry, we couldn’t find the page you’re looking
|
<p class="text-base leading-7 dark:text-neutral-300 text-black">Sorry, we couldn't find the page you're looking
|
||||||
for.
|
for.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex items-center mt-10 gap-x-2">
|
<div class="flex items-center mt-10 gap-x-2">
|
||||||
<a href="{{ url()->previous() }}">
|
<a href="{{ url()->previous() }}">
|
||||||
<x-forms.button>Go back</x-forms.button>
|
<x-forms.button>Go back</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('dashboard') }}">
|
<a href="{{ route('dashboard') }}" {{ wireNavigate() }}>
|
||||||
<x-forms.button>Dashboard</x-forms.button>
|
<x-forms.button>Dashboard</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<a href="{{ url()->previous() }}">
|
<a href="{{ url()->previous() }}">
|
||||||
<x-forms.button>Go back</x-forms.button>
|
<x-forms.button>Go back</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('dashboard') }}">
|
<a href="{{ route('dashboard') }}" {{ wireNavigate() }}>
|
||||||
<x-forms.button>Dashboard</x-forms.button>
|
<x-forms.button>Dashboard</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<a href="{{ url()->previous() }}">
|
<a href="{{ url()->previous() }}">
|
||||||
<x-forms.button>Go back</x-forms.button>
|
<x-forms.button>Go back</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('dashboard') }}">
|
<a href="{{ route('dashboard') }}" {{ wireNavigate() }}>
|
||||||
<x-forms.button>Dashboard</x-forms.button>
|
<x-forms.button>Dashboard</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<a href="{{ url()->previous() }}">
|
<a href="{{ url()->previous() }}">
|
||||||
<x-forms.button>Go back</x-forms.button>
|
<x-forms.button>Go back</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('dashboard') }}">
|
<a href="{{ route('dashboard') }}" {{ wireNavigate() }}>
|
||||||
<x-forms.button>Dashboard</x-forms.button>
|
<x-forms.button>Dashboard</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
<a target="_blank" class="text-xs" href="{{ config('constants.urls.contact') }}">Contact
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class="flex items-center justify-center size-4 text-white rounded hover:bg-coolg
|
||||||
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
|
||||||
@foreach ($projects as $project)
|
@foreach ($projects as $project)
|
||||||
<div class="relative gap-2 cursor-pointer coolbox group">
|
<div class="relative gap-2 cursor-pointer coolbox group">
|
||||||
<a href="{{ $project->navigateTo() }}" class="absolute inset-0"></a>
|
<a href="{{ $project->navigateTo() }}" {{ wireNavigate() }} class="absolute inset-0"></a>
|
||||||
<div class="flex flex-1 mx-6">
|
<div class="flex flex-1 mx-6">
|
||||||
<div class="flex flex-col justify-center flex-1">
|
<div class="flex flex-col justify-center flex-1">
|
||||||
<div class="box-title">{{ $project->name }}</div>
|
<div class="box-title">{{ $project->name }}</div>
|
||||||
|
|
@ -47,7 +47,7 @@ class="flex items-center justify-center size-4 text-white rounded hover:bg-coolg
|
||||||
<div class="relative z-10 flex items-center justify-center gap-4 text-xs font-bold">
|
<div class="relative z-10 flex items-center justify-center gap-4 text-xs font-bold">
|
||||||
@if ($project->environments->first())
|
@if ($project->environments->first())
|
||||||
@can('createAnyResource')
|
@can('createAnyResource')
|
||||||
<a class="hover:underline"
|
<a class="hover:underline" {{ wireNavigate() }}
|
||||||
href="{{ route('project.resource.create', [
|
href="{{ route('project.resource.create', [
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
'environment_uuid' => $project->environments->first()->uuid,
|
'environment_uuid' => $project->environments->first()->uuid,
|
||||||
|
|
@ -57,7 +57,7 @@ class="flex items-center justify-center size-4 text-white rounded hover:bg-coolg
|
||||||
@endcan
|
@endcan
|
||||||
@endif
|
@endif
|
||||||
@can('update', $project)
|
@can('update', $project)
|
||||||
<a class="hover:underline"
|
<a class="hover:underline" {{ wireNavigate() }}
|
||||||
href="{{ route('project.edit', ['project_uuid' => $project->uuid]) }}">
|
href="{{ route('project.edit', ['project_uuid' => $project->uuid]) }}">
|
||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -74,7 +74,7 @@ class="flex items-center justify-center size-4 text-white rounded hover:bg-coolg
|
||||||
<x-modal-input buttonTitle="Add" title="New Project">
|
<x-modal-input buttonTitle="Add" title="New Project">
|
||||||
<livewire:project.add-empty />
|
<livewire:project.add-empty />
|
||||||
</x-modal-input> your first project or
|
</x-modal-input> your first project or
|
||||||
go to the <a class="underline dark:text-white" href="{{ route('onboarding') }}">onboarding</a> page.
|
go to the <a class="underline dark:text-white" href="{{ route('onboarding') }}" {{ wireNavigate() }}>onboarding</a> page.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
@ -101,7 +101,7 @@ class="flex items-center justify-center size-4 text-white rounded hover:bg-coolg
|
||||||
@if ($servers->count() > 0)
|
@if ($servers->count() > 0)
|
||||||
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
|
||||||
@foreach ($servers as $server)
|
@foreach ($servers as $server)
|
||||||
<a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}"
|
<a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}" {{ wireNavigate() }}
|
||||||
@class([
|
@class([
|
||||||
'gap-2 border cursor-pointer coolbox group',
|
'gap-2 border cursor-pointer coolbox group',
|
||||||
'border-red-500' =>
|
'border-red-500' =>
|
||||||
|
|
@ -138,7 +138,7 @@ class="flex items-center justify-center size-4 text-white rounded hover:bg-coolg
|
||||||
<livewire:security.private-key.create from="server" />
|
<livewire:security.private-key.create from="server" />
|
||||||
</x-modal-input> a private key
|
</x-modal-input> a private key
|
||||||
or
|
or
|
||||||
go to the <a class="underline dark:text-white" href="{{ route('onboarding') }}">onboarding</a>
|
go to the <a class="underline dark:text-white" href="{{ route('onboarding') }}" {{ wireNavigate() }}>onboarding</a>
|
||||||
page.
|
page.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -150,7 +150,7 @@ class="flex items-center justify-center size-4 text-white rounded hover:bg-coolg
|
||||||
<livewire:server.create />
|
<livewire:server.create />
|
||||||
</x-modal-input> your first server
|
</x-modal-input> your first server
|
||||||
or
|
or
|
||||||
go to the <a class="underline dark:text-white" href="{{ route('onboarding') }}">onboarding</a>
|
go to the <a class="underline dark:text-white" href="{{ route('onboarding') }}" {{ wireNavigate() }}>onboarding</a>
|
||||||
page.
|
page.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ class="absolute bottom-full mb-2 w-80 max-h-96 overflow-y-auto rounded-lg shadow
|
||||||
|
|
||||||
<div class="p-4 space-y-3">
|
<div class="p-4 space-y-3">
|
||||||
@foreach ($this->deployments as $deployment)
|
@foreach ($this->deployments as $deployment)
|
||||||
<a href="{{ $deployment->deployment_url }}"
|
<a href="{{ $deployment->deployment_url }}" {{ wireNavigate() }}
|
||||||
class="flex items-start gap-3 p-3 rounded-lg dark:bg-coolgray-200 bg-gray-50 transition-all duration-200 hover:ring-2 hover:ring-coollabs dark:hover:ring-warning cursor-pointer">
|
class="flex items-start gap-3 p-3 rounded-lg dark:bg-coolgray-200 bg-gray-50 transition-all duration-200 hover:ring-2 hover:ring-coollabs dark:hover:ring-warning cursor-pointer">
|
||||||
<!-- Status indicator -->
|
<!-- Status indicator -->
|
||||||
<div class="flex-shrink-0 mt-1">
|
<div class="flex-shrink-0 mt-1">
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
@forelse ($servers as $server)
|
@forelse ($servers as $server)
|
||||||
@forelse ($server->destinations() as $destination)
|
@forelse ($server->destinations() as $destination)
|
||||||
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
|
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
|
||||||
<a class="coolbox group"
|
<a class="coolbox group" {{ wireNavigate() }}
|
||||||
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
||||||
<div class="flex flex-col justify-center mx-6">
|
<div class="flex flex-col justify-center mx-6">
|
||||||
<div class="box-title">{{ $destination->name }}</div>
|
<div class="box-title">{{ $destination->name }}</div>
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@if ($destination->getMorphClass() === 'App\Models\SwarmDocker')
|
@if ($destination->getMorphClass() === 'App\Models\SwarmDocker')
|
||||||
<a class="coolbox group"
|
<a class="coolbox group" {{ wireNavigate() }}
|
||||||
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
||||||
<div class="flex flex-col mx-6">
|
<div class="flex flex-col mx-6">
|
||||||
<div class="box-title">{{ $destination->name }}</div>
|
<div class="box-title">{{ $destination->name }}</div>
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,11 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
openModal() {
|
openModal() {
|
||||||
|
// Check if $wire is available (may not be after SPA navigation destroys/recreates component)
|
||||||
|
if (typeof $wire === 'undefined' || !$wire) {
|
||||||
|
console.warn('Global search: $wire not available, skipping open');
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.modalOpen = true;
|
this.modalOpen = true;
|
||||||
this.selectedIndex = -1;
|
this.selectedIndex = -1;
|
||||||
this.isLoadingInitialData = true;
|
this.isLoadingInitialData = true;
|
||||||
|
|
@ -79,6 +84,10 @@
|
||||||
this.creatableItems = $wire.creatableItems || [];
|
this.creatableItems = $wire.creatableItems || [];
|
||||||
this.isLoadingInitialData = false;
|
this.isLoadingInitialData = false;
|
||||||
setTimeout(() => this.$refs.searchInput?.focus(), 50);
|
setTimeout(() => this.$refs.searchInput?.focus(), 50);
|
||||||
|
}).catch(() => {
|
||||||
|
// Handle case where component was destroyed during navigation
|
||||||
|
this.modalOpen = false;
|
||||||
|
this.isLoadingInitialData = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
|
|
@ -90,7 +99,10 @@
|
||||||
this.allSearchableItems = [];
|
this.allSearchableItems = [];
|
||||||
// Ensure scroll is restored
|
// Ensure scroll is restored
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
@this.closeSearchModal();
|
// Use $wire instead of @this for SPA navigation compatibility
|
||||||
|
if ($wire) {
|
||||||
|
$wire.closeSearchModal();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
navigateResults(direction) {
|
navigateResults(direction) {
|
||||||
const results = document.querySelectorAll('.search-result-item');
|
const results = document.querySelectorAll('.search-result-item');
|
||||||
|
|
@ -120,7 +132,7 @@
|
||||||
const trimmed = value.trim().toLowerCase();
|
const trimmed = value.trim().toLowerCase();
|
||||||
|
|
||||||
if (trimmed === '') {
|
if (trimmed === '') {
|
||||||
if ($wire.isSelectingResource) {
|
if (typeof $wire !== 'undefined' && $wire && $wire.isSelectingResource) {
|
||||||
$wire.cancelResourceSelection();
|
$wire.cancelResourceSelection();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -149,7 +161,7 @@
|
||||||
(item.quickcommand && item.quickcommand.toLowerCase().includes(trimmed));
|
(item.quickcommand && item.quickcommand.toLowerCase().includes(trimmed));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (matchingItem) {
|
if (matchingItem && typeof $wire !== 'undefined' && $wire) {
|
||||||
$wire.navigateToResource(matchingItem.type);
|
$wire.navigateToResource(matchingItem.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +198,7 @@
|
||||||
// If search query is empty, close the modal
|
// If search query is empty, close the modal
|
||||||
if (!this.searchQuery || this.searchQuery === '') {
|
if (!this.searchQuery || this.searchQuery === '') {
|
||||||
// Check if we're in a selection state using Alpine-accessible Livewire state
|
// Check if we're in a selection state using Alpine-accessible Livewire state
|
||||||
if ($wire.isSelectingResource) {
|
if (typeof $wire !== 'undefined' && $wire && $wire.isSelectingResource) {
|
||||||
$wire.cancelResourceSelection();
|
$wire.cancelResourceSelection();
|
||||||
setTimeout(() => this.$refs.searchInput?.focus(), 100);
|
setTimeout(() => this.$refs.searchInput?.focus(), 100);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -227,19 +239,23 @@
|
||||||
document.removeEventListener('keydown', arrowKeyHandler);
|
document.removeEventListener('keydown', arrowKeyHandler);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watch for auto-open resource
|
// Watch for auto-open resource (only if $wire is available)
|
||||||
this.$watch('$wire.autoOpenResource', value => {
|
if (typeof $wire !== 'undefined' && $wire) {
|
||||||
if (value) {
|
this.$watch('$wire.autoOpenResource', value => {
|
||||||
// Close search modal first
|
if (value) {
|
||||||
this.closeModal();
|
// Close search modal first
|
||||||
// Open the specific resource modal after a short delay
|
this.closeModal();
|
||||||
setTimeout(() => {
|
// Open the specific resource modal after a short delay
|
||||||
this.$dispatch('open-create-modal-' + value);
|
setTimeout(() => {
|
||||||
// Reset the value so it can trigger again
|
this.$dispatch('open-create-modal-' + value);
|
||||||
@this.set('autoOpenResource', null);
|
// Reset the value so it can trigger again
|
||||||
}, 150);
|
if (typeof $wire !== 'undefined' && $wire) {
|
||||||
}
|
$wire.set('autoOpenResource', null);
|
||||||
});
|
}
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Listen for closeSearchModal event from backend
|
// Listen for closeSearchModal event from backend
|
||||||
window.addEventListener('closeSearchModal', () => {
|
window.addEventListener('closeSearchModal', () => {
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ class="font-bold dark:text-white">Stripe</a></x-forms.button>
|
||||||
<div><span class="font-bold text-red-500">WARNING:</span> Your subscription is in over-due. If your
|
<div><span class="font-bold text-red-500">WARNING:</span> Your subscription is in over-due. If your
|
||||||
latest
|
latest
|
||||||
payment is not paid within a week, all automations <span class="font-bold text-red-500">will
|
payment is not paid within a week, all automations <span class="font-bold text-red-500">will
|
||||||
be deactivated</span>. Visit <a href="{{ route('subscription.show') }}"
|
be deactivated</span>. Visit <a href="{{ route('subscription.show') }}" {{ wireNavigate() }}
|
||||||
class="underline dark:text-white">/subscription</a> to check your subscription status or pay
|
class="underline dark:text-white">/subscription</a> to check your subscription status or pay
|
||||||
your
|
your
|
||||||
invoice (or check your email for the invoice).
|
invoice (or check your email for the invoice).
|
||||||
|
|
@ -148,7 +148,7 @@ class="underline dark:text-white">/subscription</a> to check your subscription s
|
||||||
<x-banner :closable=false>
|
<x-banner :closable=false>
|
||||||
<div><span class="font-bold text-red-500">WARNING:</span> The number of active servers exceeds the limit
|
<div><span class="font-bold text-red-500">WARNING:</span> The number of active servers exceeds the limit
|
||||||
covered by your payment. If not resolved, some of your servers <span class="font-bold text-red-500">will
|
covered by your payment. If not resolved, some of your servers <span class="font-bold text-red-500">will
|
||||||
be deactivated</span>. Visit <a href="{{ route('subscription.show') }}"
|
be deactivated</span>. Visit <a href="{{ route('subscription.show') }}" {{ wireNavigate() }}
|
||||||
class="underline dark:text-white">/subscription</a> to update your subscription or remove some
|
class="underline dark:text-white">/subscription</a> to update your subscription or remove some
|
||||||
servers.
|
servers.
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -172,7 +172,7 @@ class="underline dark:text-white">/subscription</a> to update your subscription
|
||||||
highly recommended to enable at least
|
highly recommended to enable at least
|
||||||
one
|
one
|
||||||
notification channel to receive important alerts.<br>Visit <a
|
notification channel to receive important alerts.<br>Visit <a
|
||||||
href="{{ route('notifications.email') }}" class="underline dark:text-white">/notification</a> to
|
href="{{ route('notifications.email') }}" {{ wireNavigate() }} class="underline dark:text-white">/notification</a> to
|
||||||
enable notifications.</span>
|
enable notifications.</span>
|
||||||
</x-slot:description>
|
</x-slot:description>
|
||||||
<x-slot:button-text @click="disableNotification()">
|
<x-slot:button-text @click="disableNotification()">
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,27 @@
|
||||||
|
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<div class="flex flex-col items-start gap-2 min-w-fit">
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">General</a>
|
href="{{ route('project.application.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">General</a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.advanced', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Advanced</a>
|
href="{{ route('project.application.advanced', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Advanced</a>
|
||||||
@if ($application->destination->server->isSwarm())
|
@if ($application->destination->server->isSwarm())
|
||||||
<a class="menu-item" wire:current.exact="menu-item-active"
|
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.swarm', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Swarm
|
href="{{ route('project.application.swarm', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Swarm
|
||||||
Configuration</a>
|
Configuration</a>
|
||||||
@endif
|
@endif
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Environment
|
href="{{ route('project.application.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Environment
|
||||||
Variables</a>
|
Variables</a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.persistent-storage', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Persistent
|
href="{{ route('project.application.persistent-storage', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Persistent
|
||||||
Storage</a>
|
Storage</a>
|
||||||
@if ($application->git_based())
|
@if ($application->git_based())
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.source', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Git
|
href="{{ route('project.application.source', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Git
|
||||||
Source</a>
|
Source</a>
|
||||||
@endif
|
@endif
|
||||||
<a class="menu-item flex items-center gap-2" wire:current.exact="menu-item-active"
|
<a class="menu-item flex items-center gap-2" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Servers
|
href="{{ route('project.application.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Servers
|
||||||
@if ($application->server_status == false)
|
@if ($application->server_status == false)
|
||||||
<span title="One or more servers are unreachable or misconfigured.">
|
<span title="One or more servers are unreachable or misconfigured.">
|
||||||
|
|
@ -46,33 +46,33 @@
|
||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
<a class="menu-item" wire:current.exact="menu-item-active"
|
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Scheduled
|
href="{{ route('project.application.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Scheduled
|
||||||
Tasks</a>
|
Tasks</a>
|
||||||
<a class="menu-item" wire:current.exact="menu-item-active"
|
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Webhooks</a>
|
href="{{ route('project.application.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Webhooks</a>
|
||||||
@if ($application->deploymentType() !== 'deploy_key')
|
@if ($application->deploymentType() !== 'deploy_key')
|
||||||
<a class="menu-item" wire:current.exact="menu-item-active"
|
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.preview-deployments', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Preview
|
href="{{ route('project.application.preview-deployments', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Preview
|
||||||
Deployments</a>
|
Deployments</a>
|
||||||
@endif
|
@endif
|
||||||
@if ($application->build_pack !== 'dockercompose')
|
@if ($application->build_pack !== 'dockercompose')
|
||||||
<a class="menu-item" wire:current.exact="menu-item-active"
|
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.healthcheck', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Healthcheck</a>
|
href="{{ route('project.application.healthcheck', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Healthcheck</a>
|
||||||
@endif
|
@endif
|
||||||
<a class="menu-item" wire:current.exact="menu-item-active"
|
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.rollback', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Rollback</a>
|
href="{{ route('project.application.rollback', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Rollback</a>
|
||||||
<a class="menu-item" wire:current.exact="menu-item-active"
|
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Resource
|
href="{{ route('project.application.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Resource
|
||||||
Limits</a>
|
Limits</a>
|
||||||
<a class="menu-item" wire:current.exact="menu-item-active"
|
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Resource
|
href="{{ route('project.application.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Resource
|
||||||
Operations</a>
|
Operations</a>
|
||||||
<a class="menu-item" wire:current.exact="menu-item-active"
|
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Metrics</a>
|
href="{{ route('project.application.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Metrics</a>
|
||||||
<a class="menu-item" wire:current.exact="menu-item-active"
|
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Tags</a>
|
href="{{ route('project.application.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Tags</a>
|
||||||
<a class="menu-item" wire:current.exact="menu-item-active"
|
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.application.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Danger
|
href="{{ route('project.application.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Danger
|
||||||
Zone</a>
|
Zone</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
'border-error' => data_get($deployment, 'status') === 'failed',
|
'border-error' => data_get($deployment, 'status') === 'failed',
|
||||||
'border-success' => data_get($deployment, 'status') === 'finished',
|
'border-success' => data_get($deployment, 'status') === 'finished',
|
||||||
])>
|
])>
|
||||||
<a href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}" class="block">
|
<a href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}" {{ wireNavigate() }} class="block">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex items-center gap-2 mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<span @class([
|
<span @class([
|
||||||
|
|
|
||||||
|
|
@ -8,26 +8,44 @@
|
||||||
<div x-data="{
|
<div x-data="{
|
||||||
fullscreen: @entangle('fullscreen'),
|
fullscreen: @entangle('fullscreen'),
|
||||||
alwaysScroll: {{ $isKeepAliveOn ? 'true' : 'false' }},
|
alwaysScroll: {{ $isKeepAliveOn ? 'true' : 'false' }},
|
||||||
intervalId: null,
|
rafId: null,
|
||||||
showTimestamps: true,
|
showTimestamps: true,
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
renderTrigger: 0,
|
renderTrigger: 0,
|
||||||
deploymentId: '{{ $application_deployment_queue->deployment_uuid ?? 'deployment' }}',
|
deploymentId: '{{ $application_deployment_queue->deployment_uuid ?? 'deployment' }}',
|
||||||
|
// Cache for decoded HTML to avoid repeated DOMParser calls
|
||||||
|
decodeCache: new Map(),
|
||||||
|
// Cache for match count to avoid repeated DOM queries
|
||||||
|
matchCountCache: null,
|
||||||
|
lastSearchQuery: '',
|
||||||
makeFullscreen() {
|
makeFullscreen() {
|
||||||
this.fullscreen = !this.fullscreen;
|
this.fullscreen = !this.fullscreen;
|
||||||
},
|
},
|
||||||
|
scrollToBottom() {
|
||||||
|
const logsContainer = document.getElementById('logsContainer');
|
||||||
|
if (logsContainer) {
|
||||||
|
logsContainer.scrollTop = logsContainer.scrollHeight;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scheduleScroll() {
|
||||||
|
if (!this.alwaysScroll) return;
|
||||||
|
this.rafId = requestAnimationFrame(() => {
|
||||||
|
this.scrollToBottom();
|
||||||
|
// Schedule next scroll after a reasonable delay (250ms instead of 100ms)
|
||||||
|
if (this.alwaysScroll) {
|
||||||
|
setTimeout(() => this.scheduleScroll(), 250);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
toggleScroll() {
|
toggleScroll() {
|
||||||
this.alwaysScroll = !this.alwaysScroll;
|
this.alwaysScroll = !this.alwaysScroll;
|
||||||
if (this.alwaysScroll) {
|
if (this.alwaysScroll) {
|
||||||
this.intervalId = setInterval(() => {
|
this.scheduleScroll();
|
||||||
const logsContainer = document.getElementById('logsContainer');
|
|
||||||
if (logsContainer) {
|
|
||||||
logsContainer.scrollTop = logsContainer.scrollHeight;
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
} else {
|
} else {
|
||||||
clearInterval(this.intervalId);
|
if (this.rafId) {
|
||||||
this.intervalId = null;
|
cancelAnimationFrame(this.rafId);
|
||||||
|
this.rafId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
matchesSearch(text) {
|
matchesSearch(text) {
|
||||||
|
|
@ -41,17 +59,19 @@
|
||||||
}
|
}
|
||||||
const logsContainer = document.getElementById('logs');
|
const logsContainer = document.getElementById('logs');
|
||||||
if (!logsContainer) return false;
|
if (!logsContainer) return false;
|
||||||
|
|
||||||
// Check if selection is within the logs container
|
|
||||||
const range = selection.getRangeAt(0);
|
const range = selection.getRangeAt(0);
|
||||||
return logsContainer.contains(range.commonAncestorContainer);
|
return logsContainer.contains(range.commonAncestorContainer);
|
||||||
},
|
},
|
||||||
decodeHtml(text) {
|
decodeHtml(text) {
|
||||||
// Decode HTML entities, handling double-encoding with max iteration limit to prevent DoS
|
// Return cached result if available
|
||||||
|
if (this.decodeCache.has(text)) {
|
||||||
|
return this.decodeCache.get(text);
|
||||||
|
}
|
||||||
|
// Decode HTML entities with max iteration limit
|
||||||
let decoded = text;
|
let decoded = text;
|
||||||
let prev = '';
|
let prev = '';
|
||||||
let iterations = 0;
|
let iterations = 0;
|
||||||
const maxIterations = 3; // Prevent DoS from deeply nested HTML entities
|
const maxIterations = 3;
|
||||||
|
|
||||||
while (decoded !== prev && iterations < maxIterations) {
|
while (decoded !== prev && iterations < maxIterations) {
|
||||||
prev = decoded;
|
prev = decoded;
|
||||||
|
|
@ -59,11 +79,17 @@
|
||||||
decoded = doc.documentElement.textContent;
|
decoded = doc.documentElement.textContent;
|
||||||
iterations++;
|
iterations++;
|
||||||
}
|
}
|
||||||
|
// Cache the result (limit cache size to prevent memory bloat)
|
||||||
|
if (this.decodeCache.size > 5000) {
|
||||||
|
// Clear oldest entries when cache gets too large
|
||||||
|
const firstKey = this.decodeCache.keys().next().value;
|
||||||
|
this.decodeCache.delete(firstKey);
|
||||||
|
}
|
||||||
|
this.decodeCache.set(text, decoded);
|
||||||
return decoded;
|
return decoded;
|
||||||
},
|
},
|
||||||
renderHighlightedLog(el, text) {
|
renderHighlightedLog(el, text) {
|
||||||
// Skip re-render if user has text selected in logs (preserves copy ability)
|
// Skip re-render if user has text selected in logs
|
||||||
// But always render if the element is empty (initial render)
|
|
||||||
if (el.textContent && this.hasActiveLogSelection()) {
|
if (el.textContent && this.hasActiveLogSelection()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -82,11 +108,9 @@
|
||||||
|
|
||||||
let index = lowerText.indexOf(query, lastIndex);
|
let index = lowerText.indexOf(query, lastIndex);
|
||||||
while (index !== -1) {
|
while (index !== -1) {
|
||||||
// Add text before match
|
|
||||||
if (index > lastIndex) {
|
if (index > lastIndex) {
|
||||||
el.appendChild(document.createTextNode(decoded.substring(lastIndex, index)));
|
el.appendChild(document.createTextNode(decoded.substring(lastIndex, index)));
|
||||||
}
|
}
|
||||||
// Add highlighted match
|
|
||||||
const mark = document.createElement('span');
|
const mark = document.createElement('span');
|
||||||
mark.className = 'log-highlight';
|
mark.className = 'log-highlight';
|
||||||
mark.textContent = decoded.substring(index, index + this.searchQuery.length);
|
mark.textContent = decoded.substring(index, index + this.searchQuery.length);
|
||||||
|
|
@ -96,22 +120,28 @@
|
||||||
index = lowerText.indexOf(query, lastIndex);
|
index = lowerText.indexOf(query, lastIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add remaining text
|
|
||||||
if (lastIndex < decoded.length) {
|
if (lastIndex < decoded.length) {
|
||||||
el.appendChild(document.createTextNode(decoded.substring(lastIndex)));
|
el.appendChild(document.createTextNode(decoded.substring(lastIndex)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getMatchCount() {
|
getMatchCount() {
|
||||||
if (!this.searchQuery.trim()) return 0;
|
if (!this.searchQuery.trim()) return 0;
|
||||||
|
// Return cached count if search query hasn't changed
|
||||||
|
if (this.lastSearchQuery === this.searchQuery && this.matchCountCache !== null) {
|
||||||
|
return this.matchCountCache;
|
||||||
|
}
|
||||||
const logs = document.getElementById('logs');
|
const logs = document.getElementById('logs');
|
||||||
if (!logs) return 0;
|
if (!logs) return 0;
|
||||||
const lines = logs.querySelectorAll('[data-log-line]');
|
const lines = logs.querySelectorAll('[data-log-line]');
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
const query = this.searchQuery.toLowerCase();
|
||||||
lines.forEach(line => {
|
lines.forEach(line => {
|
||||||
if (line.dataset.logContent && line.dataset.logContent.toLowerCase().includes(this.searchQuery.toLowerCase())) {
|
if (line.dataset.logContent && line.dataset.logContent.toLowerCase().includes(query)) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.matchCountCache = count;
|
||||||
|
this.lastSearchQuery = this.searchQuery;
|
||||||
return count;
|
return count;
|
||||||
},
|
},
|
||||||
downloadLogs() {
|
downloadLogs() {
|
||||||
|
|
@ -135,15 +165,11 @@
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
},
|
},
|
||||||
stopScroll() {
|
stopScroll() {
|
||||||
// Scroll to the end one final time before disabling
|
this.scrollToBottom();
|
||||||
const logsContainer = document.getElementById('logsContainer');
|
|
||||||
if (logsContainer) {
|
|
||||||
logsContainer.scrollTop = logsContainer.scrollHeight;
|
|
||||||
}
|
|
||||||
this.alwaysScroll = false;
|
this.alwaysScroll = false;
|
||||||
if (this.intervalId) {
|
if (this.rafId) {
|
||||||
clearInterval(this.intervalId);
|
cancelAnimationFrame(this.rafId);
|
||||||
this.intervalId = null;
|
this.rafId = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
|
|
@ -153,30 +179,32 @@
|
||||||
skip();
|
skip();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Re-render logs after Livewire updates
|
// Re-render logs after Livewire updates (debounced)
|
||||||
|
let renderTimeout = null;
|
||||||
|
const debouncedRender = () => {
|
||||||
|
clearTimeout(renderTimeout);
|
||||||
|
renderTimeout = setTimeout(() => {
|
||||||
|
this.matchCountCache = null; // Invalidate match cache on new content
|
||||||
|
this.renderTrigger++;
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
document.addEventListener('livewire:navigated', () => {
|
document.addEventListener('livewire:navigated', () => {
|
||||||
this.$nextTick(() => { this.renderTrigger++; });
|
this.$nextTick(debouncedRender);
|
||||||
});
|
});
|
||||||
Livewire.hook('commit', ({ succeed }) => {
|
Livewire.hook('commit', ({ succeed }) => {
|
||||||
succeed(() => {
|
succeed(() => {
|
||||||
this.$nextTick(() => { this.renderTrigger++; });
|
this.$nextTick(debouncedRender);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// Stop auto-scroll when deployment finishes
|
// Stop auto-scroll when deployment finishes
|
||||||
Livewire.on('deploymentFinished', () => {
|
Livewire.on('deploymentFinished', () => {
|
||||||
// Wait for DOM to update with final logs before scrolling to end
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.stopScroll();
|
this.stopScroll();
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
// Start auto-scroll if deployment is in progress
|
// Start auto-scroll if deployment is in progress
|
||||||
if (this.alwaysScroll) {
|
if (this.alwaysScroll) {
|
||||||
this.intervalId = setInterval(() => {
|
this.scheduleScroll();
|
||||||
const logsContainer = document.getElementById('logsContainer');
|
|
||||||
if (logsContainer) {
|
|
||||||
logsContainer.scrollTop = logsContainer.scrollHeight;
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}">
|
}">
|
||||||
|
|
@ -212,7 +240,7 @@ class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap"></span>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
|
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
|
||||||
</svg>
|
</svg>
|
||||||
<input type="text" x-model="searchQuery" placeholder="Find in logs"
|
<input type="text" x-model.debounce.300ms="searchQuery" placeholder="Find in logs"
|
||||||
class="input input-sm w-48 pl-8 pr-8 dark:bg-coolgray-200" />
|
class="input input-sm w-48 pl-8 pr-8 dark:bg-coolgray-200" />
|
||||||
<button x-show="searchQuery" x-on:click="searchQuery = ''" type="button"
|
<button x-show="searchQuery" x-on:click="searchQuery = ''" type="button"
|
||||||
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
|
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
|
||||||
|
|
@ -222,6 +250,21 @@ class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-6
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
x-on:click="
|
||||||
|
$wire.copyLogs().then(logs => {
|
||||||
|
navigator.clipboard.writeText(logs);
|
||||||
|
Livewire.dispatch('success', ['Logs copied to clipboard.']);
|
||||||
|
});
|
||||||
|
"
|
||||||
|
title="Copy Logs"
|
||||||
|
class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
||||||
|
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
<button x-on:click="downloadLogs()" title="Download Logs"
|
<button x-on:click="downloadLogs()" title="Download Logs"
|
||||||
class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
||||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
|
@ -293,7 +336,7 @@ class="text-gray-500 dark:text-gray-400 py-2">
|
||||||
<div data-log-line data-log-content="{{ htmlspecialchars($searchableContent) }}"
|
<div data-log-line data-log-content="{{ htmlspecialchars($searchableContent) }}"
|
||||||
x-effect="renderTrigger; searchQuery; $el.classList.toggle('hidden', !matchesSearch($el.dataset.logContent))" @class([
|
x-effect="renderTrigger; searchQuery; $el.classList.toggle('hidden', !matchesSearch($el.dataset.logContent))" @class([
|
||||||
'mt-2' => isset($line['command']) && $line['command'],
|
'mt-2' => isset($line['command']) && $line['command'],
|
||||||
'flex gap-2',
|
'flex gap-2 log-line',
|
||||||
])>
|
])>
|
||||||
<span x-show="showTimestamps"
|
<span x-show="showTimestamps"
|
||||||
class="shrink-0 text-gray-500">{{ $line['timestamp'] }}</span>
|
class="shrink-0 text-gray-500">{{ $line['timestamp'] }}</span>
|
||||||
|
|
|
||||||
|
|
@ -236,11 +236,7 @@
|
||||||
@endif
|
@endif
|
||||||
<div class="flex flex-col gap-2 pt-6 pb-10">
|
<div class="flex flex-col gap-2 pt-6 pb-10">
|
||||||
@if ($application->build_pack === 'dockercompose')
|
@if ($application->build_pack === 'dockercompose')
|
||||||
@can('update', $application)
|
<div class="flex flex-col gap-2" @can('update', $application) x-init="$wire.dispatch('loadCompose', true)" @endcan>
|
||||||
<div class="flex flex-col gap-2" x-init="$wire.dispatch('loadCompose', true)">
|
|
||||||
@else
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
@endcan
|
|
||||||
<div x-data="{
|
<div x-data="{
|
||||||
baseDir: '{{ $application->base_directory }}',
|
baseDir: '{{ $application->base_directory }}',
|
||||||
composeLocation: '{{ $application->docker_compose_location }}',
|
composeLocation: '{{ $application->docker_compose_location }}',
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :title="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
|
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :title="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
||||||
<a class="{{ request()->routeIs('project.application.configuration') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('project.application.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('project.application.configuration', $parameters) }}">
|
href="{{ route('project.application.configuration', $parameters) }}">
|
||||||
Configuration
|
Configuration
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('project.application.deployment.index') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('project.application.deployment.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('project.application.deployment.index', $parameters) }}">
|
href="{{ route('project.application.deployment.index', $parameters) }}">
|
||||||
Deployments
|
Deployments
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -94,12 +94,12 @@ class="dark:text-warning">{{ $application->destination->server->name }}</span>.<
|
||||||
</a>
|
</a>
|
||||||
@if (count($parameters) > 0)
|
@if (count($parameters) > 0)
|
||||||
|
|
|
|
||||||
<a
|
<a {{ wireNavigate() }}
|
||||||
href="{{ route('project.application.deployment.index', [...$parameters, 'pull_request_id' => data_get($preview, 'pull_request_id')]) }}">
|
href="{{ route('project.application.deployment.index', [...$parameters, 'pull_request_id' => data_get($preview, 'pull_request_id')]) }}">
|
||||||
Deployment Logs
|
Deployment Logs
|
||||||
</a>
|
</a>
|
||||||
|
|
|
|
||||||
<a
|
<a {{ wireNavigate() }}
|
||||||
href="{{ route('project.application.logs', [...$parameters, 'pull_request_id' => data_get($preview, 'pull_request_id')]) }}">
|
href="{{ route('project.application.logs', [...$parameters, 'pull_request_id' => data_get($preview, 'pull_request_id')]) }}">
|
||||||
Application Logs
|
Application Logs
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -7,34 +7,34 @@
|
||||||
<livewire:project.database.heading :database="$database" />
|
<livewire:project.database.heading :database="$database" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<div class="flex flex-col items-start gap-2 min-w-fit">
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.database.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">General</a>
|
href="{{ route('project.database.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">General</a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.database.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Environment
|
href="{{ route('project.database.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Environment
|
||||||
Variables</a>
|
Variables</a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.database.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Servers</a>
|
href="{{ route('project.database.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Servers</a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} 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
|
href="{{ route('project.database.persistent-storage', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Persistent
|
||||||
Storage</a>
|
Storage</a>
|
||||||
@can('update', $database)
|
@can('update', $database)
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} 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
|
href="{{ route('project.database.import-backups', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Import
|
||||||
Backups</a>
|
Backups</a>
|
||||||
@endcan
|
@endcan
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} 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>
|
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"
|
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.database.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Resource
|
href="{{ route('project.database.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Resource
|
||||||
Limits</a>
|
Limits</a>
|
||||||
<a class="menu-item" wire:current.exact="menu-item-active"
|
<a class="menu-item" {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.database.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Resource
|
href="{{ route('project.database.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Resource
|
||||||
Operations</a>
|
Operations</a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.database.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Metrics</a>
|
href="{{ route('project.database.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Metrics</a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.database.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Tags</a>
|
href="{{ route('project.database.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Tags</a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' {{ wireNavigate() }} wire:current.exact="menu-item-active"
|
||||||
href="{{ route('project.database.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Danger
|
href="{{ route('project.database.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Danger
|
||||||
Zone</a>
|
Zone</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
<nav
|
<nav
|
||||||
class="flex overflow-x-scroll shrink-0 gap-6 items-center whitespace-nowrap sm:overflow-x-hidden scrollbar min-h-10">
|
class="flex overflow-x-scroll shrink-0 gap-6 items-center whitespace-nowrap sm:overflow-x-hidden scrollbar min-h-10">
|
||||||
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('project.database.configuration', $parameters) }}">
|
href="{{ route('project.database.configuration', $parameters) }}">
|
||||||
Configuration
|
Configuration
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -31,7 +31,7 @@ class="flex overflow-x-scroll shrink-0 gap-6 items-center whitespace-nowrap sm:o
|
||||||
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||||
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||||
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
|
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
|
||||||
<a class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('project.database.backup.index', $parameters) }}">
|
href="{{ route('project.database.backup.index', $parameters) }}">
|
||||||
Backups
|
Backups
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<x-forms.input
|
<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>"
|
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' target='_blank' 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"
|
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" canGate="update" :canResource="$database" />
|
id="customDockerRunOptions" label="Custom Docker Options" canGate="update" :canResource="$database" />
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
@endif
|
@endif
|
||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
<x-forms.input
|
<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>"
|
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' {{ wireNavigate() }} 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"
|
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" canGate="update"
|
id="customDockerRunOptions" label="Custom Docker Options" canGate="update"
|
||||||
:canResource="$database" />
|
:canResource="$database" />
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<x-forms.input
|
<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>"
|
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' {{ wireNavigate() }} 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"
|
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" canGate="update" :canResource="$database" />
|
id="customDockerRunOptions" label="Custom Docker Options" canGate="update" :canResource="$database" />
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
@endif
|
@endif
|
||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
<x-forms.input
|
<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>"
|
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' target='_blank' 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"
|
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" canGate="update" :canResource="$database" />
|
id="customDockerRunOptions" label="Custom Docker Options" canGate="update" :canResource="$database" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
placeholder="If empty, use default. See in docker docs." />
|
placeholder="If empty, use default. See in docker docs." />
|
||||||
</div>
|
</div>
|
||||||
<x-forms.input
|
<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>"
|
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' {{ wireNavigate() }} 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"
|
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" canGate="update" :canResource="$database" />
|
id="customDockerRunOptions" label="Custom Docker Options" canGate="update" :canResource="$database" />
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<x-forms.input
|
<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>"
|
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' {{ wireNavigate() }} 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"
|
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" canGate="update" :canResource="$database" />
|
id="customDockerRunOptions" label="Custom Docker Options" canGate="update" :canResource="$database" />
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
$backup->latest_log &&
|
$backup->latest_log &&
|
||||||
data_get($backup->latest_log, 'status') === 'success',
|
data_get($backup->latest_log, 'status') === 'success',
|
||||||
'border-gray-200 dark:border-coolgray-300' => !$backup->latest_log,
|
'border-gray-200 dark:border-coolgray-300' => !$backup->latest_log,
|
||||||
])
|
]) {{ wireNavigate() }}
|
||||||
href="{{ route('project.database.backup.execution', [...$parameters, 'backup_uuid' => $backup->uuid]) }}">
|
href="{{ route('project.database.backup.execution', [...$parameters, 'backup_uuid' => $backup->uuid]) }}">
|
||||||
@if ($backup->latest_log && data_get($backup->latest_log, 'status') === 'running')
|
@if ($backup->latest_log && data_get($backup->latest_log, 'status') === 'running')
|
||||||
<div class="absolute top-2 right-2">
|
<div class="absolute top-2 right-2">
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,14 @@
|
||||||
<ol class="flex flex-wrap items-center gap-y-1">
|
<ol class="flex flex-wrap items-center gap-y-1">
|
||||||
<li class="inline-flex items-center">
|
<li class="inline-flex items-center">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<a class="text-xs truncate lg:text-sm"
|
<a class="text-xs truncate lg:text-sm" {{ wireNavigate() }}
|
||||||
href="{{ route('project.show', ['project_uuid' => $project->uuid]) }}">
|
href="{{ route('project.show', ['project_uuid' => $project->uuid]) }}">
|
||||||
{{ $project->name }}</a>
|
{{ $project->name }}</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<a class="text-xs truncate lg:text-sm"
|
<a class="text-xs truncate lg:text-sm" {{ wireNavigate() }}
|
||||||
href="{{ route('project.resource.index', ['environment_uuid' => $environment->uuid, 'project_uuid' => $project->uuid]) }}">
|
href="{{ route('project.resource.index', ['environment_uuid' => $environment->uuid, 'project_uuid' => $project->uuid]) }}">
|
||||||
{{ $environment->name }}
|
{{ $environment->name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
<div class="relative z-10 flex items-center justify-center gap-4 text-xs font-bold">
|
<div class="relative z-10 flex items-center justify-center gap-4 text-xs font-bold">
|
||||||
@if ($project->environments->first())
|
@if ($project->environments->first())
|
||||||
@can('createAnyResource')
|
@can('createAnyResource')
|
||||||
<a class="hover:underline"
|
<a class="hover:underline" {{ wireNavigate() }}
|
||||||
href="{{ route('project.resource.create', [
|
href="{{ route('project.resource.create', [
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
'environment_uuid' => $project->environments->first()->uuid,
|
'environment_uuid' => $project->environments->first()->uuid,
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
@endcan
|
@endcan
|
||||||
@endif
|
@endif
|
||||||
@can('update', $project)
|
@can('update', $project)
|
||||||
<a class="hover:underline"
|
<a class="hover:underline" {{ wireNavigate() }}
|
||||||
href="{{ route('project.edit', ['project_uuid' => $project->uuid]) }}">
|
href="{{ route('project.edit', ['project_uuid' => $project->uuid]) }}">
|
||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class="loading loading-xs dark:text-warning loading-spinner"></span>
|
||||||
<div>
|
<div>
|
||||||
No private keys found.
|
No private keys found.
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('security.private-key.index') }}">
|
<a href="{{ route('security.private-key.index') }}" {{ wireNavigate() }}>
|
||||||
<x-forms.button>Create a new private key</x-forms.button>
|
<x-forms.button>Create a new private key</x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -385,7 +385,7 @@ function searchResources() {
|
||||||
<div class="flex flex-col justify-center gap-4 text-left xl:flex-row xl:flex-wrap">
|
<div class="flex flex-col justify-center gap-4 text-left xl:flex-row xl:flex-wrap">
|
||||||
@if ($onlyBuildServerAvailable)
|
@if ($onlyBuildServerAvailable)
|
||||||
<div> Only build servers are available, you need at least one server that is not set as build
|
<div> Only build servers are available, you need at least one server that is not set as build
|
||||||
server. <a class="underline dark:text-white" href="/servers">
|
server. <a class="underline dark:text-white" href="/servers" {{ wireNavigate() }}>
|
||||||
Go to servers page
|
Go to servers page
|
||||||
</a> </div>
|
</a> </div>
|
||||||
@else
|
@else
|
||||||
|
|
@ -404,7 +404,7 @@ function searchResources() {
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<div>No validated & reachable servers found. <a class="underline dark:text-white"
|
<div>No validated & reachable servers found. <a class="underline dark:text-white"
|
||||||
href="/servers">
|
href="/servers" {{ wireNavigate() }}>
|
||||||
Go to servers page
|
Go to servers page
|
||||||
</a></div>
|
</a></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,19 @@
|
||||||
<h1>Resources</h1>
|
<h1>Resources</h1>
|
||||||
@if ($environment->isEmpty())
|
@if ($environment->isEmpty())
|
||||||
@can('createAnyResource')
|
@can('createAnyResource')
|
||||||
<a class="button"
|
<a class="button" {{ wireNavigate() }}
|
||||||
href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}">
|
href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}">
|
||||||
Clone
|
Clone
|
||||||
</a>
|
</a>
|
||||||
@endcan
|
@endcan
|
||||||
@else
|
@else
|
||||||
@can('createAnyResource')
|
@can('createAnyResource')
|
||||||
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}"
|
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}" {{ wireNavigate() }}
|
||||||
class="button">+
|
class="button">+
|
||||||
New</a>
|
New</a>
|
||||||
@endcan
|
@endcan
|
||||||
@can('createAnyResource')
|
@can('createAnyResource')
|
||||||
<a class="button"
|
<a class="button" {{ wireNavigate() }}
|
||||||
href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}">
|
href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}">
|
||||||
Clone
|
Clone
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -36,7 +36,7 @@ class="button">+
|
||||||
<ol class="flex items-center">
|
<ol class="flex items-center">
|
||||||
<li class="inline-flex items-center" x-data="{ projectOpen: false, toggle() { this.projectOpen = !this.projectOpen }, open() { this.projectOpen = true }, close() { this.projectOpen = false } }">
|
<li class="inline-flex items-center" x-data="{ projectOpen: false, toggle() { this.projectOpen = !this.projectOpen }, open() { this.projectOpen = true }, close() { this.projectOpen = false } }">
|
||||||
<div class="flex items-center relative" @mouseenter="open()" @mouseleave="close()">
|
<div class="flex items-center relative" @mouseenter="open()" @mouseleave="close()">
|
||||||
<a class="text-xs truncate lg:text-sm hover:text-warning"
|
<a class="text-xs truncate lg:text-sm hover:text-warning" {{ wireNavigate() }}
|
||||||
href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}">
|
href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}">
|
||||||
{{ $project->name }}</a>
|
{{ $project->name }}</a>
|
||||||
<button type="button" @click.stop="toggle()" class="px-1 text-warning">
|
<button type="button" @click.stop="toggle()" class="px-1 text-warning">
|
||||||
|
|
@ -66,7 +66,7 @@ class="block px-4 py-2 text-sm truncate hover:bg-neutral-100 dark:hover:bg-coolg
|
||||||
@endphp
|
@endphp
|
||||||
<li class="inline-flex items-center" x-data="{ envOpen: false, activeEnv: null, envPositions: {}, activeRes: null, resPositions: {}, activeMenuEnv: null, menuPositions: {}, closeTimeout: null, envTimeout: null, resTimeout: null, menuTimeout: null, toggle() { this.envOpen = !this.envOpen; if (!this.envOpen) { this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; } }, open() { clearTimeout(this.closeTimeout); this.envOpen = true }, close() { this.closeTimeout = setTimeout(() => { this.envOpen = false; this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openEnv(id) { clearTimeout(this.closeTimeout); clearTimeout(this.envTimeout); this.activeEnv = id }, closeEnv() { this.envTimeout = setTimeout(() => { this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openRes(id) { clearTimeout(this.envTimeout); clearTimeout(this.resTimeout); this.activeRes = id }, closeRes() { this.resTimeout = setTimeout(() => { this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openMenu(id) { clearTimeout(this.resTimeout); clearTimeout(this.menuTimeout); this.activeMenuEnv = id }, closeMenu() { this.menuTimeout = setTimeout(() => { this.activeMenuEnv = null; }, 100) } }">
|
<li class="inline-flex items-center" x-data="{ envOpen: false, activeEnv: null, envPositions: {}, activeRes: null, resPositions: {}, activeMenuEnv: null, menuPositions: {}, closeTimeout: null, envTimeout: null, resTimeout: null, menuTimeout: null, toggle() { this.envOpen = !this.envOpen; if (!this.envOpen) { this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; } }, open() { clearTimeout(this.closeTimeout); this.envOpen = true }, close() { this.closeTimeout = setTimeout(() => { this.envOpen = false; this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openEnv(id) { clearTimeout(this.closeTimeout); clearTimeout(this.envTimeout); this.activeEnv = id }, closeEnv() { this.envTimeout = setTimeout(() => { this.activeEnv = null; this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openRes(id) { clearTimeout(this.envTimeout); clearTimeout(this.resTimeout); this.activeRes = id }, closeRes() { this.resTimeout = setTimeout(() => { this.activeRes = null; this.activeMenuEnv = null; }, 100) }, openMenu(id) { clearTimeout(this.resTimeout); clearTimeout(this.menuTimeout); this.activeMenuEnv = id }, closeMenu() { this.menuTimeout = setTimeout(() => { this.activeMenuEnv = null; }, 100) } }">
|
||||||
<div class="flex items-center relative" @mouseenter="open()" @mouseleave="close()">
|
<div class="flex items-center relative" @mouseenter="open()" @mouseleave="close()">
|
||||||
<a class="text-xs truncate lg:text-sm hover:text-warning"
|
<a class="text-xs truncate lg:text-sm hover:text-warning" {{ wireNavigate() }}
|
||||||
href="{{ route('project.resource.index', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => $environment->uuid]) }}">
|
href="{{ route('project.resource.index', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => $environment->uuid]) }}">
|
||||||
{{ $environment->name }}
|
{{ $environment->name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -106,7 +106,7 @@ class="flex items-center justify-between gap-2 px-4 py-2 text-sm hover:bg-neutra
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
<div class="border-t border-neutral-200 dark:border-coolgray-200 mt-1 pt-1">
|
<div class="border-t border-neutral-200 dark:border-coolgray-200 mt-1 pt-1">
|
||||||
<a href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}"
|
<a href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}" {{ wireNavigate() }}
|
||||||
class="flex items-center gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
class="flex items-center gap-2 px-4 py-2 text-sm hover:bg-neutral-100 dark:hover:bg-coolgray-200">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
|
@ -311,7 +311,7 @@ class="pl-1">
|
||||||
</div>
|
</div>
|
||||||
@if ($environment->isEmpty())
|
@if ($environment->isEmpty())
|
||||||
@can('createAnyResource')
|
@can('createAnyResource')
|
||||||
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}"
|
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}" {{ wireNavigate() }}
|
||||||
class="items-center justify-center coolbox">+ Add Resource</a>
|
class="items-center justify-center coolbox">+ Add Resource</a>
|
||||||
@else
|
@else
|
||||||
<div
|
<div
|
||||||
|
|
@ -352,7 +352,7 @@ class="font-semibold" x-text="search"></span>".</p>
|
||||||
class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
|
class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
|
||||||
<template x-for="item in filteredApplications" :key="item.uuid">
|
<template x-for="item in filteredApplications" :key="item.uuid">
|
||||||
<span>
|
<span>
|
||||||
<a class="h-24 coolbox group" :href="item.hrefLink">
|
<a class="h-24 coolbox group" :href="item.hrefLink" {{ wireNavigate() }}>
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<div class="flex gap-2 px-4">
|
<div class="flex gap-2 px-4">
|
||||||
<div class="pb-2 truncate box-title" x-text="item.name"></div>
|
<div class="pb-2 truncate box-title" x-text="item.name"></div>
|
||||||
|
|
@ -402,7 +402,7 @@ class="flex flex-wrap gap-1 pt-1 dark:group-hover:text-white group-hover:text-bl
|
||||||
class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
|
class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
|
||||||
<template x-for="item in filteredDatabases" :key="item.uuid">
|
<template x-for="item in filteredDatabases" :key="item.uuid">
|
||||||
<span>
|
<span>
|
||||||
<a class="h-24 coolbox group" :href="item.hrefLink">
|
<a class="h-24 coolbox group" :href="item.hrefLink" {{ wireNavigate() }}>
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<div class="flex gap-2 px-4">
|
<div class="flex gap-2 px-4">
|
||||||
<div class="pb-2 truncate box-title" x-text="item.name"></div>
|
<div class="pb-2 truncate box-title" x-text="item.name"></div>
|
||||||
|
|
@ -452,7 +452,7 @@ class="flex flex-wrap gap-1 pt-1 dark:group-hover:text-white group-hover:text-bl
|
||||||
class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
|
class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3">
|
||||||
<template x-for="item in filteredServices" :key="item.uuid">
|
<template x-for="item in filteredServices" :key="item.uuid">
|
||||||
<span>
|
<span>
|
||||||
<a class="h-24 coolbox group" :href="item.hrefLink">
|
<a class="h-24 coolbox group" :href="item.hrefLink" {{ wireNavigate() }}>
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<div class="flex gap-2 px-4">
|
<div class="flex gap-2 px-4">
|
||||||
<div class="pb-2 truncate box-title" x-text="item.name"></div>
|
<div class="pb-2 truncate box-title" x-text="item.name"></div>
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,27 @@
|
||||||
<div class="flex flex-col items-start gap-2 min-w-fit">
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<a class="menu-item sm:min-w-fit" target="_blank" href="{{ $service->documentation() }}">Documentation
|
<a class="menu-item sm:min-w-fit" target="_blank" href="{{ $service->documentation() }}">Documentation
|
||||||
<x-external-link /></a>
|
<x-external-link /></a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">General</a>
|
href="{{ route('project.service.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">General</a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Environment
|
href="{{ route('project.service.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Environment
|
||||||
Variables</a>
|
Variables</a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.storages', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Persistent
|
href="{{ route('project.service.storages', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Persistent
|
||||||
Storages</a>
|
Storages</a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Scheduled
|
href="{{ route('project.service.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Scheduled
|
||||||
Tasks</a>
|
Tasks</a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Webhooks</a>
|
href="{{ route('project.service.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Webhooks</a>
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Resource
|
href="{{ route('project.service.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Resource
|
||||||
Operations</a>
|
Operations</a>
|
||||||
|
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Tags</a>
|
href="{{ route('project.service.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Tags</a>
|
||||||
|
|
||||||
<a class='menu-item' wire:current.exact="menu-item-active"
|
<a class='menu-item' wire:current.exact="menu-item-active" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Danger
|
href="{{ route('project.service.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Danger
|
||||||
Zone</a>
|
Zone</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -104,7 +104,7 @@ class="w-4 h-4 dark:text-warning text-coollabs"
|
||||||
<div class="pt-2 text-xs">{{ formatContainerStatus($application->status) }}</div>
|
<div class="pt-2 text-xs">{{ formatContainerStatus($application->status) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center px-4">
|
<div class="flex items-center px-4">
|
||||||
<a class="mx-4 text-xs font-bold hover:underline"
|
<a class="mx-4 text-xs font-bold hover:underline" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $application->uuid]) }}">
|
href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $application->uuid]) }}">
|
||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -154,12 +154,12 @@ class="w-4 h-4 dark:text-warning text-coollabs"
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center px-4">
|
<div class="flex items-center px-4">
|
||||||
@if ($database->isBackupSolutionAvailable() || $database->is_migrated)
|
@if ($database->isBackupSolutionAvailable() || $database->is_migrated)
|
||||||
<a class="mx-4 text-xs font-bold hover:underline"
|
<a class="mx-4 text-xs font-bold hover:underline" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $database->uuid]) }}#backups">
|
href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $database->uuid]) }}#backups">
|
||||||
Backups
|
Backups
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
<a class="mx-4 text-xs font-bold hover:underline"
|
<a class="mx-4 text-xs font-bold hover:underline" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $database->uuid]) }}">
|
href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $database->uuid]) }}">
|
||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
||||||
<div class="navbar-main" x-data">
|
<div class="navbar-main" x-data">
|
||||||
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
|
||||||
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.configuration', $parameters) }}">
|
href="{{ route('project.service.configuration', $parameters) }}">
|
||||||
<button>Configuration</button>
|
<button>Configuration</button>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -127,7 +127,7 @@
|
||||||
@else
|
@else
|
||||||
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
|
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
|
||||||
<div class="text-error">
|
<div class="text-error">
|
||||||
Unable to deploy. <a class="underline font-bold cursor-pointer"
|
Unable to deploy. <a class="underline font-bold cursor-pointer" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.environment-variables', $parameters) }}">
|
href="{{ route('project.service.environment-variables', $parameters) }}">
|
||||||
Required environment variables missing.</a>
|
Required environment variables missing.</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<div class="flex flex-col items-start gap-2 min-w-fit">
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<a class="menu-item"
|
<a class="menu-item"
|
||||||
class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-active' : '' }}"
|
class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.configuration', [...$parameters, 'stack_service_uuid' => null]) }}">
|
href="{{ route('project.service.configuration', [...$parameters, 'stack_service_uuid' => null]) }}">
|
||||||
<button><- Back</button>
|
<button><- Back</button>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,20 @@
|
||||||
logsLoaded: false,
|
logsLoaded: false,
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
alwaysScroll: false,
|
alwaysScroll: false,
|
||||||
intervalId: null,
|
rafId: null,
|
||||||
scrollDebounce: null,
|
scrollDebounce: null,
|
||||||
colorLogs: localStorage.getItem('coolify-color-logs') === 'true',
|
colorLogs: localStorage.getItem('coolify-color-logs') === 'true',
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
renderTrigger: 0,
|
matchCount: 0,
|
||||||
containerName: '{{ $container ?? "logs" }}',
|
containerName: '{{ $container ?? "logs" }}',
|
||||||
makeFullscreen() {
|
makeFullscreen() {
|
||||||
this.fullscreen = !this.fullscreen;
|
this.fullscreen = !this.fullscreen;
|
||||||
if (this.fullscreen === false) {
|
if (this.fullscreen === false) {
|
||||||
this.alwaysScroll = false;
|
this.alwaysScroll = false;
|
||||||
clearInterval(this.intervalId);
|
if (this.rafId) {
|
||||||
|
cancelAnimationFrame(this.rafId);
|
||||||
|
this.rafId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleKeyDown(event) {
|
handleKeyDown(event) {
|
||||||
|
|
@ -24,67 +27,72 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isScrolling: false,
|
isScrolling: false,
|
||||||
|
scrollToBottom() {
|
||||||
|
const logsContainer = document.getElementById('logsContainer');
|
||||||
|
if (logsContainer) {
|
||||||
|
this.isScrolling = true;
|
||||||
|
logsContainer.scrollTop = logsContainer.scrollHeight;
|
||||||
|
setTimeout(() => { this.isScrolling = false; }, 50);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scheduleScroll() {
|
||||||
|
if (!this.alwaysScroll) return;
|
||||||
|
this.rafId = requestAnimationFrame(() => {
|
||||||
|
this.scrollToBottom();
|
||||||
|
if (this.alwaysScroll) {
|
||||||
|
setTimeout(() => this.scheduleScroll(), 250);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
toggleScroll() {
|
toggleScroll() {
|
||||||
this.alwaysScroll = !this.alwaysScroll;
|
this.alwaysScroll = !this.alwaysScroll;
|
||||||
if (this.alwaysScroll) {
|
if (this.alwaysScroll) {
|
||||||
this.intervalId = setInterval(() => {
|
this.scheduleScroll();
|
||||||
const logsContainer = document.getElementById('logsContainer');
|
|
||||||
if (logsContainer) {
|
|
||||||
this.isScrolling = true;
|
|
||||||
logsContainer.scrollTop = logsContainer.scrollHeight;
|
|
||||||
setTimeout(() => { this.isScrolling = false; }, 50);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
} else {
|
} else {
|
||||||
clearInterval(this.intervalId);
|
if (this.rafId) {
|
||||||
this.intervalId = null;
|
cancelAnimationFrame(this.rafId);
|
||||||
|
this.rafId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleScroll(event) {
|
handleScroll(event) {
|
||||||
// Skip if follow logs is disabled or this is a programmatic scroll
|
|
||||||
if (!this.alwaysScroll || this.isScrolling) return;
|
if (!this.alwaysScroll || this.isScrolling) return;
|
||||||
|
|
||||||
// Debounce scroll handling to avoid false positives from DOM mutations
|
|
||||||
// when Livewire re-renders and adds new log lines
|
|
||||||
clearTimeout(this.scrollDebounce);
|
clearTimeout(this.scrollDebounce);
|
||||||
this.scrollDebounce = setTimeout(() => {
|
this.scrollDebounce = setTimeout(() => {
|
||||||
const el = event.target;
|
const el = event.target;
|
||||||
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
|
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
|
||||||
// Use larger threshold (100px) to avoid accidental disables
|
|
||||||
if (distanceFromBottom > 100) {
|
if (distanceFromBottom > 100) {
|
||||||
this.alwaysScroll = false;
|
this.alwaysScroll = false;
|
||||||
clearInterval(this.intervalId);
|
if (this.rafId) {
|
||||||
this.intervalId = null;
|
cancelAnimationFrame(this.rafId);
|
||||||
|
this.rafId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, 150);
|
}, 150);
|
||||||
},
|
},
|
||||||
toggleColorLogs() {
|
toggleColorLogs() {
|
||||||
this.colorLogs = !this.colorLogs;
|
this.colorLogs = !this.colorLogs;
|
||||||
localStorage.setItem('coolify-color-logs', this.colorLogs);
|
localStorage.setItem('coolify-color-logs', this.colorLogs);
|
||||||
|
this.applyColorLogs();
|
||||||
},
|
},
|
||||||
getLogLevel(text) {
|
applyColorLogs() {
|
||||||
const lowerText = text.toLowerCase();
|
const logs = document.getElementById('logs');
|
||||||
// Error detection (highest priority)
|
if (!logs) return;
|
||||||
if (/\b(error|err|failed|failure|exception|fatal|panic|critical)\b/.test(lowerText)) {
|
const lines = logs.querySelectorAll('[data-log-line]');
|
||||||
return 'error';
|
lines.forEach(line => {
|
||||||
}
|
const content = (line.dataset.logContent || '').toLowerCase();
|
||||||
// Warning detection
|
line.classList.remove('log-error', 'log-warning', 'log-debug', 'log-info');
|
||||||
if (/\b(warn|warning|wrn|caution)\b/.test(lowerText)) {
|
if (!this.colorLogs) return;
|
||||||
return 'warning';
|
if (/\b(error|err|failed|failure|exception|fatal|panic|critical)\b/.test(content)) {
|
||||||
}
|
line.classList.add('log-error');
|
||||||
// Debug detection
|
} else if (/\b(warn|warning|wrn|caution)\b/.test(content)) {
|
||||||
if (/\b(debug|dbg|trace|verbose)\b/.test(lowerText)) {
|
line.classList.add('log-warning');
|
||||||
return 'debug';
|
} else if (/\b(debug|dbg|trace|verbose)\b/.test(content)) {
|
||||||
}
|
line.classList.add('log-debug');
|
||||||
// Info detection
|
} else if (/\b(info|inf|notice)\b/.test(content)) {
|
||||||
if (/\b(info|inf|notice)\b/.test(lowerText)) {
|
line.classList.add('log-info');
|
||||||
return 'info';
|
}
|
||||||
}
|
});
|
||||||
return null;
|
|
||||||
},
|
|
||||||
matchesSearch(line) {
|
|
||||||
if (!this.searchQuery.trim()) return true;
|
|
||||||
return line.toLowerCase().includes(this.searchQuery.toLowerCase());
|
|
||||||
},
|
},
|
||||||
hasActiveLogSelection() {
|
hasActiveLogSelection() {
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
|
|
@ -93,79 +101,62 @@
|
||||||
}
|
}
|
||||||
const logsContainer = document.getElementById('logs');
|
const logsContainer = document.getElementById('logs');
|
||||||
if (!logsContainer) return false;
|
if (!logsContainer) return false;
|
||||||
|
|
||||||
// Check if selection is within the logs container
|
|
||||||
const range = selection.getRangeAt(0);
|
const range = selection.getRangeAt(0);
|
||||||
return logsContainer.contains(range.commonAncestorContainer);
|
return logsContainer.contains(range.commonAncestorContainer);
|
||||||
},
|
},
|
||||||
decodeHtml(text) {
|
applySearch() {
|
||||||
// Decode HTML entities, handling double-encoding with max iteration limit to prevent DoS
|
const logs = document.getElementById('logs');
|
||||||
let decoded = text;
|
if (!logs) return;
|
||||||
let prev = '';
|
const lines = logs.querySelectorAll('[data-log-line]');
|
||||||
let iterations = 0;
|
const query = this.searchQuery.trim().toLowerCase();
|
||||||
const maxIterations = 3; // Prevent DoS from deeply nested HTML entities
|
let count = 0;
|
||||||
|
|
||||||
while (decoded !== prev && iterations < maxIterations) {
|
lines.forEach(line => {
|
||||||
prev = decoded;
|
const content = (line.dataset.logContent || '').toLowerCase();
|
||||||
const doc = new DOMParser().parseFromString(decoded, 'text/html');
|
const textSpan = line.querySelector('[data-line-text]');
|
||||||
decoded = doc.documentElement.textContent;
|
const matches = !query || content.includes(query);
|
||||||
iterations++;
|
|
||||||
}
|
|
||||||
return decoded;
|
|
||||||
},
|
|
||||||
renderHighlightedLog(el, text) {
|
|
||||||
// Skip re-render if user has text selected in logs (preserves copy ability)
|
|
||||||
// But always render if the element is empty (initial render)
|
|
||||||
if (el.textContent && this.hasActiveLogSelection()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const decoded = this.decodeHtml(text);
|
line.classList.toggle('hidden', !matches);
|
||||||
el.textContent = '';
|
if (matches && query) count++;
|
||||||
|
|
||||||
if (!this.searchQuery.trim()) {
|
// Update highlighting
|
||||||
el.textContent = decoded;
|
if (textSpan) {
|
||||||
return;
|
const originalText = textSpan.dataset.lineText || '';
|
||||||
}
|
if (!query) {
|
||||||
|
textSpan.textContent = originalText;
|
||||||
const query = this.searchQuery.toLowerCase();
|
} else if (matches) {
|
||||||
const lowerText = decoded.toLowerCase();
|
this.highlightText(textSpan, originalText, query);
|
||||||
let lastIndex = 0;
|
}
|
||||||
|
}
|
||||||
let index = lowerText.indexOf(query, lastIndex);
|
});
|
||||||
while (index !== -1) {
|
|
||||||
// Add text before match
|
this.matchCount = query ? count : 0;
|
||||||
if (index > lastIndex) {
|
},
|
||||||
el.appendChild(document.createTextNode(decoded.substring(lastIndex, index)));
|
highlightText(el, text, query) {
|
||||||
|
// Skip if user has selection
|
||||||
|
if (this.hasActiveLogSelection()) return;
|
||||||
|
|
||||||
|
el.textContent = '';
|
||||||
|
const lowerText = text.toLowerCase();
|
||||||
|
let lastIndex = 0;
|
||||||
|
let index = lowerText.indexOf(query, lastIndex);
|
||||||
|
|
||||||
|
while (index !== -1) {
|
||||||
|
if (index > lastIndex) {
|
||||||
|
el.appendChild(document.createTextNode(text.substring(lastIndex, index)));
|
||||||
}
|
}
|
||||||
// Add highlighted match
|
|
||||||
const mark = document.createElement('span');
|
const mark = document.createElement('span');
|
||||||
mark.className = 'log-highlight';
|
mark.className = 'log-highlight';
|
||||||
mark.textContent = decoded.substring(index, index + this.searchQuery.length);
|
mark.textContent = text.substring(index, index + query.length);
|
||||||
el.appendChild(mark);
|
el.appendChild(mark);
|
||||||
|
lastIndex = index + query.length;
|
||||||
lastIndex = index + this.searchQuery.length;
|
|
||||||
index = lowerText.indexOf(query, lastIndex);
|
index = lowerText.indexOf(query, lastIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add remaining text
|
if (lastIndex < text.length) {
|
||||||
if (lastIndex < decoded.length) {
|
el.appendChild(document.createTextNode(text.substring(lastIndex)));
|
||||||
el.appendChild(document.createTextNode(decoded.substring(lastIndex)));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getMatchCount() {
|
|
||||||
if (!this.searchQuery.trim()) return 0;
|
|
||||||
const logs = document.getElementById('logs');
|
|
||||||
if (!logs) return 0;
|
|
||||||
const lines = logs.querySelectorAll('[data-log-line]');
|
|
||||||
let count = 0;
|
|
||||||
lines.forEach(line => {
|
|
||||||
if (line.textContent.toLowerCase().includes(this.searchQuery.toLowerCase())) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return count;
|
|
||||||
},
|
|
||||||
downloadLogs() {
|
downloadLogs() {
|
||||||
const logs = document.getElementById('logs');
|
const logs = document.getElementById('logs');
|
||||||
if (!logs) return;
|
if (!logs) return;
|
||||||
|
|
@ -191,17 +182,23 @@
|
||||||
this.$wire.getLogs(true);
|
this.$wire.getLogs(true);
|
||||||
this.logsLoaded = true;
|
this.logsLoaded = true;
|
||||||
}
|
}
|
||||||
// Prevent Livewire from morphing logs container when text is selected
|
|
||||||
Livewire.hook('morph.updating', ({ el, component, toEl, skip }) => {
|
// Watch search query changes
|
||||||
if (el.id === 'logs' && this.hasActiveLogSelection()) {
|
this.$watch('searchQuery', () => {
|
||||||
skip();
|
this.applySearch();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
// Re-render logs after Livewire updates
|
|
||||||
Livewire.hook('commit', ({ succeed }) => {
|
// Apply colors after Livewire updates
|
||||||
succeed(() => {
|
Livewire.hook('morph.updated', ({ el }) => {
|
||||||
this.$nextTick(() => { this.renderTrigger++; });
|
if (el.id === 'logs') {
|
||||||
});
|
this.$nextTick(() => {
|
||||||
|
this.applyColorLogs();
|
||||||
|
this.applySearch();
|
||||||
|
if (this.alwaysScroll) {
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}" @keydown.window="handleKeyDown($event)">
|
}" @keydown.window="handleKeyDown($event)">
|
||||||
|
|
@ -242,7 +239,7 @@ class="absolute left-2 top-1/2 -translate-y-1/2 text-xs text-gray-400 pointer-ev
|
||||||
title="Number of Lines" {{ $streamLogs ? 'readonly' : '' }}
|
title="Number of Lines" {{ $streamLogs ? 'readonly' : '' }}
|
||||||
class="input input-sm w-32 pl-11 text-center dark:bg-coolgray-300" />
|
class="input input-sm w-32 pl-11 text-center dark:bg-coolgray-300" />
|
||||||
</form>
|
</form>
|
||||||
<span x-show="searchQuery.trim()" x-text="getMatchCount() + ' matches'"
|
<span x-show="searchQuery.trim()" x-text="matchCount + ' matches'"
|
||||||
class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap"></span>
|
class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|
@ -288,6 +285,21 @@ class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-
|
||||||
</svg>
|
</svg>
|
||||||
@endif
|
@endif
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
x-on:click="
|
||||||
|
$wire.copyLogs().then(logs => {
|
||||||
|
navigator.clipboard.writeText(logs);
|
||||||
|
Livewire.dispatch('success', ['Logs copied to clipboard.']);
|
||||||
|
});
|
||||||
|
"
|
||||||
|
title="Copy Logs"
|
||||||
|
class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
||||||
|
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
<button x-on:click="downloadLogs()" title="Download Logs"
|
<button x-on:click="downloadLogs()" title="Download Logs"
|
||||||
class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
||||||
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
|
@ -359,7 +371,7 @@ class="flex overflow-y-auto overflow-x-hidden flex-col px-4 py-2 w-full min-w-0
|
||||||
Showing last {{ number_format($maxDisplayLines) }} of {{ number_format($totalLines) }} lines
|
Showing last {{ number_format($maxDisplayLines) }} of {{ number_format($totalLines) }} lines
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<div x-show="searchQuery.trim() && getMatchCount() === 0"
|
<div x-show="searchQuery.trim() && matchCount === 0"
|
||||||
class="text-gray-500 dark:text-gray-400 py-2">
|
class="text-gray-500 dark:text-gray-400 py-2">
|
||||||
No matches found.
|
No matches found.
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -383,23 +395,12 @@ class="text-gray-500 dark:text-gray-400 py-2">
|
||||||
// Format: 2025-Dec-04 09:44:58.198879
|
// Format: 2025-Dec-04 09:44:58.198879
|
||||||
$timestamp = "{$year}-{$monthName}-{$day} {$time}.{$microseconds}";
|
$timestamp = "{$year}-{$monthName}-{$day} {$time}.{$microseconds}";
|
||||||
}
|
}
|
||||||
|
|
||||||
@endphp
|
@endphp
|
||||||
<div data-log-line data-log-content="{{ $line }}"
|
<div data-log-line data-log-content="{{ $line }}" class="flex gap-2 log-line">
|
||||||
x-effect="renderTrigger; searchQuery; $el.classList.toggle('hidden', !matchesSearch($el.dataset.logContent))"
|
|
||||||
x-bind:class="{
|
|
||||||
'bg-red-500/10 dark:bg-red-500/15': colorLogs && getLogLevel($el.dataset.logContent) === 'error',
|
|
||||||
'bg-yellow-500/10 dark:bg-yellow-500/15': colorLogs && getLogLevel($el.dataset.logContent) === 'warning',
|
|
||||||
'bg-purple-500/10 dark:bg-purple-500/15': colorLogs && getLogLevel($el.dataset.logContent) === 'debug',
|
|
||||||
'bg-blue-500/10 dark:bg-blue-500/15': colorLogs && getLogLevel($el.dataset.logContent) === 'info',
|
|
||||||
}"
|
|
||||||
class="flex gap-2">
|
|
||||||
@if ($timestamp && $showTimeStamps)
|
@if ($timestamp && $showTimeStamps)
|
||||||
<span class="shrink-0 text-gray-500">{{ $timestamp }}</span>
|
<span class="shrink-0 text-gray-500">{{ $timestamp }}</span>
|
||||||
@endif
|
@endif
|
||||||
<span data-line-text="{{ $logContent }}"
|
<span data-line-text="{{ $logContent }}" class="whitespace-pre-wrap break-all">{{ $logContent }}</span>
|
||||||
x-effect="renderTrigger; searchQuery; renderHighlightedLog($el, $el.dataset.lineText)"
|
|
||||||
class="whitespace-pre-wrap break-all"></span>
|
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<div class="alert alert-warning">Metrics are not available for Docker Compose applications yet!</div>
|
<div class="alert alert-warning">Metrics are not available for Docker Compose applications yet!</div>
|
||||||
@elseif(!$resource->destination->server->isMetricsEnabled())
|
@elseif(!$resource->destination->server->isMetricsEnabled())
|
||||||
<div class="alert alert-warning">Metrics are only available for servers with Sentinel & Metrics enabled!</div>
|
<div class="alert alert-warning">Metrics are only available for servers with Sentinel & Metrics enabled!</div>
|
||||||
<div>Go to <a class="underline dark:text-white" href="{{ route('server.show', $resource->destination->server->uuid) }}">Server settings</a> to enable it.</div>
|
<div>Go to <a class="underline dark:text-white" href="{{ route('server.show', $resource->destination->server->uuid) }}" {{ wireNavigate() }}>Server settings</a> to enable it.</div>
|
||||||
@else
|
@else
|
||||||
@if (!str($resource->status)->contains('running'))
|
@if (!str($resource->status)->contains('running'))
|
||||||
<div class="alert alert-warning">Metrics are only available when the application container is running!</div>
|
<div class="alert alert-warning">Metrics are only available when the application container is running!</div>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<div class="flex flex-col flex-wrap gap-2 pt-4">
|
<div class="flex flex-col flex-wrap gap-2 pt-4">
|
||||||
@forelse($resource->scheduled_tasks as $task)
|
@forelse($resource->scheduled_tasks as $task)
|
||||||
@if ($resource->type() == 'application')
|
@if ($resource->type() == 'application')
|
||||||
<a class="coolbox"
|
<a class="coolbox" {{ wireNavigate() }}
|
||||||
href="{{ route('project.application.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}">
|
href="{{ route('project.application.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}">
|
||||||
<span class="flex flex-col">
|
<span class="flex flex-col">
|
||||||
<span class="text-lg font-bold">{{ $task->name }}
|
<span class="text-lg font-bold">{{ $task->name }}
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
@elseif ($resource->type() == 'service')
|
@elseif ($resource->type() == 'service')
|
||||||
<a class="coolbox"
|
<a class="coolbox" {{ wireNavigate() }}
|
||||||
href="{{ route('project.service.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}">
|
href="{{ route('project.service.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}">
|
||||||
<span class="flex flex-col">
|
<span class="flex flex-col">
|
||||||
<span class="text-lg font-bold">{{ $task->name }}
|
<span class="text-lg font-bold">{{ $task->name }}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
@forelse ($project->environments->sortBy('created_at') as $environment)
|
@forelse ($project->environments->sortBy('created_at') as $environment)
|
||||||
<div class="gap-2 coolbox group">
|
<div class="gap-2 coolbox group">
|
||||||
<div class="flex flex-1 mx-6">
|
<div class="flex flex-1 mx-6">
|
||||||
<a class="flex flex-col justify-center flex-1"
|
<a class="flex flex-col justify-center flex-1" {{ wireNavigate() }}
|
||||||
href="{{ route('project.resource.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid]) }}">
|
href="{{ route('project.resource.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid]) }}">
|
||||||
<div class="font-bold dark:text-white"> {{ $environment->name }}</div>
|
<div class="font-bold dark:text-white"> {{ $environment->name }}</div>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
</a>
|
</a>
|
||||||
@can('update', $project)
|
@can('update', $project)
|
||||||
<div class="flex items-center justify-center gap-2 text-xs">
|
<div class="flex items-center justify-center gap-2 text-xs">
|
||||||
<a class="font-bold hover:underline"
|
<a class="font-bold hover:underline" {{ wireNavigate() }}
|
||||||
href="{{ route('project.environment.edit', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid]) }}">
|
href="{{ route('project.environment.edit', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid]) }}">
|
||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<h2>API Tokens</h2>
|
<h2>API Tokens</h2>
|
||||||
@if (!$isApiEnabled)
|
@if (!$isApiEnabled)
|
||||||
<div>API is disabled. If you want to use the API, please enable it in the <a
|
<div>API is disabled. If you want to use the API, please enable it in the <a
|
||||||
href="{{ route('settings.advanced') }}" class="underline dark:text-white">Settings</a> menu.</div>
|
href="{{ route('settings.advanced') }}" class="underline dark:text-white" {{ wireNavigate() }}>Settings</a> menu.</div>
|
||||||
@else
|
@else
|
||||||
<div>Tokens are created with the current team as scope.</div>
|
<div>Tokens are created with the current team as scope.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
@can('view', $key)
|
@can('view', $key)
|
||||||
{{-- Admin/Owner: Clickable link --}}
|
{{-- Admin/Owner: Clickable link --}}
|
||||||
<a class="coolbox group"
|
<a class="coolbox group"
|
||||||
href="{{ route('security.private-key.show', ['private_key_uuid' => data_get($key, 'uuid')]) }}">
|
href="{{ route('security.private-key.show', ['private_key_uuid' => data_get($key, 'uuid')]) }}" {{ wireNavigate() }}>
|
||||||
<div class="flex flex-col justify-center mx-6">
|
<div class="flex flex-col justify-center mx-6">
|
||||||
<div class="box-title">
|
<div class="box-title">
|
||||||
{{ data_get($key, 'name') }}
|
{{ data_get($key, 'name') }}
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div>Metrics are disabled for this server. Enable them in <a class="underline dark:text-white"
|
<div>Metrics are disabled for this server. Enable them in <a class="underline dark:text-white"
|
||||||
href="{{ route('server.show', ['server_uuid' => $server->uuid]) }}">General</a> settings.</div>
|
href="{{ route('server.show', ['server_uuid' => $server->uuid]) }}" {{ wireNavigate() }}>General</a> settings.</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,12 @@
|
||||||
<h4 class="pt-4 pb-2">Available Destinations</h4>
|
<h4 class="pt-4 pb-2">Available Destinations</h4>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
@foreach ($server->standaloneDockers as $docker)
|
@foreach ($server->standaloneDockers as $docker)
|
||||||
<a href="{{ route('destination.show', ['destination_uuid' => data_get($docker, 'uuid')]) }}">
|
<a href="{{ route('destination.show', ['destination_uuid' => data_get($docker, 'uuid')]) }}" {{ wireNavigate() }}>
|
||||||
<x-forms.button>{{ data_get($docker, 'network') }} </x-forms.button>
|
<x-forms.button>{{ data_get($docker, 'network') }} </x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
@endforeach
|
@endforeach
|
||||||
@foreach ($server->swarmDockers as $docker)
|
@foreach ($server->swarmDockers as $docker)
|
||||||
<a href="{{ route('destination.show', ['destination_uuid' => data_get($docker, 'uuid')]) }}">
|
<a href="{{ route('destination.show', ['destination_uuid' => data_get($docker, 'uuid')]) }}" {{ wireNavigate() }}>
|
||||||
<x-forms.button>{{ data_get($docker, 'network') }} </x-forms.button>
|
<x-forms.button>{{ data_get($docker, 'network') }} </x-forms.button>
|
||||||
</a>
|
</a>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
<div class="subtitle">All your servers are here.</div>
|
<div class="subtitle">All your servers are here.</div>
|
||||||
<div class="grid gap-4 lg:grid-cols-2 -mt-1">
|
<div class="grid gap-4 lg:grid-cols-2 -mt-1">
|
||||||
@forelse ($servers as $server)
|
@forelse ($servers as $server)
|
||||||
<a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}"
|
<a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}" {{ wireNavigate() }}
|
||||||
@class([
|
@class([
|
||||||
'gap-2 border cursor-pointer coolbox group',
|
'gap-2 border cursor-pointer coolbox group',
|
||||||
'border-red-500' =>
|
'border-red-500' =>
|
||||||
|
|
|
||||||
|
|
@ -65,14 +65,14 @@ class="mx-1 dark:hover:fill-white fill-black dark:fill-warning">
|
||||||
class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap pt-2">
|
class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap pt-2">
|
||||||
<a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}" href="{{ route('server.show', [
|
<a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}" href="{{ route('server.show', [
|
||||||
'server_uuid' => data_get($server, 'uuid'),
|
'server_uuid' => data_get($server, 'uuid'),
|
||||||
]) }}">
|
]) }}" {{ wireNavigate() }}>
|
||||||
Configuration
|
Configuration
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
|
@if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
|
||||||
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }} flex items-center gap-1" href="{{ route('server.proxy', [
|
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }} flex items-center gap-1" href="{{ route('server.proxy', [
|
||||||
'server_uuid' => data_get($server, 'uuid'),
|
'server_uuid' => data_get($server, 'uuid'),
|
||||||
]) }}">
|
]) }}" {{ wireNavigate() }}>
|
||||||
Proxy
|
Proxy
|
||||||
@if ($this->hasTraefikOutdated)
|
@if ($this->hasTraefikOutdated)
|
||||||
<svg class="w-4 h-4 text-warning" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
<svg class="w-4 h-4 text-warning" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
@ -84,7 +84,7 @@ class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar
|
||||||
@endif
|
@endif
|
||||||
<a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}" href="{{ route('server.resources', [
|
<a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}" href="{{ route('server.resources', [
|
||||||
'server_uuid' => data_get($server, 'uuid'),
|
'server_uuid' => data_get($server, 'uuid'),
|
||||||
]) }}">
|
]) }}" {{ wireNavigate() }}>
|
||||||
Resources
|
Resources
|
||||||
</a>
|
</a>
|
||||||
@can('canAccessTerminal')
|
@can('canAccessTerminal')
|
||||||
|
|
@ -97,7 +97,7 @@ class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar
|
||||||
@can('update', $server)
|
@can('update', $server)
|
||||||
<a class="{{ request()->routeIs('server.security.patches') ? 'dark:text-white' : '' }}" href="{{ route('server.security.patches', [
|
<a class="{{ request()->routeIs('server.security.patches') ? 'dark:text-white' : '' }}" href="{{ route('server.security.patches', [
|
||||||
'server_uuid' => data_get($server, 'uuid'),
|
'server_uuid' => data_get($server, 'uuid'),
|
||||||
]) }}">
|
]) }}" {{ wireNavigate() }}>
|
||||||
Security
|
Security
|
||||||
</a>
|
</a>
|
||||||
@endcan
|
@endcan
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if ($containers->count() > 0)
|
@if ($activeTab === 'managed')
|
||||||
@if ($activeTab === 'managed')
|
@php
|
||||||
|
$managedResources = $server->definedResources()->sortBy('name', SORT_NATURAL);
|
||||||
|
@endphp
|
||||||
|
@if ($managedResources->count() > 0)
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
|
|
@ -59,7 +62,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@forelse ($server->definedResources()->sortBy('name',SORT_NATURAL) as $resource)
|
@foreach ($managedResources as $resource)
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||||
{{ data_get($resource->project(), 'name') }}
|
{{ data_get($resource->project(), 'name') }}
|
||||||
|
|
@ -68,7 +71,7 @@
|
||||||
{{ data_get($resource, 'environment.name') }}
|
{{ data_get($resource, 'environment.name') }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4 text-sm whitespace-nowrap hover:underline">
|
<td class="px-5 py-4 text-sm whitespace-nowrap hover:underline">
|
||||||
<a class=""
|
<a class="" {{ wireNavigate() }}
|
||||||
href="{{ $resource->link() }}">{{ $resource->name }}
|
href="{{ $resource->link() }}">{{ $resource->name }}
|
||||||
<x-internal-link /></a>
|
<x-internal-link /></a>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -83,8 +86,7 @@
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@endforeach
|
||||||
@endforelse
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -92,7 +94,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@elseif ($activeTab === 'unmanaged')
|
@else
|
||||||
|
<div>No managed resources found.</div>
|
||||||
|
@endif
|
||||||
|
@elseif ($activeTab === 'unmanaged')
|
||||||
|
@if (count($unmanagedContainers) > 0)
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
|
|
@ -116,7 +122,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@forelse ($containers->sortBy('name',SORT_NATURAL) as $resource)
|
@foreach (collect($unmanagedContainers)->sortBy('name', SORT_NATURAL) as $resource)
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||||
{{ data_get($resource, 'Names') }}
|
{{ data_get($resource, 'Names') }}
|
||||||
|
|
@ -146,19 +152,15 @@
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@endforeach
|
||||||
@endforelse
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
</div>
|
||||||
@else
|
@else
|
||||||
@if ($activeTab === 'managed')
|
|
||||||
<div>No managed resources found.</div>
|
|
||||||
@elseif ($activeTab === 'unmanaged')
|
|
||||||
<div>No unmanaged resources found.</div>
|
<div>No unmanaged resources found.</div>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<span class="text-xs text-neutral-500">(experimental)</span>
|
<span class="text-xs text-neutral-500">(experimental)</span>
|
||||||
<x-helper
|
<x-helper
|
||||||
helper="Only available for apt, dnf and zypper package managers atm, more coming
|
helper="Only available for apt, dnf and zypper package managers atm, more coming
|
||||||
soon.<br/>Status notifications sent every week.<br/>You can disable notifications in the <a class='dark:text-white underline' href='{{ route('notifications.email') }}'>notification settings</a>." />
|
soon.<br/>Status notifications sent every week.<br/>You can disable notifications in the <a class='dark:text-white underline' href='{{ route('notifications.email') }}' {{ wireNavigate() }}>notification settings</a>." />
|
||||||
@if (isDev())
|
@if (isDev())
|
||||||
<x-forms.button type="button" wire:click="sendTestEmail">
|
<x-forms.button type="button" wire:click="sendTestEmail">
|
||||||
Send Test Email (dev only)</x-forms.button>
|
Send Test Email (dev only)</x-forms.button>
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
Please validate your server to enable Instance Backup.
|
Please validate your server to enable Instance Backup.
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ route('server.show', [$server->uuid]) }}"
|
<a href="{{ route('server.show', [$server->uuid]) }}"
|
||||||
class="text-black hover:text-gray-700 dark:text-white dark:hover:text-gray-200 underline">
|
class="text-black hover:text-gray-700 dark:text-white dark:hover:text-gray-200 underline" {{ wireNavigate() }}>
|
||||||
Go to Server Settings to Validate
|
Go to Server Settings to Validate
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,14 @@ class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
environments!
|
environments!
|
||||||
</x-callout>
|
</x-callout>
|
||||||
@endif
|
@endif
|
||||||
|
<h4 class="pt-4">UI Settings</h4>
|
||||||
|
<div class="md:w-96">
|
||||||
|
<x-forms.checkbox instantSave id="is_wire_navigate_enabled" label="SPA Navigation"
|
||||||
|
helper="Enable single-page application (SPA) style navigation with prefetching on hover. When enabled, page transitions are smoother without full page reloads and pages are prefetched when hovering over links. Disable if you experience navigation issues." />
|
||||||
|
</div>
|
||||||
<h4 class="pt-4">Confirmation Settings</h4>
|
<h4 class="pt-4">Confirmation Settings</h4>
|
||||||
<div class="md:w-96">
|
<div class="md:w-96">
|
||||||
<x-forms.checkbox instantSave id=" is_sponsorship_popup_enabled" label="Show Sponsorship Popup"
|
<x-forms.checkbox instantSave id="is_sponsorship_popup_enabled" label="Show Sponsorship Popup"
|
||||||
helper="Show monthly sponsorship reminders to support Coolify development. Disable to hide these messages permanently." />
|
helper="Show monthly sponsorship reminders to support Coolify development. Disable to hide these messages permanently." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
href="{{ route('shared-variables.environment.show', [
|
href="{{ route('shared-variables.environment.show', [
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
'environment_uuid' => $environment->uuid,
|
'environment_uuid' => $environment->uuid,
|
||||||
]) }}">
|
]) }}" {{ wireNavigate() }}>
|
||||||
<div class="flex flex-col justify-center flex-1 mx-6 ">
|
<div class="flex flex-col justify-center flex-1 mx-6 ">
|
||||||
<div class="box-title"> {{ $environment->name }}</div>
|
<div class="box-title"> {{ $environment->name }}</div>
|
||||||
<div class="box-description">
|
<div class="box-description">
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,19 @@
|
||||||
<div class="subtitle">Set Team / Project / Environment wide variables.</div>
|
<div class="subtitle">Set Team / Project / Environment wide variables.</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 -mt-1">
|
<div class="flex flex-col gap-2 -mt-1">
|
||||||
<a class="coolbox group" href="{{ route('shared-variables.team.index') }}">
|
<a class="coolbox group" href="{{ route('shared-variables.team.index') }}" {{ wireNavigate() }}>
|
||||||
<div class="flex flex-col justify-center mx-6">
|
<div class="flex flex-col justify-center mx-6">
|
||||||
<div class="box-title">Team wide</div>
|
<div class="box-title">Team wide</div>
|
||||||
<div class="box-description">Usable for all resources in a team.</div>
|
<div class="box-description">Usable for all resources in a team.</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a class="coolbox group" href="{{ route('shared-variables.project.index') }}">
|
<a class="coolbox group" href="{{ route('shared-variables.project.index') }}" {{ wireNavigate() }}>
|
||||||
<div class="flex flex-col justify-center mx-6">
|
<div class="flex flex-col justify-center mx-6">
|
||||||
<div class="box-title">Project wide</div>
|
<div class="box-title">Project wide</div>
|
||||||
<div class="box-description">Usable for all resources in a project.</div>
|
<div class="box-description">Usable for all resources in a project.</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a class="coolbox group" href="{{ route('shared-variables.environment.index') }}">
|
<a class="coolbox group" href="{{ route('shared-variables.environment.index') }}" {{ wireNavigate() }}>
|
||||||
<div class="flex flex-col justify-center mx-6">
|
<div class="flex flex-col justify-center mx-6">
|
||||||
<div class="box-title">Environment wide</div>
|
<div class="box-title">Environment wide</div>
|
||||||
<div class="box-description">Usable for all resources in an environment.</div>
|
<div class="box-description">Usable for all resources in an environment.</div>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
@forelse ($projects as $project)
|
@forelse ($projects as $project)
|
||||||
<a class="coolbox group"
|
<a class="coolbox group"
|
||||||
href="{{ route('shared-variables.project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
href="{{ route('shared-variables.project.show', ['project_uuid' => data_get($project, 'uuid')]) }}" {{ wireNavigate() }}>
|
||||||
<div class="flex flex-col justify-center mx-6 ">
|
<div class="flex flex-col justify-center mx-6 ">
|
||||||
<div class="box-title">{{ $project->name }}</div>
|
<div class="box-title">{{ $project->name }}</div>
|
||||||
<div class="box-description ">
|
<div class="box-description ">
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,7 @@ class="bg-transparent border-transparent hover:bg-transparent hover:border-trans
|
||||||
</td>
|
</td>
|
||||||
<td class="px-5 py-4 text-sm whitespace-nowrap"><a
|
<td class="px-5 py-4 text-sm whitespace-nowrap"><a
|
||||||
class=""
|
class=""
|
||||||
|
{{ wireNavigate() }}
|
||||||
href="{{ $resource->link() }}">{{ $resource->name }}
|
href="{{ $resource->link() }}">{{ $resource->name }}
|
||||||
<x-internal-link /></a>
|
<x-internal-link /></a>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue