Merge branch 'next' into next

This commit is contained in:
Andras Bacsai
2025-05-21 09:33:47 +02:00
committed by GitHub
68 changed files with 943 additions and 282 deletions

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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':

View File

@@ -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,
];
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ServiceChecked implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public ?int $teamId = null;
public function __construct($teamId = null)
{
if (is_null($teamId) && auth()->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}"),
];
}
}

View File

@@ -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}"),
];
}
}

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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],
);

View File

@@ -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()
{

View File

@@ -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();

View File

@@ -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()
{

View File

@@ -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()

View File

@@ -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();

View File

@@ -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()

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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')],
],

View File

@@ -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)

View File

@@ -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()) {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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());

View File

@@ -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);
}
}
}

View File

@@ -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) {

View File

@@ -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,

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg baseProfile="tiny-ps" version="1.2" xmlns="http://www.w3.org/2000/svg" width="1445" height="1445">
<title>Onetime Secret</title>
<path d="M0 0 C476.85 0 953.7 0 1445 0 C1445 476.85 1445 953.7 1445 1445 C968.15 1445 491.3 1445 0 1445 C0 968.15 0 491.3 0 0 Z " fill="#DC4A22" transform="translate(0,0)"/>
<path d="M0 0 C1.12 -0.01 2.23 -0.01 3.38 -0.02 C41.15 -0.21 76.81 2.74 113.82 10.2 C114.86 10.41 115.9 10.62 116.96 10.83 C161.35 20.12 209.54 39.34 243.83 69.46 C245.42 70.85 247.07 72.16 248.73 73.45 C254.39 78.13 258.33 82.3 259.33 89.77 C259.67 94.06 259.76 98.33 259.8 102.63 C259.86 104.53 259.93 106.42 260 108.32 C260.18 113.35 260.29 118.38 260.38 123.41 C260.49 128.6 260.67 133.8 260.84 138.99 C261.1 147.11 261.33 155.23 261.53 163.34 C261.83 175.27 262.35 187.18 262.94 199.09 C263.68 214.47 264.13 229.81 263.82 245.2 C261.17 246.09 259.19 246.43 256.44 246.65 C255.55 246.73 254.67 246.8 253.75 246.87 C252.8 246.95 251.84 247.03 250.85 247.11 C249.85 247.19 248.85 247.27 247.82 247.36 C244.61 247.62 241.41 247.88 238.2 248.14 C220.73 249.55 220.73 249.55 203.3 251.45 C194.21 252.56 185.08 253.29 175.96 254.06 C172.8 254.33 169.65 254.6 166.49 254.88 C164.46 255.05 162.42 255.23 160.39 255.4 C159.46 255.48 158.53 255.56 157.57 255.64 C151.97 256.11 146.45 256.28 140.82 256.2 C138.68 243.45 137 230.7 135.64 217.85 C135.32 214.89 134.98 211.93 134.64 208.97 C132.79 192.63 132.79 192.63 132.36 184.48 C132.15 175.49 132.15 175.49 128.49 167.56 C125.04 164.52 121.18 162.74 116.94 161.07 C114.82 160.2 112.83 159.21 110.8 158.12 C80.32 141.85 47 136.87 12.76 136.77 C11.92 136.76 11.07 136.75 10.2 136.75 C-17.89 136.61 -48.49 141.47 -71.18 159.2 C-72.06 159.8 -72.95 160.4 -73.86 161.02 C-84.5 171.07 -90.16 185.2 -90.57 199.64 C-90.58 201.63 -90.59 203.62 -90.57 205.62 C-90.55 207.63 -90.57 209.65 -90.59 211.67 C-90.53 230.13 -84.96 246.98 -72.01 260.46 C-50.22 280.75 -16.06 289.26 11.82 297.2 C12.92 297.52 12.92 297.52 14.05 297.85 C22.12 300.2 30.25 302.11 38.49 303.8 C39.2 303.94 39.91 304.09 40.64 304.24 C41.37 304.39 42.11 304.54 42.86 304.69 C44.41 305.01 45.96 305.32 47.51 305.64 C48.29 305.8 49.07 305.96 49.88 306.12 C72.96 310.87 95.76 316.81 118.58 322.62 C122.56 323.63 126.54 324.63 130.53 325.61 C139.39 327.79 148.17 330.02 156.82 332.95 C157.82 333.29 158.82 333.63 159.85 333.98 C190.66 344.63 219.58 361 243.57 383.13 C245.59 384.99 247.66 386.75 249.76 388.52 C281.28 416.23 294.78 458.59 298.82 499.2 C298.92 500.14 299.01 501.08 299.11 502.05 C299.92 511.11 300.01 520.14 300.01 529.23 C300.01 531.8 300.03 534.38 300.05 536.95 C300.13 561.32 297.52 587 289.82 610.2 C289.4 611.58 288.98 612.96 288.55 614.34 C280.43 640.37 267.58 664.96 248.89 684.98 C247.03 686.98 245.27 689.04 243.51 691.14 C217.39 720.99 179.38 739.53 142.6 752.24 C140.33 753.03 138.08 753.84 135.84 754.67 C118.41 760.94 99.88 764.04 81.64 766.95 C80.42 767.15 79.2 767.35 77.94 767.55 C56.34 770.86 34.74 771.53 12.93 771.46 C9.31 771.45 5.7 771.46 2.08 771.47 C-20.96 771.53 -43.98 770.84 -66.82 767.54 C-69.76 767.12 -72.71 766.73 -75.66 766.33 C-88.7 764.55 -101.63 762.58 -114.49 759.78 C-117.51 759.13 -120.54 758.5 -123.56 757.86 C-172.26 747.51 -227.93 730.44 -264.18 694.2 C-264.01 682.71 -262.42 671.27 -260.93 659.89 C-260.52 656.79 -260.12 653.69 -259.72 650.59 C-259.61 649.72 -259.5 648.85 -259.38 647.95 C-258.25 638.97 -257.22 629.98 -256.18 620.98 C-253.53 597.94 -250.67 574.93 -247.73 551.92 C-247.1 546.96 -246.47 541.99 -245.84 537.03 C-244.62 527.42 -243.4 517.81 -242.18 508.2 C-234.65 508.05 -227.3 508.65 -219.81 509.38 C-217.83 509.56 -215.85 509.75 -213.87 509.94 C-211.06 510.2 -208.26 510.48 -205.45 510.75 C-201.85 511.09 -198.24 511.44 -194.64 511.78 C-190.29 512.19 -185.94 512.61 -181.59 513.02 C-172.94 513.85 -164.29 514.66 -155.63 515.46 C-154.13 515.6 -154.13 515.6 -152.59 515.74 C-148.87 516.08 -145.15 516.42 -141.43 516.76 C-138.96 516.99 -136.49 517.21 -134.02 517.44 C-133.31 517.5 -132.61 517.57 -131.89 517.63 C-127.62 518.03 -123.43 518.59 -119.18 519.2 C-119.18 549.56 -119.18 579.92 -119.18 611.2 C-117.2 611.86 -115.22 612.52 -113.18 613.2 C-111.62 613.9 -110.08 614.62 -108.56 615.38 C-107.71 615.79 -106.87 616.2 -106 616.63 C-105.11 617.07 -104.22 617.5 -103.3 617.95 C-94.41 622.24 -85.56 626.13 -76.18 629.2 C-75.2 629.53 -75.2 629.53 -74.21 629.87 C-36.36 642.38 7.85 640.3 46.82 635.2 C47.85 635.07 48.89 634.95 49.95 634.81 C72.64 631.87 101.76 625.8 117.18 607.39 C133.19 585.5 135.64 558.23 132.42 531.87 C128.75 509.68 119.41 489.96 101.11 476.15 C63.62 451.54 13.28 441.78 -30.3 434.25 C-57.97 429.42 -85.35 422.51 -112.18 414.2 C-113.12 413.91 -113.12 413.91 -114.08 413.61 C-152.43 401.72 -184.97 385.07 -214.18 357.2 C-214.84 356.59 -215.51 355.98 -216.19 355.35 C-233.23 339.23 -244.14 316.55 -250.18 294.2 C-250.38 293.45 -250.59 292.69 -250.8 291.91 C-251.49 289.33 -252.15 286.73 -252.8 284.14 C-253.01 283.36 -253.21 282.57 -253.43 281.77 C-257.5 264.99 -257.52 247.53 -257.49 230.37 C-257.49 226.97 -257.51 223.57 -257.54 220.18 C-257.69 166.04 -242.45 114.53 -204.18 75.2 C-162.48 33.33 -97.49 11.38 -40.18 3.2 C-39.49 3.1 -38.8 3 -38.09 2.9 C-25.41 1.06 -12.81 0.05 0 0 Z " fill="#FEFEFE" transform="translate(707.17578125,357.796875)"/>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/svgs/pgbackweb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

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

@@ -0,0 +1 @@
<svg enable-background="new 0 0 1024 1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><circle cx="512" cy="512" fill="#ffa10f" r="512"/><path d="m221.2 466c0-9.6 7.7-17.2 17.2-17.2 4.8 0 8.6 1.9 11.5 4.8 0-1.9 0-2.9 0-4.8 0-32.6 25.9-58.5 58.5-58.5 8.6 0 16.3 1.9 24 4.8 0-1.9 0-2.9 0-4.8 0-48.9 39.3-88.2 88.2-88.2 47.9 0 87.2 39.3 88.2 87.2-16.3 14.4-28.7 31.6-36.4 51.7-15.3-9.6-34.5-15.3-53.7-15.3-41.2 0-76.7 23-93 58.5h-63.2-24c-9.7-.9-17.3-8.6-17.3-18.2zm462.8-52.7c-22-22-51.7-35.5-85.3-35.5-60.4 0-111.2 45-118.8 103.5-15.3-20.1-39.3-32.6-66.1-32.6-45 0-82.4 36.4-82.4 82.4 0 13.4 2.9 25.9 8.6 36.4-28.7 5.7-49.8 27.8-49.8 54.6 0 30.7 28.7 55.6 64.2 55.6 15.3 0 29.7-4.8 41.2-13.4l132.2-129.3c14.4-13.4 33.5-21.1 55.6-21.1 45 0 80.5 35.5 82.4 79.5 1 13.4-5.7 25.9-19.2 33.5-17.2 10.5-39.3 4.8-48.9-11.5-9.6-17.2-2.9-38.3 14.4-48.9 3.8-1.9 8.6-3.8 12.5-4.8-3.8-1-7.7-1-11.5-1-32.6 0-58.5 25.9-58.5 58.5s25.9 58.5 58.5 58.5h3.8 1.9 115c35.5-1.9 69-33.5 69-73.8 0-40.2-34.5-73.8-74.7-73.8-6.7 11.5-14.4 18.2-24 25.9 9.6-17.2 15.3-36.4 15.3-57.5.1-33.4-13.3-63.1-35.4-85.2z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

9
public/svgs/superset.svg Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256px" height="128px" viewBox="0 0 256 128" version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<title>Superset</title>
<g>
<path d="M190.218924,0 C168.269282,0 148.049828,12.3487941 128.508879,33.9252584 C109.307183,12.0095415 88.748476,0 65.7810761,0 C27.7508614,0 0,27.1402067 0,63.67771 C0,100.215213 27.7508614,127.016168 65.7810761,127.016168 C89.1555791,127.016168 107.271667,116.058309 127.491121,94.2104426 C147.03207,116.12616 166.912271,127.084018 190.218924,127.084018 C228.249139,127.016168 256,100.316989 256,63.67771 C256,27.038431 228.249139,0 190.218924,0 Z M66.0524781,88.6806255 C49.9379804,88.6806255 40.3371323,78.0620196 40.3371323,64.0169626 C40.3371323,49.9719056 49.9379804,39.0479724 66.0524781,39.0479724 C79.6225815,39.0479724 90.716141,49.9719056 102.725682,64.6954678 C91.3946462,78.4012722 79.4190299,88.6806255 66.0524781,88.6806255 Z M189.065465,88.6806255 C175.698913,88.6806255 164.401802,78.0620196 152.392261,64.0169626 C164.741055,49.2934005 175.359661,39.0479724 189.065465,39.0479724 C205.179963,39.0479724 214.679035,50.1076067 214.679035,64.0169626 C214.679035,77.9263186 205.179963,88.6806255 189.065465,88.6806255 Z" fill="#484848"></path>
<path d="M156.124039,117.958124 L181.703684,87.4253909 C171.526107,84.3721177 162.12881,75.2122979 152.392261,63.8473363 L127.491121,94.2104426 C135.643361,103.668805 145.322237,111.695521 156.124039,117.958124 Z" fill="#20A7C9"></path>
<path d="M128.508879,33.8913332 C120.41092,24.2972701 110.793109,16.0907501 100.045587,9.60084813 L74.432017,40.4728333 C84.1685661,43.8653591 92.7855818,52.6180758 101.945402,63.7794858 L102.963159,64.4919162 L128.508879,33.8913332 Z" fill="#20A7C9"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

28
public/svgs/yamtrack.svg Normal file
View File

@@ -0,0 +1,28 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M2325 5114 c-16 -2 -68 -9 -115 -15 -248 -32 -535 -120 -768 -234
-277 -135 -475 -277 -693 -494 -409 -410 -660 -916 -734 -1484 -20 -155 -20
-474 0 -637 72 -579 320 -1087 734 -1500 321 -321 693 -540 1116 -658 260 -73
468 -97 770 -89 381 10 695 85 1030 246 439 212 811 544 1070 956 192 305 329
695 371 1061 20 167 14 500 -11 664 -83 547 -332 1046 -715 1431 -69 69 -160
153 -203 188 -281 226 -644 409 -987 496 -210 54 -320 67 -585 70 -137 2 -263
1 -280 -1z m1718 -1178 c87 -42 119 -126 79 -206 -19 -39 -22 -39 -116 -20
-110 22 -314 -13 -472 -80 -514 -220 -984 -798 -1213 -1491 -71 -216 -94 -337
-110 -580 -14 -206 -26 -219 -209 -219 -75 0 -104 -5 -154 -25 -34 -14 -62
-25 -63 -25 -1 0 -10 18 -20 39 l-17 40 42 63 c116 174 214 452 271 773 30
170 38 487 15 643 -50 342 -193 587 -386 664 -45 19 -75 23 -170 23 -110 0
-118 -1 -181 -33 -72 -35 -180 -138 -234 -224 -17 -27 -32 -48 -35 -48 -3 0
-24 18 -45 40 -39 40 -40 43 -39 109 0 57 6 80 33 136 62 125 199 234 350 277
89 26 262 27 351 4 328 -88 554 -451 620 -996 6 -52 13 -96 15 -98 2 -2 20 28
39 67 46 90 91 162 179 281 307 417 699 747 1026 864 171 61 346 70 444 22z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -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);
}
}

