Merge branch 'next' into feat/deployment-token
This commit is contained in:
@@ -30,7 +30,7 @@ class GetContainersStatus
|
|||||||
$this->containerReplicates = $containerReplicates;
|
$this->containerReplicates = $containerReplicates;
|
||||||
$this->server = $server;
|
$this->server = $server;
|
||||||
if (! $this->server->isFunctional()) {
|
if (! $this->server->isFunctional()) {
|
||||||
return 'Server is not ready.';
|
return 'Server is not functional.';
|
||||||
}
|
}
|
||||||
$this->applications = $this->server->applications();
|
$this->applications = $this->server->applications();
|
||||||
$skip_these_applications = collect([]);
|
$skip_these_applications = collect([]);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
$user = User::create([
|
$user = User::create([
|
||||||
'id' => 0,
|
'id' => 0,
|
||||||
'name' => $input['name'],
|
'name' => $input['name'],
|
||||||
'email' => $input['email'],
|
'email' => strtolower($input['email']),
|
||||||
'password' => Hash::make($input['password']),
|
'password' => Hash::make($input['password']),
|
||||||
]);
|
]);
|
||||||
$team = $user->teams()->first();
|
$team = $user->teams()->first();
|
||||||
@@ -52,7 +52,7 @@ class CreateNewUser implements CreatesNewUsers
|
|||||||
} else {
|
} else {
|
||||||
$user = User::create([
|
$user = User::create([
|
||||||
'name' => $input['name'],
|
'name' => $input['name'],
|
||||||
'email' => $input['email'],
|
'email' => strtolower($input['email']),
|
||||||
'password' => Hash::make($input['password']),
|
'password' => Hash::make($input['password']),
|
||||||
]);
|
]);
|
||||||
$team = $user->teams()->first();
|
$team = $user->teams()->first();
|
||||||
|
|||||||
41
app/Actions/Server/ResourcesCheck.php
Normal file
41
app/Actions/Server/ResourcesCheck.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class ResourcesCheck
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$seconds = 60;
|
||||||
|
try {
|
||||||
|
Application::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
ServiceApplication::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
ServiceDatabase::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandalonePostgresql::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneRedis::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneMongodb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneMysql::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneMariadb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneKeydb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneDragonfly::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
StandaloneClickhouse::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
269
app/Actions/Server/ServerCheck.php
Normal file
269
app/Actions/Server/ServerCheck.php
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Proxy\CheckProxy;
|
||||||
|
use App\Actions\Proxy\StartProxy;
|
||||||
|
use App\Jobs\CheckAndStartSentinelJob;
|
||||||
|
use App\Jobs\ServerStorageCheckJob;
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\ApplicationPreview;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
|
use Arr;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class ServerCheck
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public Server $server;
|
||||||
|
|
||||||
|
public bool $isSentinel = false;
|
||||||
|
|
||||||
|
public $containers;
|
||||||
|
|
||||||
|
public $databases;
|
||||||
|
|
||||||
|
public function handle(Server $server, $data = null)
|
||||||
|
{
|
||||||
|
$this->server = $server;
|
||||||
|
try {
|
||||||
|
if ($this->server->isFunctional() === false) {
|
||||||
|
return 'Server is not functional.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->server->isSwarmWorker() && ! $this->server->isBuildServer()) {
|
||||||
|
|
||||||
|
if (isset($data)) {
|
||||||
|
$data = collect($data);
|
||||||
|
|
||||||
|
$this->server->sentinelHeartbeat();
|
||||||
|
|
||||||
|
$this->containers = collect(data_get($data, 'containers'));
|
||||||
|
|
||||||
|
$filesystemUsageRoot = data_get($data, 'filesystem_usage_root.used_percentage');
|
||||||
|
ServerStorageCheckJob::dispatch($this->server, $filesystemUsageRoot);
|
||||||
|
|
||||||
|
$containerReplicates = null;
|
||||||
|
$this->isSentinel = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
|
||||||
|
// ServerStorageCheckJob::dispatch($this->server);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($this->containers)) {
|
||||||
|
return 'No containers found.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($containerReplicates)) {
|
||||||
|
foreach ($containerReplicates as $containerReplica) {
|
||||||
|
$name = data_get($containerReplica, 'Name');
|
||||||
|
$this->containers = $this->containers->map(function ($container) use ($name, $containerReplica) {
|
||||||
|
if (data_get($container, 'Spec.Name') === $name) {
|
||||||
|
$replicas = data_get($containerReplica, 'Replicas');
|
||||||
|
$running = str($replicas)->explode('/')[0];
|
||||||
|
$total = str($replicas)->explode('/')[1];
|
||||||
|
if ($running === $total) {
|
||||||
|
data_set($container, 'State.Status', 'running');
|
||||||
|
data_set($container, 'State.Health.Status', 'healthy');
|
||||||
|
} else {
|
||||||
|
data_set($container, 'State.Status', 'starting');
|
||||||
|
data_set($container, 'State.Health.Status', 'unhealthy');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $container;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->checkContainers();
|
||||||
|
|
||||||
|
if ($this->server->isSentinelEnabled() && $this->isSentinel === false) {
|
||||||
|
CheckAndStartSentinelJob::dispatch($this->server);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->server->isLogDrainEnabled()) {
|
||||||
|
$this->checkLogDrainContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->server->proxySet() && ! $this->server->proxy->force_stop) {
|
||||||
|
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === '/coolify-proxy';
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundProxyContainer) {
|
||||||
|
try {
|
||||||
|
$shouldStart = CheckProxy::run($this->server);
|
||||||
|
if ($shouldStart) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||||
|
$this->server->save();
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkLogDrainContainer()
|
||||||
|
{
|
||||||
|
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
|
||||||
|
return data_get($value, 'Name') === '/coolify-log-drain';
|
||||||
|
})->first();
|
||||||
|
if ($foundLogDrainContainer) {
|
||||||
|
$status = data_get($foundLogDrainContainer, 'State.Status');
|
||||||
|
if ($status !== 'running') {
|
||||||
|
StartLogDrain::dispatch($this->server);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StartLogDrain::dispatch($this->server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkContainers()
|
||||||
|
{
|
||||||
|
foreach ($this->containers as $container) {
|
||||||
|
if ($this->isSentinel) {
|
||||||
|
$labels = Arr::undot(data_get($container, 'labels'));
|
||||||
|
} else {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$labels = Arr::undot(data_get($container, 'Spec.Labels'));
|
||||||
|
} else {
|
||||||
|
$labels = Arr::undot(data_get($container, 'Config.Labels'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
$managed = data_get($labels, 'coolify.managed');
|
||||||
|
if (! $managed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$uuid = data_get($labels, 'coolify.name');
|
||||||
|
if (! $uuid) {
|
||||||
|
$uuid = data_get($labels, 'com.docker.compose.service');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isSentinel) {
|
||||||
|
$containerStatus = data_get($container, 'state');
|
||||||
|
$containerHealth = data_get($container, 'health_status');
|
||||||
|
} else {
|
||||||
|
$containerStatus = data_get($container, 'State.Status');
|
||||||
|
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||||
|
}
|
||||||
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
|
|
||||||
|
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
|
$serviceId = data_get($labels, 'coolify.serviceId');
|
||||||
|
$databaseId = data_get($labels, 'coolify.databaseId');
|
||||||
|
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
|
|
||||||
|
if ($applicationId) {
|
||||||
|
// Application
|
||||||
|
if ($pullRequestId != 0) {
|
||||||
|
if (str($applicationId)->contains('-')) {
|
||||||
|
$applicationId = str($applicationId)->before('-');
|
||||||
|
}
|
||||||
|
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
|
if ($preview) {
|
||||||
|
$preview->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$application = Application::where('id', $applicationId)->first();
|
||||||
|
if ($application) {
|
||||||
|
$application->update([
|
||||||
|
'status' => $containerStatus,
|
||||||
|
'last_online_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif (isset($serviceId)) {
|
||||||
|
// Service
|
||||||
|
$subType = data_get($labels, 'coolify.service.subType');
|
||||||
|
$subId = data_get($labels, 'coolify.service.subId');
|
||||||
|
$service = Service::where('id', $serviceId)->first();
|
||||||
|
if (! $service) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($subType === 'application') {
|
||||||
|
$service = ServiceApplication::where('id', $subId)->first();
|
||||||
|
} else {
|
||||||
|
$service = ServiceDatabase::where('id', $subId)->first();
|
||||||
|
}
|
||||||
|
if ($service) {
|
||||||
|
$service->update([
|
||||||
|
'status' => $containerStatus,
|
||||||
|
'last_online_at' => now(),
|
||||||
|
]);
|
||||||
|
if ($subType === 'database') {
|
||||||
|
$isPublic = data_get($service, 'is_public');
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->isSentinel) {
|
||||||
|
return data_get($value, 'name') === $uuid.'-proxy';
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($service);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Database
|
||||||
|
if (is_null($this->databases)) {
|
||||||
|
$this->databases = $this->server->databases();
|
||||||
|
}
|
||||||
|
$database = $this->databases->where('uuid', $uuid)->first();
|
||||||
|
if ($database) {
|
||||||
|
$database->update([
|
||||||
|
'status' => $containerStatus,
|
||||||
|
'last_online_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$isPublic = data_get($database, 'is_public');
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->isSentinel) {
|
||||||
|
return data_get($value, 'name') === $uuid.'-proxy';
|
||||||
|
} else {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($database);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ use App\Models\Environment;
|
|||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
@@ -41,6 +42,7 @@ class Init extends Command
|
|||||||
$this->disable_metrics();
|
$this->disable_metrics();
|
||||||
$this->replace_slash_in_environment_name();
|
$this->replace_slash_in_environment_name();
|
||||||
$this->restore_coolify_db_backup();
|
$this->restore_coolify_db_backup();
|
||||||
|
$this->update_user_emails();
|
||||||
//
|
//
|
||||||
$this->update_traefik_labels();
|
$this->update_traefik_labels();
|
||||||
if (! isCloud() || $this->option('force-cloud')) {
|
if (! isCloud() || $this->option('force-cloud')) {
|
||||||
@@ -92,6 +94,15 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function update_user_emails()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
User::whereRaw('email ~ \'[A-Z]\'')->get()->each(fn (User $user) => $user->update(['email' => strtolower($user->email)]));
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in updating user emails: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function update_traefik_labels()
|
private function update_traefik_labels()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
58
app/Console/Commands/Weird.php
Normal file
58
app/Console/Commands/Weird.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Actions\Server\ServerCheck;
|
||||||
|
use App\Enums\ProxyStatus;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Str;
|
||||||
|
|
||||||
|
class Weird extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'weird {--number=1} {--run}';
|
||||||
|
|
||||||
|
protected $description = 'Weird stuff';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (! isDev()) {
|
||||||
|
$this->error('This command can only be run in development mode');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$run = $this->option('run');
|
||||||
|
if ($run) {
|
||||||
|
$servers = Server::all();
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
ServerCheck::dispatch($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$number = $this->option('number');
|
||||||
|
for ($i = 0; $i < $number; $i++) {
|
||||||
|
$uuid = Str::uuid();
|
||||||
|
$server = Server::create([
|
||||||
|
'name' => 'localhost-'.$uuid,
|
||||||
|
'description' => 'This is a test docker container in development mode',
|
||||||
|
'ip' => 'coolify-testing-host',
|
||||||
|
'team_id' => 0,
|
||||||
|
'private_key_id' => 1,
|
||||||
|
'proxy' => [
|
||||||
|
'type' => ProxyTypes::NONE->value,
|
||||||
|
'status' => ProxyStatus::EXITED->value,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$server->settings->update([
|
||||||
|
'is_usable' => true,
|
||||||
|
'is_reachable' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ use App\Jobs\PullTemplatesFromCDN;
|
|||||||
use App\Jobs\ScheduledTaskJob;
|
use App\Jobs\ScheduledTaskJob;
|
||||||
use App\Jobs\ServerCheckJob;
|
use App\Jobs\ServerCheckJob;
|
||||||
use App\Jobs\ServerCleanupMux;
|
use App\Jobs\ServerCleanupMux;
|
||||||
|
use App\Jobs\ServerStorageCheckJob;
|
||||||
use App\Jobs\UpdateCoolifyJob;
|
use App\Jobs\UpdateCoolifyJob;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
@@ -31,7 +32,7 @@ class Kernel extends ConsoleKernel
|
|||||||
|
|
||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
$this->allServers = Server::where('ip', '!=', '1.2.3.4')->get();
|
$this->allServers = Server::where('ip', '!=', '1.2.3.4');
|
||||||
|
|
||||||
$this->settings = instanceSettings();
|
$this->settings = instanceSettings();
|
||||||
|
|
||||||
@@ -41,13 +42,16 @@ class Kernel extends ConsoleKernel
|
|||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyMinute();
|
$schedule->command('horizon:snapshot')->everyMinute();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||||
|
$schedule->job(new CheckHelperImageJob)->everyFiveMinutes()->onOneServer();
|
||||||
|
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->checkScheduledBackups($schedule);
|
|
||||||
$this->checkResources($schedule);
|
$this->checkResources($schedule);
|
||||||
|
|
||||||
|
$this->checkScheduledBackups($schedule);
|
||||||
$this->checkScheduledTasks($schedule);
|
$this->checkScheduledTasks($schedule);
|
||||||
|
|
||||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||||
|
|
||||||
$schedule->job(new CheckHelperImageJob)->everyFiveMinutes()->onOneServer();
|
|
||||||
} else {
|
} else {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
@@ -57,9 +61,11 @@ class Kernel extends ConsoleKernel
|
|||||||
$this->scheduleUpdates($schedule);
|
$this->scheduleUpdates($schedule);
|
||||||
|
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->checkScheduledBackups($schedule);
|
|
||||||
$this->checkResources($schedule);
|
$this->checkResources($schedule);
|
||||||
|
|
||||||
$this->pullImages($schedule);
|
$this->pullImages($schedule);
|
||||||
|
|
||||||
|
$this->checkScheduledBackups($schedule);
|
||||||
$this->checkScheduledTasks($schedule);
|
$this->checkScheduledTasks($schedule);
|
||||||
|
|
||||||
$schedule->command('cleanup:database --yes')->daily();
|
$schedule->command('cleanup:database --yes')->daily();
|
||||||
@@ -69,7 +75,7 @@ class Kernel extends ConsoleKernel
|
|||||||
|
|
||||||
private function pullImages($schedule): void
|
private function pullImages($schedule): void
|
||||||
{
|
{
|
||||||
$servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true);
|
$servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get();
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
if ($server->isSentinelEnabled()) {
|
if ($server->isSentinelEnabled()) {
|
||||||
$schedule->job(function () use ($server) {
|
$schedule->job(function () use ($server) {
|
||||||
@@ -103,23 +109,33 @@ class Kernel extends ConsoleKernel
|
|||||||
private function checkResources($schedule): void
|
private function checkResources($schedule): void
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
$servers = $this->allServers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false);
|
$servers = $this->allServers->whereHas('team.subscription')->get();
|
||||||
$own = Team::find(0)->servers;
|
$own = Team::find(0)->servers;
|
||||||
$servers = $servers->merge($own);
|
$servers = $servers->merge($own);
|
||||||
} else {
|
} else {
|
||||||
$servers = $this->allServers;
|
$servers = $this->allServers->get();
|
||||||
}
|
}
|
||||||
|
// $schedule->job(new \App\Jobs\ResourcesCheck)->everyMinute()->onOneServer();
|
||||||
|
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$lastSentinelUpdate = $server->sentinel_updated_at;
|
|
||||||
$serverTimezone = $server->settings->server_timezone;
|
$serverTimezone = $server->settings->server_timezone;
|
||||||
|
|
||||||
|
// Sentinel check
|
||||||
|
$lastSentinelUpdate = $server->sentinel_updated_at;
|
||||||
if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
|
if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
|
||||||
|
// Check container status every minute if Sentinel does not activated
|
||||||
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
||||||
|
// $schedule->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer();
|
||||||
|
|
||||||
|
// Check storage usage every 10 minutes if Sentinel does not activated
|
||||||
|
$schedule->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
|
||||||
}
|
}
|
||||||
if ($server->settings->force_docker_cleanup) {
|
if ($server->settings->force_docker_cleanup) {
|
||||||
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
||||||
} else {
|
} else {
|
||||||
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
|
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup multiplexed connections every hour
|
// Cleanup multiplexed connections every hour
|
||||||
$schedule->job(new ServerCleanupMux($server))->hourly()->onOneServer();
|
$schedule->job(new ServerCleanupMux($server))->hourly()->onOneServer();
|
||||||
|
|
||||||
@@ -134,14 +150,11 @@ class Kernel extends ConsoleKernel
|
|||||||
|
|
||||||
private function checkScheduledBackups($schedule): void
|
private function checkScheduledBackups($schedule): void
|
||||||
{
|
{
|
||||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
$scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get();
|
||||||
if ($scheduled_backups->isEmpty()) {
|
if ($scheduled_backups->isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
foreach ($scheduled_backups as $scheduled_backup) {
|
foreach ($scheduled_backups as $scheduled_backup) {
|
||||||
if (! $scheduled_backup->enabled) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (is_null(data_get($scheduled_backup, 'database'))) {
|
if (is_null(data_get($scheduled_backup, 'database'))) {
|
||||||
$scheduled_backup->delete();
|
$scheduled_backup->delete();
|
||||||
|
|
||||||
@@ -150,7 +163,7 @@ class Kernel extends ConsoleKernel
|
|||||||
|
|
||||||
$server = $scheduled_backup->server();
|
$server = $scheduled_backup->server();
|
||||||
|
|
||||||
if (! $server) {
|
if (is_null($server)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$serverTimezone = $server->settings->server_timezone;
|
$serverTimezone = $server->settings->server_timezone;
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||||
]);
|
]);
|
||||||
if (! $this->server->isFunctional()) {
|
if ($this->server->isFunctional() === false) {
|
||||||
$this->application_deployment_queue->addLogEntry('Server is not functional.');
|
$this->application_deployment_queue->addLogEntry('Server is not functional.');
|
||||||
$this->fail('Server is not functional.');
|
$this->fail('Server is not functional.');
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if (is_null($this->containers)) {
|
if (is_null($this->containers)) {
|
||||||
return 'No containers found.';
|
return 'No containers found.';
|
||||||
}
|
}
|
||||||
ServerStorageCheckJob::dispatch($this->server);
|
|
||||||
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
|
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
|
||||||
|
|
||||||
if ($this->server->isSentinelEnabled()) {
|
if ($this->server->isSentinelEnabled()) {
|
||||||
|
|||||||
32
app/Jobs/ServerCheckNewJob.php
Normal file
32
app/Jobs/ServerCheckNewJob.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Server\ServerCheck;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class ServerCheckNewJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $tries = 1;
|
||||||
|
|
||||||
|
public $timeout = 60;
|
||||||
|
|
||||||
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ServerCheck::run($this->server);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,8 +30,8 @@ class ServerStorageCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isFunctional()) {
|
if ($this->server->isFunctional() === false) {
|
||||||
return 'Server is not ready.';
|
return 'Server is not functional.';
|
||||||
}
|
}
|
||||||
$team = data_get($this->server, 'team');
|
$team = data_get($this->server, 'team');
|
||||||
$serverDiskUsageNotificationThreshold = data_get($this->server, 'settings.server_disk_usage_notification_threshold');
|
$serverDiskUsageNotificationThreshold = data_get($this->server, 'settings.server_disk_usage_notification_threshold');
|
||||||
|
|||||||
@@ -3,16 +3,19 @@
|
|||||||
namespace App\Livewire\Admin;
|
namespace App\Livewire\Admin;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Index extends Component
|
class Index extends Component
|
||||||
{
|
{
|
||||||
public $active_subscribers = [];
|
public int $activeSubscribers;
|
||||||
|
|
||||||
public $inactive_subscribers = [];
|
public int $inactiveSubscribers;
|
||||||
|
|
||||||
public $search = '';
|
public Collection $foundUsers;
|
||||||
|
|
||||||
|
public string $search = '';
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
@@ -29,39 +32,21 @@ class Index extends Component
|
|||||||
public function submitSearch()
|
public function submitSearch()
|
||||||
{
|
{
|
||||||
if ($this->search !== '') {
|
if ($this->search !== '') {
|
||||||
$this->inactive_subscribers = User::whereDoesntHave('teams', function ($query) {
|
$this->foundUsers = User::where(function ($query) {
|
||||||
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
|
||||||
})->where(function ($query) {
|
|
||||||
$query->where('name', 'like', "%{$this->search}%")
|
$query->where('name', 'like', "%{$this->search}%")
|
||||||
->orWhere('email', 'like', "%{$this->search}%");
|
->orWhere('email', 'like', "%{$this->search}%");
|
||||||
})->get()->filter(function ($user) {
|
})->get();
|
||||||
return $user->id !== 0;
|
|
||||||
});
|
|
||||||
$this->active_subscribers = User::whereHas('teams', function ($query) {
|
|
||||||
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
|
||||||
})->where(function ($query) {
|
|
||||||
$query->where('name', 'like', "%{$this->search}%")
|
|
||||||
->orWhere('email', 'like', "%{$this->search}%");
|
|
||||||
})->get()->filter(function ($user) {
|
|
||||||
return $user->id !== 0;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$this->getSubscribers();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubscribers()
|
public function getSubscribers()
|
||||||
{
|
{
|
||||||
$this->inactive_subscribers = User::whereDoesntHave('teams', function ($query) {
|
$this->inactiveSubscribers = User::whereDoesntHave('teams', function ($query) {
|
||||||
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
||||||
})->get()->filter(function ($user) {
|
})->count();
|
||||||
return $user->id !== 0;
|
$this->activeSubscribers = User::whereHas('teams', function ($query) {
|
||||||
});
|
|
||||||
$this->active_subscribers = User::whereHas('teams', function ($query) {
|
|
||||||
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
||||||
})->get()->filter(function ($user) {
|
})->count();
|
||||||
return $user->id !== 0;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function switchUser(int $user_id)
|
public function switchUser(int $user_id)
|
||||||
|
|||||||
@@ -16,28 +16,28 @@ class Dashboard extends Component
|
|||||||
|
|
||||||
public Collection $servers;
|
public Collection $servers;
|
||||||
|
|
||||||
public Collection $private_keys;
|
public Collection $privateKeys;
|
||||||
|
|
||||||
public $deployments_per_server;
|
public array $deploymentsPerServer = [];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
|
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get();
|
||||||
$this->servers = Server::ownedByCurrentTeam()->get();
|
$this->servers = Server::ownedByCurrentTeam()->get();
|
||||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
$this->projects = Project::ownedByCurrentTeam()->get();
|
||||||
$this->get_deployments();
|
$this->loadDeployments();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cleanup_queue()
|
public function cleanupQueue()
|
||||||
{
|
{
|
||||||
Artisan::queue('cleanup:deployment-queue', [
|
Artisan::queue('cleanup:deployment-queue', [
|
||||||
'--team-id' => currentTeam()->id,
|
'--team-id' => currentTeam()->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_deployments()
|
public function loadDeployments()
|
||||||
{
|
{
|
||||||
$this->deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $this->servers->pluck('id'))->get([
|
$this->deploymentsPerServer = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $this->servers->pluck('id'))->get([
|
||||||
'id',
|
'id',
|
||||||
'application_id',
|
'application_id',
|
||||||
'application_name',
|
'application_name',
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire\Destination;
|
|
||||||
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Form extends Component
|
|
||||||
{
|
|
||||||
public mixed $destination;
|
|
||||||
|
|
||||||
protected $rules = [
|
|
||||||
'destination.name' => 'required',
|
|
||||||
'destination.network' => 'required',
|
|
||||||
'destination.server.ip' => 'required',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'destination.name' => 'name',
|
|
||||||
'destination.network' => 'network',
|
|
||||||
'destination.server.ip' => 'IP Address/Domain',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
$this->validate();
|
|
||||||
$this->destination->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if ($this->destination->getMorphClass() === \App\Models\StandaloneDocker::class) {
|
|
||||||
if ($this->destination->attachedTo()) {
|
|
||||||
return $this->dispatch('error', 'You must delete all resources before deleting this destination.');
|
|
||||||
}
|
|
||||||
instant_remote_process(["docker network disconnect {$this->destination->network} coolify-proxy"], $this->destination->server, throwError: false);
|
|
||||||
instant_remote_process(['docker network rm -f '.$this->destination->network], $this->destination->server);
|
|
||||||
}
|
|
||||||
$this->destination->delete();
|
|
||||||
|
|
||||||
return redirect()->route('destination.all');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
app/Livewire/Destination/Index.php
Normal file
23
app/Livewire/Destination/Index.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Destination;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Index extends Component
|
||||||
|
{
|
||||||
|
#[Locked]
|
||||||
|
public $servers;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->servers = Server::isUsable()->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.destination.index');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,111 +3,89 @@
|
|||||||
namespace App\Livewire\Destination\New;
|
namespace App\Livewire\Destination\New;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDocker as ModelsStandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
use App\Models\SwarmDocker;
|
use App\Models\SwarmDocker;
|
||||||
use Illuminate\Support\Collection;
|
use Livewire\Attributes\Locked;
|
||||||
|
use Livewire\Attributes\Rule;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class Docker extends Component
|
class Docker extends Component
|
||||||
{
|
{
|
||||||
|
#[Locked]
|
||||||
|
public $servers;
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
|
public Server $selectedServer;
|
||||||
|
|
||||||
|
#[Rule(['required', 'string'])]
|
||||||
public string $name;
|
public string $name;
|
||||||
|
|
||||||
|
#[Rule(['required', 'string'])]
|
||||||
public string $network;
|
public string $network;
|
||||||
|
|
||||||
public ?Collection $servers = null;
|
#[Rule(['required', 'string'])]
|
||||||
|
public string $serverId;
|
||||||
|
|
||||||
public Server $server;
|
#[Rule(['required', 'boolean'])]
|
||||||
|
public bool $isSwarm = false;
|
||||||
|
|
||||||
public ?int $server_id = null;
|
public function mount(?string $server_id = null)
|
||||||
|
|
||||||
public bool $is_swarm = false;
|
|
||||||
|
|
||||||
protected $rules = [
|
|
||||||
'name' => 'required|string',
|
|
||||||
'network' => 'required|string',
|
|
||||||
'server_id' => 'required|integer',
|
|
||||||
'is_swarm' => 'boolean',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'name' => 'name',
|
|
||||||
'network' => 'network',
|
|
||||||
'server_id' => 'server',
|
|
||||||
'is_swarm' => 'swarm',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
{
|
||||||
if (is_null($this->servers)) {
|
$this->network = new Cuid2;
|
||||||
$this->servers = Server::isReachable()->get();
|
$this->servers = Server::isUsable()->get();
|
||||||
}
|
if ($server_id) {
|
||||||
if (request()->query('server_id')) {
|
$this->selectedServer = $this->servers->find($server_id);
|
||||||
$this->server_id = request()->query('server_id');
|
|
||||||
} else {
|
} else {
|
||||||
if ($this->servers->count() > 0) {
|
$this->selectedServer = $this->servers->first();
|
||||||
$this->server_id = $this->servers->first()->id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (request()->query('network_name')) {
|
|
||||||
$this->network = request()->query('network_name');
|
|
||||||
} else {
|
|
||||||
$this->network = new Cuid2;
|
|
||||||
}
|
|
||||||
if ($this->servers->count() > 0) {
|
|
||||||
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
|
|
||||||
}
|
}
|
||||||
|
$this->generateName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generate_name()
|
public function updatedServerId()
|
||||||
{
|
{
|
||||||
$this->server = Server::find($this->server_id);
|
$this->selectedServer = $this->servers->find($this->serverId);
|
||||||
$this->name = str("{$this->server->name}-{$this->network}")->kebab();
|
$this->generateName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateName()
|
||||||
|
{
|
||||||
|
$name = data_get($this->selectedServer, 'name', new Cuid2);
|
||||||
|
$this->name = str("{$name}-{$this->network}")->kebab();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->validate();
|
|
||||||
try {
|
try {
|
||||||
$this->server = Server::find($this->server_id);
|
$this->validate();
|
||||||
if ($this->is_swarm) {
|
if ($this->isSwarm) {
|
||||||
$found = $this->server->swarmDockers()->where('network', $this->network)->first();
|
$found = $this->selectedServer->swarmDockers()->where('network', $this->network)->first();
|
||||||
if ($found) {
|
if ($found) {
|
||||||
$this->dispatch('error', 'Network already added to this server.');
|
throw new \Exception('Network already added to this server.');
|
||||||
|
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
$docker = SwarmDocker::create([
|
$docker = SwarmDocker::create([
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'network' => $this->network,
|
'network' => $this->network,
|
||||||
'server_id' => $this->server_id,
|
'server_id' => $this->selectedServer->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$found = $this->server->standaloneDockers()->where('network', $this->network)->first();
|
$found = $this->selectedServer->standaloneDockers()->where('network', $this->network)->first();
|
||||||
if ($found) {
|
if ($found) {
|
||||||
$this->dispatch('error', 'Network already added to this server.');
|
throw new \Exception('Network already added to this server.');
|
||||||
|
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
$docker = ModelsStandaloneDocker::create([
|
$docker = StandaloneDocker::create([
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'network' => $this->network,
|
'network' => $this->network,
|
||||||
'server_id' => $this->server_id,
|
'server_id' => $this->selectedServer->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->createNetworkAndAttachToProxy();
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->selectedServer);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->selectedServer, false);
|
||||||
return redirect()->route('destination.show', $docker->uuid);
|
$this->dispatch('reloadWindow');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createNetworkAndAttachToProxy()
|
|
||||||
{
|
|
||||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
|
||||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,71 +5,91 @@ namespace App\Livewire\Destination;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
use App\Models\SwarmDocker;
|
use App\Models\SwarmDocker;
|
||||||
use Illuminate\Support\Collection;
|
use Livewire\Attributes\Locked;
|
||||||
|
use Livewire\Attributes\Rule;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Show extends Component
|
class Show extends Component
|
||||||
{
|
{
|
||||||
public Server $server;
|
#[Locked]
|
||||||
|
public $destination;
|
||||||
|
|
||||||
public Collection|array $networks = [];
|
#[Rule(['string', 'required'])]
|
||||||
|
public string $name;
|
||||||
|
|
||||||
private function createNetworkAndAttachToProxy()
|
#[Rule(['string', 'required'])]
|
||||||
|
public string $network;
|
||||||
|
|
||||||
|
#[Rule(['string', 'required'])]
|
||||||
|
public string $serverIp;
|
||||||
|
|
||||||
|
public function mount(string $destination_uuid)
|
||||||
{
|
{
|
||||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
try {
|
||||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
$destination = StandaloneDocker::whereUuid($destination_uuid)->first() ??
|
||||||
}
|
SwarmDocker::whereUuid($destination_uuid)->firstOrFail();
|
||||||
|
|
||||||
public function add($name)
|
$ownedByTeam = Server::ownedByCurrentTeam()->each(function ($server) use ($destination) {
|
||||||
{
|
if ($server->standaloneDockers->contains($destination) || $server->swarmDockers->contains($destination)) {
|
||||||
if ($this->server->isSwarm()) {
|
$this->destination = $destination;
|
||||||
$found = $this->server->swarmDockers()->where('network', $name)->first();
|
$this->syncData();
|
||||||
if ($found) {
|
}
|
||||||
$this->dispatch('error', 'Network already added to this server.');
|
});
|
||||||
|
if ($ownedByTeam === false) {
|
||||||
return;
|
return redirect()->route('destination.index');
|
||||||
} else {
|
|
||||||
SwarmDocker::create([
|
|
||||||
'name' => $this->server->name.'-'.$name,
|
|
||||||
'network' => $this->name,
|
|
||||||
'server_id' => $this->server->id,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
} else {
|
$this->destination = $destination;
|
||||||
$found = $this->server->standaloneDockers()->where('network', $name)->first();
|
$this->syncData();
|
||||||
if ($found) {
|
} catch (\Throwable $e) {
|
||||||
$this->dispatch('error', 'Network already added to this server.');
|
return handleError($e, $this);
|
||||||
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
StandaloneDocker::create([
|
|
||||||
'name' => $this->server->name.'-'.$name,
|
|
||||||
'network' => $name,
|
|
||||||
'server_id' => $this->server->id,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$this->createNetworkAndAttachToProxy();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scan()
|
public function syncData(bool $toModel = false)
|
||||||
{
|
{
|
||||||
if ($this->server->isSwarm()) {
|
if ($toModel) {
|
||||||
$alreadyAddedNetworks = $this->server->swarmDockers;
|
$this->validate();
|
||||||
|
$this->destination->name = $this->name;
|
||||||
|
$this->destination->network = $this->network;
|
||||||
|
$this->destination->server->ip = $this->serverIp;
|
||||||
|
$this->destination->save();
|
||||||
} else {
|
} else {
|
||||||
$alreadyAddedNetworks = $this->server->standaloneDockers;
|
$this->name = $this->destination->name;
|
||||||
|
$this->network = $this->destination->network;
|
||||||
|
$this->serverIp = $this->destination->server->ip;
|
||||||
}
|
}
|
||||||
$networks = instant_remote_process(['docker network ls --format "{{json .}}"'], $this->server, false);
|
}
|
||||||
$this->networks = format_docker_command_output_to_json($networks)->filter(function ($network) {
|
|
||||||
return $network['Name'] !== 'bridge' && $network['Name'] !== 'host' && $network['Name'] !== 'none';
|
|
||||||
})->filter(function ($network) use ($alreadyAddedNetworks) {
|
|
||||||
return ! $alreadyAddedNetworks->contains('network', $network['Name']);
|
|
||||||
});
|
|
||||||
if ($this->networks->count() === 0) {
|
|
||||||
$this->dispatch('success', 'No new destinations found on this server.');
|
|
||||||
|
|
||||||
return;
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->syncData(true);
|
||||||
|
$this->dispatch('success', 'Destination saved.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
$this->dispatch('success', 'Scan done.');
|
}
|
||||||
|
|
||||||
|
public function delete()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->destination->getMorphClass() === \App\Models\StandaloneDocker::class) {
|
||||||
|
if ($this->destination->attachedTo()) {
|
||||||
|
return $this->dispatch('error', 'You must delete all resources before deleting this destination.');
|
||||||
|
}
|
||||||
|
instant_remote_process(["docker network disconnect {$this->destination->network} coolify-proxy"], $this->destination->server, throwError: false);
|
||||||
|
instant_remote_process(['docker network rm -f '.$this->destination->network], $this->destination->server);
|
||||||
|
}
|
||||||
|
$this->destination->delete();
|
||||||
|
|
||||||
|
return redirect()->route('destination.index');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.destination.show');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,55 +5,39 @@ namespace App\Livewire;
|
|||||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Livewire\Attributes\Rule;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Help extends Component
|
class Help extends Component
|
||||||
{
|
{
|
||||||
use WithRateLimiting;
|
use WithRateLimiting;
|
||||||
|
|
||||||
|
#[Rule(['required', 'min:10', 'max:1000'])]
|
||||||
public string $description;
|
public string $description;
|
||||||
|
|
||||||
|
#[Rule(['required', 'min:3'])]
|
||||||
public string $subject;
|
public string $subject;
|
||||||
|
|
||||||
public ?string $path = null;
|
|
||||||
|
|
||||||
protected $rules = [
|
|
||||||
'description' => 'required|min:10',
|
|
||||||
'subject' => 'required|min:3',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->path = Route::current()?->uri() ?? null;
|
|
||||||
if (isDev()) {
|
|
||||||
$this->description = "I'm having trouble with {$this->path}";
|
|
||||||
$this->subject = "Help with {$this->path}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->rateLimit(3, 30);
|
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$debug = "Route: {$this->path}";
|
$this->rateLimit(3, 30);
|
||||||
|
|
||||||
|
$settings = instanceSettings();
|
||||||
$mail = new MailMessage;
|
$mail = new MailMessage;
|
||||||
$mail->view(
|
$mail->view(
|
||||||
'emails.help',
|
'emails.help',
|
||||||
[
|
[
|
||||||
'description' => $this->description,
|
'description' => $this->description,
|
||||||
'debug' => $debug,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$mail->subject("[HELP]: {$this->subject}");
|
$mail->subject("[HELP]: {$this->subject}");
|
||||||
$settings = instanceSettings();
|
|
||||||
$type = set_transanctional_email_settings($settings);
|
$type = set_transanctional_email_settings($settings);
|
||||||
if (! $type) {
|
|
||||||
|
// Sending feedback through Cloud API
|
||||||
|
if ($type === false) {
|
||||||
$url = 'https://app.coolify.io/api/feedback';
|
$url = 'https://app.coolify.io/api/feedback';
|
||||||
if (isDev()) {
|
|
||||||
$url = 'http://localhost:80/api/feedback';
|
|
||||||
}
|
|
||||||
Http::post($url, [
|
Http::post($url, [
|
||||||
'content' => 'User: `'.auth()->user()?->email.'` with subject: `'.$this->subject.'` has the following problem: `'.$this->description.'`',
|
'content' => 'User: `'.auth()->user()?->email.'` with subject: `'.$this->subject.'` has the following problem: `'.$this->description.'`',
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -4,47 +4,94 @@ namespace App\Livewire\Notifications;
|
|||||||
|
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\Test;
|
use App\Notifications\Test;
|
||||||
|
use Livewire\Attributes\Rule;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Discord extends Component
|
class Discord extends Component
|
||||||
{
|
{
|
||||||
public Team $team;
|
public Team $team;
|
||||||
|
|
||||||
protected $rules = [
|
#[Rule(['boolean'])]
|
||||||
'team.discord_enabled' => 'nullable|boolean',
|
public bool $discordEnabled = false;
|
||||||
'team.discord_webhook_url' => 'required|url',
|
|
||||||
'team.discord_notifications_test' => 'nullable|boolean',
|
|
||||||
'team.discord_notifications_deployments' => 'nullable|boolean',
|
|
||||||
'team.discord_notifications_status_changes' => 'nullable|boolean',
|
|
||||||
'team.discord_notifications_database_backups' => 'nullable|boolean',
|
|
||||||
'team.discord_notifications_scheduled_tasks' => 'nullable|boolean',
|
|
||||||
'team.discord_notifications_server_disk_usage' => 'nullable|boolean',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
#[Rule(['url', 'nullable'])]
|
||||||
'team.discord_webhook_url' => 'Discord Webhook',
|
public ?string $discordWebhookUrl = null;
|
||||||
];
|
|
||||||
|
#[Rule(['boolean'])]
|
||||||
|
public bool $discordNotificationsTest = false;
|
||||||
|
|
||||||
|
#[Rule(['boolean'])]
|
||||||
|
public bool $discordNotificationsDeployments = false;
|
||||||
|
|
||||||
|
#[Rule(['boolean'])]
|
||||||
|
public bool $discordNotificationsStatusChanges = false;
|
||||||
|
|
||||||
|
#[Rule(['boolean'])]
|
||||||
|
public bool $discordNotificationsDatabaseBackups = false;
|
||||||
|
|
||||||
|
#[Rule(['boolean'])]
|
||||||
|
public bool $discordNotificationsScheduledTasks = false;
|
||||||
|
|
||||||
|
#[Rule(['boolean'])]
|
||||||
|
public bool $discordNotificationsServerDiskUsage = false;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->team = auth()->user()->currentTeam();
|
try {
|
||||||
|
$this->team = auth()->user()->currentTeam();
|
||||||
|
$this->syncData();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->validate();
|
||||||
|
$this->team->discord_enabled = $this->discordEnabled;
|
||||||
|
$this->team->discord_webhook_url = $this->discordWebhookUrl;
|
||||||
|
$this->team->discord_notifications_test = $this->discordNotificationsTest;
|
||||||
|
$this->team->discord_notifications_deployments = $this->discordNotificationsDeployments;
|
||||||
|
$this->team->discord_notifications_status_changes = $this->discordNotificationsStatusChanges;
|
||||||
|
$this->team->discord_notifications_database_backups = $this->discordNotificationsDatabaseBackups;
|
||||||
|
$this->team->discord_notifications_scheduled_tasks = $this->discordNotificationsScheduledTasks;
|
||||||
|
$this->team->discord_notifications_server_disk_usage = $this->discordNotificationsServerDiskUsage;
|
||||||
|
try {
|
||||||
|
$this->saveModel();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->discordEnabled = $this->team->discord_enabled;
|
||||||
|
$this->discordWebhookUrl = $this->team->discord_webhook_url;
|
||||||
|
$this->discordNotificationsTest = $this->team->discord_notifications_test;
|
||||||
|
$this->discordNotificationsDeployments = $this->team->discord_notifications_deployments;
|
||||||
|
$this->discordNotificationsStatusChanges = $this->team->discord_notifications_status_changes;
|
||||||
|
$this->discordNotificationsDatabaseBackups = $this->team->discord_notifications_database_backups;
|
||||||
|
$this->discordNotificationsScheduledTasks = $this->team->discord_notifications_scheduled_tasks;
|
||||||
|
$this->discordNotificationsServerDiskUsage = $this->team->discord_notifications_server_disk_usage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->submit();
|
$this->syncData(true);
|
||||||
} catch (\Throwable) {
|
} catch (\Throwable $e) {
|
||||||
$this->team->discord_enabled = false;
|
return handleError($e, $this);
|
||||||
$this->validate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->resetErrorBag();
|
try {
|
||||||
$this->validate();
|
$this->resetErrorBag();
|
||||||
$this->saveModel();
|
$this->syncData(true);
|
||||||
|
$this->saveModel();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveModel()
|
public function saveModel()
|
||||||
@@ -56,8 +103,12 @@ class Discord extends Component
|
|||||||
|
|
||||||
public function sendTestNotification()
|
public function sendTestNotification()
|
||||||
{
|
{
|
||||||
$this->team?->notify(new Test);
|
try {
|
||||||
$this->dispatch('success', 'Test notification sent.');
|
$this->team->notify(new Test);
|
||||||
|
$this->dispatch('success', 'Test notification sent.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
|||||||
@@ -3,24 +3,17 @@
|
|||||||
namespace App\Livewire\Project;
|
namespace App\Livewire\Project;
|
||||||
|
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
|
use Livewire\Attributes\Rule;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class AddEmpty extends Component
|
class AddEmpty extends Component
|
||||||
{
|
{
|
||||||
public string $name = '';
|
#[Rule(['required', 'string', 'min:3'])]
|
||||||
|
public string $name;
|
||||||
|
|
||||||
|
#[Rule(['nullable', 'string'])]
|
||||||
public string $description = '';
|
public string $description = '';
|
||||||
|
|
||||||
protected $rules = [
|
|
||||||
'name' => 'required|string|min:3',
|
|
||||||
'description' => 'nullable|string',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'name' => 'Project Name',
|
|
||||||
'description' => 'Project Description',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -34,8 +27,6 @@ class AddEmpty extends Component
|
|||||||
return redirect()->route('project.show', $project->uuid);
|
return redirect()->route('project.show', $project->uuid);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
} finally {
|
|
||||||
$this->name = '';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire\Project;
|
|
||||||
|
|
||||||
use App\Models\Environment;
|
|
||||||
use App\Models\Project;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class AddEnvironment extends Component
|
|
||||||
{
|
|
||||||
public Project $project;
|
|
||||||
|
|
||||||
public string $name = '';
|
|
||||||
|
|
||||||
public string $description = '';
|
|
||||||
|
|
||||||
protected $rules = [
|
|
||||||
'name' => 'required|string|min:3',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'name' => 'Environment Name',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->validate();
|
|
||||||
$environment = Environment::create([
|
|
||||||
'name' => $this->name,
|
|
||||||
'project_id' => $this->project->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return redirect()->route('project.resource.index', [
|
|
||||||
'project_uuid' => $this->project->uuid,
|
|
||||||
'environment_name' => $environment->name,
|
|
||||||
]);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
handleError($e, $this);
|
|
||||||
} finally {
|
|
||||||
$this->name = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,55 +4,92 @@ namespace App\Livewire\Project\Application;
|
|||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\PrivateKey;
|
use App\Models\PrivateKey;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
|
use Livewire\Attributes\Rule;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Source extends Component
|
class Source extends Component
|
||||||
{
|
{
|
||||||
public $applicationId;
|
|
||||||
|
|
||||||
public Application $application;
|
public Application $application;
|
||||||
|
|
||||||
public $private_keys;
|
#[Locked]
|
||||||
|
public $privateKeys;
|
||||||
|
|
||||||
protected $rules = [
|
#[Rule(['nullable', 'string'])]
|
||||||
'application.git_repository' => 'required',
|
public ?string $privateKeyName = null;
|
||||||
'application.git_branch' => 'required',
|
|
||||||
'application.git_commit_sha' => 'nullable',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
#[Rule(['nullable', 'integer'])]
|
||||||
'application.git_repository' => 'repository',
|
public ?int $privateKeyId = null;
|
||||||
'application.git_branch' => 'branch',
|
|
||||||
'application.git_commit_sha' => 'commit sha',
|
#[Rule(['required', 'string'])]
|
||||||
];
|
public string $gitRepository;
|
||||||
|
|
||||||
|
#[Rule(['required', 'string'])]
|
||||||
|
public string $gitBranch;
|
||||||
|
|
||||||
|
#[Rule(['nullable', 'string'])]
|
||||||
|
public ?string $gitCommitSha = null;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->get_private_keys();
|
try {
|
||||||
|
$this->syncData();
|
||||||
|
$this->getPrivateKeys();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_private_keys()
|
public function syncData(bool $toModel = false)
|
||||||
{
|
{
|
||||||
$this->private_keys = PrivateKey::whereTeamId(currentTeam()->id)->get()->reject(function ($key) {
|
if ($toModel) {
|
||||||
return $key->id == $this->application->private_key_id;
|
$this->validate();
|
||||||
|
$this->application->update([
|
||||||
|
'git_repository' => $this->gitRepository,
|
||||||
|
'git_branch' => $this->gitBranch,
|
||||||
|
'git_commit_sha' => $this->gitCommitSha,
|
||||||
|
'private_key_id' => $this->privateKeyId,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$this->gitRepository = $this->application->git_repository;
|
||||||
|
$this->gitBranch = $this->application->git_branch;
|
||||||
|
$this->gitCommitSha = $this->application->git_commit_sha;
|
||||||
|
$this->privateKeyId = $this->application->private_key_id;
|
||||||
|
$this->privateKeyName = data_get($this->application, 'private_key.name');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPrivateKeys()
|
||||||
|
{
|
||||||
|
$this->privateKeys = PrivateKey::whereTeamId(currentTeam()->id)->get()->reject(function ($key) {
|
||||||
|
return $key->id == $this->privateKeyId;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPrivateKey(int $private_key_id)
|
public function setPrivateKey(int $privateKeyId)
|
||||||
{
|
{
|
||||||
$this->application->private_key_id = $private_key_id;
|
try {
|
||||||
$this->application->save();
|
$this->privateKeyId = $privateKeyId;
|
||||||
$this->application->refresh();
|
$this->syncData(true);
|
||||||
$this->get_private_keys();
|
$this->getPrivateKeys();
|
||||||
|
$this->application->refresh();
|
||||||
|
$this->privateKeyName = $this->application->private_key->name;
|
||||||
|
$this->dispatch('success', 'Private key updated!');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->validate();
|
try {
|
||||||
if (! $this->application->git_commit_sha) {
|
if (str($this->gitCommitSha)->isEmpty()) {
|
||||||
$this->application->git_commit_sha = 'HEAD';
|
$this->gitCommitSha = 'HEAD';
|
||||||
|
}
|
||||||
|
$this->syncData(true);
|
||||||
|
$this->dispatch('success', 'Application source updated!');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
$this->application->save();
|
|
||||||
$this->dispatch('success', 'Application source updated!');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,34 +3,47 @@
|
|||||||
namespace App\Livewire\Project;
|
namespace App\Livewire\Project;
|
||||||
|
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
|
use Livewire\Attributes\Rule;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Edit extends Component
|
class Edit extends Component
|
||||||
{
|
{
|
||||||
public Project $project;
|
public Project $project;
|
||||||
|
|
||||||
protected $rules = [
|
#[Rule(['required', 'string', 'min:3', 'max:255'])]
|
||||||
'project.name' => 'required|min:3|max:255',
|
public string $name;
|
||||||
'project.description' => 'nullable|string|max:255',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
#[Rule(['nullable', 'string', 'max:255'])]
|
||||||
|
public ?string $description = null;
|
||||||
|
|
||||||
|
public function mount(string $project_uuid)
|
||||||
{
|
{
|
||||||
$projectUuid = request()->route('project_uuid');
|
try {
|
||||||
$teamId = currentTeam()->id;
|
$this->project = Project::where('team_id', currentTeam()->id)->where('uuid', $project_uuid)->firstOrFail();
|
||||||
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
|
$this->syncData();
|
||||||
if (! $project) {
|
} catch (\Throwable $e) {
|
||||||
return redirect()->route('dashboard');
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->validate();
|
||||||
|
$this->project->update([
|
||||||
|
'name' => $this->name,
|
||||||
|
'description' => $this->description,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$this->name = $this->project->name;
|
||||||
|
$this->description = $this->project->description;
|
||||||
}
|
}
|
||||||
$this->project = $project;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->syncData(true);
|
||||||
$this->project->save();
|
|
||||||
$this->dispatch('saved');
|
|
||||||
$this->dispatch('success', 'Project updated.');
|
$this->dispatch('success', 'Project updated.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ namespace App\Livewire\Project;
|
|||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
|
use Livewire\Attributes\Rule;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class EnvironmentEdit extends Component
|
class EnvironmentEdit extends Component
|
||||||
@@ -12,29 +14,45 @@ class EnvironmentEdit extends Component
|
|||||||
|
|
||||||
public Application $application;
|
public Application $application;
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
public $environment;
|
public $environment;
|
||||||
|
|
||||||
public array $parameters;
|
#[Rule(['required', 'string', 'min:3', 'max:255'])]
|
||||||
|
public string $name;
|
||||||
|
|
||||||
protected $rules = [
|
#[Rule(['nullable', 'string', 'max:255'])]
|
||||||
'environment.name' => 'required|min:3|max:255',
|
public ?string $description = null;
|
||||||
'environment.description' => 'nullable|min:3|max:255',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
public function mount(string $project_uuid, string $environment_name)
|
||||||
{
|
{
|
||||||
$this->parameters = get_route_parameters();
|
try {
|
||||||
$this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->first();
|
$this->project = Project::ownedByCurrentTeam()->where('uuid', $project_uuid)->firstOrFail();
|
||||||
$this->environment = $this->project->environments()->where('name', request()->route('environment_name'))->first();
|
$this->environment = $this->project->environments()->where('name', $environment_name)->firstOrFail();
|
||||||
|
$this->syncData();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->validate();
|
||||||
|
$this->environment->update([
|
||||||
|
'name' => $this->name,
|
||||||
|
'description' => $this->description,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$this->name = $this->environment->name;
|
||||||
|
$this->description = $this->environment->description;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->validate();
|
|
||||||
try {
|
try {
|
||||||
$this->environment->save();
|
$this->syncData(true);
|
||||||
|
$this->redirectRoute('project.environment.edit', ['environment_name' => $this->environment->name, 'project_uuid' => $this->project->uuid]);
|
||||||
return redirect()->route('project.environment.edit', ['project_uuid' => $this->project->uuid, 'environment_name' => $this->environment->name]);
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,27 +2,46 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Project;
|
namespace App\Livewire\Project;
|
||||||
|
|
||||||
|
use App\Models\Environment;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
|
use Livewire\Attributes\Rule;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Show extends Component
|
class Show extends Component
|
||||||
{
|
{
|
||||||
public Project $project;
|
public Project $project;
|
||||||
|
|
||||||
public $environments;
|
#[Rule(['required', 'string', 'min:3'])]
|
||||||
|
public string $name;
|
||||||
|
|
||||||
public function mount()
|
#[Rule(['nullable', 'string'])]
|
||||||
|
public ?string $description = null;
|
||||||
|
|
||||||
|
public function mount(string $project_uuid)
|
||||||
{
|
{
|
||||||
$projectUuid = request()->route('project_uuid');
|
try {
|
||||||
$teamId = currentTeam()->id;
|
$this->project = Project::where('team_id', currentTeam()->id)->where('uuid', $project_uuid)->firstOrFail();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
|
return handleError($e, $this);
|
||||||
if (! $project) {
|
|
||||||
return redirect()->route('dashboard');
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->environments = $project->environments->sortBy('created_at');
|
public function submit()
|
||||||
$this->project = $project;
|
{
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$environment = Environment::create([
|
||||||
|
'name' => $this->name,
|
||||||
|
'project_id' => $this->project->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->route('project.resource.index', [
|
||||||
|
'project_uuid' => $this->project->uuid,
|
||||||
|
'environment_name' => $environment->name,
|
||||||
|
]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Livewire\Server\Destination;
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
@@ -8,7 +8,7 @@ use App\Models\SwarmDocker;
|
|||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Show extends Component
|
class Destinations extends Component
|
||||||
{
|
{
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
|
||||||
@@ -86,6 +86,6 @@ class Show extends Component
|
|||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.server.destination.show');
|
return view('livewire.server.destinations');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,102 +3,83 @@
|
|||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
|
use Livewire\Attributes\Rule;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class SettingsEmail extends Component
|
class SettingsEmail extends Component
|
||||||
{
|
{
|
||||||
public InstanceSettings $settings;
|
public InstanceSettings $settings;
|
||||||
|
|
||||||
public string $emails;
|
#[Rule(['boolean'])]
|
||||||
|
public bool $smtpEnabled = false;
|
||||||
|
|
||||||
protected $rules = [
|
#[Rule(['nullable', 'string'])]
|
||||||
'settings.smtp_enabled' => 'nullable|boolean',
|
public ?string $smtpHost = null;
|
||||||
'settings.smtp_host' => 'required',
|
|
||||||
'settings.smtp_port' => 'required|numeric',
|
|
||||||
'settings.smtp_encryption' => 'nullable',
|
|
||||||
'settings.smtp_username' => 'nullable',
|
|
||||||
'settings.smtp_password' => 'nullable',
|
|
||||||
'settings.smtp_timeout' => 'nullable',
|
|
||||||
'settings.smtp_from_address' => 'required|email',
|
|
||||||
'settings.smtp_from_name' => 'required',
|
|
||||||
'settings.resend_enabled' => 'nullable|boolean',
|
|
||||||
'settings.resend_api_key' => 'nullable',
|
|
||||||
|
|
||||||
];
|
#[Rule(['nullable', 'numeric', 'min:1', 'max:65535'])]
|
||||||
|
public ?int $smtpPort = null;
|
||||||
|
|
||||||
protected $validationAttributes = [
|
#[Rule(['nullable', 'string'])]
|
||||||
'settings.smtp_from_address' => 'From Address',
|
public ?string $smtpEncryption = null;
|
||||||
'settings.smtp_from_name' => 'From Name',
|
|
||||||
'settings.smtp_recipients' => 'Recipients',
|
#[Rule(['nullable', 'string'])]
|
||||||
'settings.smtp_host' => 'Host',
|
public ?string $smtpUsername = null;
|
||||||
'settings.smtp_port' => 'Port',
|
|
||||||
'settings.smtp_encryption' => 'Encryption',
|
#[Rule(['nullable'])]
|
||||||
'settings.smtp_username' => 'Username',
|
public ?string $smtpPassword = null;
|
||||||
'settings.smtp_password' => 'Password',
|
|
||||||
'settings.smtp_timeout' => 'Timeout',
|
#[Rule(['nullable', 'numeric'])]
|
||||||
'settings.resend_api_key' => 'Resend API Key',
|
public ?int $smtpTimeout = null;
|
||||||
];
|
|
||||||
|
#[Rule(['nullable', 'email'])]
|
||||||
|
public ?string $smtpFromAddress = null;
|
||||||
|
|
||||||
|
#[Rule(['nullable', 'string'])]
|
||||||
|
public ?string $smtpFromName = null;
|
||||||
|
|
||||||
|
#[Rule(['boolean'])]
|
||||||
|
public bool $resendEnabled = false;
|
||||||
|
|
||||||
|
#[Rule(['nullable', 'string'])]
|
||||||
|
public ?string $resendApiKey = null;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (isInstanceAdmin()) {
|
if (isInstanceAdmin() === false) {
|
||||||
$this->settings = instanceSettings();
|
|
||||||
$this->emails = auth()->user()->email;
|
|
||||||
} else {
|
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
|
$this->settings = instanceSettings();
|
||||||
|
$this->syncData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submitFromFields()
|
public function syncData(bool $toModel = false)
|
||||||
{
|
{
|
||||||
try {
|
if ($toModel) {
|
||||||
$this->resetErrorBag();
|
$this->validate();
|
||||||
$this->validate([
|
$this->settings->smtp_enabled = $this->smtpEnabled;
|
||||||
'settings.smtp_from_address' => 'required|email',
|
$this->settings->smtp_host = $this->smtpHost;
|
||||||
'settings.smtp_from_name' => 'required',
|
$this->settings->smtp_port = $this->smtpPort;
|
||||||
]);
|
$this->settings->smtp_encryption = $this->smtpEncryption;
|
||||||
|
$this->settings->smtp_username = $this->smtpUsername;
|
||||||
|
$this->settings->smtp_password = $this->smtpPassword;
|
||||||
|
$this->settings->smtp_timeout = $this->smtpTimeout;
|
||||||
|
|
||||||
|
$this->settings->resend_enabled = $this->resendEnabled;
|
||||||
|
$this->settings->resend_api_key = $this->resendApiKey;
|
||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
$this->dispatch('success', 'Settings saved.');
|
} else {
|
||||||
} catch (\Throwable $e) {
|
$this->smtpEnabled = $this->settings->smtp_enabled;
|
||||||
return handleError($e, $this);
|
$this->smtpHost = $this->settings->smtp_host;
|
||||||
}
|
$this->smtpPort = $this->settings->smtp_port;
|
||||||
}
|
$this->smtpEncryption = $this->settings->smtp_encryption;
|
||||||
|
$this->smtpUsername = $this->settings->smtp_username;
|
||||||
|
$this->smtpPassword = $this->settings->smtp_password;
|
||||||
|
$this->smtpTimeout = $this->settings->smtp_timeout;
|
||||||
|
$this->smtpFromAddress = $this->settings->smtp_from_address;
|
||||||
|
$this->smtpFromName = $this->settings->smtp_from_name;
|
||||||
|
|
||||||
public function submitResend()
|
$this->resendEnabled = $this->settings->resend_enabled;
|
||||||
{
|
$this->resendApiKey = $this->settings->resend_api_key;
|
||||||
try {
|
|
||||||
$this->resetErrorBag();
|
|
||||||
$this->validate([
|
|
||||||
'settings.smtp_from_address' => 'required|email',
|
|
||||||
'settings.smtp_from_name' => 'required',
|
|
||||||
'settings.resend_api_key' => 'required',
|
|
||||||
]);
|
|
||||||
$this->settings->save();
|
|
||||||
$this->dispatch('success', 'Settings saved.');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->settings->resend_enabled = false;
|
|
||||||
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function instantSaveResend()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->settings->smtp_enabled = false;
|
|
||||||
$this->submitResend();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function instantSave()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->settings->resend_enabled = false;
|
|
||||||
$this->submit();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,20 +87,29 @@ class SettingsEmail extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->resetErrorBag();
|
$this->resetErrorBag();
|
||||||
$this->validate([
|
$this->syncData(true);
|
||||||
'settings.smtp_from_address' => 'required|email',
|
|
||||||
'settings.smtp_from_name' => 'required',
|
|
||||||
'settings.smtp_host' => 'required',
|
|
||||||
'settings.smtp_port' => 'required|numeric',
|
|
||||||
'settings.smtp_encryption' => 'nullable',
|
|
||||||
'settings.smtp_username' => 'nullable',
|
|
||||||
'settings.smtp_password' => 'nullable',
|
|
||||||
'settings.smtp_timeout' => 'nullable',
|
|
||||||
]);
|
|
||||||
$this->settings->save();
|
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function instantSave(string $type)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($type === 'SMTP') {
|
||||||
|
$this->resendEnabled = false;
|
||||||
|
} else {
|
||||||
|
$this->smtpEnabled = false;
|
||||||
|
}
|
||||||
|
$this->syncData(true);
|
||||||
|
if ($this->smtpEnabled || $this->resendEnabled) {
|
||||||
|
$this->dispatch('success', "{$type} enabled.");
|
||||||
|
} else {
|
||||||
|
$this->dispatch('success', "{$type} disabled.");
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ class AdminView extends Component
|
|||||||
|
|
||||||
public function delete($id, $password)
|
public function delete($id, $password)
|
||||||
{
|
{
|
||||||
|
if (! isInstanceAdmin()) {
|
||||||
|
return redirect()->route('dashboard');
|
||||||
|
}
|
||||||
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
||||||
if (! Hash::check($password, Auth::user()->password)) {
|
if (! Hash::check($password, Auth::user()->password)) {
|
||||||
$this->addError('password', 'The provided password is incorrect.');
|
$this->addError('password', 'The provided password is incorrect.');
|
||||||
|
|||||||
@@ -117,14 +117,31 @@ class Application extends BaseModel
|
|||||||
if ($application->fqdn === '') {
|
if ($application->fqdn === '') {
|
||||||
$application->fqdn = null;
|
$application->fqdn = null;
|
||||||
}
|
}
|
||||||
$application->forceFill([
|
$payload = [];
|
||||||
'fqdn' => $application->fqdn,
|
if ($application->isDirty('fqdn')) {
|
||||||
'install_command' => str($application->install_command)->trim(),
|
$payload['fqdn'] = $application->fqdn;
|
||||||
'build_command' => str($application->build_command)->trim(),
|
}
|
||||||
'start_command' => str($application->start_command)->trim(),
|
if ($application->isDirty('install_command')) {
|
||||||
'base_directory' => str($application->base_directory)->trim(),
|
$payload['install_command'] = str($application->install_command)->trim();
|
||||||
'publish_directory' => str($application->publish_directory)->trim(),
|
}
|
||||||
]);
|
if ($application->isDirty('build_command')) {
|
||||||
|
$payload['build_command'] = str($application->build_command)->trim();
|
||||||
|
}
|
||||||
|
if ($application->isDirty('start_command')) {
|
||||||
|
$payload['start_command'] = str($application->start_command)->trim();
|
||||||
|
}
|
||||||
|
if ($application->isDirty('base_directory')) {
|
||||||
|
$payload['base_directory'] = str($application->base_directory)->trim();
|
||||||
|
}
|
||||||
|
if ($application->isDirty('publish_directory')) {
|
||||||
|
$payload['publish_directory'] = str($application->publish_directory)->trim();
|
||||||
|
}
|
||||||
|
if ($application->isDirty('status')) {
|
||||||
|
$payload['last_online_at'] = now();
|
||||||
|
}
|
||||||
|
if (count($payload) > 0) {
|
||||||
|
$application->forceFill($payload);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
static::created(function ($application) {
|
static::created(function ($application) {
|
||||||
ApplicationSetting::create([
|
ApplicationSetting::create([
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ class ApplicationPreview extends BaseModel
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
static::saving(function ($preview) {
|
||||||
|
if ($preview->isDirty('status')) {
|
||||||
|
$preview->forceFill(['last_online_at' => now()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function findPreviewByApplicationAndPullId(int $application_id, int $pull_request_id)
|
public static function findPreviewByApplicationAndPullId(int $application_id, int $pull_request_id)
|
||||||
|
|||||||
@@ -507,20 +507,6 @@ $schema://$host {
|
|||||||
return Server::whereTeamId($teamId)->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_build_server', true);
|
return Server::whereTeamId($teamId)->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_build_server', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function skipServer()
|
|
||||||
{
|
|
||||||
if ($this->ip === '1.2.3.4') {
|
|
||||||
// ray('skipping 1.2.3.4');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if ($this->settings->force_disabled === true) {
|
|
||||||
// ray('force_disabled');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isForceDisabled()
|
public function isForceDisabled()
|
||||||
{
|
{
|
||||||
return $this->settings->force_disabled;
|
return $this->settings->force_disabled;
|
||||||
@@ -691,7 +677,7 @@ $schema://$host {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this, false);
|
$containers = instant_remote_process(["docker container inspect $(docker container ls -aq) --format '{{json .}}'"], $this, false);
|
||||||
$containers = format_docker_command_output_to_json($containers);
|
$containers = format_docker_command_output_to_json($containers);
|
||||||
$containerReplicates = collect([]);
|
$containerReplicates = collect([]);
|
||||||
}
|
}
|
||||||
@@ -917,11 +903,23 @@ $schema://$host {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function skipServer()
|
||||||
|
{
|
||||||
|
if ($this->ip === '1.2.3.4') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ($this->settings->force_disabled === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public function isFunctional()
|
public function isFunctional()
|
||||||
{
|
{
|
||||||
$isFunctional = $this->settings->is_reachable && $this->settings->is_usable && ! $this->settings->force_disabled;
|
$isFunctional = $this->settings->is_reachable && $this->settings->is_usable && $this->settings->force_disabled === false && $this->ip !== '1.2.3.4';
|
||||||
|
|
||||||
if (! $isFunctional) {
|
if ($isFunctional === false) {
|
||||||
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -976,10 +974,10 @@ $schema://$host {
|
|||||||
|
|
||||||
public function serverStatus(): bool
|
public function serverStatus(): bool
|
||||||
{
|
{
|
||||||
if ($this->status() === false) {
|
if ($this->isFunctional() === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($this->isFunctional() === false) {
|
if ($this->status() === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -988,7 +986,7 @@ $schema://$host {
|
|||||||
|
|
||||||
public function status(): bool
|
public function status(): bool
|
||||||
{
|
{
|
||||||
if ($this->skipServer()) {
|
if ($this->isFunctional() === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
['uptime' => $uptime] = $this->validateConnection(false);
|
['uptime' => $uptime] = $this->validateConnection(false);
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ class ServiceApplication extends BaseModel
|
|||||||
$service->persistentStorages()->delete();
|
$service->persistentStorages()->delete();
|
||||||
$service->fileStorages()->delete();
|
$service->fileStorages()->delete();
|
||||||
});
|
});
|
||||||
|
static::saving(function ($service) {
|
||||||
|
if ($service->isDirty('status')) {
|
||||||
|
$service->forceFill(['last_online_at' => now()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function restart()
|
public function restart()
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ class ServiceDatabase extends BaseModel
|
|||||||
$service->persistentStorages()->delete();
|
$service->persistentStorages()->delete();
|
||||||
$service->fileStorages()->delete();
|
$service->fileStorages()->delete();
|
||||||
});
|
});
|
||||||
|
static::saving(function ($service) {
|
||||||
|
if ($service->isDirty('status')) {
|
||||||
|
$service->forceFill(['last_online_at' => now()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function restart()
|
public function restart()
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ class StandaloneClickhouse extends BaseModel
|
|||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
|
static::saving(function ($database) {
|
||||||
|
if ($database->isDirty('status')) {
|
||||||
|
$database->forceFill(['last_online_at' => now()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ class StandaloneDragonfly extends BaseModel
|
|||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
|
static::saving(function ($database) {
|
||||||
|
if ($database->isDirty('status')) {
|
||||||
|
$database->forceFill(['last_online_at' => now()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ class StandaloneKeydb extends BaseModel
|
|||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
|
static::saving(function ($database) {
|
||||||
|
if ($database->isDirty('status')) {
|
||||||
|
$database->forceFill(['last_online_at' => now()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ class StandaloneMariadb extends BaseModel
|
|||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
|
static::saving(function ($database) {
|
||||||
|
if ($database->isDirty('status')) {
|
||||||
|
$database->forceFill(['last_online_at' => now()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ class StandaloneMongodb extends BaseModel
|
|||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
|
static::saving(function ($database) {
|
||||||
|
if ($database->isDirty('status')) {
|
||||||
|
$database->forceFill(['last_online_at' => now()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ class StandaloneMysql extends BaseModel
|
|||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
|
static::saving(function ($database) {
|
||||||
|
if ($database->isDirty('status')) {
|
||||||
|
$database->forceFill(['last_online_at' => now()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ class StandalonePostgresql extends BaseModel
|
|||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
|
static::saving(function ($database) {
|
||||||
|
if ($database->isDirty('status')) {
|
||||||
|
$database->forceFill(['last_online_at' => now()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function workdir()
|
public function workdir()
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ class StandaloneRedis extends BaseModel
|
|||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
|
static::saving(function ($database) {
|
||||||
|
if ($database->isDirty('status')) {
|
||||||
|
$database->forceFill(['last_online_at' => now()]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function serverStatus(): Attribute
|
protected function serverStatus(): Attribute
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ class FortifyServiceProvider extends ServiceProvider
|
|||||||
});
|
});
|
||||||
|
|
||||||
Fortify::authenticateUsing(function (Request $request) {
|
Fortify::authenticateUsing(function (Request $request) {
|
||||||
$user = User::where('email', $request->email)->with('teams')->first();
|
$email = strtolower($request->email);
|
||||||
|
$user = User::where('email', $email)->with('teams')->first();
|
||||||
if (
|
if (
|
||||||
$user &&
|
$user &&
|
||||||
Hash::check($request->password, $user->password)
|
Hash::check($request->password, $user->password)
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ class Input extends Component
|
|||||||
public bool $isMultiline = false,
|
public bool $isMultiline = false,
|
||||||
public string $defaultClass = 'input',
|
public string $defaultClass = 'input',
|
||||||
public string $autocomplete = 'off',
|
public string $autocomplete = 'off',
|
||||||
|
public ?int $minlength = null,
|
||||||
|
public ?int $maxlength = null,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function render(): View|Closure|string
|
public function render(): View|Closure|string
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ class Textarea extends Component
|
|||||||
public bool $realtimeValidation = false,
|
public bool $realtimeValidation = false,
|
||||||
public bool $allowToPeak = true,
|
public bool $allowToPeak = true,
|
||||||
public string $defaultClass = 'input scrollbar font-mono',
|
public string $defaultClass = 'input scrollbar font-mono',
|
||||||
public string $defaultClassInput = 'input'
|
public string $defaultClassInput = 'input',
|
||||||
|
public ?int $minlength = null,
|
||||||
|
public ?int $maxlength = null,
|
||||||
) {
|
) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ return [
|
|||||||
'production' => [
|
'production' => [
|
||||||
's6' => [
|
's6' => [
|
||||||
'autoScalingStrategy' => 'size',
|
'autoScalingStrategy' => 'size',
|
||||||
|
'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
|
||||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6),
|
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6),
|
||||||
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
||||||
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
||||||
@@ -206,6 +207,7 @@ return [
|
|||||||
'local' => [
|
'local' => [
|
||||||
's6' => [
|
's6' => [
|
||||||
'autoScalingStrategy' => 'size',
|
'autoScalingStrategy' => 'size',
|
||||||
|
'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
|
||||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6),
|
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6),
|
||||||
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
||||||
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ return [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'queue' => [
|
'queue' => [
|
||||||
'connection' => env('TELESCOPE_QUEUE_CONNECTION', null),
|
'connection' => env('TELESCOPE_QUEUE_CONNECTION', 'redis'),
|
||||||
'queue' => env('TELESCOPE_QUEUE', null),
|
'queue' => env('TELESCOPE_QUEUE', 'default'),
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<?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('applications', function (Blueprint $table) {
|
||||||
|
$table->timestamp('last_online_at')->default(now())->after('updated_at');
|
||||||
|
});
|
||||||
|
Schema::table('application_previews', function (Blueprint $table) {
|
||||||
|
$table->timestamp('last_online_at')->default(now())->after('updated_at');
|
||||||
|
});
|
||||||
|
Schema::table('service_applications', function (Blueprint $table) {
|
||||||
|
$table->timestamp('last_online_at')->default(now())->after('updated_at');
|
||||||
|
});
|
||||||
|
Schema::table('service_databases', function (Blueprint $table) {
|
||||||
|
$table->timestamp('last_online_at')->default(now())->after('updated_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_postgresqls', function (Blueprint $table) {
|
||||||
|
$table->timestamp('last_online_at')->default(now())->after('updated_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_redis', function (Blueprint $table) {
|
||||||
|
$table->timestamp('last_online_at')->default(now())->after('updated_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_mongodbs', function (Blueprint $table) {
|
||||||
|
$table->timestamp('last_online_at')->default(now())->after('updated_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_mysqls', function (Blueprint $table) {
|
||||||
|
$table->timestamp('last_online_at')->default(now())->after('updated_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_mariadbs', function (Blueprint $table) {
|
||||||
|
$table->timestamp('last_online_at')->default(now())->after('updated_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_keydbs', function (Blueprint $table) {
|
||||||
|
$table->timestamp('last_online_at')->default(now())->after('updated_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_dragonflies', function (Blueprint $table) {
|
||||||
|
$table->timestamp('last_online_at')->default(now())->after('updated_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_clickhouses', function (Blueprint $table) {
|
||||||
|
$table->timestamp('last_online_at')->default(now())->after('updated_at');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_online_at');
|
||||||
|
});
|
||||||
|
Schema::table('application_previews', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_online_at');
|
||||||
|
});
|
||||||
|
Schema::table('service_applications', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_online_at');
|
||||||
|
});
|
||||||
|
Schema::table('service_databases', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_online_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_postgresqls', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_online_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_redis', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_online_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_mongodbs', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_online_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_mysqls', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_online_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_mariadbs', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_online_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_keydbs', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_online_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_dragonflies', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_online_at');
|
||||||
|
});
|
||||||
|
Schema::table('standalone_clickhouses', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('last_online_at');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -41,8 +41,9 @@
|
|||||||
@if ($id !== 'null') wire:model={{ $id }} @endif
|
@if ($id !== 'null') wire:model={{ $id }} @endif
|
||||||
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
||||||
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled"
|
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled"
|
||||||
type="{{ $type }}" @disabled($disabled)
|
type="{{ $type }}" @disabled($disabled) min="{{ $attributes->get('min') }}"
|
||||||
min="{{ $attributes->get('min') }}" max="{{ $attributes->get('max') }}"
|
max="{{ $attributes->get('max') }}" minlength="{{ $attributes->get('minlength') }}"
|
||||||
|
maxlength="{{ $attributes->get('maxlength') }}"
|
||||||
@if ($id !== 'null') id={{ $id }} @endif name="{{ $name }}"
|
@if ($id !== 'null') id={{ $id }} @endif name="{{ $name }}"
|
||||||
placeholder="{{ $attributes->get('placeholder') }}">
|
placeholder="{{ $attributes->get('placeholder') }}">
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -51,8 +51,8 @@
|
|||||||
type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}"
|
type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}"
|
||||||
name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"
|
name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"
|
||||||
aria-placeholder="{{ $attributes->get('placeholder') }}">
|
aria-placeholder="{{ $attributes->get('placeholder') }}">
|
||||||
<textarea x-cloak x-show="type !== 'password'" placeholder="{{ $placeholder }}"
|
<textarea minlength="{{ $minlength }}" maxlength="{{ $maxlength }}" x-cloak x-show="type !== 'password'"
|
||||||
{{ $attributes->merge(['class' => $defaultClass]) }}
|
placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
|
||||||
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}"
|
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}"
|
||||||
@else
|
@else
|
||||||
wire:model={{ $value ?? $id }}
|
wire:model={{ $value ?? $id }}
|
||||||
@@ -62,7 +62,8 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<textarea {{ $allowTab ? '@keydown.tab=handleKeydown' : '' }} placeholder="{{ $placeholder }}"
|
<textarea minlength="{{ $minlength }}" maxlength="{{ $maxlength }}"
|
||||||
|
{{ $allowTab ? '@keydown.tab=handleKeydown' : '' }} placeholder="{{ $placeholder }}"
|
||||||
{{ !$spellcheck ? 'spellcheck=false' : '' }} {{ $attributes->merge(['class' => $defaultClass]) }}
|
{{ !$spellcheck ? 'spellcheck=false' : '' }} {{ $attributes->merge(['class' => $defaultClass]) }}
|
||||||
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}"
|
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}"
|
||||||
@else
|
@else
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a title="Destinations"
|
<a title="Destinations"
|
||||||
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.all') }}">
|
href="{{ route('destination.index') }}" wire:navigate>
|
||||||
|
|
||||||
<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">
|
||||||
<path fill="none" stroke="currentColor" stroke-linecap="round"
|
<path fill="none" stroke="currentColor" stroke-linecap="round"
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
<x-layout>
|
|
||||||
<x-slot:title>
|
|
||||||
Destinations | Coolify
|
|
||||||
</x-slot>
|
|
||||||
<div class="flex items-start gap-2">
|
|
||||||
<h1>Destinations</h1>
|
|
||||||
@if ($servers->count() > 0)
|
|
||||||
<x-modal-input buttonTitle="+ Add" title="New Destination">
|
|
||||||
<livewire:destination.new.docker :server_id="$server_id" />
|
|
||||||
</x-modal-input>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
<div class="subtitle">Network endpoints to deploy your resources.</div>
|
|
||||||
<div class="grid gap-2 lg:grid-cols-1">
|
|
||||||
@forelse ($destinations as $destination)
|
|
||||||
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
|
|
||||||
<a class="box group"
|
|
||||||
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
|
||||||
<div class="flex flex-col mx-6">
|
|
||||||
<div class="box-title">{{ $destination->name }}</div>
|
|
||||||
<div class="box-description">server: {{ $destination->server->name }}</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
@endif
|
|
||||||
@if ($destination->getMorphClass() === 'App\Models\SwarmDocker')
|
|
||||||
<a class="box group"
|
|
||||||
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
|
||||||
<div class="flex flex-col mx-6">
|
|
||||||
<div class="box-title">{{ $destination->name }}</div>
|
|
||||||
<div class="box-description">server: {{ $destination->server->name }}</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
@endif
|
|
||||||
@empty
|
|
||||||
<div>
|
|
||||||
@if ($servers->count() === 0)
|
|
||||||
<div>No servers found. Please add one first.</div>
|
|
||||||
@else
|
|
||||||
<div>No destinations found.</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
@endforelse
|
|
||||||
</div>
|
|
||||||
</x-layout>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<x-layout>
|
|
||||||
<livewire:destination.form :destination="$destination" />
|
|
||||||
</x-layout>
|
|
||||||
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
{{ Illuminate\Mail\Markdown::parse('---') }}
|
{{ Illuminate\Mail\Markdown::parse('---') }}
|
||||||
|
|
||||||
{{ Illuminate\Mail\Markdown::parse($debug) }}
|
{{-- {{ Illuminate\Mail\Markdown::parse($debug) }} --}}
|
||||||
|
|||||||
@@ -6,26 +6,25 @@
|
|||||||
<x-forms.input wire:model="search" placeholder="Search for a user" />
|
<x-forms.input wire:model="search" placeholder="Search for a user" />
|
||||||
<x-forms.button type="submit">Search</x-forms.button>
|
<x-forms.button type="submit">Search</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
<h3 class="pt-4">Active Subscribers</h3>
|
<div class="pt-4">Active Subscribers : {{ $activeSubscribers }}</div>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div>Inactive Subscribers : {{ $inactiveSubscribers }}</div>
|
||||||
@forelse ($active_subscribers as $user)
|
@if ($search)
|
||||||
<div class="flex gap-2 box" wire:click="switchUser('{{ $user->id }}')">
|
@if ($foundUsers->count() > 0)
|
||||||
<p>{{ $user->name }}</p>
|
<div class="flex flex-wrap gap-2 pt-4">
|
||||||
<p>{{ $user->email }}</p>
|
@foreach ($foundUsers as $user)
|
||||||
|
<div class="box w-64 group">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="box-title">{{ $user->name }}</div>
|
||||||
|
<div class="box-description">{{ $user->email }}</div>
|
||||||
|
<div class="box-description">Active:
|
||||||
|
{{ $user->teams()->whereRelation('subscription', 'stripe_subscription_id', '!=', null)->exists() ? 'Yes' : 'No' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@else
|
||||||
<p>No active subscribers</p>
|
<div>No users found with {{ $search }}</div>
|
||||||
@endforelse
|
@endif
|
||||||
</div>
|
@endif
|
||||||
<h3 class="pt-4">Inactive Subscribers</h3>
|
|
||||||
<div class="flex flex-col flex-wrap gap-2">
|
|
||||||
@forelse ($inactive_subscribers as $user)
|
|
||||||
<div class="flex gap-2 box" wire:click="switchUser('{{ $user->id }}')">
|
|
||||||
<p>{{ $user->name }}</p>
|
|
||||||
<p>{{ $user->email }}</p>
|
|
||||||
</div>
|
|
||||||
@empty
|
|
||||||
<p>No inactive subscribers</p>
|
|
||||||
@endforelse
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
@if ($private_keys->count() === 0)
|
@if ($privateKeys->count() === 0)
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<div class='font-bold dark:text-warning'>No private keys found.</div>
|
<div class='font-bold dark:text-warning'>No private keys found.</div>
|
||||||
<div class="flex items-center gap-1">Before you can add your server, first <x-modal-input
|
<div class="flex items-center gap-1">Before you can add your server, first <x-modal-input
|
||||||
@@ -126,26 +126,17 @@
|
|||||||
<section>
|
<section>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h3 class="pb-2">Deployments</h3>
|
<h3 class="pb-2">Deployments</h3>
|
||||||
@if (count($deployments_per_server) > 0)
|
@if (count($deploymentsPerServer) > 0)
|
||||||
<x-loading />
|
<x-loading />
|
||||||
@endif
|
@endif
|
||||||
<x-modal-confirmation
|
<x-modal-confirmation title="Confirm Cleanup Queues?" buttonTitle="Cleanup Queues" isErrorButton
|
||||||
title="Confirm Cleanup Queues?"
|
submitAction="cleanupQueue" :actions="['All running Deployment Queues will be cleaned up.']" :confirmWithText="false" :confirmWithPassword="false"
|
||||||
buttonTitle="Cleanup Queues"
|
step2ButtonText="Permanently Cleanup Deployment Queues" :dispatchEvent="true"
|
||||||
isErrorButton
|
dispatchEventType="success" dispatchEventMessage="Deployment Queues cleanup started." />
|
||||||
submitAction="cleanup_queue"
|
|
||||||
:actions="['All running Deployment Queues will be cleaned up.']"
|
|
||||||
:confirmWithText="false"
|
|
||||||
:confirmWithPassword="false"
|
|
||||||
step2ButtonText="Permanently Cleanup Deployment Queues"
|
|
||||||
:dispatchEvent="true"
|
|
||||||
dispatchEventType="success"
|
|
||||||
dispatchEventMessage="Deployment Queues cleanup started."
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div wire:poll.3000ms="get_deployments" class="grid grid-cols-1">
|
<div wire:poll.3000ms="loadDeployments" class="grid grid-cols-1">
|
||||||
@forelse ($deployments_per_server as $server_name => $deployments)
|
@forelse ($deploymentsPerServer as $serverName => $deployments)
|
||||||
<h4 class="pb-2">{{ $server_name }}</h4>
|
<h4 class="pb-2">{{ $serverName }}</h4>
|
||||||
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
|
||||||
@foreach ($deployments as $deployment)
|
@foreach ($deployments as $deployment)
|
||||||
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
|
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
|
||||||
@@ -187,7 +178,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{{-- <x-forms.button wire:click='getIptables'>Get IPTABLES</x-forms.button> --}}
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
<div>
|
|
||||||
<form class="flex flex-col">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<h1>Destination</h1>
|
|
||||||
<x-forms.button wire:click.prevent='submit' type="submit">
|
|
||||||
Save
|
|
||||||
</x-forms.button>
|
|
||||||
@if ($destination->network !== 'coolify')
|
|
||||||
<x-modal-confirmation title="Confirm Destination Deletion?" buttonTitle="Delete Destination" isErrorButton
|
|
||||||
submitAction="delete" :actions="['This will delete the selected destination/network.']" confirmationText="{{ $destination->name }}"
|
|
||||||
confirmationLabel="Please confirm the execution of the actions by entering the Destination Name below"
|
|
||||||
shortConfirmationLabel="Destination Name" :confirmWithPassword="false" step2ButtonText="Permanently Delete" />
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
|
|
||||||
<div class="subtitle ">A Docker network in a non-swarm environment.</div>
|
|
||||||
@else
|
|
||||||
<div class="subtitle ">Your swarm docker network. WIP</div>
|
|
||||||
@endif
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<x-forms.input id="destination.name" label="Name" />
|
|
||||||
<x-forms.input id="destination.server.ip" label="Server IP" readonly />
|
|
||||||
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
|
|
||||||
<x-forms.input id="destination.network" label="Docker Network" readonly />
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
44
resources/views/livewire/destination/index.blade.php
Normal file
44
resources/views/livewire/destination/index.blade.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<div>
|
||||||
|
<x-slot:title>
|
||||||
|
Destinations | Coolify
|
||||||
|
</x-slot>
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
|
<h1>Destinations</h1>
|
||||||
|
@if ($servers->count() > 0)
|
||||||
|
<x-modal-input buttonTitle="+ Add" title="New Destination">
|
||||||
|
<livewire:destination.new.docker />
|
||||||
|
</x-modal-input>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="subtitle">Network endpoints to deploy your resources.</div>
|
||||||
|
<div class="grid gap-2 lg:grid-cols-1">
|
||||||
|
@forelse ($servers as $server)
|
||||||
|
@forelse ($server->destinations() as $destination)
|
||||||
|
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
|
||||||
|
<a class="box group"
|
||||||
|
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}"
|
||||||
|
wire:navigate>
|
||||||
|
<div class="flex flex-col mx-6">
|
||||||
|
<div class="box-title">{{ $destination->name }}</div>
|
||||||
|
<div class="box-description">Server: {{ $destination->server->name }}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
@if ($destination->getMorphClass() === 'App\Models\SwarmDocker')
|
||||||
|
<a class="box group"
|
||||||
|
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}"
|
||||||
|
wire:navigate>
|
||||||
|
<div class="flex flex-col mx-6">
|
||||||
|
<div class="box-title">{{ $destination->name }}</div>
|
||||||
|
<div class="box-description">server: {{ $destination->server->name }}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
@empty
|
||||||
|
<div>No destinations found.</div>
|
||||||
|
@endforelse
|
||||||
|
@empty
|
||||||
|
<div>No servers found.</div>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<x-forms.input id="name" label="Name" required />
|
<x-forms.input id="name" label="Name" required />
|
||||||
<x-forms.input id="network" label="Network" required />
|
<x-forms.input id="network" label="Network" required />
|
||||||
</div>
|
</div>
|
||||||
<x-forms.select id="server_id" label="Select a server" required wire:change="generate_name">
|
<x-forms.select id="serverId" label="Select a server" required wire:change="generateName">
|
||||||
<option disabled>Select a server</option>
|
<option disabled>Select a server</option>
|
||||||
@foreach ($servers as $server)
|
@foreach ($servers as $server)
|
||||||
<option value="{{ $server->id }}">{{ $server->name }}</option>
|
<option value="{{ $server->id }}">{{ $server->name }}</option>
|
||||||
|
|||||||
@@ -1,42 +1,29 @@
|
|||||||
<div>
|
<div>
|
||||||
@if ($server->isFunctional())
|
<form class="flex flex-col">
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h2>Destinations</h2>
|
<h1>Destination</h1>
|
||||||
<x-modal-input buttonTitle="+ Add" title="New Destination">
|
<x-forms.button wire:click.prevent='submit' type="submit">
|
||||||
<livewire:destination.new.docker :server_id="$server->id" />
|
Save
|
||||||
</x-modal-input>
|
</x-forms.button>
|
||||||
<x-forms.button wire:click='scan'>Scan for Destinations</x-forms.button>
|
@if ($network !== 'coolify')
|
||||||
</div>
|
<x-modal-confirmation title="Confirm Destination Deletion?" buttonTitle="Delete Destination" isErrorButton
|
||||||
<div>Destinations are used to segregate resources by network.</div>
|
submitAction="delete" :actions="['This will delete the selected destination/network.']" confirmationText="{{ $destination->name }}"
|
||||||
<div class="flex gap-2 pt-6">
|
confirmationLabel="Please confirm the execution of the actions by entering the Destination Name below"
|
||||||
Available for using:
|
shortConfirmationLabel="Destination Name" :confirmWithPassword="false" step2ButtonText="Permanently Delete" />
|
||||||
@forelse ($server->standaloneDockers as $docker)
|
|
||||||
<a href="{{ route('destination.show', ['destination_uuid' => data_get($docker, 'uuid')]) }}">
|
|
||||||
<button class="dark:text-white btn-link">{{ data_get($docker, 'network') }} </button>
|
|
||||||
</a>
|
|
||||||
@empty
|
|
||||||
@endforelse
|
|
||||||
@forelse ($server->swarmDockers as $docker)
|
|
||||||
<a href="{{ route('destination.show', ['destination_uuid' => data_get($docker, 'uuid')]) }}">
|
|
||||||
<button class="dark:text-white btn-link">{{ data_get($docker, 'network') }} </button>
|
|
||||||
</a>
|
|
||||||
@empty
|
|
||||||
@endforelse
|
|
||||||
</div>
|
|
||||||
<div class="pt-2">
|
|
||||||
@if (count($networks) > 0)
|
|
||||||
<h3 class="pb-4">Found Destinations</h3>
|
|
||||||
@endif
|
@endif
|
||||||
<div class="flex flex-wrap gap-2 ">
|
|
||||||
@foreach ($networks as $network)
|
|
||||||
<div class="min-w-fit">
|
|
||||||
<x-forms.button wire:click="add('{{ data_get($network, 'Name') }}')">Add
|
|
||||||
{{ data_get($network, 'Name') }}</x-forms.button>
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@else
|
|
||||||
<div>Server is not validated. Validate first.</div>
|
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
|
||||||
@endif
|
<div class="subtitle ">A simple Docker network.</div>
|
||||||
|
@else
|
||||||
|
<div class="subtitle ">A swarm Docker network. WIP</div>
|
||||||
|
@endif
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input id="name" label="Name" />
|
||||||
|
<x-forms.input id="serverIp" label="Server IP" readonly />
|
||||||
|
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
|
||||||
|
<x-forms.input id="network" label="Docker Network" readonly />
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<div class="flex flex-col w-full gap-2">
|
<div class="flex flex-col w-full gap-2">
|
||||||
<div>Your feedback helps us to improve Coolify. Thank you! 💜</div>
|
<div>Your feedback helps us to improve Coolify. Thank you! 💜</div>
|
||||||
<form wire:submit="submit" class="flex flex-col gap-4 pt-4">
|
<form wire:submit="submit" class="flex flex-col gap-4 pt-4">
|
||||||
<x-forms.input id="subject" label="Subject" placeholder="Summary of your problem."></x-forms.input>
|
<x-forms.input minlength="3" required id="subject" label="Subject" placeholder="Help with..."></x-forms.input>
|
||||||
<x-forms.textarea rows="10" id="description" label="Description" class="font-sans" spellcheck
|
<x-forms.textarea minlength="10" maxlength="1000" required rows="10" id="description" label="Description"
|
||||||
placeholder="Please provide as much information as possible."></x-forms.textarea>
|
class="font-sans" spellcheck
|
||||||
|
placeholder="Having trouble with... Please provide as much information as possible."></x-forms.textarea>
|
||||||
<div></div>
|
<div></div>
|
||||||
<x-forms.button class="w-full mt-4" type="submit" @click="modalOpen=false">Send</x-forms.button>
|
<x-forms.button class="w-full mt-4" type="submit">Send</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<x-forms.button type="submit">
|
<x-forms.button type="submit">
|
||||||
Save
|
Save
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@if ($team->discord_enabled)
|
@if ($discordEnabled)
|
||||||
<x-forms.button class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
|
<x-forms.button class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
|
||||||
wire:click="sendTestNotification">
|
wire:click="sendTestNotification">
|
||||||
Send Test Notifications
|
Send Test Notifications
|
||||||
@@ -17,27 +17,26 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="w-32">
|
<div class="w-32">
|
||||||
<x-forms.checkbox instantSave id="team.discord_enabled" label="Enabled" />
|
<x-forms.checkbox instantSave id="discordEnabled" label="Enabled" />
|
||||||
</div>
|
</div>
|
||||||
<x-forms.input type="password"
|
<x-forms.input type="password"
|
||||||
helper="Generate a webhook in Discord.<br>Example: https://discord.com/api/webhooks/...." required
|
helper="Generate a webhook in Discord.<br>Example: https://discord.com/api/webhooks/...." required
|
||||||
id="team.discord_webhook_url" label="Webhook" />
|
id="discordWebhookUrl" label="Webhook" />
|
||||||
</form>
|
</form>
|
||||||
@if (data_get($team, 'discord_enabled'))
|
@if ($discordEnabled)
|
||||||
<h2 class="mt-4">Subscribe to events</h2>
|
<h2 class="mt-4">Subscribe to events</h2>
|
||||||
<div class="w-64">
|
<div class="w-64">
|
||||||
@if (isDev())
|
@if (isDev())
|
||||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_test" label="Test" />
|
<x-forms.checkbox instantSave="saveModel" id="discordNotificationsTest" label="Test" />
|
||||||
@endif
|
@endif
|
||||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_status_changes"
|
<x-forms.checkbox instantSave="saveModel" id="discordNotificationsStatusChanges"
|
||||||
label="Container Status Changes" />
|
label="Container Status Changes" />
|
||||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_deployments"
|
<x-forms.checkbox instantSave="saveModel" id="discordNotificationsDeployments"
|
||||||
label="Application Deployments" />
|
label="Application Deployments" />
|
||||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_database_backups"
|
<x-forms.checkbox instantSave="saveModel" id="discordNotificationsDatabaseBackups" label="Backup Status" />
|
||||||
label="Backup Status" />
|
<x-forms.checkbox instantSave="saveModel" id="discordNotificationsScheduledTasks"
|
||||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_scheduled_tasks"
|
|
||||||
label="Scheduled Tasks Status" />
|
label="Scheduled Tasks Status" />
|
||||||
<x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_server_disk_usage"
|
<x-forms.checkbox instantSave="saveModel" id="discordNotificationsServerDiskUsage"
|
||||||
label="Server Disk Usage" />
|
label="Server Disk Usage" />
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
|
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
|
||||||
<x-forms.input placeholder="Your Cool Project" id="name" label="Name" required />
|
<x-forms.input placeholder="Your Cool Project" id="name" label="Name" required />
|
||||||
<x-forms.input placeholder="This is my cool project everyone knows about" id="description" label="Description" />
|
<x-forms.input placeholder="This is my cool project everyone knows about" id="description" label="Description" />
|
||||||
<div class="subtitle">New project will have a default production environment.</div>
|
<div class="subtitle">New project will have a default <span class="dark:text-warning font-bold">production</span>
|
||||||
<x-forms.button type="submit" @click="slideOverOpen=false">
|
environment.</div>
|
||||||
|
<x-forms.button type="submit">
|
||||||
Continue
|
Continue
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
|
|
||||||
<x-forms.input placeholder="production" id="name" label="Name" required />
|
|
||||||
<x-forms.button type="submit" @click="slideOverOpen=false">
|
|
||||||
Save
|
|
||||||
</x-forms.button>
|
|
||||||
</form>
|
|
||||||
@@ -27,24 +27,22 @@
|
|||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input placeholder="coollabsio/coolify-example" id="application.git_repository"
|
<x-forms.input placeholder="coollabsio/coolify-example" id="gitRepository" label="Repository" />
|
||||||
label="Repository" />
|
<x-forms.input placeholder="main" id="gitBranch" label="Branch" />
|
||||||
<x-forms.input placeholder="main" id="application.git_branch" label="Branch" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
<x-forms.input placeholder="HEAD" id="application.git_commit_sha" placeholder="HEAD"
|
<x-forms.input placeholder="HEAD" id="gitCommitSha" placeholder="HEAD" label="Commit SHA" />
|
||||||
label="Commit SHA" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (data_get($application, 'private_key_id'))
|
@if ($privateKeyId)
|
||||||
<h3 class="pt-4">Deploy Key</h3>
|
<h3 class="pt-4">Deploy Key</h3>
|
||||||
<div class="py-2 pt-4">Currently attached Private Key: <span
|
<div class="py-2 pt-4">Currently attached Private Key: <span
|
||||||
class="dark:text-warning">{{ data_get($application, 'private_key.name') }}</span>
|
class="dark:text-warning">{{ $privateKeyName }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 class="py-2 ">Select another Private Key</h4>
|
<h4 class="py-2 ">Select another Private Key</h4>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
@foreach ($private_keys as $key)
|
@foreach ($privateKeys as $key)
|
||||||
<x-forms.button wire:click.defer="setPrivateKey('{{ $key->id }}')">{{ $key->name }}
|
<x-forms.button wire:click.defer="setPrivateKey('{{ $key->id }}')">{{ $key->name }}
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|||||||
@@ -11,10 +11,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-2 pb-10">Edit project details here.</div>
|
<div class="pt-2 pb-10">Edit project details here.</div>
|
||||||
|
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input label="Name" id="project.name" />
|
<x-forms.input label="Name" id="name" />
|
||||||
<x-forms.input label="Description" id="project.description" />
|
<x-forms.input label="Description" id="description" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<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"
|
||||||
href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}">
|
href="{{ route('project.show', ['project_uuid' => $project->uuid]) }}">
|
||||||
{{ $project->name }}</a>
|
{{ $project->name }}</a>
|
||||||
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
|
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
|
||||||
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
@@ -26,7 +26,9 @@
|
|||||||
<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"
|
||||||
href="{{ route('project.resource.index', ['environment_name' => data_get($parameters, 'environment_name'), 'project_uuid' => data_get($parameters, 'project_uuid')]) }}">{{ data_get($parameters, 'environment_name') }}</a>
|
href="{{ route('project.resource.index', ['environment_name' => $environment->name, 'project_uuid' => $project->uuid]) }}">
|
||||||
|
{{ $environment->name }}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -43,8 +45,8 @@
|
|||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input label="Name" id="environment.name" />
|
<x-forms.input label="Name" id="name" />
|
||||||
<x-forms.input label="Description" id="environment.description" />
|
<x-forms.input label="Description" id="description" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,13 +5,18 @@
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h1>Environments</h1>
|
<h1>Environments</h1>
|
||||||
<x-modal-input buttonTitle="+ Add" title="New Environment">
|
<x-modal-input buttonTitle="+ Add" title="New Environment">
|
||||||
<livewire:project.add-environment :project="$project" />
|
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
|
||||||
|
<x-forms.input placeholder="production" id="name" label="Name" required />
|
||||||
|
<x-forms.button type="submit">
|
||||||
|
Save
|
||||||
|
</x-forms.button>
|
||||||
|
</form>
|
||||||
</x-modal-input>
|
</x-modal-input>
|
||||||
<livewire:project.delete-project :disabled="$project->resource_count() > 0" :project_id="$project->id" />
|
<livewire:project.delete-project :disabled="$project->resource_count() > 0" :project_id="$project->id" />
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs truncate subtitle lg:text-sm">{{ $project->name }}.</div>
|
<div class="text-xs truncate subtitle lg:text-sm">{{ $project->name }}.</div>
|
||||||
<div class="grid gap-2 lg:grid-cols-2">
|
<div class="grid gap-2 lg:grid-cols-2">
|
||||||
@forelse ($environments as $environment)
|
@forelse ($project->environments->sortBy('created_at') as $environment)
|
||||||
<div class="gap-2 border border-transparent cursor-pointer box group" x-data
|
<div class="gap-2 border border-transparent cursor-pointer box group" x-data
|
||||||
x-on:click="goto('{{ $project->uuid }}','{{ $environment->name }}')">
|
x-on:click="goto('{{ $project->uuid }}','{{ $environment->name }}')">
|
||||||
<div class="flex flex-1 mx-6">
|
<div class="flex flex-1 mx-6">
|
||||||
@@ -28,12 +33,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{-- <div class="flex items-center justify-center gap-2 pt-4 pb-2 mr-4 text-xs lg:py-0 lg:justify-normal">
|
|
||||||
<a class="mx-4 font-bold hover:underline"
|
|
||||||
href="{{ route('project.environment.edit', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => $environment->name]) }}">
|
|
||||||
Settings
|
|
||||||
</a>
|
|
||||||
</div> --}}
|
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
<p>No environments found.</p>
|
<p>No environments found.</p>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<div>
|
<div>
|
||||||
|
<x-slot:title>
|
||||||
|
{{ data_get_str($server, 'name')->limit(10) }} > Advanced | Coolify
|
||||||
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<x-server.navbar :server="$server" />
|
||||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 sm:flex-row">
|
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" activeMenu="advanced" />
|
<x-server.sidebar :server="$server" activeMenu="advanced" />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Server Destinations | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Metrics | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<x-server.navbar :server="$server" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
@@ -8,139 +8,29 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<h2>Metrics</h2>
|
<h2>Metrics</h2>
|
||||||
<div class="pb-4">Basic metrics for your container.</div>
|
<div class="pb-4">Basic metrics for your container.</div>
|
||||||
<div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()">
|
@if ($server->isMetricsEnabled())
|
||||||
<x-forms.select label="Interval" wire:change="setInterval" id="interval">
|
<div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()">
|
||||||
<option value="5">5 minutes (live)</option>
|
<x-forms.select label="Interval" wire:change="setInterval" id="interval">
|
||||||
<option value="10">10 minutes (live)</option>
|
<option value="5">5 minutes (live)</option>
|
||||||
<option value="30">30 minutes</option>
|
<option value="10">10 minutes (live)</option>
|
||||||
<option value="60">1 hour</option>
|
<option value="30">30 minutes</option>
|
||||||
<option value="720">12 hours</option>
|
<option value="60">1 hour</option>
|
||||||
<option value="10080">1 week</option>
|
<option value="720">12 hours</option>
|
||||||
<option value="43200">30 days</option>
|
<option value="10080">1 week</option>
|
||||||
</x-forms.select>
|
<option value="43200">30 days</option>
|
||||||
<h4 class="pt-4">CPU (%)</h4>
|
</x-forms.select>
|
||||||
<div wire:ignore id="{!! $chartId !!}-cpu"></div>
|
<h4 class="pt-4">CPU (%)</h4>
|
||||||
|
<div wire:ignore id="{!! $chartId !!}-cpu"></div>
|
||||||
<script>
|
|
||||||
checkTheme();
|
|
||||||
const optionsServerCpu = {
|
|
||||||
stroke: {
|
|
||||||
curve: 'straight',
|
|
||||||
},
|
|
||||||
chart: {
|
|
||||||
height: '150px',
|
|
||||||
id: '{!! $chartId !!}-cpu',
|
|
||||||
type: 'area',
|
|
||||||
toolbar: {
|
|
||||||
show: true,
|
|
||||||
tools: {
|
|
||||||
download: false,
|
|
||||||
selection: false,
|
|
||||||
zoom: true,
|
|
||||||
zoomin: false,
|
|
||||||
zoomout: false,
|
|
||||||
pan: false,
|
|
||||||
reset: true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
animations: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fill: {
|
|
||||||
type: 'gradient',
|
|
||||||
},
|
|
||||||
dataLabels: {
|
|
||||||
enabled: false,
|
|
||||||
offsetY: -10,
|
|
||||||
style: {
|
|
||||||
colors: ['#FCD452'],
|
|
||||||
},
|
|
||||||
background: {
|
|
||||||
enabled: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
show: true,
|
|
||||||
borderColor: '',
|
|
||||||
},
|
|
||||||
colors: [baseColor],
|
|
||||||
xaxis: {
|
|
||||||
type: 'datetime',
|
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
name: 'CPU %',
|
|
||||||
data: []
|
|
||||||
}],
|
|
||||||
noData: {
|
|
||||||
text: 'Loading...',
|
|
||||||
style: {
|
|
||||||
color: textColor,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
enabled: true,
|
|
||||||
marker: {
|
|
||||||
show: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
show: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const serverCpuChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-cpu`),
|
|
||||||
optionsServerCpu);
|
|
||||||
serverCpuChart.render();
|
|
||||||
document.addEventListener('livewire:init', () => {
|
|
||||||
Livewire.on('refreshChartData-{!! $chartId !!}-cpu', (chartData) => {
|
|
||||||
checkTheme();
|
|
||||||
serverCpuChart.updateOptions({
|
|
||||||
series: [{
|
|
||||||
data: chartData[0].seriesData,
|
|
||||||
}],
|
|
||||||
colors: [baseColor],
|
|
||||||
xaxis: {
|
|
||||||
type: 'datetime',
|
|
||||||
labels: {
|
|
||||||
show: true,
|
|
||||||
style: {
|
|
||||||
colors: textColor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
yaxis: {
|
|
||||||
show: true,
|
|
||||||
labels: {
|
|
||||||
show: true,
|
|
||||||
style: {
|
|
||||||
colors: textColor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
noData: {
|
|
||||||
text: 'Loading...',
|
|
||||||
style: {
|
|
||||||
color: textColor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h4>Memory (%)</h4>
|
|
||||||
<div wire:ignore id="{!! $chartId !!}-memory"></div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
checkTheme();
|
checkTheme();
|
||||||
const optionsServerMemory = {
|
const optionsServerCpu = {
|
||||||
stroke: {
|
stroke: {
|
||||||
curve: 'straight',
|
curve: 'straight',
|
||||||
},
|
},
|
||||||
chart: {
|
chart: {
|
||||||
height: '150px',
|
height: '150px',
|
||||||
id: '{!! $chartId !!}-memory',
|
id: '{!! $chartId !!}-cpu',
|
||||||
type: 'area',
|
type: 'area',
|
||||||
toolbar: {
|
toolbar: {
|
||||||
show: true,
|
show: true,
|
||||||
@@ -178,15 +68,9 @@
|
|||||||
colors: [baseColor],
|
colors: [baseColor],
|
||||||
xaxis: {
|
xaxis: {
|
||||||
type: 'datetime',
|
type: 'datetime',
|
||||||
labels: {
|
|
||||||
show: true,
|
|
||||||
style: {
|
|
||||||
colors: textColor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
series: [{
|
series: [{
|
||||||
name: "Memory (%)",
|
name: 'CPU %',
|
||||||
data: []
|
data: []
|
||||||
}],
|
}],
|
||||||
noData: {
|
noData: {
|
||||||
@@ -205,13 +89,13 @@
|
|||||||
show: false
|
show: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const serverMemoryChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-memory`),
|
const serverCpuChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-cpu`),
|
||||||
optionsServerMemory);
|
optionsServerCpu);
|
||||||
serverMemoryChart.render();
|
serverCpuChart.render();
|
||||||
document.addEventListener('livewire:init', () => {
|
document.addEventListener('livewire:init', () => {
|
||||||
Livewire.on('refreshChartData-{!! $chartId !!}-memory', (chartData) => {
|
Livewire.on('refreshChartData-{!! $chartId !!}-cpu', (chartData) => {
|
||||||
checkTheme();
|
checkTheme();
|
||||||
serverMemoryChart.updateOptions({
|
serverCpuChart.updateOptions({
|
||||||
series: [{
|
series: [{
|
||||||
data: chartData[0].seriesData,
|
data: chartData[0].seriesData,
|
||||||
}],
|
}],
|
||||||
@@ -226,7 +110,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
min: 0,
|
|
||||||
show: true,
|
show: true,
|
||||||
labels: {
|
labels: {
|
||||||
show: true,
|
show: true,
|
||||||
@@ -246,8 +129,129 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4>Memory (%)</h4>
|
||||||
|
<div wire:ignore id="{!! $chartId !!}-memory"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
checkTheme();
|
||||||
|
const optionsServerMemory = {
|
||||||
|
stroke: {
|
||||||
|
curve: 'straight',
|
||||||
|
},
|
||||||
|
chart: {
|
||||||
|
height: '150px',
|
||||||
|
id: '{!! $chartId !!}-memory',
|
||||||
|
type: 'area',
|
||||||
|
toolbar: {
|
||||||
|
show: true,
|
||||||
|
tools: {
|
||||||
|
download: false,
|
||||||
|
selection: false,
|
||||||
|
zoom: true,
|
||||||
|
zoomin: false,
|
||||||
|
zoomout: false,
|
||||||
|
pan: false,
|
||||||
|
reset: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
type: 'gradient',
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
offsetY: -10,
|
||||||
|
style: {
|
||||||
|
colors: ['#FCD452'],
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
enabled: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
show: true,
|
||||||
|
borderColor: '',
|
||||||
|
},
|
||||||
|
colors: [baseColor],
|
||||||
|
xaxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
style: {
|
||||||
|
colors: textColor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
name: "Memory (%)",
|
||||||
|
data: []
|
||||||
|
}],
|
||||||
|
noData: {
|
||||||
|
text: 'Loading...',
|
||||||
|
style: {
|
||||||
|
color: textColor,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: true,
|
||||||
|
marker: {
|
||||||
|
show: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const serverMemoryChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-memory`),
|
||||||
|
optionsServerMemory);
|
||||||
|
serverMemoryChart.render();
|
||||||
|
document.addEventListener('livewire:init', () => {
|
||||||
|
Livewire.on('refreshChartData-{!! $chartId !!}-memory', (chartData) => {
|
||||||
|
checkTheme();
|
||||||
|
serverMemoryChart.updateOptions({
|
||||||
|
series: [{
|
||||||
|
data: chartData[0].seriesData,
|
||||||
|
}],
|
||||||
|
colors: [baseColor],
|
||||||
|
xaxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
style: {
|
||||||
|
colors: textColor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
min: 0,
|
||||||
|
show: true,
|
||||||
|
labels: {
|
||||||
|
show: true,
|
||||||
|
style: {
|
||||||
|
colors: textColor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
noData: {
|
||||||
|
text: 'Loading...',
|
||||||
|
style: {
|
||||||
|
color: textColor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@else
|
||||||
|
<div>Metrics are disabled for this server.</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Server Destinations | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Destinations | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<x-server.navbar :server="$server" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Server Log Drains | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Log Drains | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<x-server.navbar :server="$server" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Server Connection | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Private Key | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<x-server.navbar :server="$server" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Server Configurations | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > General | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" />
|
<x-server.navbar :server="$server" />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
<div>
|
<div>
|
||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
Settings | Coolify
|
Transactional Email | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-settings.navbar />
|
<x-settings.navbar />
|
||||||
<div class="flex items-center gap-2">
|
<form wire:submit='submit' class="flex flex-col gap-2 pb-4">
|
||||||
<h2>Transactional Email</h2>
|
<div class="flex items-center gap-2">
|
||||||
</div>
|
<h2>Transactional Email</h2>
|
||||||
<div class="pb-4 ">Email settings for password resets, invitations, etc.</div>
|
<x-forms.button type="submit">
|
||||||
<form wire:submit='submitFromFields' class="flex flex-col gap-2 pb-4">
|
Save
|
||||||
<x-forms.input required id="settings.smtp_from_name" helper="Name used in emails." label="From Name" />
|
</x-forms.button>
|
||||||
<x-forms.input required id="settings.smtp_from_address" helper="Email address used in emails."
|
</div>
|
||||||
label="From Address" />
|
<div class="pb-4 ">Email settings for password resets, invitations, etc.</div>
|
||||||
<x-forms.button type="submit">
|
<div class="flex gap-4">
|
||||||
Save
|
<x-forms.input required id="smtpFromName" helper="Name used in emails." label="From Name" />
|
||||||
</x-forms.button>
|
<x-forms.input required id="smtpFromAddress" helper="Email address used in emails." label="From Address" />
|
||||||
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="p-4 border dark:border-coolgray-300">
|
<div class="p-4 border dark:border-coolgray-300">
|
||||||
@@ -25,27 +27,26 @@
|
|||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-32">
|
<div class="w-32">
|
||||||
<x-forms.checkbox instantSave id="settings.smtp_enabled" label="Enabled" />
|
<x-forms.checkbox instantSave='instantSave("SMTP")' id="smtpEnabled" label="Enabled" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||||
<x-forms.input required id="settings.smtp_host" placeholder="smtp.mailgun.org" label="Host" />
|
<x-forms.input required id="smtpHost" placeholder="smtp.mailgun.org" label="Host" />
|
||||||
<x-forms.input required id="settings.smtp_port" placeholder="587" label="Port" />
|
<x-forms.input required id="smtpPort" placeholder="587" label="Port" />
|
||||||
<x-forms.input id="settings.smtp_encryption" helper="If SMTP uses SSL, set it to 'tls'."
|
<x-forms.input id="smtpEncryption" helper="If SMTP uses SSL, set it to 'tls'." placeholder="tls"
|
||||||
placeholder="tls" label="Encryption" />
|
label="Encryption" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||||
<x-forms.input id="settings.smtp_username" label="SMTP Username" />
|
<x-forms.input id="smtpUsername" label="SMTP Username" />
|
||||||
<x-forms.input id="settings.smtp_password" type="password" label="SMTP Password"
|
<x-forms.input id="smtpPassword" type="password" label="SMTP Password"
|
||||||
autocomplete="new-password" />
|
autocomplete="new-password" />
|
||||||
<x-forms.input id="settings.smtp_timeout" helper="Timeout value for sending emails."
|
<x-forms.input id="smtpTimeout" helper="Timeout value for sending emails." label="Timeout" />
|
||||||
label="Timeout" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4 border dark:border-coolgray-300">
|
<div class="p-4 border dark:border-coolgray-300">
|
||||||
<form wire:submit='submitResend' class="flex flex-col">
|
<form wire:submit='submit' class="flex flex-col">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<h3>Resend</h3>
|
<h3>Resend</h3>
|
||||||
<x-forms.button type="submit">
|
<x-forms.button type="submit">
|
||||||
@@ -53,12 +54,12 @@
|
|||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-32">
|
<div class="w-32">
|
||||||
<x-forms.checkbox instantSave='instantSaveResend' id="settings.resend_enabled" label="Enabled" />
|
<x-forms.checkbox instantSave='instantSave("Resend")' id="resendEnabled" label="Enabled" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||||
<x-forms.input type="password" id="settings.resend_api_key" placeholder="API key" required
|
<x-forms.input type="password" id="resendApiKey" placeholder="API key" required label="API Key"
|
||||||
label="Host" autocomplete="new-password" />
|
autocomplete="new-password" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ Route::group([
|
|||||||
}
|
}
|
||||||
$data = request()->all();
|
$data = request()->all();
|
||||||
|
|
||||||
|
// \App\Jobs\ServerCheckNewJob::dispatch($server, $data);
|
||||||
PushServerUpdateJob::dispatch($server, $data);
|
PushServerUpdateJob::dispatch($server, $data);
|
||||||
|
|
||||||
return response()->json(['message' => 'ok'], 200);
|
return response()->json(['message' => 'ok'], 200);
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ use App\Http\Controllers\UploadController;
|
|||||||
use App\Livewire\Admin\Index as AdminIndex;
|
use App\Livewire\Admin\Index as AdminIndex;
|
||||||
use App\Livewire\Boarding\Index as BoardingIndex;
|
use App\Livewire\Boarding\Index as BoardingIndex;
|
||||||
use App\Livewire\Dashboard;
|
use App\Livewire\Dashboard;
|
||||||
|
use App\Livewire\Destination\Index as DestinationIndex;
|
||||||
|
use App\Livewire\Destination\Show as DestinationShow;
|
||||||
use App\Livewire\Dev\Compose as Compose;
|
use App\Livewire\Dev\Compose as Compose;
|
||||||
use App\Livewire\ForcePasswordReset;
|
use App\Livewire\ForcePasswordReset;
|
||||||
use App\Livewire\Notifications\Discord as NotificationDiscord;
|
use App\Livewire\Notifications\Discord as NotificationDiscord;
|
||||||
@@ -38,7 +40,7 @@ use App\Livewire\Server\Advanced as ServerAdvanced;
|
|||||||
use App\Livewire\Server\Charts as ServerCharts;
|
use App\Livewire\Server\Charts as ServerCharts;
|
||||||
use App\Livewire\Server\CloudflareTunnels;
|
use App\Livewire\Server\CloudflareTunnels;
|
||||||
use App\Livewire\Server\Delete as DeleteServer;
|
use App\Livewire\Server\Delete as DeleteServer;
|
||||||
use App\Livewire\Server\Destination\Show as DestinationShow;
|
use App\Livewire\Server\Destinations as ServerDestinations;
|
||||||
use App\Livewire\Server\Index as ServerIndex;
|
use App\Livewire\Server\Index as ServerIndex;
|
||||||
use App\Livewire\Server\LogDrains;
|
use App\Livewire\Server\LogDrains;
|
||||||
use App\Livewire\Server\PrivateKey\Show as PrivateKeyShow;
|
use App\Livewire\Server\PrivateKey\Show as PrivateKeyShow;
|
||||||
@@ -72,8 +74,6 @@ use App\Livewire\Terminal\Index as TerminalIndex;
|
|||||||
use App\Models\GitlabApp;
|
use App\Models\GitlabApp;
|
||||||
use App\Models\ScheduledDatabaseBackupExecution;
|
use App\Models\ScheduledDatabaseBackupExecution;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDocker;
|
|
||||||
use App\Models\SwarmDocker;
|
|
||||||
use App\Providers\RouteServiceProvider;
|
use App\Providers\RouteServiceProvider;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
@@ -98,14 +98,14 @@ Route::middleware(['throttle:login'])->group(function () {
|
|||||||
Route::get('/auth/{provider}/redirect', [OauthController::class, 'redirect'])->name('auth.redirect');
|
Route::get('/auth/{provider}/redirect', [OauthController::class, 'redirect'])->name('auth.redirect');
|
||||||
Route::get('/auth/{provider}/callback', [OauthController::class, 'callback'])->name('auth.callback');
|
Route::get('/auth/{provider}/callback', [OauthController::class, 'callback'])->name('auth.callback');
|
||||||
|
|
||||||
Route::prefix('magic')->middleware(['auth'])->group(function () {
|
// Route::prefix('magic')->middleware(['auth'])->group(function () {
|
||||||
Route::get('/servers', [MagicController::class, 'servers']);
|
// Route::get('/servers', [MagicController::class, 'servers']);
|
||||||
Route::get('/destinations', [MagicController::class, 'destinations']);
|
// Route::get('/destinations', [MagicController::class, 'destinations']);
|
||||||
Route::get('/projects', [MagicController::class, 'projects']);
|
// Route::get('/projects', [MagicController::class, 'projects']);
|
||||||
Route::get('/environments', [MagicController::class, 'environments']);
|
// Route::get('/environments', [MagicController::class, 'environments']);
|
||||||
Route::get('/project/new', [MagicController::class, 'newProject']);
|
// Route::get('/project/new', [MagicController::class, 'newProject']);
|
||||||
Route::get('/environment/new', [MagicController::class, 'newEnvironment']);
|
// Route::get('/environment/new', [MagicController::class, 'newEnvironment']);
|
||||||
});
|
// });
|
||||||
|
|
||||||
Route::middleware(['auth', 'verified'])->group(function () {
|
Route::middleware(['auth', 'verified'])->group(function () {
|
||||||
Route::middleware(['throttle:force-password-reset'])->group(function () {
|
Route::middleware(['throttle:force-password-reset'])->group(function () {
|
||||||
@@ -212,7 +212,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||||||
Route::get('/private-key', PrivateKeyShow::class)->name('server.private-key');
|
Route::get('/private-key', PrivateKeyShow::class)->name('server.private-key');
|
||||||
Route::get('/resources', ResourcesShow::class)->name('server.resources');
|
Route::get('/resources', ResourcesShow::class)->name('server.resources');
|
||||||
Route::get('/cloudflare-tunnels', CloudflareTunnels::class)->name('server.cloudflare-tunnels');
|
Route::get('/cloudflare-tunnels', CloudflareTunnels::class)->name('server.cloudflare-tunnels');
|
||||||
Route::get('/destinations', DestinationShow::class)->name('server.destinations');
|
Route::get('/destinations', ServerDestinations::class)->name('server.destinations');
|
||||||
Route::get('/log-drains', LogDrains::class)->name('server.log-drains');
|
Route::get('/log-drains', LogDrains::class)->name('server.log-drains');
|
||||||
Route::get('/metrics', ServerCharts::class)->name('server.charts');
|
Route::get('/metrics', ServerCharts::class)->name('server.charts');
|
||||||
Route::get('/danger', DeleteServer::class)->name('server.delete');
|
Route::get('/danger', DeleteServer::class)->name('server.delete');
|
||||||
@@ -221,6 +221,8 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||||||
Route::get('/proxy/logs', ProxyLogs::class)->name('server.proxy.logs');
|
Route::get('/proxy/logs', ProxyLogs::class)->name('server.proxy.logs');
|
||||||
Route::get('/terminal', ExecuteContainerCommand::class)->name('server.command');
|
Route::get('/terminal', ExecuteContainerCommand::class)->name('server.command');
|
||||||
});
|
});
|
||||||
|
Route::get('/destinations', DestinationIndex::class)->name('destination.index');
|
||||||
|
Route::get('/destination/{destination_uuid}', DestinationShow::class)->name('destination.show');
|
||||||
|
|
||||||
// Route::get('/security', fn () => view('security.index'))->name('security.index');
|
// Route::get('/security', fn () => view('security.index'))->name('security.index');
|
||||||
Route::get('/security/private-key', SecurityPrivateKeyIndex::class)->name('security.private-key.index');
|
Route::get('/security/private-key', SecurityPrivateKeyIndex::class)->name('security.private-key.index');
|
||||||
@@ -312,52 +314,7 @@ Route::middleware(['auth'])->group(function () {
|
|||||||
return response()->json(['message' => $e->getMessage()], 500);
|
return response()->json(['message' => $e->getMessage()], 500);
|
||||||
}
|
}
|
||||||
})->name('download.backup');
|
})->name('download.backup');
|
||||||
Route::get('/destinations', function () {
|
|
||||||
$servers = Server::isUsable()->get();
|
|
||||||
$destinations = collect([]);
|
|
||||||
foreach ($servers as $server) {
|
|
||||||
$destinations = $destinations->merge($server->destinations());
|
|
||||||
}
|
|
||||||
$pre_selected_server_uuid = data_get(request()->query(), 'server');
|
|
||||||
if ($pre_selected_server_uuid) {
|
|
||||||
$server = $servers->firstWhere('uuid', $pre_selected_server_uuid);
|
|
||||||
if ($server) {
|
|
||||||
$server_id = $server->id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('destination.all', [
|
|
||||||
'destinations' => $destinations,
|
|
||||||
'servers' => $servers,
|
|
||||||
'server_id' => $server_id ?? null,
|
|
||||||
]);
|
|
||||||
})->name('destination.all');
|
|
||||||
// Route::get('/destination/new', function () {
|
|
||||||
// $servers = Server::isUsable()->get();
|
|
||||||
// $pre_selected_server_uuid = data_get(request()->query(), 'server');
|
|
||||||
// if ($pre_selected_server_uuid) {
|
|
||||||
// $server = $servers->firstWhere('uuid', $pre_selected_server_uuid);
|
|
||||||
// if ($server) {
|
|
||||||
// $server_id = $server->id;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return view('destination.new', [
|
|
||||||
// "servers" => $servers,
|
|
||||||
// "server_id" => $server_id ?? null,
|
|
||||||
// ]);
|
|
||||||
// })->name('destination.new');
|
|
||||||
Route::get('/destination/{destination_uuid}', function () {
|
|
||||||
$standalone_dockers = StandaloneDocker::where('uuid', request()->destination_uuid)->first();
|
|
||||||
$swarm_dockers = SwarmDocker::where('uuid', request()->destination_uuid)->first();
|
|
||||||
if (! $standalone_dockers && ! $swarm_dockers) {
|
|
||||||
abort(404);
|
|
||||||
}
|
|
||||||
$destination = $standalone_dockers ? $standalone_dockers : $swarm_dockers;
|
|
||||||
|
|
||||||
return view('destination.show', [
|
|
||||||
'destination' => $destination->load(['server']),
|
|
||||||
]);
|
|
||||||
})->name('destination.show');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::any('/{any}', function () {
|
Route::any('/{any}', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user