Use Dokploy projects in dashboard widget
This commit is contained in:
@@ -14,11 +14,63 @@ class DokployPlatformHealth extends Widget
|
||||
|
||||
protected function getViewData(): array
|
||||
{
|
||||
$projects = $this->loadProjects();
|
||||
|
||||
return [
|
||||
'composes' => $this->loadComposes(),
|
||||
'projects' => $projects,
|
||||
'composes' => empty($projects) ? $this->loadComposes() : [],
|
||||
];
|
||||
}
|
||||
|
||||
protected function loadProjects(): array
|
||||
{
|
||||
$client = app(DokployClient::class);
|
||||
$projectMap = config('dokploy.projects', []);
|
||||
$results = [];
|
||||
|
||||
if (empty($projectMap)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($projectMap as $label => $projectId) {
|
||||
$project = $client->findProject((string) $projectId);
|
||||
|
||||
if (! $project) {
|
||||
$results[] = [
|
||||
'label' => ucfirst((string) $label),
|
||||
'project_id' => (string) $projectId,
|
||||
'name' => (string) $projectId,
|
||||
'status' => 'unreachable',
|
||||
'error' => "Project {$projectId} not found.",
|
||||
'applications' => [],
|
||||
'services' => [],
|
||||
'updated_at' => null,
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$applicationsPayload = Arr::get($project, 'applications', []);
|
||||
$applications = $this->formatApplications(is_array($applicationsPayload) ? $applicationsPayload : [], $client);
|
||||
$services = $this->formatProjectServices($project);
|
||||
|
||||
$results[] = [
|
||||
'label' => ucfirst((string) $label),
|
||||
'project_id' => Arr::get($project, 'projectId', $projectId),
|
||||
'name' => Arr::get($project, 'name') ?? Arr::get($project, 'projectName') ?? (string) $projectId,
|
||||
'description' => Arr::get($project, 'description'),
|
||||
'status' => $this->deriveProjectStatus($applications, $services),
|
||||
'applications' => $applications,
|
||||
'services' => $services,
|
||||
'updated_at' => Arr::get($project, 'updatedAt') ?? Arr::get($project, 'createdAt'),
|
||||
'applications_count' => count($applications),
|
||||
'services_count' => count($services),
|
||||
];
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected function loadComposes(): array
|
||||
{
|
||||
$client = app(DokployClient::class);
|
||||
@@ -62,7 +114,7 @@ class DokployPlatformHealth extends Widget
|
||||
'label' => 'Dokploy',
|
||||
'compose_id' => '-',
|
||||
'status' => 'unconfigured',
|
||||
'error' => 'Set DOKPLOY_COMPOSE_IDS in .env to enable monitoring.',
|
||||
'error' => 'Set DOKPLOY_PROJECT_IDS or DOKPLOY_COMPOSE_IDS in .env to enable monitoring.',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -70,6 +122,158 @@ class DokployPlatformHealth extends Widget
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected function formatApplications(array $applications, DokployClient $client): array
|
||||
{
|
||||
return collect($applications)
|
||||
->map(function (array $application) use ($client) {
|
||||
$applicationId = $this->extractApplicationId($application);
|
||||
$statusPayload = [];
|
||||
|
||||
if ($applicationId) {
|
||||
try {
|
||||
$statusPayload = $client->applicationStatus($applicationId);
|
||||
} catch (\Throwable $exception) {
|
||||
$statusPayload = [];
|
||||
}
|
||||
}
|
||||
|
||||
$applicationDetails = Arr::get($statusPayload, 'application', []);
|
||||
$monitoring = Arr::get($statusPayload, 'monitoring', []);
|
||||
|
||||
$status = Arr::get($application, 'applicationStatus')
|
||||
?? Arr::get($application, 'status')
|
||||
?? Arr::get($applicationDetails, 'applicationStatus')
|
||||
?? Arr::get($applicationDetails, 'status')
|
||||
?? 'unknown';
|
||||
|
||||
return [
|
||||
'id' => $applicationId ?? Arr::get($application, 'id'),
|
||||
'name' => Arr::get($application, 'name')
|
||||
?? Arr::get($application, 'appName')
|
||||
?? Arr::get($applicationDetails, 'name')
|
||||
?? Arr::get($applicationDetails, 'appName')
|
||||
?? $applicationId,
|
||||
'status' => $status,
|
||||
'repository' => Arr::get($application, 'repository')
|
||||
?? Arr::get($applicationDetails, 'repository')
|
||||
?? Arr::get($application, 'repo')
|
||||
?? Arr::get($applicationDetails, 'repo'),
|
||||
'branch' => Arr::get($application, 'branch')
|
||||
?? Arr::get($applicationDetails, 'branch')
|
||||
?? Arr::get($application, 'gitBranch')
|
||||
?? Arr::get($applicationDetails, 'gitBranch'),
|
||||
'url' => Arr::get($application, 'url')
|
||||
?? Arr::get($applicationDetails, 'url')
|
||||
?? Arr::get($application, 'domain')
|
||||
?? Arr::get($applicationDetails, 'domain'),
|
||||
'server' => Arr::get($application, 'serverName')
|
||||
?? Arr::get($applicationDetails, 'serverName')
|
||||
?? Arr::get($application, 'server'),
|
||||
'last_deploy' => Arr::get($application, 'lastDeploymentAt')
|
||||
?? Arr::get($applicationDetails, 'lastDeploymentAt')
|
||||
?? Arr::get($application, 'updatedAt')
|
||||
?? Arr::get($applicationDetails, 'updatedAt')
|
||||
?? Arr::get($application, 'createdAt'),
|
||||
'monitoring' => $this->formatMonitoring($monitoring),
|
||||
];
|
||||
})
|
||||
->filter(fn (array $application) => filled($application['name']))
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
protected function extractApplicationId(array $application): ?string
|
||||
{
|
||||
return Arr::get($application, 'applicationId')
|
||||
?? Arr::get($application, 'appId')
|
||||
?? Arr::get($application, 'id');
|
||||
}
|
||||
|
||||
protected function formatProjectServices(array $project): array
|
||||
{
|
||||
return collect([
|
||||
...$this->normalizeServiceList((array) Arr::get($project, 'compose', []), 'compose', 'composeId', 'composeStatus'),
|
||||
...$this->normalizeServiceList((array) Arr::get($project, 'mysql', []), 'mysql', 'mysqlId', 'applicationStatus'),
|
||||
...$this->normalizeServiceList((array) Arr::get($project, 'postgres', []), 'postgres', 'postgresId', 'applicationStatus'),
|
||||
...$this->normalizeServiceList((array) Arr::get($project, 'mariadb', []), 'mariadb', 'mariadbId', 'applicationStatus'),
|
||||
...$this->normalizeServiceList((array) Arr::get($project, 'mongo', []), 'mongo', 'mongoId', 'applicationStatus'),
|
||||
...$this->normalizeServiceList((array) Arr::get($project, 'redis', []), 'redis', 'redisId', 'applicationStatus'),
|
||||
])
|
||||
->filter(fn (array $service) => filled($service['name']))
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
protected function normalizeServiceList(array $services, string $type, string $idKey, string $statusKey): array
|
||||
{
|
||||
return collect($services)
|
||||
->map(function (array $service) use ($type, $idKey, $statusKey) {
|
||||
return [
|
||||
'type' => $type,
|
||||
'id' => Arr::get($service, $idKey) ?? Arr::get($service, 'id'),
|
||||
'name' => Arr::get($service, 'name') ?? Arr::get($service, 'appName') ?? Arr::get($service, 'serviceName'),
|
||||
'status' => Arr::get($service, $statusKey) ?? Arr::get($service, 'status') ?? Arr::get($service, 'composeStatus', 'unknown'),
|
||||
'version' => Arr::get($service, 'dockerImage') ?? Arr::get($service, 'image'),
|
||||
'external_port' => Arr::get($service, 'externalPort'),
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
protected function formatMonitoring(array $monitoring): array
|
||||
{
|
||||
$metrics = [];
|
||||
$allowed = [
|
||||
'cpuPercent' => 'CPU',
|
||||
'cpu' => 'CPU',
|
||||
'memoryPercent' => 'Memory',
|
||||
'memory' => 'Memory',
|
||||
'uptime' => 'Uptime',
|
||||
];
|
||||
|
||||
foreach ($allowed as $key => $label) {
|
||||
$value = Arr::get($monitoring, $key);
|
||||
|
||||
if (filled($value) && ! is_array($value)) {
|
||||
$metrics[] = [
|
||||
'label' => $label,
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $metrics;
|
||||
}
|
||||
|
||||
protected function deriveProjectStatus(array $applications, array $services): string
|
||||
{
|
||||
$statuses = collect($applications)
|
||||
->pluck('status')
|
||||
->merge(collect($services)->pluck('status'))
|
||||
->filter()
|
||||
->map(fn ($status) => strtolower((string) $status))
|
||||
->values();
|
||||
|
||||
if ($statuses->contains(fn ($status) => in_array($status, ['error', 'failed', 'unreachable', 'unhealthy'], true))) {
|
||||
return 'error';
|
||||
}
|
||||
|
||||
if ($statuses->contains(fn ($status) => in_array($status, ['deploying', 'pending', 'starting'], true))) {
|
||||
return 'deploying';
|
||||
}
|
||||
|
||||
if ($statuses->contains(fn ($status) => in_array($status, ['stopped', 'inactive', 'paused'], true))) {
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
if ($statuses->contains(fn ($status) => in_array($status, ['done', 'running', 'healthy'], true))) {
|
||||
return 'done';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
protected function formatServices(array $services): array
|
||||
{
|
||||
return collect($services)
|
||||
|
||||
@@ -40,6 +40,37 @@ class DokployClient
|
||||
}, 30);
|
||||
}
|
||||
|
||||
public function projects(): array
|
||||
{
|
||||
return $this->cached($this->projectsCacheKey(), function () {
|
||||
$projects = $this->get('/project.all');
|
||||
|
||||
return is_array($projects) ? $projects : [];
|
||||
}, 60);
|
||||
}
|
||||
|
||||
public function findProject(string $projectIdOrName): ?array
|
||||
{
|
||||
$projects = $this->projects();
|
||||
|
||||
foreach ($projects as $project) {
|
||||
if (Arr::get($project, 'projectId') === $projectIdOrName) {
|
||||
return $project;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($projects as $project) {
|
||||
if (
|
||||
Arr::get($project, 'name') === $projectIdOrName
|
||||
|| Arr::get($project, 'projectName') === $projectIdOrName
|
||||
) {
|
||||
return $project;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function recentDeployments(string $applicationId, int $limit = 5): array
|
||||
{
|
||||
return $this->cached($this->deploymentCacheKey($applicationId), function () use ($applicationId, $limit) {
|
||||
@@ -321,6 +352,11 @@ class DokployClient
|
||||
return "dokploy.compose.deployments.{$composeId}";
|
||||
}
|
||||
|
||||
protected function projectsCacheKey(): string
|
||||
{
|
||||
return 'dokploy.projects';
|
||||
}
|
||||
|
||||
protected function forgetApplicationCaches(string $applicationId): void
|
||||
{
|
||||
Cache::forget($this->applicationCacheKey($applicationId));
|
||||
|
||||
@@ -8,5 +8,6 @@ return [
|
||||
],
|
||||
'web_url' => env('DOKPLOY_WEB_URL'),
|
||||
'applications' => json_decode(env('DOKPLOY_APPLICATION_IDS', '{}'), true) ?? [],
|
||||
'projects' => json_decode(env('DOKPLOY_PROJECT_IDS', '{}'), true) ?? [],
|
||||
'composes' => json_decode(env('DOKPLOY_COMPOSE_IDS', '{}'), true) ?? [],
|
||||
];
|
||||
|
||||
@@ -5,87 +5,263 @@
|
||||
|
||||
$statusColors = [
|
||||
'done' => 'success',
|
||||
'healthy' => 'success',
|
||||
'running' => 'success',
|
||||
'deploying' => 'warning',
|
||||
'pending' => 'warning',
|
||||
'starting' => 'warning',
|
||||
'warning' => 'warning',
|
||||
'unreachable' => 'danger',
|
||||
'error' => 'danger',
|
||||
'failed' => 'danger',
|
||||
'unhealthy' => 'danger',
|
||||
];
|
||||
|
||||
$statusIcons = [
|
||||
'done' => Heroicon::CheckCircle,
|
||||
'healthy' => Heroicon::CheckCircle,
|
||||
'running' => Heroicon::CheckCircle,
|
||||
'deploying' => Heroicon::ArrowPath,
|
||||
'pending' => Heroicon::Clock,
|
||||
'starting' => Heroicon::Clock,
|
||||
'warning' => Heroicon::ExclamationTriangle,
|
||||
'unreachable' => Heroicon::ExclamationTriangle,
|
||||
'error' => Heroicon::XCircle,
|
||||
'failed' => Heroicon::XCircle,
|
||||
'unhealthy' => Heroicon::XCircle,
|
||||
];
|
||||
|
||||
$serviceColors = [
|
||||
'running' => 'success',
|
||||
'done' => 'success',
|
||||
'healthy' => 'success',
|
||||
'starting' => 'warning',
|
||||
'deploying' => 'warning',
|
||||
'pending' => 'warning',
|
||||
'unhealthy' => 'danger',
|
||||
'error' => 'danger',
|
||||
'failed' => 'danger',
|
||||
];
|
||||
|
||||
$cardsGrid = (new ComponentAttributeBag())->grid(['default' => 1, 'lg' => 2]);
|
||||
$stacked = (new ComponentAttributeBag())->grid(['default' => 1], GridDirection::Column);
|
||||
$detailsGrid = (new ComponentAttributeBag())->grid(['default' => 1, 'md' => 2], GridDirection::Column);
|
||||
$serviceGrid = (new ComponentAttributeBag())->grid(['default' => 1], GridDirection::Column);
|
||||
@endphp
|
||||
|
||||
<x-filament-widgets::widget>
|
||||
<x-filament::section heading="Infra Status (Dokploy)">
|
||||
<div {{ $cardsGrid }}>
|
||||
@forelse($composes as $compose)
|
||||
<x-filament::card
|
||||
:heading="$compose['label']"
|
||||
:description="$compose['name'] ?? '–'"
|
||||
>
|
||||
<x-slot name="afterHeader">
|
||||
<x-filament::badge
|
||||
:color="$statusColors[$compose['status']] ?? 'gray'"
|
||||
:icon="$statusIcons[$compose['status']] ?? Heroicon::QuestionMarkCircle"
|
||||
>
|
||||
{{ ucfirst($compose['status']) }}
|
||||
</x-filament::badge>
|
||||
<x-filament::badge color="gray" :icon="Heroicon::Identification">
|
||||
{{ $compose['compose_id'] }}
|
||||
</x-filament::badge>
|
||||
</x-slot>
|
||||
|
||||
@if(isset($compose['error']))
|
||||
<x-filament::badge color="danger" :icon="Heroicon::ExclamationTriangle">
|
||||
{{ $compose['error'] }}
|
||||
</x-filament::badge>
|
||||
@else
|
||||
<div {{ $serviceGrid }}>
|
||||
@forelse($compose['services'] as $service)
|
||||
<x-filament::badge
|
||||
:color="$serviceColors[$service['status']] ?? 'gray'"
|
||||
:icon="array_key_exists($service['status'] ?? '', $serviceColors) ? Heroicon::Server : Heroicon::QuestionMarkCircle"
|
||||
>
|
||||
{{ $service['name'] }}: {{ strtoupper($service['status'] ?? 'N/A') }}
|
||||
</x-filament::badge>
|
||||
@empty
|
||||
<x-filament::badge color="gray" :icon="Heroicon::QuestionMarkCircle">
|
||||
No services reported.
|
||||
</x-filament::badge>
|
||||
@endforelse
|
||||
<x-filament::badge color="gray" :icon="Heroicon::Clock">
|
||||
Last deploy:
|
||||
{{ $compose['last_deploy'] ? \Illuminate\Support\Carbon::parse($compose['last_deploy'])->diffForHumans() : '—' }}
|
||||
@if(!empty($projects))
|
||||
<div {{ $cardsGrid }}>
|
||||
@forelse($projects as $project)
|
||||
<x-filament::card :heading="$project['label']" :description="$project['name'] ?? '–'">
|
||||
<x-slot name="afterHeader">
|
||||
<x-filament::badge
|
||||
:color="$statusColors[$project['status']] ?? 'gray'"
|
||||
:icon="$statusIcons[$project['status']] ?? Heroicon::QuestionMarkCircle"
|
||||
>
|
||||
{{ ucfirst($project['status']) }}
|
||||
</x-filament::badge>
|
||||
<x-filament::badge color="gray" :icon="Heroicon::Identification">
|
||||
{{ $project['project_id'] }}
|
||||
</x-filament::badge>
|
||||
</x-slot>
|
||||
|
||||
<div {{ $stacked }}>
|
||||
@if(isset($project['error']))
|
||||
<x-filament::badge color="danger" :icon="Heroicon::ExclamationTriangle">
|
||||
{{ $project['error'] }}
|
||||
</x-filament::badge>
|
||||
@else
|
||||
@if(!empty($project['description']))
|
||||
<x-filament::badge color="gray" :icon="Heroicon::InformationCircle">
|
||||
{{ $project['description'] }}
|
||||
</x-filament::badge>
|
||||
@endif
|
||||
|
||||
<div {{ $detailsGrid }}>
|
||||
<x-filament::badge color="gray" :icon="Heroicon::RectangleStack">
|
||||
Apps: {{ $project['applications_count'] ?? 0 }}
|
||||
</x-filament::badge>
|
||||
<x-filament::badge color="gray" :icon="Heroicon::ServerStack">
|
||||
Services: {{ $project['services_count'] ?? 0 }}
|
||||
</x-filament::badge>
|
||||
<x-filament::badge color="gray" :icon="Heroicon::Clock">
|
||||
Last update:
|
||||
{{ $project['updated_at'] ? \Illuminate\Support\Carbon::parse($project['updated_at'])->diffForHumans() : '—' }}
|
||||
</x-filament::badge>
|
||||
</div>
|
||||
|
||||
<div {{ $stacked }}>
|
||||
<x-filament::badge color="gray" :icon="Heroicon::RectangleStack">
|
||||
Applications
|
||||
</x-filament::badge>
|
||||
|
||||
@forelse($project['applications'] as $application)
|
||||
<div {{ $stacked }}>
|
||||
<x-filament::badge
|
||||
:color="$statusColors[$application['status']] ?? 'gray'"
|
||||
:icon="$statusIcons[$application['status']] ?? Heroicon::QuestionMarkCircle"
|
||||
>
|
||||
{{ $application['name'] ?? 'App' }}
|
||||
@if(!empty($application['status']))
|
||||
({{ strtoupper($application['status']) }})
|
||||
@endif
|
||||
</x-filament::badge>
|
||||
|
||||
<div {{ $detailsGrid }}>
|
||||
@if(!empty($application['id']))
|
||||
<x-filament::badge color="gray" :icon="Heroicon::Identification">
|
||||
{{ $application['id'] }}
|
||||
</x-filament::badge>
|
||||
@endif
|
||||
@if(!empty($application['repository']))
|
||||
<x-filament::badge color="gray" :icon="Heroicon::CodeBracket">
|
||||
{{ $application['repository'] }}
|
||||
</x-filament::badge>
|
||||
@endif
|
||||
@if(!empty($application['branch']))
|
||||
<x-filament::badge color="gray" :icon="Heroicon::ChevronDown">
|
||||
{{ $application['branch'] }}
|
||||
</x-filament::badge>
|
||||
@endif
|
||||
@if(!empty($application['server']))
|
||||
<x-filament::badge color="gray" :icon="Heroicon::Server">
|
||||
{{ $application['server'] }}
|
||||
</x-filament::badge>
|
||||
@endif
|
||||
@if(!empty($application['url']))
|
||||
<x-filament::badge color="gray" :icon="Heroicon::GlobeAlt">
|
||||
{{ $application['url'] }}
|
||||
</x-filament::badge>
|
||||
@endif
|
||||
<x-filament::badge color="gray" :icon="Heroicon::Clock">
|
||||
Last deploy:
|
||||
{{ $application['last_deploy'] ? \Illuminate\Support\Carbon::parse($application['last_deploy'])->diffForHumans() : '—' }}
|
||||
</x-filament::badge>
|
||||
</div>
|
||||
|
||||
@if(!empty($application['monitoring']))
|
||||
<div {{ $detailsGrid }}>
|
||||
@foreach($application['monitoring'] as $metric)
|
||||
<x-filament::badge color="gray" :icon="Heroicon::ChartBar">
|
||||
{{ $metric['label'] }}: {{ $metric['value'] }}
|
||||
</x-filament::badge>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@empty
|
||||
<x-filament::badge color="gray" :icon="Heroicon::QuestionMarkCircle">
|
||||
No applications reported.
|
||||
</x-filament::badge>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<div {{ $stacked }}>
|
||||
<x-filament::badge color="gray" :icon="Heroicon::ServerStack">
|
||||
Services
|
||||
</x-filament::badge>
|
||||
|
||||
<div {{ $serviceGrid }}>
|
||||
@forelse($project['services'] as $service)
|
||||
<x-filament::badge
|
||||
:color="$serviceColors[$service['status']] ?? 'gray'"
|
||||
:icon="Heroicon::Server"
|
||||
>
|
||||
{{ strtoupper($service['type'] ?? 'service') }}
|
||||
@if(!empty($service['name']))
|
||||
- {{ $service['name'] }}
|
||||
@endif
|
||||
@if(!empty($service['status']))
|
||||
({{ strtoupper($service['status']) }})
|
||||
@endif
|
||||
</x-filament::badge>
|
||||
<div {{ $detailsGrid }}>
|
||||
@if(!empty($service['id']))
|
||||
<x-filament::badge color="gray" :icon="Heroicon::Identification">
|
||||
{{ $service['id'] }}
|
||||
</x-filament::badge>
|
||||
@endif
|
||||
@if(!empty($service['version']))
|
||||
<x-filament::badge color="gray" :icon="Heroicon::Cube">
|
||||
{{ $service['version'] }}
|
||||
</x-filament::badge>
|
||||
@endif
|
||||
@if(!empty($service['external_port']))
|
||||
<x-filament::badge color="gray" :icon="Heroicon::Signal">
|
||||
Port {{ $service['external_port'] }}
|
||||
</x-filament::badge>
|
||||
@endif
|
||||
</div>
|
||||
@empty
|
||||
<x-filament::badge color="gray" :icon="Heroicon::QuestionMarkCircle">
|
||||
No services reported.
|
||||
</x-filament::badge>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</x-filament::card>
|
||||
@empty
|
||||
<x-filament::empty-state
|
||||
heading="No Dokploy compose stacks configured."
|
||||
:icon="Heroicon::QuestionMarkCircle"
|
||||
/>
|
||||
@endforelse
|
||||
</div>
|
||||
</x-filament::card>
|
||||
@empty
|
||||
<x-filament::empty-state
|
||||
heading="No Dokploy projects configured."
|
||||
:icon="Heroicon::QuestionMarkCircle"
|
||||
/>
|
||||
@endforelse
|
||||
</div>
|
||||
@else
|
||||
<div {{ $cardsGrid }}>
|
||||
@forelse($composes as $compose)
|
||||
<x-filament::card
|
||||
:heading="$compose['label']"
|
||||
:description="$compose['name'] ?? '–'"
|
||||
>
|
||||
<x-slot name="afterHeader">
|
||||
<x-filament::badge
|
||||
:color="$statusColors[$compose['status']] ?? 'gray'"
|
||||
:icon="$statusIcons[$compose['status']] ?? Heroicon::QuestionMarkCircle"
|
||||
>
|
||||
{{ ucfirst($compose['status']) }}
|
||||
</x-filament::badge>
|
||||
<x-filament::badge color="gray" :icon="Heroicon::Identification">
|
||||
{{ $compose['compose_id'] }}
|
||||
</x-filament::badge>
|
||||
</x-slot>
|
||||
|
||||
@if(isset($compose['error']))
|
||||
<x-filament::badge color="danger" :icon="Heroicon::ExclamationTriangle">
|
||||
{{ $compose['error'] }}
|
||||
</x-filament::badge>
|
||||
@else
|
||||
<div {{ $serviceGrid }}>
|
||||
@forelse($compose['services'] as $service)
|
||||
<x-filament::badge
|
||||
:color="$serviceColors[$service['status']] ?? 'gray'"
|
||||
:icon="array_key_exists($service['status'] ?? '', $serviceColors) ? Heroicon::Server : Heroicon::QuestionMarkCircle"
|
||||
>
|
||||
{{ $service['name'] }}: {{ strtoupper($service['status'] ?? 'N/A') }}
|
||||
</x-filament::badge>
|
||||
@empty
|
||||
<x-filament::badge color="gray" :icon="Heroicon::QuestionMarkCircle">
|
||||
No services reported.
|
||||
</x-filament::badge>
|
||||
@endforelse
|
||||
<x-filament::badge color="gray" :icon="Heroicon::Clock">
|
||||
Last deploy:
|
||||
{{ $compose['last_deploy'] ? \Illuminate\Support\Carbon::parse($compose['last_deploy'])->diffForHumans() : '—' }}
|
||||
</x-filament::badge>
|
||||
</div>
|
||||
@endif
|
||||
</x-filament::card>
|
||||
@empty
|
||||
<x-filament::empty-state
|
||||
heading="No Dokploy compose stacks configured."
|
||||
:icon="Heroicon::QuestionMarkCircle"
|
||||
/>
|
||||
@endforelse
|
||||
</div>
|
||||
@endif
|
||||
</x-filament::section>
|
||||
</x-filament-widgets::widget>
|
||||
|
||||
114
tests/Feature/DokployPlatformHealthWidgetTest.php
Normal file
114
tests/Feature/DokployPlatformHealthWidgetTest.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Filament\Widgets\DokployPlatformHealth;
|
||||
use App\Services\Dokploy\DokployClient;
|
||||
use Livewire\Livewire;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DokployPlatformHealthWidgetTest extends TestCase
|
||||
{
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Mockery::close();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_widget_uses_projects_when_configured(): void
|
||||
{
|
||||
config()->set('dokploy.projects', [
|
||||
'core' => 'proj_1',
|
||||
]);
|
||||
|
||||
$fakeClient = Mockery::mock(DokployClient::class);
|
||||
$fakeClient->shouldReceive('findProject')
|
||||
->with('proj_1')
|
||||
->andReturn([
|
||||
'projectId' => 'proj_1',
|
||||
'name' => 'Core',
|
||||
'description' => 'Main stack',
|
||||
'updatedAt' => now()->toIso8601String(),
|
||||
'applications' => [
|
||||
[
|
||||
'applicationId' => 'app_1',
|
||||
'name' => 'API',
|
||||
'applicationStatus' => 'running',
|
||||
'repository' => 'repo/api',
|
||||
'branch' => 'main',
|
||||
],
|
||||
],
|
||||
'redis' => [
|
||||
[
|
||||
'redisId' => 'redis_1',
|
||||
'name' => 'Redis',
|
||||
'applicationStatus' => 'done',
|
||||
'externalPort' => 6379,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$fakeClient->shouldReceive('applicationStatus')
|
||||
->with('app_1')
|
||||
->andReturn([
|
||||
'application' => [
|
||||
'name' => 'API',
|
||||
'applicationStatus' => 'running',
|
||||
'updatedAt' => now()->toIso8601String(),
|
||||
],
|
||||
'monitoring' => [
|
||||
'cpuPercent' => 12,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->app->instance(DokployClient::class, $fakeClient);
|
||||
|
||||
Livewire::test(DokployPlatformHealth::class)
|
||||
->assertStatus(200)
|
||||
->assertSee('Core')
|
||||
->assertSee('API')
|
||||
->assertSee('Redis');
|
||||
}
|
||||
|
||||
public function test_widget_falls_back_to_compose_status_when_projects_missing(): void
|
||||
{
|
||||
config()->set('dokploy.projects', []);
|
||||
config()->set('dokploy.composes', [
|
||||
'stack' => 'cmp_main',
|
||||
]);
|
||||
|
||||
$fakeClient = Mockery::mock(DokployClient::class);
|
||||
$fakeClient->shouldReceive('composeStatus')
|
||||
->with('cmp_main')
|
||||
->andReturn([
|
||||
'compose' => [
|
||||
'name' => 'Main Stack',
|
||||
'composeStatus' => 'done',
|
||||
'updatedAt' => now()->toIso8601String(),
|
||||
],
|
||||
'services' => [
|
||||
[
|
||||
'serviceName' => 'web',
|
||||
'status' => 'running',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$fakeClient->shouldReceive('composeDeployments')
|
||||
->with('cmp_main', 1)
|
||||
->andReturn([
|
||||
[
|
||||
'createdAt' => now()->toIso8601String(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->app->instance(DokployClient::class, $fakeClient);
|
||||
|
||||
Livewire::test(DokployPlatformHealth::class)
|
||||
->assertStatus(200)
|
||||
->assertSee('Main Stack')
|
||||
->assertSee('web');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user