View File

@@ -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 {

View File

@@ -87,7 +87,6 @@
params.push(this.password);
}
params.push(this.selectedActions);
return $wire[methodName](...params)
.then(result => {
if (result === true) {

View File

@@ -1,6 +1,6 @@
<div class="flex flex-col items-start gap-2 min-w-fit">
<a class="{{ request()->routeIs('server.security.patches') ? 'menu-item menu-item-active' : 'menu-item' }}"
href="{{ route('server.security.patches', $parameters) }}">
<button>Server Patching</button>
Server Patching
</a>
</div>

View File

@@ -1,8 +1,7 @@
@props(['closeWithX' => false, 'fullScreen' => false])
<div x-data="{
slideOverOpen: false
}" class="relative w-auto h-auto" {{ $attributes }}
>
}" {{ $attributes->merge(['class' => 'relative w-auto h-auto']) }}>
{{ $slot }}
<template x-teleport="body">
<div x-show="slideOverOpen" @if (!$closeWithX) @keydown.window.escape="slideOverOpen=false" @endif
@@ -29,7 +28,7 @@
<h2 class="text-2xl leading-6" id="slide-over-title">
{{ $title }}</h2>
<div class="flex items-center h-auto ml-3">
<button class="icon" @click="slideOverOpen=false"
<button @click="slideOverOpen=false"
class="absolute top-0 right-0 z-30 flex items-center justify-center px-3 py-2 mt-4 mr-2 space-x-1 text-xs font-normal border-none rounded-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
<nav wire:poll.10000ms="check_status">
<nav wire:poll.10000ms="checkStatus">
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :title="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
<div class="navbar-main">
<nav class="flex shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10">
@@ -81,8 +81,7 @@
'This application will be stopped.',
'All non-persistent data of this application will be deleted.',
]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Confirm" :dispatchEvent="true"
dispatchEventType="stopEvent">
step1ButtonText="Continue" step2ButtonText="Confirm">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -112,15 +111,5 @@
</div>
@endif
</div>
</div>
@script
<script>
$wire.$on('stopEvent', () => {
$wire.$dispatch('info',
'Gracefully stopping application, it could take a while depending on the application.');
$wire.$call('stop');
});
</script>
@endscript
</nav>

