Use Dokploy projects in dashboard widget
This commit is contained in:
@@ -14,11 +14,63 @@ class DokployPlatformHealth extends Widget
|
|||||||
|
|
||||||
protected function getViewData(): array
|
protected function getViewData(): array
|
||||||
{
|
{
|
||||||
|
$projects = $this->loadProjects();
|
||||||
|
|
||||||
return [
|
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
|
protected function loadComposes(): array
|
||||||
{
|
{
|
||||||
$client = app(DokployClient::class);
|
$client = app(DokployClient::class);
|
||||||
@@ -62,7 +114,7 @@ class DokployPlatformHealth extends Widget
|
|||||||
'label' => 'Dokploy',
|
'label' => 'Dokploy',
|
||||||
'compose_id' => '-',
|
'compose_id' => '-',
|
||||||
'status' => 'unconfigured',
|
'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;
|
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
|
protected function formatServices(array $services): array
|
||||||
{
|
{
|
||||||
return collect($services)
|
return collect($services)
|
||||||
|
|||||||
@@ -40,6 +40,37 @@ class DokployClient
|
|||||||
}, 30);
|
}, 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
|
public function recentDeployments(string $applicationId, int $limit = 5): array
|
||||||
{
|
{
|
||||||
return $this->cached($this->deploymentCacheKey($applicationId), function () use ($applicationId, $limit) {
|
return $this->cached($this->deploymentCacheKey($applicationId), function () use ($applicationId, $limit) {
|
||||||
@@ -321,6 +352,11 @@ class DokployClient
|
|||||||
return "dokploy.compose.deployments.{$composeId}";
|
return "dokploy.compose.deployments.{$composeId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function projectsCacheKey(): string
|
||||||
|
{
|
||||||
|
return 'dokploy.projects';
|
||||||
|
}
|
||||||
|
|
||||||
protected function forgetApplicationCaches(string $applicationId): void
|
protected function forgetApplicationCaches(string $applicationId): void
|
||||||
{
|
{
|
||||||
Cache::forget($this->applicationCacheKey($applicationId));
|
Cache::forget($this->applicationCacheKey($applicationId));
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ return [
|
|||||||
],
|
],
|
||||||
'web_url' => env('DOKPLOY_WEB_URL'),
|
'web_url' => env('DOKPLOY_WEB_URL'),
|
||||||
'applications' => json_decode(env('DOKPLOY_APPLICATION_IDS', '{}'), true) ?? [],
|
'applications' => json_decode(env('DOKPLOY_APPLICATION_IDS', '{}'), true) ?? [],
|
||||||
|
'projects' => json_decode(env('DOKPLOY_PROJECT_IDS', '{}'), true) ?? [],
|
||||||
'composes' => json_decode(env('DOKPLOY_COMPOSE_IDS', '{}'), true) ?? [],
|
'composes' => json_decode(env('DOKPLOY_COMPOSE_IDS', '{}'), true) ?? [],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -5,38 +5,213 @@
|
|||||||
|
|
||||||
$statusColors = [
|
$statusColors = [
|
||||||
'done' => 'success',
|
'done' => 'success',
|
||||||
|
'healthy' => 'success',
|
||||||
|
'running' => 'success',
|
||||||
'deploying' => 'warning',
|
'deploying' => 'warning',
|
||||||
'pending' => 'warning',
|
'pending' => 'warning',
|
||||||
|
'starting' => 'warning',
|
||||||
|
'warning' => 'warning',
|
||||||
'unreachable' => 'danger',
|
'unreachable' => 'danger',
|
||||||
'error' => 'danger',
|
'error' => 'danger',
|
||||||
'failed' => 'danger',
|
'failed' => 'danger',
|
||||||
|
'unhealthy' => 'danger',
|
||||||
];
|
];
|
||||||
|
|
||||||
$statusIcons = [
|
$statusIcons = [
|
||||||
'done' => Heroicon::CheckCircle,
|
'done' => Heroicon::CheckCircle,
|
||||||
|
'healthy' => Heroicon::CheckCircle,
|
||||||
|
'running' => Heroicon::CheckCircle,
|
||||||
'deploying' => Heroicon::ArrowPath,
|
'deploying' => Heroicon::ArrowPath,
|
||||||
'pending' => Heroicon::Clock,
|
'pending' => Heroicon::Clock,
|
||||||
|
'starting' => Heroicon::Clock,
|
||||||
|
'warning' => Heroicon::ExclamationTriangle,
|
||||||
'unreachable' => Heroicon::ExclamationTriangle,
|
'unreachable' => Heroicon::ExclamationTriangle,
|
||||||
'error' => Heroicon::XCircle,
|
'error' => Heroicon::XCircle,
|
||||||
'failed' => Heroicon::XCircle,
|
'failed' => Heroicon::XCircle,
|
||||||
|
'unhealthy' => Heroicon::XCircle,
|
||||||
];
|
];
|
||||||
|
|
||||||
$serviceColors = [
|
$serviceColors = [
|
||||||
'running' => 'success',
|
'running' => 'success',
|
||||||
'done' => 'success',
|
'done' => 'success',
|
||||||
|
'healthy' => 'success',
|
||||||
'starting' => 'warning',
|
'starting' => 'warning',
|
||||||
'deploying' => 'warning',
|
'deploying' => 'warning',
|
||||||
|
'pending' => 'warning',
|
||||||
'unhealthy' => 'danger',
|
'unhealthy' => 'danger',
|
||||||
'error' => 'danger',
|
'error' => 'danger',
|
||||||
'failed' => 'danger',
|
'failed' => 'danger',
|
||||||
];
|
];
|
||||||
|
|
||||||
$cardsGrid = (new ComponentAttributeBag())->grid(['default' => 1, 'lg' => 2]);
|
$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);
|
$serviceGrid = (new ComponentAttributeBag())->grid(['default' => 1], GridDirection::Column);
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<x-filament-widgets::widget>
|
<x-filament-widgets::widget>
|
||||||
<x-filament::section heading="Infra Status (Dokploy)">
|
<x-filament::section heading="Infra Status (Dokploy)">
|
||||||
|
@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>
|
||||||
|
</x-filament::card>
|
||||||
|
@empty
|
||||||
|
<x-filament::empty-state
|
||||||
|
heading="No Dokploy projects configured."
|
||||||
|
:icon="Heroicon::QuestionMarkCircle"
|
||||||
|
/>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
<div {{ $cardsGrid }}>
|
<div {{ $cardsGrid }}>
|
||||||
@forelse($composes as $compose)
|
@forelse($composes as $compose)
|
||||||
<x-filament::card
|
<x-filament::card
|
||||||
@@ -87,5 +262,6 @@
|
|||||||
/>
|
/>
|
||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
|
@endif
|
||||||
</x-filament::section>
|
</x-filament::section>
|
||||||
</x-filament-widgets::widget>
|
</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