diff --git a/app/Actions/Application/StopApplication.php b/app/Actions/Application/StopApplication.php index f5f16f3fe..01c258b65 100644 --- a/app/Actions/Application/StopApplication.php +++ b/app/Actions/Application/StopApplication.php @@ -3,6 +3,7 @@ namespace App\Actions\Application; use App\Actions\Server\CleanupDocker; +use App\Events\ServiceStatusChanged; use App\Models\Application; use Lorisleiva\Actions\Concerns\AsAction; @@ -14,6 +15,7 @@ class StopApplication public function handle(Application $application, bool $previewDeployments = false, bool $dockerCleanup = true) { + ray('StopApplication'); try { $server = $application->destination->server; if (! $server->isFunctional()) { @@ -38,6 +40,8 @@ class StopApplication } } catch (\Exception $e) { return $e->getMessage(); + } finally { + ServiceStatusChanged::dispatch($application->environment->project->team->id); } } } diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php index 161e7dad7..5c0cadd55 100644 --- a/app/Actions/Database/StopDatabase.php +++ b/app/Actions/Database/StopDatabase.php @@ -3,6 +3,7 @@ namespace App\Actions\Database; use App\Actions\Server\CleanupDocker; +use App\Events\ServiceStatusChanged; use App\Models\StandaloneClickhouse; use App\Models\StandaloneDragonfly; use App\Models\StandaloneKeydb; @@ -19,23 +20,30 @@ class StopDatabase public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database, bool $isDeleteOperation = false, bool $dockerCleanup = true) { - $server = $database->destination->server; - if (! $server->isFunctional()) { - return 'Server is not functional'; - } - - $this->stopContainer($database, $database->uuid, 30); - if ($isDeleteOperation) { - if ($dockerCleanup) { - CleanupDocker::dispatch($server, true); + try { + $server = $database->destination->server; + if (! $server->isFunctional()) { + return 'Server is not functional'; } + + $this->stopContainer($database, $database->uuid, 30); + if ($isDeleteOperation) { + if ($dockerCleanup) { + CleanupDocker::dispatch($server, true); + } + } + + if ($database->is_public) { + StopDatabaseProxy::run($database); + } + + return 'Database stopped successfully'; + } catch (\Exception $e) { + return 'Database stop failed: '.$e->getMessage(); + } finally { + ServiceStatusChanged::dispatch($database->environment->project->team->id); } - if ($database->is_public) { - StopDatabaseProxy::run($database); - } - - return 'Database stopped successfully'; } private function stopContainer($database, string $containerName, int $timeout = 30): void diff --git a/app/Actions/Docker/GetContainersStatus.php b/app/Actions/Docker/GetContainersStatus.php index 091268043..d7275ca90 100644 --- a/app/Actions/Docker/GetContainersStatus.php +++ b/app/Actions/Docker/GetContainersStatus.php @@ -4,6 +4,7 @@ namespace App\Actions\Docker; use App\Actions\Database\StartDatabaseProxy; use App\Actions\Shared\ComplexStatusCheck; +use App\Events\ServiceChecked; use App\Models\ApplicationPreview; use App\Models\Server; use App\Models\ServiceDatabase; @@ -341,5 +342,6 @@ class GetContainersStatus } // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); } + ServiceChecked::dispatch($this->server->team->id); } } diff --git a/app/Actions/Server/CheckUpdates.php b/app/Actions/Server/CheckUpdates.php index 01a9a764d..55b06e863 100644 --- a/app/Actions/Server/CheckUpdates.php +++ b/app/Actions/Server/CheckUpdates.php @@ -84,7 +84,7 @@ class CheckUpdates $out['osId'] = $osId; $out['package_manager'] = $packageManager; $rebootRequired = instant_remote_process(['LANG=C dnf needs-restarting -r'], $server); - $out['reboot_required'] = $rebootRequired === '0' ? true : false; + $out['reboot_required'] = $rebootRequired !== '0'; return $out; case 'apt': diff --git a/app/Actions/Server/UpdatePackage.php b/app/Actions/Server/UpdatePackage.php index 85e495784..75d931f93 100644 --- a/app/Actions/Server/UpdatePackage.php +++ b/app/Actions/Server/UpdatePackage.php @@ -49,33 +49,4 @@ class UpdatePackage ]; } } - - private function parseAptOutput(string $output): array - { - $updates = []; - $lines = explode("\n", $output); - - foreach ($lines as $line) { - // Skip the "Listing... Done" line and empty lines - if (empty($line) || str_contains($line, 'Listing...')) { - continue; - } - - // Example line: package/stable 2.0-1 amd64 [upgradable from: 1.0-1] - if (preg_match('/^(.+?)\/(\S+)\s+(\S+)\s+(\S+)\s+\[upgradable from: (.+?)\]/', $line, $matches)) { - $updates[] = [ - 'package' => $matches[1], - 'repository' => $matches[2], - 'new_version' => $matches[3], - 'architecture' => $matches[4], - 'current_version' => $matches[5], - ]; - } - } - - return [ - 'total_updates' => count($updates), - 'updates' => $updates, - ]; - } } diff --git a/app/Actions/Service/StartService.php b/app/Actions/Service/StartService.php index d48594e62..1e7779a75 100644 --- a/app/Actions/Service/StartService.php +++ b/app/Actions/Service/StartService.php @@ -41,6 +41,6 @@ class StartService } } - return remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged'); + return remote_process($commands, $service->server, type_uuid: $service->uuid); } } diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php index 4bc2ecf03..9638db57f 100644 --- a/app/Actions/Service/StopService.php +++ b/app/Actions/Service/StopService.php @@ -3,6 +3,7 @@ namespace App\Actions\Service; use App\Actions\Server\CleanupDocker; +use App\Events\ServiceStatusChanged; use App\Models\Service; use Lorisleiva\Actions\Concerns\AsAction; @@ -31,6 +32,8 @@ class StopService } } catch (\Exception $e) { return $e->getMessage(); + } finally { + ServiceStatusChanged::dispatch($service->environment->project->team->id); } } } diff --git a/app/Events/ServiceChecked.php b/app/Events/ServiceChecked.php new file mode 100644 index 000000000..3f130a0fb --- /dev/null +++ b/app/Events/ServiceChecked.php @@ -0,0 +1,35 @@ +check() && auth()->user()->currentTeam()) { + $teamId = auth()->user()->currentTeam()->id; + } + $this->teamId = $teamId; + } + + public function broadcastOn(): array + { + if (is_null($this->teamId)) { + return []; + } + + return [ + new PrivateChannel("team.{$this->teamId}"), + ]; + } +} diff --git a/app/Events/ServiceStatusChanged.php b/app/Events/ServiceStatusChanged.php index f5a30e874..97ca4b0f8 100644 --- a/app/Events/ServiceStatusChanged.php +++ b/app/Events/ServiceStatusChanged.php @@ -13,24 +13,22 @@ class ServiceStatusChanged implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; - public int|string|null $userId = null; - - public function __construct($userId = null) - { - if (is_null($userId)) { - $userId = Auth::id() ?? null; + public function __construct( + public ?int $teamId = null + ) { + if (is_null($this->teamId) && Auth::check() && Auth::user()->currentTeam()) { + $this->teamId = Auth::user()->currentTeam()->id; } - $this->userId = $userId; } - public function broadcastOn(): ?array + public function broadcastOn(): array { - if (is_null($this->userId)) { + if (is_null($this->teamId)) { return []; } return [ - new PrivateChannel("user.{$this->userId}"), + new PrivateChannel("team.{$this->teamId}"), ]; } } diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 3e0627b21..10d58ecec 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -989,7 +989,33 @@ class ApplicationsController extends Controller $dockerComposeDomainsJson = collect(); if ($request->has('docker_compose_domains')) { - $yaml = Yaml::parse($application->docker_compose_raw); + if (! $request->has('docker_compose_raw')) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The base64 encoded docker_compose_raw is required.', + ], + ], 422); + } + + if (! isBase64Encoded($request->docker_compose_raw)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $dockerComposeRaw = base64_decode($request->docker_compose_raw); + if (mb_detect_encoding($dockerComposeRaw, 'ASCII', true) === false) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $yaml = Yaml::parse($dockerComposeRaw); $services = data_get($yaml, 'services'); $dockerComposeDomains = collect($request->docker_compose_domains); if ($dockerComposeDomains->count() > 0) { @@ -1095,7 +1121,34 @@ class ApplicationsController extends Controller $dockerComposeDomainsJson = collect(); if ($request->has('docker_compose_domains')) { - $yaml = Yaml::parse($application->docker_compose_raw); + if (! $request->has('docker_compose_raw')) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The base64 encoded docker_compose_raw is required.', + ], + ], 422); + } + + if (! isBase64Encoded($request->docker_compose_raw)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $dockerComposeRaw = base64_decode($request->docker_compose_raw); + if (mb_detect_encoding($dockerComposeRaw, 'ASCII', true) === false) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $dockerComposeRaw = base64_decode($request->docker_compose_raw); + $yaml = Yaml::parse($dockerComposeRaw); $services = data_get($yaml, 'services'); $dockerComposeDomains = collect($request->docker_compose_domains); if ($dockerComposeDomains->count() > 0) { @@ -1918,7 +1971,34 @@ class ApplicationsController extends Controller $dockerComposeDomainsJson = collect(); if ($request->has('docker_compose_domains')) { - $yaml = Yaml::parse($application->docker_compose_raw); + if (! $request->has('docker_compose_raw')) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The base64 encoded docker_compose_raw is required.', + ], + ], 422); + } + + if (! isBase64Encoded($request->docker_compose_raw)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $dockerComposeRaw = base64_decode($request->docker_compose_raw); + if (mb_detect_encoding($dockerComposeRaw, 'ASCII', true) === false) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $dockerComposeRaw = base64_decode($request->docker_compose_raw); + $yaml = Yaml::parse($dockerComposeRaw); $services = data_get($yaml, 'services'); $dockerComposeDomains = collect($request->docker_compose_domains); if ($dockerComposeDomains->count() > 0) { diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 46606e24a..5c7f20902 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -319,9 +319,10 @@ class DeployController extends Controller default: // Database resource StartDatabase::dispatch($resource); - $resource->update([ - 'started_at' => now(), - ]); + + $resource->started_at ??= now(); + $resource->save(); + $message = "Database {$resource->name} started."; break; } diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index aab6a222d..558ec6e3c 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -5,7 +5,7 @@ namespace App\Jobs; use App\Actions\Docker\GetContainersStatus; use App\Enums\ApplicationDeploymentStatus; use App\Enums\ProcessStatus; -use App\Events\ApplicationStatusChanged; +use App\Events\ServiceStatusChanged; use App\Models\Application; use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationPreview; @@ -331,7 +331,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->application_deployment_queue->addLogEntry("Gracefully shutting down build container: {$this->deployment_uuid}"); $this->graceful_shutdown_container($this->deployment_uuid); - ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id')); + ServiceStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id')); } } @@ -507,7 +507,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue if ($this->env_filename) { $command .= " --env-file {$this->workdir}/{$this->env_filename}"; } - $command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build --pull"; + if ($this->force_rebuild) { + $command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build --pull --no-cache"; + } else { + $command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build --pull"; + } $this->execute_remote_command( [executeInDocker($this->deployment_uuid, $command), 'hidden' => true], ); diff --git a/app/Livewire/Project/Application/Configuration.php b/app/Livewire/Project/Application/Configuration.php index 267ca72ad..f0b95a9c8 100644 --- a/app/Livewire/Project/Application/Configuration.php +++ b/app/Livewire/Project/Application/Configuration.php @@ -17,7 +17,15 @@ class Configuration extends Component public $servers; - protected $listeners = ['buildPackUpdated' => '$refresh']; + public function getListeners() + { + $teamId = auth()->user()->currentTeam()->id; + + return [ + "echo-private:team.{$teamId},ServiceChecked" => '$refresh', + 'buildPackUpdated' => '$refresh', + ]; + } public function mount() { diff --git a/app/Livewire/Project/Application/Deployment/Index.php b/app/Livewire/Project/Application/Deployment/Index.php index 0567a6e8a..c957615ac 100644 --- a/app/Livewire/Project/Application/Deployment/Index.php +++ b/app/Livewire/Project/Application/Deployment/Index.php @@ -28,6 +28,15 @@ class Index extends Component protected $queryString = ['pull_request_id']; + public function getListeners() + { + $teamId = auth()->user()->currentTeam()->id; + + return [ + "echo-private:team.{$teamId},ServiceChecked" => '$refresh', + ]; + } + public function mount() { $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); diff --git a/app/Livewire/Project/Application/Deployment/Show.php b/app/Livewire/Project/Application/Deployment/Show.php index 7b2ac09d3..cdac47d3d 100644 --- a/app/Livewire/Project/Application/Deployment/Show.php +++ b/app/Livewire/Project/Application/Deployment/Show.php @@ -18,7 +18,15 @@ class Show extends Component public $isKeepAliveOn = true; - protected $listeners = ['refreshQueue']; + public function getListeners() + { + $teamId = auth()->user()->currentTeam()->id; + + return [ + "echo-private:team.{$teamId},ServiceChecked" => '$refresh', + 'refreshQueue', + ]; + } public function mount() { diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php index 5b0ae12ef..06b29fad1 100644 --- a/app/Livewire/Project/Application/Heading.php +++ b/app/Livewire/Project/Application/Heading.php @@ -4,7 +4,6 @@ namespace App\Livewire\Project\Application; use App\Actions\Application\StopApplication; use App\Actions\Docker\GetContainersStatus; -use App\Events\ApplicationStatusChanged; use App\Models\Application; use Livewire\Component; use Visus\Cuid2\Cuid2; @@ -28,7 +27,8 @@ class Heading extends Component $teamId = auth()->user()->currentTeam()->id; return [ - "echo-private:team.{$teamId},ApplicationStatusChanged" => 'check_status', + "echo-private:team.{$teamId},ServiceStatusChanged" => 'checkStatus', + "echo-private:team.{$teamId},ServiceChecked" => '$refresh', 'compose_loaded' => '$refresh', 'update_links' => '$refresh', ]; @@ -46,13 +46,12 @@ class Heading extends Component $this->lastDeploymentLink = $this->application->gitCommitLink(data_get($lastDeployment, 'commit')); } - public function check_status($showNotification = false) + public function checkStatus() { if ($this->application->destination->server->isFunctional()) { GetContainersStatus::dispatch($this->application->destination->server); - } - if ($showNotification) { - $this->dispatch('success', 'Success', 'Application status updated.'); + } else { + $this->dispatch('error', 'Server is not functional.'); } } @@ -111,16 +110,8 @@ class Heading extends Component public function stop() { - StopApplication::run($this->application, false, $this->docker_cleanup); - $this->application->status = 'exited'; - $this->application->save(); - if ($this->application->additional_servers->count() > 0) { - $this->application->additional_servers->each(function ($server) { - $server->pivot->status = 'exited:unhealthy'; - $server->pivot->save(); - }); - } - ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id')); + $this->dispatch('info', 'Gracefully stopping application, it could take a while depending on the application.'); + StopApplication::dispatch($this->application, false, $this->docker_cleanup); } public function restart() diff --git a/app/Livewire/Project/Database/Configuration.php b/app/Livewire/Project/Database/Configuration.php index 938abba54..6c4d0867e 100644 --- a/app/Livewire/Project/Database/Configuration.php +++ b/app/Livewire/Project/Database/Configuration.php @@ -2,6 +2,7 @@ namespace App\Livewire\Project\Database; +use Auth; use Livewire\Component; class Configuration extends Component @@ -14,6 +15,15 @@ class Configuration extends Component public $environment; + public function getListeners() + { + $teamId = Auth::user()->currentTeam()->id; + + return [ + "echo-private:team.{$teamId},ServiceChecked" => '$refresh', + ]; + } + public function mount() { $this->currentRoute = request()->route()->getName(); diff --git a/app/Livewire/Project/Database/Heading.php b/app/Livewire/Project/Database/Heading.php index 9ddb1909c..e11402844 100644 --- a/app/Livewire/Project/Database/Heading.php +++ b/app/Livewire/Project/Database/Heading.php @@ -6,7 +6,7 @@ use App\Actions\Database\RestartDatabase; use App\Actions\Database\StartDatabase; use App\Actions\Database\StopDatabase; use App\Actions\Docker\GetContainersStatus; -use Illuminate\Support\Facades\Auth; +use App\Events\ServiceStatusChanged; use Livewire\Component; class Heading extends Component @@ -19,36 +19,40 @@ class Heading extends Component public function getListeners() { - $userId = Auth::id(); + $teamId = auth()->user()->currentTeam()->id; return [ - "echo-private:user.{$userId},DatabaseStatusChanged" => 'activityFinished', + "echo-private:team.{$teamId},ServiceStatusChanged" => 'checkStatus', + "echo-private:team.{$teamId},ServiceChecked" => 'activityFinished', + 'refresh' => '$refresh', + 'compose_loaded' => '$refresh', + 'update_links' => '$refresh', ]; } public function activityFinished() { - $this->database->update([ - 'started_at' => now(), - ]); - $this->check_status(); + try { + $this->database->started_at ??= now(); + $this->database->save(); - if (is_null($this->database->config_hash) || $this->database->isConfigurationChanged()) { - $this->database->isConfigurationChanged(true); - $this->dispatch('configurationChanged'); - } else { + if (is_null($this->database->config_hash) || $this->database->isConfigurationChanged()) { + $this->database->isConfigurationChanged(true); + } $this->dispatch('configurationChanged'); + } catch (\Exception $e) { + return handleError($e, $this); + } finally { + $this->dispatch('refresh'); } } - public function check_status($showNotification = false) + public function checkStatus() { if ($this->database->destination->server->isFunctional()) { - GetContainersStatus::run($this->database->destination->server); - } - - if ($showNotification) { - $this->dispatch('success', 'Database status updated.'); + GetContainersStatus::dispatch($this->database->destination->server); + } else { + $this->dispatch('error', 'Server is not functional.'); } } @@ -59,23 +63,24 @@ class Heading extends Component public function stop() { - StopDatabase::run($this->database, false, $this->docker_cleanup); - $this->database->status = 'exited'; - $this->database->save(); - $this->check_status(); - $this->dispatch('refresh'); + try { + $this->dispatch('info', 'Gracefully stopping database, it could take a while depending on the size of the database.'); + StopDatabase::dispatch($this->database, false, $this->docker_cleanup); + } catch (\Exception $e) { + $this->dispatch('error', $e->getMessage()); + } } public function restart() { $activity = RestartDatabase::run($this->database); - $this->dispatch('activityMonitor', $activity->id); + $this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class); } public function start() { $activity = StartDatabase::run($this->database); - $this->dispatch('activityMonitor', $activity->id); + $this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class); } public function render() diff --git a/app/Livewire/Project/Service/Configuration.php b/app/Livewire/Project/Service/Configuration.php index 20067b1f9..8ac74e7de 100644 --- a/app/Livewire/Project/Service/Configuration.php +++ b/app/Livewire/Project/Service/Configuration.php @@ -2,7 +2,6 @@ namespace App\Livewire\Project\Service; -use App\Actions\Docker\GetContainersStatus; use App\Models\Service; use Illuminate\Support\Facades\Auth; use Livewire\Component; @@ -27,13 +26,10 @@ class Configuration extends Component public function getListeners() { - $userId = Auth::id(); + $teamId = Auth::user()->currentTeam()->id; return [ - "echo-private:user.{$userId},ServiceStatusChanged" => 'check_status', - 'refreshStatus' => '$refresh', - 'check_status', - 'refreshServices', + "echo-private:team.{$teamId},ServiceChecked" => 'serviceChecked', ]; } @@ -97,19 +93,15 @@ class Configuration extends Component } } - public function check_status() + public function serviceChecked() { try { - if ($this->service->server->isFunctional()) { - GetContainersStatus::dispatch($this->service->server); - } $this->service->applications->each(function ($application) { $application->refresh(); }); $this->service->databases->each(function ($database) { $database->refresh(); }); - $this->dispatch('refreshStatus'); } catch (\Exception $e) { return handleError($e, $this); } diff --git a/app/Livewire/Project/Service/EditDomain.php b/app/Livewire/Project/Service/EditDomain.php index fb1c05255..b7f73159e 100644 --- a/app/Livewire/Project/Service/EditDomain.php +++ b/app/Livewire/Project/Service/EditDomain.php @@ -47,7 +47,6 @@ class EditDomain extends Component $this->application->service->parse(); $this->dispatch('refresh'); $this->dispatch('configurationChanged'); - $this->dispatch('refreshStatus'); } catch (\Throwable $e) { $originalFqdn = $this->application->getOriginal('fqdn'); if ($originalFqdn !== $this->application->fqdn) { diff --git a/app/Livewire/Project/Service/Navbar.php b/app/Livewire/Project/Service/Heading.php similarity index 60% rename from app/Livewire/Project/Service/Navbar.php rename to app/Livewire/Project/Service/Heading.php index 5da425cbd..153b10fc8 100644 --- a/app/Livewire/Project/Service/Navbar.php +++ b/app/Livewire/Project/Service/Heading.php @@ -2,6 +2,7 @@ namespace App\Livewire\Project\Service; +use App\Actions\Docker\GetContainersStatus; use App\Actions\Service\StartService; use App\Actions\Service\StopService; use App\Enums\ProcessStatus; @@ -11,7 +12,7 @@ use Illuminate\Support\Facades\Auth; use Livewire\Component; use Spatie\Activitylog\Models\Activity; -class Navbar extends Component +class Heading extends Component { public Service $service; @@ -35,35 +36,44 @@ class Navbar extends Component public function getListeners() { - $userId = Auth::id(); + $teamId = Auth::user()->currentTeam()->id; return [ - "echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted', + "echo-private:team.{$teamId},ServiceStatusChanged" => 'checkStatus', + "echo-private:team.{$teamId},ServiceChecked" => 'serviceChecked', + 'refresh' => '$refresh', 'envsUpdated' => '$refresh', - 'refreshStatus' => '$refresh', ]; } - public function serviceStarted() + public function checkStatus() { - // $this->dispatch('success', 'Service status changed.'); - if (is_null($this->service->config_hash) || $this->service->isConfigurationChanged()) { - $this->service->isConfigurationChanged(true); - $this->dispatch('configurationChanged'); + if ($this->service->server->isFunctional()) { + GetContainersStatus::dispatch($this->service->server); } else { - $this->dispatch('configurationChanged'); + $this->dispatch('error', 'Server is not functional.'); } } - public function check_status_without_notification() + public function serviceChecked() { - $this->dispatch('check_status'); - } + try { + $this->service->applications->each(function ($application) { + $application->refresh(); + }); + $this->service->databases->each(function ($database) { + $database->refresh(); + }); + if (is_null($this->service->config_hash) || $this->service->isConfigurationChanged()) { + $this->service->isConfigurationChanged(true); + } + $this->dispatch('configurationChanged'); + } catch (\Exception $e) { + return handleError($e, $this); + } finally { + $this->dispatch('refresh')->self(); + } - public function check_status() - { - $this->dispatch('check_status'); - $this->dispatch('success', 'Service status updated.'); } public function checkDeployments() @@ -86,34 +96,33 @@ class Navbar extends Component public function start() { $activity = StartService::run($this->service, pullLatestImages: true); - $this->dispatch('activityMonitor', $activity->id); + $this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class); } public function forceDeploy() { try { - $activities = Activity::where('properties->type_uuid', $this->service->uuid)->where('properties->status', ProcessStatus::IN_PROGRESS->value)->orWhere('properties->status', ProcessStatus::QUEUED->value)->get(); + $activities = Activity::where('properties->type_uuid', $this->service->uuid) + ->where(function ($q) { + $q->where('properties->status', ProcessStatus::IN_PROGRESS->value) + ->orWhere('properties->status', ProcessStatus::QUEUED->value); + })->get(); foreach ($activities as $activity) { $activity->properties->status = ProcessStatus::ERROR->value; $activity->save(); } $activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true); - $this->dispatch('activityMonitor', $activity->id); + $this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class); } catch (\Exception $e) { $this->dispatch('error', $e->getMessage()); } } - public function stop($cleanupContainers = false) + public function stop() { try { - StopService::run($this->service, false, $this->docker_cleanup); - ServiceStatusChanged::dispatch(); - if ($cleanupContainers) { - $this->dispatch('success', 'Containers cleaned up.'); - } else { - $this->dispatch('success', 'Service stopped.'); - } + $this->dispatch('info', 'Gracefully stopping service, it could take a while depending on the service.'); + StopService::dispatch($this->service, false, $this->docker_cleanup); } catch (\Exception $e) { $this->dispatch('error', $e->getMessage()); } @@ -128,7 +137,7 @@ class Navbar extends Component return; } $activity = StartService::run($this->service, stopBeforeStart: true); - $this->dispatch('activityMonitor', $activity->id); + $this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class); } public function pullAndRestartEvent() @@ -140,12 +149,12 @@ class Navbar extends Component return; } $activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true); - $this->dispatch('activityMonitor', $activity->id); + $this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class); } public function render() { - return view('livewire.project.service.navbar', [ + return view('livewire.project.service.heading', [ 'checkboxes' => [ ['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')], ], diff --git a/app/Livewire/Project/Shared/Destination.php b/app/Livewire/Project/Shared/Destination.php index 71a913add..9d5331ff6 100644 --- a/app/Livewire/Project/Shared/Destination.php +++ b/app/Livewire/Project/Shared/Destination.php @@ -119,17 +119,14 @@ class Destination extends Component public function refreshServers() { GetContainersStatus::run($this->resource->destination->server); - // ContainerStatusJob::dispatchSync($this->resource->destination->server); $this->loadData(); $this->dispatch('refresh'); - ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id')); } public function addServer(int $network_id, int $server_id) { $this->resource->additional_networks()->attach($network_id, ['server_id' => $server_id]); $this->loadData(); - ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id')); } public function removeServer(int $network_id, int $server_id, $password) diff --git a/app/Livewire/Project/Shared/ExecuteContainerCommand.php b/app/Livewire/Project/Shared/ExecuteContainerCommand.php index 98289c536..df69a2250 100644 --- a/app/Livewire/Project/Shared/ExecuteContainerCommand.php +++ b/app/Livewire/Project/Shared/ExecuteContainerCommand.php @@ -35,6 +35,15 @@ class ExecuteContainerCommand extends Component 'command' => 'required', ]; + public function getListeners() + { + $teamId = auth()->user()->currentTeam()->id; + + return [ + "echo-private:team.{$teamId},ServiceChecked" => '$refresh', + ]; + } + public function mount() { if (! auth()->user()->isAdmin()) { diff --git a/app/Livewire/Project/Shared/Logs.php b/app/Livewire/Project/Shared/Logs.php index 12022b1ee..2d760fae2 100644 --- a/app/Livewire/Project/Shared/Logs.php +++ b/app/Livewire/Project/Shared/Logs.php @@ -37,6 +37,15 @@ class Logs extends Component public $cpu; + public function getListeners() + { + $teamId = auth()->user()->currentTeam()->id; + + return [ + "echo-private:team.{$teamId},ServiceChecked" => '$refresh', + ]; + } + public function loadContainers($server_id) { try { diff --git a/app/Livewire/Project/Shared/ScheduledTask/Show.php b/app/Livewire/Project/Shared/ScheduledTask/Show.php index 6d9c6982a..fe6e36d5c 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Show.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Show.php @@ -46,6 +46,15 @@ class Show extends Component #[Locked] public string $task_uuid; + public function getListeners() + { + $teamId = auth()->user()->currentTeam()->id; + + return [ + "echo-private:team.{$teamId},ServiceChecked" => '$refresh', + ]; + } + public function mount(string $task_uuid, string $project_uuid, string $environment_uuid, ?string $application_uuid = null, ?string $service_uuid = null) { try { diff --git a/app/Livewire/Server/Security/Patches.php b/app/Livewire/Server/Security/Patches.php index 183055813..9392fc351 100644 --- a/app/Livewire/Server/Security/Patches.php +++ b/app/Livewire/Server/Security/Patches.php @@ -67,8 +67,18 @@ class Patches extends Component public function updateAllPackages() { + if (! $this->packageManager || ! $this->osId) { + $this->dispatch('error', message: 'Run “Check for updates” first.'); + return; + } + try { - $activity = UpdatePackage::run(server: $this->server, packageManager: $this->packageManager, osId: $this->osId, all: true); + $activity = UpdatePackage::run( + server: $this->server, + packageManager: $this->packageManager, + osId: $this->osId, + all: true + ); $this->dispatch('activityMonitor', $activity->id, ServerPackageUpdated::class); } catch (\Exception $e) { $this->dispatch('error', message: $e->getMessage()); diff --git a/app/Models/OauthSetting.php b/app/Models/OauthSetting.php index 3d82e89f2..bfd332c87 100644 --- a/app/Models/OauthSetting.php +++ b/app/Models/OauthSetting.php @@ -25,11 +25,11 @@ class OauthSetting extends Model { switch ($this->provider) { case 'azure': - return filled($this->client_id) && filled($this->client_secret) && filled($this->redirect_uri) && filled($this->tenant); + return filled($this->client_id) && filled($this->client_secret) && filled($this->tenant); case 'authentik': - return filled($this->client_id) && filled($this->client_secret) && filled($this->redirect_uri) && filled($this->base_url); + return filled($this->client_id) && filled($this->client_secret) && filled($this->base_url); default: - return filled($this->client_id) && filled($this->client_secret) && filled($this->redirect_uri); + return filled($this->client_id) && filled($this->client_secret); } } } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 8b2d9fb6a..7d6d55c5d 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -478,7 +478,7 @@ function queryDatabaseByUuidWithinTeam(string $uuid, string $teamId) { $postgresql = StandalonePostgresql::whereUuid($uuid)->first(); if ($postgresql && $postgresql->team()->id == $teamId) { - return $postgresql->unsetRelation('environment')->unsetRelation('destination'); + return $postgresql->unsetRelation('environment'); } $redis = StandaloneRedis::whereUuid($uuid)->first(); if ($redis && $redis->team()->id == $teamId) { diff --git a/bootstrap/helpers/socialite.php b/bootstrap/helpers/socialite.php index 16870e33d..fe19752cb 100644 --- a/bootstrap/helpers/socialite.php +++ b/bootstrap/helpers/socialite.php @@ -7,6 +7,10 @@ function get_socialite_provider(string $provider) { $oauth_setting = OauthSetting::firstWhere('provider', $provider); + if (! filled($oauth_setting->redirect_uri)) { + $oauth_setting->update(['redirect_uri' => route('auth.callback', $provider)]); + } + if ($provider === 'azure') { $azure_config = new \SocialiteProviders\Manager\Config( $oauth_setting->client_id, diff --git a/public/svgs/onetimesecret.svg b/public/svgs/onetimesecret.svg new file mode 100644 index 000000000..eff9738dd --- /dev/null +++ b/public/svgs/onetimesecret.svg @@ -0,0 +1,6 @@ + + +Onetime Secret + + + diff --git a/public/svgs/pgbackweb.png b/public/svgs/pgbackweb.png new file mode 100644 index 000000000..6c76c8075 Binary files /dev/null and b/public/svgs/pgbackweb.png differ diff --git a/public/svgs/seafile.svg b/public/svgs/seafile.svg new file mode 100644 index 000000000..e1c516594 --- /dev/null +++ b/public/svgs/seafile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/superset.svg b/public/svgs/superset.svg new file mode 100644 index 000000000..522c3b28a --- /dev/null +++ b/public/svgs/superset.svg @@ -0,0 +1,9 @@ + + + Superset + + + + + + diff --git a/public/svgs/yamtrack.svg b/public/svgs/yamtrack.svg new file mode 100644 index 000000000..8fd79ded2 --- /dev/null +++ b/public/svgs/yamtrack.svg @@ -0,0 +1,28 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + diff --git a/resources/css/app.css b/resources/css/app.css index 1150917e7..77fa2d66b 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -38,12 +38,13 @@ color utility to any element that depends on these defaults. */ @layer base { + *, ::after, ::before, ::backdrop, ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); + border-color: var(--color-coolgray-200, currentcolor); } } @@ -161,9 +162,9 @@ section { * Utility classes */ .input[type="password"] { - padding-right: var(--padding-10); + @apply pr-[2.4rem]; } .lds-heart { animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1); -} +} \ No newline at end of file diff --git a/resources/css/utilities.css b/resources/css/utilities.css index 44d9c60df..4993b2302 100644 --- a/resources/css/utilities.css +++ b/resources/css/utilities.css @@ -11,7 +11,7 @@ } @utility input-sticky { - @apply block py-1.5 w-full text-sm text-black rounded-sm border-0 ring-1 ring-inset dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 focus:ring-2 dark:focus:ring-coolgray-300 focus:ring-neutral-400; + @apply block py-1.5 w-full text-sm text-black rounded-sm border-0 ring-1 ring-inset dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 focus:ring-2 focus:ring-neutral-400 dark:focus:ring-coolgray-300; } @utility input-sticky-active { @@ -20,12 +20,12 @@ /* Focus */ @utility input-focus { - @apply focus:ring-2 dark:focus:ring-coolgray-300 focus:ring-neutral-400; + @apply focus:ring-2 focus:ring-neutral-400 dark:focus:ring-coolgray-300; } /* input, select before */ @utility input-select { - @apply block py-1.5 w-full text-sm text-black rounded-sm border-0 ring-1 ring-inset dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 dark:disabled:bg-coolgray-100/40 dark:disabled:ring-transparent disabled:bg-neutral-200 disabled:text-neutral-500; + @apply block py-1.5 w-full text-sm text-black rounded-sm border-0 ring-1 ring-inset dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 disabled:bg-neutral-200 disabled:text-neutral-500 dark:disabled:bg-coolgray-100/40 dark:disabled:ring-transparent; } /* Readonly */ @@ -62,19 +62,19 @@ } @utility dropdown-item { - @apply flex relative gap-2 justify-start items-center py-1 pr-4 pl-2 w-full text-xs transition-colors cursor-pointer select-none dark:text-white hover:bg-neutral-100 dark:hover:bg-coollabs outline-hidden data-disabled:pointer-events-none data-disabled:opacity-50; + @apply flex relative gap-2 justify-start items-center py-1 pr-4 pl-2 w-full text-xs transition-colors cursor-pointer select-none dark:text-white hover:bg-neutral-100 dark:hover:bg-coollabs outline-none data-disabled:pointer-events-none data-disabled:opacity-50; } @utility dropdown-item-no-padding { - @apply flex relative gap-2 justify-start items-center py-1 w-full text-xs transition-colors cursor-pointer select-none dark:text-white hover:bg-neutral-100 dark:hover:bg-coollabs outline-hidden data-disabled:pointer-events-none data-disabled:opacity-50; + @apply flex relative gap-2 justify-start items-center py-1 w-full text-xs transition-colors cursor-pointer select-none dark:text-white hover:bg-neutral-100 dark:hover:bg-coollabs outline-none data-disabled:pointer-events-none data-disabled:opacity-50; } @utility badge { - @apply inline-block w-3 h-3 text-xs font-bold leading-none rounded-full border border-neutral-200 dark:border-black; + @apply inline-block w-3 h-3 text-xs font-bold rounded-full leading-none border border-neutral-200 dark:border-black; } -@utility badge-absolute { - @apply absolute top-0 right-0 w-2 h-2 rounded-t-none rounded-r-none border-none; +@utility badge-dashboard { + @apply absolute top-0 right-0 w-2.5 h-2.5 rounded-bl-full text-xs font-bold leading-none border border-neutral-200 dark:border-black; } @utility badge-success { @@ -110,7 +110,7 @@ } @utility scrollbar { - @apply scrollbar-thumb-coollabs-100 dark:scrollbar-track-coolgray-200 scrollbar-track-neutral-200 scrollbar-thin; + @apply scrollbar-thumb-coollabs-100 scrollbar-track-neutral-200 dark:scrollbar-track-coolgray-200 scrollbar-thin; } @utility main { @@ -166,7 +166,7 @@ } @utility bg-coollabs-gradient { - @apply text-transparent text-white from-purple-500 via-pink-500 to-red-500 bg-linear-to-r; + @apply from-purple-500 via-pink-500 to-red-500 bg-linear-to-r; } @utility text-helper { @@ -194,11 +194,11 @@ } @utility fullscreen { - @apply overflow-y-auto fixed top-0 left-0 w-full h-full bg-white z-9999 dark:bg-coolgray-100 scrollbar; + @apply overflow-y-auto fixed top-0 left-0 w-full h-full bg-white z-[9999] dark:bg-coolgray-100 scrollbar; } @utility toast { - @apply z-1; + @apply z-[1]; } @utility dz-button { diff --git a/resources/views/components/modal-confirmation.blade.php b/resources/views/components/modal-confirmation.blade.php index 626d55329..3d7bf83dd 100644 --- a/resources/views/components/modal-confirmation.blade.php +++ b/resources/views/components/modal-confirmation.blade.php @@ -87,7 +87,6 @@ params.push(this.password); } params.push(this.selectedActions); - return $wire[methodName](...params) .then(result => { if (result === true) { diff --git a/resources/views/components/server/sidebar-security.blade.php b/resources/views/components/server/sidebar-security.blade.php index e89092179..6f6d9d8a0 100644 --- a/resources/views/components/server/sidebar-security.blade.php +++ b/resources/views/components/server/sidebar-security.blade.php @@ -1,6 +1,6 @@
- + Server Patching
diff --git a/resources/views/components/slide-over.blade.php b/resources/views/components/slide-over.blade.php index 74ce135b5..3cb8ec3ab 100644 --- a/resources/views/components/slide-over.blade.php +++ b/resources/views/components/slide-over.blade.php @@ -1,8 +1,7 @@ @props(['closeWithX' => false, 'fullScreen' => false])
+}" {{ $attributes->merge(['class' => 'relative w-auto h-auto']) }}> {{ $slot }}