View File

@@ -1,4 +1,4 @@
<nav wire:poll.10000ms="check_status">
<nav wire:poll.10000ms="checkStatus">
<x-resources.breadcrumbs :resource="$database" :parameters="$parameters" />
<x-slide-over @startdatabase.window="slideOverOpen = true" closeWithX fullScreen>
<x-slot:title>Database Startup</x-slot:title>
@@ -60,8 +60,7 @@
'If the database is currently in use data could be lost.',
'All non-persistent data of this database (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the database again).',
]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Stop Database" :dispatchEvent="true"
dispatchEventType="stopEvent">
step1ButtonText="Continue" step2ButtonText="Stop Database">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -93,12 +92,9 @@
window.dispatchEvent(new CustomEvent('startdatabase'));
$wire.$call('start');
});
$wire.$on('stopEvent', () => {
$wire.$dispatch('info', 'Stopping database.');
$wire.$call('stop');
});
$wire.$on('restartEvent', () => {
$wire.$dispatch('info', 'Restarting database.');
window.dispatchEvent(new CustomEvent('startdatabase'));
$wire.$call('restart');
});
</script>

View File

@@ -277,12 +277,12 @@
@if ($current_step === 'select-postgresql-type')
<h2>Select a Postgresql type</h2>
<div>If you need extra extensions, you can select Supabase PostgreSQL (or others), otherwise select PostgreSQL
16 (default).</div>
17 (default).</div>
<div class="flex flex-col gap-6 pt-8">
<div class="gap-2 border border-transparent cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex "
wire:click="setPostgresqlType('postgres:16-alpine')">
wire:click="setPostgresqlType('postgres:17-alpine')">
<div class="flex flex-col">
<div class="box-title">PostgreSQL 16 (default)</div>
<div class="box-title">PostgreSQL 17 (default)</div>
<div class="box-description">
PostgreSQL is a powerful, open-source object-relational database system (no extensions).
</div>
@@ -297,7 +297,7 @@
</div>
</div>
<div class="gap-2 border border-transparent cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
wire:click="setPostgresqlType('supabase/postgres:15.6.1.113')">
wire:click="setPostgresqlType('supabase/postgres:17.4.1.032')">
<div class="flex flex-col">
<div class="box-title">Supabase PostgreSQL (with extensions)</div>
<div class="box-description">
@@ -314,9 +314,9 @@
</div>
</div>
<div class="gap-2 border border-transparent cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
wire:click="setPostgresqlType('postgis/postgis')">
wire:click="setPostgresqlType('postgis/postgis:17-3.5-alpine')">
<div class="flex flex-col">
<div class="box-title">PostGIS</div>
<div class="box-title">PostGIS (AMD only)</div>
<div class="box-description">
PostGIS is a PostgreSQL extension for geographic objects.
</div>
@@ -331,9 +331,9 @@
</div>
</div>
<div class="gap-2 border border-transparent cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
wire:click="setPostgresqlType('pgvector/pgvector:pg16')">
wire:click="setPostgresqlType('pgvector/pgvector:pg17')">
<div class="flex flex-col">
<div class="box-title">PGVector (16)</div>
<div class="box-title">PGVector (17)</div>
<div class="box-description">
PGVector is a PostgreSQL extension for vector data types.
</div>

