Allow sendCommand on Starting or Running Servers (#1061)

* Replace `string` with `enum`

* Add title

* Allow sendCommand on `Starting` or `Running` servers

* refactor: Use Filament interfaces

* Use `getLabel` instead of `str->headline`

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>

---------

Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
This commit is contained in:
MartinOscar 2025-03-06 15:55:00 +01:00 committed by GitHub
parent a9e4495c91
commit 1fdc428f3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 70 additions and 79 deletions

View File

@ -2,7 +2,11 @@
namespace App\Enums; namespace App\Enums;
enum ContainerStatus: string use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;
enum ContainerStatus: string implements HasColor, HasIcon, HasLabel
{ {
// Docker Based // Docker Based
case Created = 'created'; case Created = 'created';
@ -19,7 +23,7 @@ enum ContainerStatus: string
// HTTP Based // HTTP Based
case Missing = 'missing'; case Missing = 'missing';
public function icon(): string public function getIcon(): string
{ {
return match ($this) { return match ($this) {
@ -36,8 +40,17 @@ enum ContainerStatus: string
}; };
} }
public function color(): string public function getColor(bool $hex = false): string
{ {
if ($hex) {
return match ($this) {
self::Created, self::Restarting => '#2563EB',
self::Starting, self::Paused, self::Removing, self::Stopping => '#D97706',
self::Running => '#22C55E',
self::Exited, self::Missing, self::Dead, self::Offline => '#EF4444',
};
}
return match ($this) { return match ($this) {
self::Created => 'primary', self::Created => 'primary',
self::Starting => 'warning', self::Starting => 'warning',
@ -53,14 +66,19 @@ enum ContainerStatus: string
}; };
} }
public function colorHex(): string public function getLabel(): string
{ {
return match ($this) { return str($this->value)->title();
self::Created, self::Restarting => '#2563EB', }
self::Starting, self::Paused, self::Removing, self::Stopping => '#D97706',
self::Running => '#22C55E', public function isOffline(): bool
self::Exited, self::Missing, self::Dead, self::Offline => '#EF4444', {
}; return in_array($this, [ContainerStatus::Offline, ContainerStatus::Missing]);
}
public function isStartingOrRunning(): bool
{
return in_array($this, [ContainerStatus::Starting, ContainerStatus::Running]);
} }
public function isStartingOrStopping(): bool public function isStartingOrStopping(): bool

View File

@ -2,7 +2,11 @@
namespace App\Enums; namespace App\Enums;
enum ServerState: string use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;
enum ServerState: string implements HasColor, HasIcon, HasLabel
{ {
case Normal = 'normal'; case Normal = 'normal';
case Installing = 'installing'; case Installing = 'installing';
@ -11,7 +15,7 @@ enum ServerState: string
case Suspended = 'suspended'; case Suspended = 'suspended';
case RestoringBackup = 'restoring_backup'; case RestoringBackup = 'restoring_backup';
public function icon(): string public function getIcon(): string
{ {
return match ($this) { return match ($this) {
self::Normal => 'tabler-heart', self::Normal => 'tabler-heart',
@ -23,7 +27,7 @@ enum ServerState: string
}; };
} }
public function color(): string public function getColor(): string
{ {
return match ($this) { return match ($this) {
self::Normal => 'primary', self::Normal => 'primary',
@ -34,4 +38,9 @@ enum ServerState: string
self::RestoringBackup => 'primary', self::RestoringBackup => 'primary',
}; };
} }
public function getLabel(): string
{
return str($this->value)->headline();
}
} }

View File

@ -2,8 +2,6 @@
namespace App\Filament\Admin\Resources\ServerResource\Pages; namespace App\Filament\Admin\Resources\ServerResource\Pages;
use App\Enums\ContainerStatus;
use App\Enums\ServerState;
use App\Enums\SuspendAction; use App\Enums\SuspendAction;
use App\Filament\Admin\Resources\ServerResource; use App\Filament\Admin\Resources\ServerResource;
use App\Filament\Admin\Resources\ServerResource\RelationManagers\AllocationsRelationManager; use App\Filament\Admin\Resources\ServerResource\RelationManagers\AllocationsRelationManager;
@ -127,16 +125,9 @@ class EditServer extends EditRecord
ToggleButtons::make('condition') ToggleButtons::make('condition')
->label(trans('admin/server.server_status')) ->label(trans('admin/server.server_status'))
->formatStateUsing(fn (Server $server) => $server->condition) ->formatStateUsing(fn (Server $server) => $server->condition)
->options(fn ($state) => collect(array_merge(ContainerStatus::cases(), ServerState::cases())) ->options(fn ($state) => [$state->value => $state->getLabel()])
->filter(fn ($condition) => $condition->value === $state) ->colors(fn ($state) => [$state->value => $state->getColor()])
->mapWithKeys(fn ($state) => [$state->value => str($state->value)->replace('_', ' ')->ucwords()]) ->icons(fn ($state) => [$state->value => $state->getIcon()])
)
->colors(collect(array_merge(ContainerStatus::cases(), ServerState::cases()))->mapWithKeys(
fn ($status) => [$status->value => $status->color()]
))
->icons(collect(array_merge(ContainerStatus::cases(), ServerState::cases()))->mapWithKeys(
fn ($status) => [$status->value => $status->icon()]
))
->columnSpan([ ->columnSpan([
'default' => 2, 'default' => 2,
'sm' => 1, 'sm' => 1,

View File

@ -34,8 +34,8 @@ class ListServers extends ListRecords
->label(trans('admin/server.condition')) ->label(trans('admin/server.condition'))
->default('unknown') ->default('unknown')
->badge() ->badge()
->icon(fn (Server $server) => $server->conditionIcon()) ->icon(fn (Server $server) => $server->condition->getIcon())
->color(fn (Server $server) => $server->conditionColor()), ->color(fn (Server $server) => $server->condition->getColor()),
TextColumn::make('uuid') TextColumn::make('uuid')
->hidden() ->hidden()
->label('UUID') ->label('UUID')

View File

@ -75,7 +75,7 @@ class ServerConsole extends Widget
protected function canSendCommand(): bool protected function canSendCommand(): bool
{ {
return $this->authorizeSendCommand() && !$this->server->isInConflictState() && $this->server->retrieveStatus() === 'running'; return $this->authorizeSendCommand() && !$this->server->isInConflictState() && $this->server->retrieveStatus()->isStartingOrRunning();
} }
public function up(): void public function up(): void

View File

@ -9,7 +9,6 @@ use App\Models\Server;
use Carbon\CarbonInterface; use Carbon\CarbonInterface;
use Filament\Widgets\StatsOverviewWidget; use Filament\Widgets\StatsOverviewWidget;
use Illuminate\Support\Number; use Illuminate\Support\Number;
use Illuminate\Support\Str;
class ServerOverview extends StatsOverviewWidget class ServerOverview extends StatsOverviewWidget
{ {
@ -38,7 +37,7 @@ class ServerOverview extends StatsOverviewWidget
private function status(): string private function status(): string
{ {
$status = Str::title($this->server->condition); $status = $this->server->condition->getLabel();
$uptime = collect(cache()->get("servers.{$this->server->id}.uptime"))->last() ?? 0; $uptime = collect(cache()->get("servers.{$this->server->id}.uptime"))->last() ?? 0;
if ($uptime === 0) { if ($uptime === 0) {
@ -52,10 +51,10 @@ class ServerOverview extends StatsOverviewWidget
public function cpuUsage(): string public function cpuUsage(): string
{ {
$status = ContainerStatus::tryFrom($this->server->retrieveStatus()); $status = $this->server->retrieveStatus();
if ($status === ContainerStatus::Offline || $status === ContainerStatus::Missing) { if ($status->isOffline()) {
return 'Offline'; return ContainerStatus::Offline->getLabel();
} }
$data = collect(cache()->get("servers.{$this->server->id}.cpu_absolute"))->last(default: 0); $data = collect(cache()->get("servers.{$this->server->id}.cpu_absolute"))->last(default: 0);
@ -66,10 +65,10 @@ class ServerOverview extends StatsOverviewWidget
public function memoryUsage(): string public function memoryUsage(): string
{ {
$status = ContainerStatus::tryFrom($this->server->retrieveStatus()); $status = $this->server->retrieveStatus();
if ($status === ContainerStatus::Offline || $status === ContainerStatus::Missing) { if ($status->isOffline()) {
return 'Offline'; return ContainerStatus::Offline->getLabel();
} }
$latestMemoryUsed = collect(cache()->get("servers.{$this->server->id}.memory_bytes"))->last(default: 0); $latestMemoryUsed = collect(cache()->get("servers.{$this->server->id}.memory_bytes"))->last(default: 0);

View File

@ -114,7 +114,7 @@ use App\Services\Subusers\SubuserDeletionService;
* *
* @property string[]|null $docker_labels * @property string[]|null $docker_labels
* @property string|null $ports * @property string|null $ports
* @property-read mixed $condition * @property-read ContainerStatus|ServerState $condition
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\EggVariable> $eggVariables * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\EggVariable> $eggVariables
* @property-read int|null $egg_variables_count * @property-read int|null $egg_variables_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ServerVariable> $serverVariables * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ServerVariable> $serverVariables
@ -438,17 +438,17 @@ class Server extends Model implements Validatable
])->toPsrResponse(); ])->toPsrResponse();
} }
public function retrieveStatus(): string public function retrieveStatus(): ContainerStatus
{ {
$status = cache()->get("servers.$this->uuid.container.status"); $status = cache()->get("servers.$this->uuid.container.status");
if ($status) { if ($status === null) {
return $status; $this->node->serverStatuses();
$status = cache()->get("servers.$this->uuid.container.status");
} }
$this->node->serverStatuses(); return ContainerStatus::tryFrom($status) ?? ContainerStatus::Missing;
return cache()->get("servers.$this->uuid.container.status") ?? 'missing';
} }
/** /**
@ -474,7 +474,7 @@ class Server extends Model implements Validatable
return 'Suspended'; return 'Suspended';
} }
if ($resourceAmount === 0) { if ($resourceAmount === 0) {
return 'Offline'; return ContainerStatus::Offline->getLabel();
} }
return now()->subMillis($resourceAmount)->diffForHumans(syntax: CarbonInterface::DIFF_ABSOLUTE, short: true, parts: 4); return now()->subMillis($resourceAmount)->diffForHumans(syntax: CarbonInterface::DIFF_ABSOLUTE, short: true, parts: 4);
@ -499,34 +499,7 @@ class Server extends Model implements Validatable
public function condition(): Attribute public function condition(): Attribute
{ {
return Attribute::make( return Attribute::make(
get: fn () => $this->isSuspended() ? ServerState::Suspended->value : $this->status->value ?? $this->retrieveStatus(), get: fn () => $this->isSuspended() ? ServerState::Suspended : $this->status ?? $this->retrieveStatus(),
); );
} }
public function conditionIcon(): string
{
if ($this->status === null) {
$containerStatus = ContainerStatus::from($this->retrieveStatus());
return $containerStatus->icon();
}
return $this->status->icon();
}
public function conditionColor(): string
{
if ($this->status === null) {
$containerStatus = ContainerStatus::from($this->retrieveStatus());
return $containerStatus->color();
}
return $this->status->color();
}
public function conditionColorHex(): string
{
return ContainerStatus::from($this->retrieveStatus())->colorHex();
}
} }

View File

@ -2,6 +2,7 @@
namespace App\Services\Schedules; namespace App\Services\Schedules;
use App\Enums\ContainerStatus;
use App\Models\Task; use App\Models\Task;
use Exception; use Exception;
use App\Models\Schedule; use App\Models\Schedule;
@ -41,10 +42,10 @@ class ProcessScheduleService
// Check that the server is currently in a starting or running state before executing // Check that the server is currently in a starting or running state before executing
// this schedule if this option has been set. // this schedule if this option has been set.
try { try {
$details = $this->serverRepository->setServer($schedule->server)->getDetails(); $state = fluent($this->serverRepository->setServer($schedule->server)->getDetails())->get('state') ?? ContainerStatus::Offline;
$state = $details['state'] ?? 'offline';
// If the server is stopping or offline just do nothing with this task. // If the server is stopping or offline just do nothing with this task.
if (in_array($state, ['offline', 'stopping'])) { if ($state->isOffline()) {
$job->failed(); $job->failed();
return; return;

View File

@ -18,7 +18,7 @@
<!-- Status Strip Outside the Box --> <!-- Status Strip Outside the Box -->
<div <div
class="absolute left-0 top-1 bottom-0 w-1 rounded-lg" class="absolute left-0 top-1 bottom-0 w-1 rounded-lg"
style="background-color: {{ $server->conditionColorHex() }};"> style="background-color: {{ $server->condition->getColor(true) }};">
</div> </div>
<!-- Card Component --> <!-- Card Component -->
@ -26,9 +26,9 @@
<!-- Header --> <!-- Header -->
<div class="flex items-center mb-5 gap-2"> <div class="flex items-center mb-5 gap-2">
<x-filament::icon-button <x-filament::icon-button
:icon="$server->conditionIcon()" :icon="$server->condition->getIcon()"
:color="$server->conditionColor()" :color="$server->condition->getColor()"
:tooltip="\Illuminate\Support\Str::title($server->condition)" :tooltip="$server->condition->getLabel()"
size="xl" size="xl"
/> />
<h2 class="text-xl font-bold"> <h2 class="text-xl font-bold">