View File

@@ -67,16 +67,16 @@
<div class="pb-2 truncate box-title" x-text="item.name"></div>
<div class="flex-1"></div>
<template x-if="item.status.startsWith('running')">
<div title="running" class="bg-success badge badge-absolute"></div>
<div title="running" class="bg-success badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div title="exited" class="bg-error badge badge-absolute"></div>
<div title="exited" class="bg-error badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="bg-warning badge badge-absolute"></div>
<div title="restarting" class="bg-warning badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div title="degraded" class="bg-warning badge badge-absolute"></div>
<div title="degraded" class="bg-warning badge-dashboard"></div>
</template>
</div>
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>
@@ -113,16 +113,16 @@
<div class="pb-2 truncate box-title" x-text="item.name"></div>
<div class="flex-1"></div>
<template x-if="item.status.startsWith('running')">
<div title="running" class="bg-success badge badge-absolute"></div>
<div title="running" class="bg-success badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div title="exited" class="bg-error badge badge-absolute"></div>
<div title="exited" class="bg-error badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="bg-warning badge badge-absolute"></div>
<div title="restarting" class="bg-warning badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div title="degraded" class="bg-warning badge badge-absolute"></div>
<div title="degraded" class="bg-warning badge-dashboard"></div>
</template>
</div>
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>
@@ -159,16 +159,16 @@
<div class="pb-2 truncate box-title" x-text="item.name"></div>
<div class="flex-1"></div>
<template x-if="item.status.startsWith('running')">
<div title="running" class="bg-success badge badge-absolute"></div>
<div title="running" class="bg-success badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div title="exited" class="bg-error badge badge-absolute"></div>
<div title="exited" class="bg-error badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div title="restarting" class="bg-warning badge badge-absolute"></div>
<div title="restarting" class="bg-warning badge-dashboard"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div title="degraded" class="bg-warning badge badge-absolute"></div>
<div title="degraded" class="bg-warning badge-dashboard"></div>
</template>
</div>
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>

View File

@@ -2,7 +2,7 @@
<x-slot:title>
{{ data_get_str($service, 'name')->limit(10) }} > Configuration | Coolify
</x-slot>
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
<livewire:project.service.heading :service="$service" :parameters="$parameters" :query="$query" />
<div class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
<div class="flex flex-col items-start gap-2 min-w-fit">
@@ -36,7 +36,7 @@
@if ($currentRoute === 'project.service.configuration')
<livewire:project.service.stack-form :service="$service" />
<h3>Services</h3>
<div class="grid grid-cols-1 gap-2 pt-4 xl:grid-cols-1" wire:poll.10000ms="check_status">
<div class="grid grid-cols-1 gap-2 pt-4 xl:grid-cols-1">
@foreach ($applications as $application)
<div @class([
'border-l border-dashed border-red-500' => str(

View File

@@ -1,4 +1,4 @@
<div>
<div wire:poll.10000ms="checkStatus">
<livewire:project.shared.configuration-checker :resource="$service" />
<x-slide-over @startservice.window="slideOverOpen = true" closeWithX fullScreen>
<x-slot:title>Service Startup</x-slot:title>
@@ -38,10 +38,9 @@
</svg>
Restart
</x-forms.button>
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop"
:checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Stop Service" :dispatchEvent="true"
dispatchEventType="stopEvent">
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Stop Service">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -67,10 +66,9 @@
</svg>
Restart
</x-forms.button>
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop"
:checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Stop Service" :dispatchEvent="true"
dispatchEventType="stopEvent">
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Stop Service">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
@@ -96,10 +94,9 @@
Deploy
</button>
@else
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" submitAction="stop"
:checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Stop Service" :dispatchEvent="true"
dispatchEventType="stopEvent">
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Stop Service">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"

View File

@@ -1,5 +1,5 @@
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }">
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
<livewire:project.service.heading :service="$service" :parameters="$parameters" :query="$query" />
<div class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
<div class="flex flex-col items-start gap-2 min-w-fit">
<a class="menu-item"

View File

@@ -12,28 +12,31 @@
<livewire:project.database.heading :database="$resource" />
@elseif ($type === 'service')
<livewire:project.shared.configuration-checker :resource="$resource" />
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" title="Terminal" />
<livewire:project.service.heading :service="$resource" :parameters="$parameters" title="Terminal" />
@elseif ($type === 'server')
<x-server.navbar :server="$server" :parameters="$parameters" />
@endif
@if(!$hasShell)
@if (!$hasShell)
<div class="flex items-center justify-center w-full py-4 mx-auto">
<div class="p-4 w-full rounded-sm border dark:bg-coolgray-100 dark:border-coolgray-300">
<div class="flex flex-col items-center justify-center space-y-4">
<svg class="w-12 h-12 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<div class="text-center">
<h3 class="text-lg font-medium">Terminal Not Available</h3>
<p class="mt-2 text-sm text-gray-500">No shell (bash/sh) is available in this container. Please ensure either bash or sh is installed to use the terminal.</p>
<p class="mt-2 text-sm text-gray-500">No shell (bash/sh) is available in this container. Please
ensure either bash or sh is installed to use the terminal.</p>
</div>
</div>
</div>
</div>
@else
@if ($type === 'server')
<form class="w-full" wire:submit="$dispatchSelf('connectToServer')" wire:init="$dispatchSelf('connectToServer')">
<form class="w-full" wire:submit="$dispatchSelf('connectToServer')"
wire:init="$dispatchSelf('connectToServer')">
<x-forms.button class="w-full" type="submit">Reconnect</x-forms.button>
</form>
<div class="mx-auto w-full">

View File

@@ -42,7 +42,7 @@
@endforelse
</div>
@elseif ($type === 'service')
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" :query="$query" title="Logs" />
<livewire:project.service.heading :service="$resource" :parameters="$parameters" :query="$query" title="Logs" />
<div class="pt-4">
<div class="subtitle">Here you can see the logs of the service.</div>
@forelse ($containers as $container)

View File

@@ -6,7 +6,7 @@
<h1>Scheduled Task</h1>
<livewire:project.application.heading :application="$resource" />
@elseif ($type === 'service')
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" />
<livewire:project.service.heading :service="$resource" :parameters="$parameters" />
@endif
<form wire:submit="submit" class="w-full">

View File

@@ -14,15 +14,16 @@
<x-server.sidebar-security :server="$server" :parameters="$parameters" />
<form wire:submit='submit' class="w-full">
<div>
<div class="flex items-center gap-2">
<div class="flex items-center gap-2 flex-row">
<h2>Server Patching</h2>
<x-forms.button type="button" wire:click="$dispatch('checkForUpdatesDispatch')">Manually
Check</x-forms.button>
</div>
<div>Check if your server has updates available.</div>
<div class="text-xs pt-1">(only available for apt, dnf and zypper package managers atm, more coming
soon as well as more features...)
<span class="text-xs text-neutral-500">(experimental)</span>
<x-helper
helper="Only available for apt, dnf and zypper package managers atm, more coming
soon. <br/> Also scheduled patching and notifications are coming soon..." />
<x-forms.button type="button" wire:click="$dispatch('checkForUpdatesDispatch')">
Check Now</x-forms.button>
</div>
<div>Update your servers automatically.</div>
<div>
<div class="flex flex-col gap-6 pt-4">
<div class="flex flex-col">

View File

@@ -26,7 +26,7 @@
label="Client ID" />
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.client_secret"
type="password" label="Client Secret" autocomplete="new-password" />
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.redirect_uri"
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.redirect_uri" placeholder="{{ route('auth.callback', $oauth_setting->provider) }}"
label="Redirect URI" />
@if ($oauth_setting->provider == 'azure')
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.tenant"

View File

@@ -6,7 +6,7 @@
services:
authentik-server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.2.3}
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.4.1}
restart: unless-stopped
command: server
environment:
@@ -35,7 +35,7 @@ services:
redis:
condition: service_healthy
authentik-worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.2.3}
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.4.1}
restart: unless-stopped
command: worker
environment:

View File

@@ -80,7 +80,7 @@ services:
- ACTIVE_STORAGE_SERVICE=${ACTIVE_STORAGE_SERVICE:-local}
command: ['bundle', 'exec', 'sidekiq', '-C', 'config/sidekiq.yml']
volumes:
- sidekiq-data:/app/storage
- rails-data:/app/storage
healthcheck:
test: ["CMD-SHELL", "bundle exec rails runner 'puts Sidekiq.redis(&:info)' > /dev/null 2>&1"]
interval: 30s

View File

@@ -15,7 +15,7 @@ services:
- LOG_LEVEL=${LOG_LEVEL:-info}
- LOG_JSON=${LOG_JSON:-false}
- DIUN_WATCH_WORKERS=${DIUN_WATCH_WORKERS:-20}
- DIUN_WATCH_SCHEDULE=${CRON_WATCH_SCHEDULE:- * */6 * * *}
- DIUN_WATCH_SCHEDULE=${DIUN_WATCH_SCHEDULE:- * */6 * * *}
- DIUN_WATCH_JITTER=${DIUN_WATCH_JITTER:-30s}
- DIUN_PROVIDERS_DOCKER=${DIUN_PROVIDERS_DOCKER:-true}
- DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=${DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT:-true}

View File

@@ -1,5 +1,5 @@
# documentation: https://github.com/maybe-finance/maybe
# slogan: Maybe: The OS for your personal finances.
# slogan: Maybe, the OS for your personal finances.
# tags: finances,wallets,coins,stocks,investments,open,source
# logo: svgs/maybe.svg
# port: 3000
@@ -17,29 +17,75 @@ services:
- GOOD_JOB_EXECUTION_MODE=${GOOD_JOB_EXECUTION_MODE:-async}
- SECRET_KEY_BASE=${SERVICE_BASE64_64_SECRETKEYBASE}
- DB_HOST=postgres
- POSTGRES_DB=${POSTGRES_DB:-maybe_db}
- POSTGRES_DB=${POSTGRES_DB:-maybe-db}
- POSTGRES_USER=${SERVICE_USER_POSTGRES}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- DB_PORT=5432
- REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@redis:6379/1
- OPENAI_ACCESS_TOKEN=${OPENAI_ACCESS_TOKEN}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test:
- CMD
- curl
- "-f"
- "http://localhost:3000"
interval: 10s
timeout: 10s
retries: 5
worker:
image: ghcr.io/maybe-finance/maybe:latest
command: bundle exec sidekiq
environment:
- POSTGRES_USER=${SERVICE_USER_POSTGRES}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- POSTGRES_DB=${POSTGRES_DB:-maybe-db}
- SECRET_KEY_BASE=${SERVICE_BASE64_64_SECRETKEYBASE}
- SELF_HOSTED=true
- RAILS_FORCE_SSL=${RAILS_FORCE_SSL:-false}
- RAILS_ASSUME_SSL=${RAILS_ASSUME_SSL:-false}
- GOOD_JOB_EXECUTION_MODE=${GOOD_JOB_EXECUTION_MODE:-async}
- DB_HOST=postgres
- DB_PORT=5432
- REDIS_URL=redis://default:${SERVICE_PASSWORD_REDIS}@redis:6379/1
- OPENAI_ACCESS_TOKEN=${OPENAI_ACCESS_TOKEN}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
exclude_from_hc: true
postgres:
image: postgres:16
image: postgres:16-alpine
volumes:
- maybe_postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=${SERVICE_USER_POSTGRES}
- POSTGRES_DB=${POSTGRES_DB:-maybe_db}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- POSTGRES_DB=${POSTGRES_DB:-maybe-db}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 20s
retries: 10
redis:
image: redis:8-alpine
command: redis-server --appendonly yes --requirepass ${SERVICE_PASSWORD_REDIS}
volumes:
- redis_data:/data
environment:
- REDIS_PASSWORD=${SERVICE_PASSWORD_REDIS}
- REDIS_PORT=6379
- REDIS_DB=1
healthcheck:
test: ["CMD", "redis-cli", "--pass", "${SERVICE_PASSWORD_REDIS}", "ping"]
interval: 10s
timeout: 3s
retries: 3

View File

@@ -5,7 +5,7 @@
# port: 5230
services:
wg-easy:
memos:
image: neosmemo/memos:stable
volumes:
- memos/:/var/opt/memos

View File

@@ -0,0 +1,44 @@
# documentation: https://docs.onetimesecret.com
# slogan: Share sensitive information securely with self-destructing links that are only viewable once.
# tags: auth,password,secret,secure
# logo: svgs/onetimesecret.svg
# port: 3000
services:
onetimesecret:
image: onetimesecret/onetimesecret:latest
environment:
- SERVICE_FQDN_ONETIMESECRET_3000
- AUTH_AUTOVERIFY=${AUTH_AUTOVERIFY:-true}
- AUTH_SIGNUP=${AUTH_SIGNUP:-true}
- COLONEL=${COLONEL:-admin@example.com}
- HOST=${HOST:-localhost}
- REDIS_URL=redis://:${SERVICE_PASSWORD_REDIS}@redis:6379/0
- SECRET=${SERVICE_PASSWORD_ONETIMESECRET}
- SSL=${SSL:-false}
- RACK_ENV=production
depends_on:
redis:
condition: service_healthy
healthcheck:
test:
- CMD
- ruby
- "-rnet/http"
- "-e"
- "exit(Net::HTTP.get_response(URI('http://localhost:3000')).is_a?(Net::HTTPSuccess) ? 0 : 1)"
interval: 30s
timeout: 10s
retries: 3
redis:
image: redis:8-alpine
command: redis-server --requirepass ${SERVICE_PASSWORD_REDIS}
healthcheck:
test:
- CMD
- redis-cli
- ping
interval: 30s
timeout: 10s
retries: 3

View File

@@ -10,27 +10,32 @@ services:
volumes:
- app_logs:/app/storage/logs
- app_public:/app/storage/public
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:80 || exit 1"]
interval: 10s
timeout: 1s
retries: 3
environment:
SERVICE_FQDN_PAYMENTER: ${SERVICE_FQDN_PAYMENTER_80}
DB_DATABASE: ${MYSQL_DATABASE:-paymenter-db}
DB_PASSWORD: ${SERVICE_PASSWORD_MYSQL}
DB_USERNAME: ${SERVICE_USER_MYSQL}
APP_ENV: "production"
CACHE_STORE: "redis"
SESSION_DRIVER: "redis"
QUEUE_CONNECTION: "redis"
REDIS_HOST: "redis"
APP_ENV: production
CACHE_STORE: redis
SESSION_DRIVER: redis
QUEUE_CONNECTION: redis
REDIS_HOST: redis
REDIS_USERNAME: default
REDIS_PASSWORD: "${SERVICE_PASSWORD_64_REDIS}"
DB_CONNECTION: "mariadb"
DB_HOST: "mariadb"
DB_PORT: "3306"
REDIS_PASSWORD: ${SERVICE_PASSWORD_64_REDIS}
DB_CONNECTION: mariadb
DB_HOST: mariadb
DB_PORT: 3306
APP_KEY: ${SERVICE_BASE64_KEY}
depends_on:
mariadb:
condition: service_healthy
redis:
condition: service_started
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:80 || exit 1"]
interval: 10s
timeout: 1s
retries: 3
mariadb:
image: mariadb:11

View File

@@ -0,0 +1,34 @@
# documentation: https://github.com/eduardolat/pgbackweb
# slogan: Effortless PostgreSQL backups with a user-friendly web interface!
# tags: backup, postgresql, web-interface
# logo: svgs/pgbackweb.svg
# port: 8085
services:
pgbackweb:
image: eduardolat/pgbackweb:latest
volumes:
- pgbackweb_backups:/backups
environment:
- SERVICE_FQDN_PGBACKWEB_8085
- PBW_ENCRYPTION_KEY=${SERVICE_PASSWORD_64_PGBACKWEB}
- PBW_POSTGRES_CONN_STRING=postgresql://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@postgres:5432/${POSTGRES_DB:-pgbackweb-db}?sslmode=disable
- TZ=${TIME_ZONE:-UTC}
depends_on:
postgres:
condition: service_healthy
exclude_from_hc: true
postgres:
image: postgres:17
environment:
- POSTGRES_USER=${SERVICE_USER_POSTGRES}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- POSTGRES_DB=${POSTGRES_DB:-pgbackweb-db}
volumes:
- pgbackweb_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${SERVICE_USER_POSTGRES} -d pgbackweb"]
interval: 5s
timeout: 5s
retries: 5

View File

@@ -0,0 +1,75 @@
# documentation: https://manual.seafile.com
# slogan: Open source cloud storage system for file sync, share and document collaboration
# tags: file-manager,file-sharing,storage
# logo: svgs/seafile.svg
# port: 80
services:
seafile-db:
image: mariadb:10.11
environment:
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_64_MYSQLDBROOT}
- MYSQL_LOG_CONSOLE=true
- MARIADB_AUTO_UPGRADE=1
volumes:
- seafile-mysql-db:/var/lib/mysql"
healthcheck:
test:
[
"CMD",
"/usr/local/bin/healthcheck.sh",
"--connect",
"--mariadbupgrade",
"--innodb_initialized",
]
interval: 20s
start_period: 30s
timeout: 5s
retries: 10
memcached:
image: memcached:1.6.29
entrypoint: memcached -m 256
healthcheck:
test:
[
"CMD-SHELL",
'bash -c "echo version | (exec 3<>/dev/tcp/localhost/11211; cat >&3; timeout 0.5 cat <&3; exec 3<&-)"',
]
interval: 20s
timeout: 5s
retries: 10
seafile:
image: seafileltd/seafile-mc:12.0-latest
volumes:
- seafile-data:/shared
environment:
- SERVICE_FQDN_SEAFILE_80
- DB_HOST=${SEAFILE_MYSQL_DB_HOST:-seafile-db}
- DB_PORT=${SEAFILE_MYSQL_DB_PORT:-3306}
- DB_ROOT_PASSWD=${SERVICE_PASSWORD_64_MYSQLDBROOT}
- DB_USER=${SEAFILE_MYSQL_DB_USER:-seafile}
- DB_PASSWORD=${SERVICE_PASSWORD_64_MYSQLDBUSER}
- SEAFILE_MYSQL_DB_CCNET_DB_NAME=${SEAFILE_MYSQL_DB_CCNET_DB_NAME:-ccnet_db}
- SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=${SEAFILE_MYSQL_DB_SEAFILE_DB_NAME:-seafile_db}
- SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=${SEAFILE_MYSQL_DB_SEAHUB_DB_NAME:-seahub_db}
- TIME_ZONE=${TIME_ZONE:-Etc/UTC}
- INIT_SEAFILE_ADMIN_EMAIL=${INIT_SEAFILE_ADMIN_EMAIL:-me@example.com}
- INIT_SEAFILE_ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN}
- SEAFILE_SERVER_HOSTNAME=${SERVICE_URL_SEAFILE_80}
- SEAFILE_SERVER_PROTOCOL=${SEAFILE_SERVER_PROTOCOL:-https}
- SITE_ROOT=${SITE_ROOT:-/}
- NON_ROOT=${NON_ROOT:-false}
- JWT_PRIVATE_KEY=${SERVICE_PASSWORD_64_JWT}
- SEAFILE_LOG_TO_STDOUT=${SEAFILE_LOG_TO_STDOUT:-true}
depends_on:
seafile-db:
condition: service_healthy
memcached:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:80/api2/ping"]
interval: 20s
timeout: 5s
retries: 10

View File

@@ -4,7 +4,7 @@
services:
snapdrop:
image: lscr.io/linuxserver/snapdrop:latest
image: 'linuxserver/snapdrop:version-b8b78cc2'
environment:
- SERVICE_FQDN_SNAPDROP
- PUID=1000

View File

@@ -0,0 +1,87 @@
# documentation: https://github.com/amancevice/docker-superset
# slogan: Modern data exploration and visualization platform (unofficial community docker image)
# tags: analytics,bi,dashboard,database,sql,unofficial
# logo: svgs/superset.svg
# port: 8088
services:
superset:
image: amancevice/superset:latest
environment:
- SERVICE_FQDN_SUPERSET_8088
- SECRET_KEY=${SERVICE_BASE64_64_SUPERSETSECRETKEY}
- MAPBOX_API_KEY=${MAPBOX_API_KEY}
- POSTGRES_USER=${SERVICE_USER_POSTGRES}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- POSTGRES_DB=${POSTGRES_DB:-superset-db}
- REDIS_PASSWORD=${SERVICE_PASSWORD_REDIS}
volumes:
- type: bind
source: ./superset/superset_config.py
target: /etc/superset/superset_config.py
content: |
"""
For more configuration options, see:
- https://superset.apache.org/docs/configuration/configuring-superset
"""
import os
SECRET_KEY = os.getenv("SECRET_KEY")
MAPBOX_API_KEY = os.getenv("MAPBOX_API_KEY", "")
CACHE_CONFIG = {
"CACHE_TYPE": "RedisCache",
"CACHE_DEFAULT_TIMEOUT": 300,
"CACHE_KEY_PREFIX": "superset_",
"CACHE_REDIS_HOST": "redis",
"CACHE_REDIS_PORT": 6379,
"CACHE_REDIS_DB": 1,
"CACHE_REDIS_URL": f"redis://:{os.getenv('REDIS_PASSWORD')}@redis:6379/1",
}
FILTER_STATE_CACHE_CONFIG = {**CACHE_CONFIG, "CACHE_KEY_PREFIX": "superset_filter_"}
EXPLORE_FORM_DATA_CACHE_CONFIG = {**CACHE_CONFIG, "CACHE_KEY_PREFIX": "superset_explore_form_"}
SQLALCHEMY_TRACK_MODIFICATIONS = True
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{os.getenv('POSTGRES_USER')}:{os.getenv('POSTGRES_PASSWORD')}@postgres:5432/{os.getenv('POSTGRES_DB')}"
# Uncomment if you want to load example data (using "superset load_examples") at the
# same location as your metadata postgresql instance. Otherwise, the default sqlite
# will be used, which will not persist in volume when restarting superset by default.
#SQLALCHEMY_EXAMPLES_URI = SQLALCHEMY_DATABASE_URI
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:8088/health"]
interval: 5s
timeout: 20s
retries: 10
postgres:
image: postgres:17-alpine
environment:
- POSTGRES_USER=${SERVICE_USER_POSTGRES}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- POSTGRES_DB=${POSTGRES_DB:-superset-db}
volumes:
- superset_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 20s
retries: 10
redis:
image: redis:8-alpine
volumes:
- superset_redis_data:/data
command: redis-server --requirepass ${SERVICE_PASSWORD_REDIS}
healthcheck:
test: redis-cli ping
interval: 5s
timeout: 20s
retries: 10

View File

@@ -11,7 +11,7 @@ services:
- SERVICE_FQDN_TYPESENSE_8108
- TYPESENSE_ENABLE_CORS=${TYPESENSE_ENABLE_CORS:-true}
- TYPESENSE_DATA_DIR=/data
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY:-xyz}
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY:?}
volumes:
- typesense_data:/data
healthcheck:

View File

@@ -0,0 +1,66 @@
# documentation: https://github.com/FuzzyGrim/Yamtrack/wiki
# slogan: Yamtrack is a self hosted media tracker for movies, tv shows, anime, manga, video games and books.
# tags: self-hosted, automation, tracker, media, movies, shows, anime, manga, games, books, comics
# logo: svgs/yamtrack.svg
# port: 8000
services:
yamtrack:
image: ghcr.io/fuzzygrim/yamtrack
environment:
- SERVICE_FQDN_YAMTRACK_8000
- URLS=${SERVICE_FQDN_YAMTRACK}
- TZ=${TZ:-Europe/Berlin}
- SECRET=${SERVICE_PASSWORD_SECRET}
- REGISTRATION=${REGISTRATION_ENABLED:-true}
- REDIS_URL=redis://redis:6379
- DB_HOST=postgres
- DB_NAME=${POSTGRESQL_DATABASE:-yamtrack-db}
- DB_USER=${SERVICE_USER_POSTGRESQL}
- DB_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
- DB_PORT=5432
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test:
[
"CMD",
"wget",
"--no-verbose",
"--tries=1",
"--spider",
"http://127.0.0.1:8000/health/",
]
interval: 5s
timeout: 20s
retries: 10
postgres:
image: postgres:16-alpine
environment:
- POSTGRES_USER=${SERVICE_USER_POSTGRESQL}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
- POSTGRES_DB=${POSTGRESQL_DATABASE:-yamtrack-db}
volumes:
- yamtrack_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 20s
retries: 10
redis:
image: redis:7-alpine
volumes:
- yamtrack_redis_data:/data
healthcheck:
test:
- CMD
- redis-cli
- ping
interval: 5s
timeout: 20s
retries: 10

View File

@@ -0,0 +1,43 @@
# documentation: https://github.com/FuzzyGrim/Yamtrack/wiki
# slogan: Yamtrack is a self hosted media tracker for movies, tv shows, anime, manga, video games and books.
# tags: self-hosted, automation, tracker, media, movies, shows, anime, manga, games, books, comics
# logo: svgs/yamtrack.svg
# port: 8000
services:
yamtrack:
image: ghcr.io/fuzzygrim/yamtrack
environment:
- SERVICE_FQDN_YAMTRACK_8000
- URLS=${SERVICE_FQDN_YAMTRACK}
- TZ=${TZ:-Europe/Berlin}
- SECRET=${SERVICE_PASSWORD_SECRET}
- REGISTRATION=${REGISTRATION_ENABLED:-true}
- REDIS_URL=redis://redis:6379
volumes:
- yamtrack_data:/yamtrack/db
depends_on:
redis:
condition: service_healthy
healthcheck:
test:
[
"CMD-SHELL",
"wget --quiet --tries=1 --spider http://127.0.0.1:8000/health/ || exit 1",
]
interval: 5s
timeout: 20s
retries: 10
redis:
image: redis:7-alpine
volumes:
- yamtrack_redis_data:/data
healthcheck:
test:
- CMD
- redis-cli
- ping
interval: 5s
timeout: 20s
retries: 10

File diff suppressed because one or more lines are too long