admin widget zu dokploy geswitched
This commit is contained in:
@@ -112,14 +112,16 @@ class PostResource extends Resource
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(2),
|
||||
]),
|
||||
TextInput::make('slug')
|
||||
->label('Slug')
|
||||
->required()
|
||||
->unique(BlogPost::class, 'slug', ignoreRecord: true)
|
||||
->maxLength(255),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
Section::make('Bild und Kategorie')
|
||||
->schema([
|
||||
TextInput::make('slug')
|
||||
->label('Slug')
|
||||
->required()
|
||||
->unique(BlogPost::class, 'slug', ignoreRecord: true)
|
||||
->maxLength(255)
|
||||
->columnSpanFull(),
|
||||
FileUpload::make('featured_image')
|
||||
->label('Featured Image')
|
||||
->image()
|
||||
@@ -194,6 +196,8 @@ class PostResource extends Resource
|
||||
->label('Titel (DE)')
|
||||
->getStateUsing(fn ($record) => $record->getTranslation('title', 'de'))
|
||||
->searchable()
|
||||
->limit(50)
|
||||
->url(fn ($record) => static::getUrl('edit', ['record' => $record]))
|
||||
->sortable(),
|
||||
TextColumn::make('category_label')
|
||||
->label('Kategorie')
|
||||
@@ -225,10 +229,10 @@ class PostResource extends Resource
|
||||
->trueIcon('heroicon-o-check-circle')
|
||||
->falseIcon('heroicon-o-x-circle'),
|
||||
TextColumn::make('published_at')
|
||||
->label('Veröffentlicht am')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
->label('Veröffentlichungsdatum')
|
||||
->icon('heroicon-o-calendar')
|
||||
->dateTime('d.m.Y H:i')
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->label('Erstellt am')
|
||||
->dateTime()
|
||||
@@ -240,9 +244,9 @@ class PostResource extends Resource
|
||||
->label('Veröffentlicht'),
|
||||
])
|
||||
->actions([
|
||||
ViewAction::make(),
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
DeleteAction::make()
|
||||
->icon('heroicon-o-trash')
|
||||
->label(''),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\CoolifyActionLogs\Pages;
|
||||
|
||||
use App\Filament\Resources\CoolifyActionLogs\CoolifyActionLogResource;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageCoolifyActionLogs extends ManageRecords
|
||||
{
|
||||
protected static string $resource = CoolifyActionLogResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\CoolifyActionLogs;
|
||||
namespace App\Filament\Resources\InfrastructureActionLogs;
|
||||
|
||||
use App\Filament\Resources\CoolifyActionLogs\Pages\ManageCoolifyActionLogs;
|
||||
use App\Models\CoolifyActionLog;
|
||||
use App\Filament\Resources\InfrastructureActionLogs\Pages\ManageInfrastructureActionLogs;
|
||||
use App\Models\InfrastructureActionLog;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
@@ -11,9 +11,9 @@ use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use UnitEnum;
|
||||
|
||||
class CoolifyActionLogResource extends Resource
|
||||
class InfrastructureActionLogResource extends Resource
|
||||
{
|
||||
protected static ?string $model = CoolifyActionLog::class;
|
||||
protected static ?string $model = InfrastructureActionLog::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
@@ -64,7 +64,7 @@ class CoolifyActionLogResource extends Resource
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageCoolifyActionLogs::route('/'),
|
||||
'index' => ManageInfrastructureActionLogs::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\InfrastructureActionLogs\Pages;
|
||||
|
||||
use App\Filament\Resources\InfrastructureActionLogs\InfrastructureActionLogResource;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageInfrastructureActionLogs extends ManageRecords
|
||||
{
|
||||
protected static string $resource = InfrastructureActionLogResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\SuperAdmin\Pages;
|
||||
|
||||
use App\Models\CoolifyActionLog;
|
||||
use App\Services\Coolify\CoolifyClient;
|
||||
use BackedEnum;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class CoolifyDeployments extends Page
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-wrench-screwdriver';
|
||||
|
||||
protected static ?string $navigationLabel = 'Infrastructure';
|
||||
|
||||
protected static ?string $title = 'Infrastructure Controls';
|
||||
|
||||
protected string $view = 'filament.super-admin.pages.coolify-deployments';
|
||||
|
||||
public array $services = [];
|
||||
|
||||
public array $recentLogs = [];
|
||||
|
||||
public ?string $coolifyWebUrl = null;
|
||||
|
||||
public function mount(CoolifyClient $client): void
|
||||
{
|
||||
$this->coolifyWebUrl = config('coolify.web_url');
|
||||
$this->refreshServices($client);
|
||||
$this->refreshLogs();
|
||||
}
|
||||
|
||||
public function restart(string $serviceId): void
|
||||
{
|
||||
$this->performAction($serviceId, 'restart');
|
||||
}
|
||||
|
||||
public function redeploy(string $serviceId): void
|
||||
{
|
||||
$this->performAction($serviceId, 'redeploy');
|
||||
}
|
||||
|
||||
protected function performAction(string $serviceId, string $action): void
|
||||
{
|
||||
$client = app(CoolifyClient::class);
|
||||
|
||||
if (! $this->isKnownService($serviceId)) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('Unknown service')
|
||||
->body("The service ID {$serviceId} is not configured.")
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$action === 'restart'
|
||||
? $client->restartService($serviceId, auth()->user())
|
||||
: $client->redeployService($serviceId, auth()->user());
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(ucfirst($action).' requested')
|
||||
->body("Coolify accepted the {$action} action for {$serviceId}.")
|
||||
->send();
|
||||
} catch (\Throwable $exception) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('Coolify request failed')
|
||||
->body($exception->getMessage())
|
||||
->send();
|
||||
}
|
||||
|
||||
$this->refreshServices($client);
|
||||
$this->refreshLogs();
|
||||
}
|
||||
|
||||
protected function refreshServices(CoolifyClient $client): void
|
||||
{
|
||||
$serviceMap = config('coolify.services', []);
|
||||
$results = [];
|
||||
|
||||
foreach ($serviceMap as $label => $id) {
|
||||
try {
|
||||
$status = $client->serviceStatus($id);
|
||||
$results[] = [
|
||||
'label' => ucfirst($label),
|
||||
'service_id' => $id,
|
||||
'status' => Arr::get($status, 'data.status', 'unknown'),
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
$results[] = [
|
||||
'label' => ucfirst($label),
|
||||
'service_id' => $id,
|
||||
'status' => 'error',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->services = $results;
|
||||
}
|
||||
|
||||
protected function refreshLogs(): void
|
||||
{
|
||||
$this->recentLogs = CoolifyActionLog::query()
|
||||
->with('user')
|
||||
->latest()
|
||||
->limit(5)
|
||||
->get()
|
||||
->map(fn ($log) => [
|
||||
'created_at' => $log->created_at->diffForHumans(),
|
||||
'user' => $log->user?->name ?? 'System',
|
||||
'service_id' => $log->service_id,
|
||||
'action' => $log->action,
|
||||
'status_code' => $log->status_code,
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
protected function isKnownService(string $serviceId): bool
|
||||
{
|
||||
return in_array($serviceId, array_values(config('coolify.services', [])), true);
|
||||
}
|
||||
}
|
||||
129
app/Filament/SuperAdmin/Pages/DokployDeployments.php
Normal file
129
app/Filament/SuperAdmin/Pages/DokployDeployments.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\SuperAdmin\Pages;
|
||||
|
||||
use App\Models\InfrastructureActionLog;
|
||||
use App\Services\Dokploy\DokployClient;
|
||||
use BackedEnum;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class DokployDeployments extends Page
|
||||
{
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-wrench-screwdriver';
|
||||
|
||||
protected static ?string $navigationLabel = 'Infrastructure';
|
||||
|
||||
protected static ?string $title = 'Infrastructure Controls';
|
||||
|
||||
protected string $view = 'filament.super-admin.pages.dokploy-deployments';
|
||||
|
||||
public array $applications = [];
|
||||
|
||||
public array $recentLogs = [];
|
||||
|
||||
public ?string $dokployWebUrl = null;
|
||||
|
||||
public function mount(DokployClient $client): void
|
||||
{
|
||||
$this->dokployWebUrl = config('dokploy.web_url');
|
||||
$this->refreshApplications($client);
|
||||
$this->refreshLogs();
|
||||
}
|
||||
|
||||
public function reload(string $applicationId): void
|
||||
{
|
||||
$this->performAction($applicationId, 'reload');
|
||||
}
|
||||
|
||||
public function redeploy(string $applicationId): void
|
||||
{
|
||||
$this->performAction($applicationId, 'redeploy');
|
||||
}
|
||||
|
||||
protected function performAction(string $applicationId, string $action): void
|
||||
{
|
||||
$client = app(DokployClient::class);
|
||||
|
||||
if (! $this->isKnownApplication($applicationId)) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('Unknown service')
|
||||
->body("The application ID {$applicationId} is not configured.")
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$action === 'reload'
|
||||
? $client->reloadApplication($applicationId, auth()->user())
|
||||
: $client->redeployApplication($applicationId, auth()->user());
|
||||
|
||||
Notification::make()
|
||||
->success()
|
||||
->title(ucfirst($action).' requested')
|
||||
->body("Dokploy accepted the {$action} action for {$applicationId}.")
|
||||
->send();
|
||||
} catch (\Throwable $exception) {
|
||||
Notification::make()
|
||||
->danger()
|
||||
->title('Dokploy request failed')
|
||||
->body($exception->getMessage())
|
||||
->send();
|
||||
}
|
||||
|
||||
$this->refreshApplications($client);
|
||||
$this->refreshLogs();
|
||||
}
|
||||
|
||||
protected function refreshApplications(DokployClient $client): void
|
||||
{
|
||||
$applicationMap = config('dokploy.applications', []);
|
||||
$results = [];
|
||||
|
||||
foreach ($applicationMap as $label => $id) {
|
||||
try {
|
||||
$status = $client->applicationStatus($id);
|
||||
$application = Arr::get($status, 'application', []);
|
||||
|
||||
$results[] = [
|
||||
'label' => ucfirst($label),
|
||||
'application_id' => $id,
|
||||
'status' => Arr::get($application, 'applicationStatus', 'unknown'),
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
$results[] = [
|
||||
'label' => ucfirst($label),
|
||||
'application_id' => $id,
|
||||
'status' => 'error',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->applications = $results;
|
||||
}
|
||||
|
||||
protected function refreshLogs(): void
|
||||
{
|
||||
$this->recentLogs = InfrastructureActionLog::query()
|
||||
->with('user')
|
||||
->latest()
|
||||
->limit(5)
|
||||
->get()
|
||||
->map(fn ($log) => [
|
||||
'created_at' => $log->created_at->diffForHumans(),
|
||||
'user' => $log->user?->name ?? 'System',
|
||||
'service_id' => $log->service_id,
|
||||
'action' => $log->action,
|
||||
'status_code' => $log->status_code,
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
protected function isKnownApplication(string $applicationId): bool
|
||||
{
|
||||
return in_array($applicationId, array_values(config('dokploy.applications', [])), true);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Services\Coolify\CoolifyClient;
|
||||
use Filament\Widgets\Widget;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class CoolifyPlatformHealth extends Widget
|
||||
{
|
||||
protected string $view = 'filament.widgets.coolify-platform-health';
|
||||
|
||||
protected ?string $pollingInterval = '60s';
|
||||
|
||||
protected function getViewData(): array
|
||||
{
|
||||
return [
|
||||
'services' => $this->loadServices(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function loadServices(): array
|
||||
{
|
||||
$client = app(CoolifyClient::class);
|
||||
$serviceMap = config('coolify.services', []);
|
||||
$results = [];
|
||||
|
||||
foreach ($serviceMap as $label => $serviceId) {
|
||||
try {
|
||||
$status = $client->serviceStatus($serviceId);
|
||||
$results[] = [
|
||||
'label' => ucfirst($label),
|
||||
'service_id' => $serviceId,
|
||||
'status' => Arr::get($status, 'data.status', 'unknown'),
|
||||
'cpu' => Arr::get($status, 'data.metrics.cpu_percent'),
|
||||
'memory' => Arr::get($status, 'data.metrics.memory_percent'),
|
||||
'last_deploy' => Arr::get($status, 'data.last_deployment.finished_at'),
|
||||
];
|
||||
} catch (\Throwable $exception) {
|
||||
$results[] = [
|
||||
'label' => ucfirst($label),
|
||||
'service_id' => $serviceId,
|
||||
'status' => 'unreachable',
|
||||
'error' => $exception->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($results)) {
|
||||
return [
|
||||
[
|
||||
'label' => 'Coolify',
|
||||
'service_id' => '-',
|
||||
'status' => 'unconfigured',
|
||||
'error' => 'Set COOLIFY_SERVICE_IDS in .env to enable monitoring.',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
91
app/Filament/Widgets/DokployPlatformHealth.php
Normal file
91
app/Filament/Widgets/DokployPlatformHealth.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Services\Dokploy\DokployClient;
|
||||
use Filament\Widgets\Widget;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class DokployPlatformHealth extends Widget
|
||||
{
|
||||
protected string $view = 'filament.widgets.dokploy-platform-health';
|
||||
|
||||
protected ?string $pollingInterval = '60s';
|
||||
|
||||
protected function getViewData(): array
|
||||
{
|
||||
return [
|
||||
'applications' => $this->loadApplications(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function loadApplications(): array
|
||||
{
|
||||
$client = app(DokployClient::class);
|
||||
$applicationMap = config('dokploy.applications', []);
|
||||
$results = [];
|
||||
|
||||
foreach ($applicationMap as $label => $applicationId) {
|
||||
try {
|
||||
$status = $client->applicationStatus($applicationId);
|
||||
$deployments = $client->recentDeployments($applicationId, 1);
|
||||
$application = Arr::get($status, 'application', []);
|
||||
$monitoring = Arr::get($status, 'monitoring', []);
|
||||
|
||||
$results[] = [
|
||||
'label' => ucfirst($label),
|
||||
'application_id' => $applicationId,
|
||||
'app_name' => Arr::get($application, 'appName') ?? Arr::get($application, 'name'),
|
||||
'status' => Arr::get($application, 'applicationStatus', 'unknown'),
|
||||
'cpu' => $this->extractMetric($monitoring, [
|
||||
'metrics.cpuPercent',
|
||||
'metrics.cpu_percent',
|
||||
'cpuPercent',
|
||||
'cpu_percent',
|
||||
]),
|
||||
'memory' => $this->extractMetric($monitoring, [
|
||||
'metrics.memoryPercent',
|
||||
'metrics.memory_percent',
|
||||
'memoryPercent',
|
||||
'memory_percent',
|
||||
]),
|
||||
'last_deploy' => Arr::get($deployments, '0.createdAt')
|
||||
?? Arr::get($deployments, '0.created_at')
|
||||
?? Arr::get($application, 'updatedAt')
|
||||
?? Arr::get($application, 'lastDeploymentAt'),
|
||||
];
|
||||
} catch (\Throwable $exception) {
|
||||
$results[] = [
|
||||
'label' => ucfirst($label),
|
||||
'application_id' => $applicationId,
|
||||
'status' => 'unreachable',
|
||||
'error' => $exception->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($results)) {
|
||||
return [
|
||||
[
|
||||
'label' => 'Dokploy',
|
||||
'application_id' => '-',
|
||||
'status' => 'unconfigured',
|
||||
'error' => 'Set DOKPLOY_APPLICATION_IDS in .env to enable monitoring.',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
protected function extractMetric(array $source, array $candidates): mixed
|
||||
{
|
||||
foreach ($candidates as $key) {
|
||||
if (Arr::has($source, $key)) {
|
||||
return Arr::get($source, $key);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class CoolifyActionLog extends Model
|
||||
class InfrastructureActionLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
@@ -4,10 +4,10 @@ namespace App\Providers\Filament;
|
||||
|
||||
use App\Filament\Blog\Resources\CategoryResource;
|
||||
use App\Filament\Blog\Resources\PostResource;
|
||||
use App\Filament\Resources\CoolifyActionLogs\CoolifyActionLogResource;
|
||||
use App\Filament\Resources\InfrastructureActionLogs\InfrastructureActionLogResource;
|
||||
use App\Filament\Resources\LegalPageResource;
|
||||
use App\Filament\Widgets\CoolifyPlatformHealth;
|
||||
use App\Filament\Widgets\CreditAlertsWidget;
|
||||
use App\Filament\Widgets\DokployPlatformHealth;
|
||||
use App\Filament\Widgets\PlatformStatsWidget;
|
||||
use App\Filament\Widgets\RevenueTrendWidget;
|
||||
use App\Filament\Widgets\TopTenantsByRevenue;
|
||||
@@ -61,7 +61,7 @@ class SuperAdminPanelProvider extends PanelProvider
|
||||
TopTenantsByRevenue::class,
|
||||
TopTenantsByUploads::class,
|
||||
\App\Filament\Widgets\StorageCapacityWidget::class,
|
||||
CoolifyPlatformHealth::class,
|
||||
DokployPlatformHealth::class,
|
||||
])
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
@@ -87,7 +87,7 @@ class SuperAdminPanelProvider extends PanelProvider
|
||||
PostResource::class,
|
||||
CategoryResource::class,
|
||||
LegalPageResource::class,
|
||||
CoolifyActionLogResource::class,
|
||||
InfrastructureActionLogResource::class,
|
||||
])
|
||||
->authGuard('web');
|
||||
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Coolify;
|
||||
|
||||
use Illuminate\Http\Client\Factory as HttpFactory;
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CoolifyClient
|
||||
{
|
||||
public function __construct(private readonly HttpFactory $http) {}
|
||||
|
||||
public function serviceStatus(string $serviceId): array
|
||||
{
|
||||
return $this->cached("coolify.service.$serviceId", fn () => $this->get("/services/{$serviceId}"), 30);
|
||||
}
|
||||
|
||||
public function recentDeployments(string $serviceId, int $limit = 5): array
|
||||
{
|
||||
return $this->cached("coolify.deployments.$serviceId", function () use ($serviceId, $limit) {
|
||||
$response = $this->get("/services/{$serviceId}/deployments?per_page={$limit}");
|
||||
|
||||
return Arr::get($response, 'data', []);
|
||||
}, 60);
|
||||
}
|
||||
|
||||
public function restartService(string $serviceId, ?Authenticatable $actor = null): array
|
||||
{
|
||||
return $this->dispatchAction($serviceId, 'restart', function () use ($serviceId) {
|
||||
return $this->post("/services/{$serviceId}/actions/restart");
|
||||
}, $actor);
|
||||
}
|
||||
|
||||
public function redeployService(string $serviceId, ?Authenticatable $actor = null): array
|
||||
{
|
||||
return $this->dispatchAction($serviceId, 'redeploy', function () use ($serviceId) {
|
||||
return $this->post("/services/{$serviceId}/actions/redeploy");
|
||||
}, $actor);
|
||||
}
|
||||
|
||||
protected function cached(string $key, callable $callback, int $seconds): mixed
|
||||
{
|
||||
return Cache::remember($key, now()->addSeconds($seconds), $callback);
|
||||
}
|
||||
|
||||
protected function get(string $path): array
|
||||
{
|
||||
$response = $this->request()->get($path);
|
||||
|
||||
if ($response->failed()) {
|
||||
$this->logFailure('GET', $path, $response);
|
||||
throw new RequestException($response);
|
||||
}
|
||||
|
||||
return $response->json() ?? [];
|
||||
}
|
||||
|
||||
protected function post(string $path, array $payload = []): array
|
||||
{
|
||||
$response = $this->request()->post($path, $payload);
|
||||
|
||||
if ($response->failed()) {
|
||||
$this->logFailure('POST', $path, $response);
|
||||
throw new RequestException($response);
|
||||
}
|
||||
|
||||
return $response->json() ?? [];
|
||||
}
|
||||
|
||||
protected function request(): PendingRequest
|
||||
{
|
||||
$baseUrl = config('coolify.api.base_url');
|
||||
$token = config('coolify.api.token');
|
||||
$timeout = config('coolify.api.timeout', 5);
|
||||
|
||||
if (! $baseUrl || ! $token) {
|
||||
throw new \RuntimeException('Coolify API is not configured.');
|
||||
}
|
||||
|
||||
return $this->http
|
||||
->baseUrl($baseUrl)
|
||||
->timeout($timeout)
|
||||
->acceptJson()
|
||||
->withToken($token);
|
||||
}
|
||||
|
||||
protected function logFailure(string $method, string $path, \Illuminate\Http\Client\Response $response): void
|
||||
{
|
||||
Log::error('[Coolify] API request failed', [
|
||||
'method' => $method,
|
||||
'path' => $path,
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function dispatchAction(string $serviceId, string $action, callable $callback, ?Authenticatable $actor = null): array
|
||||
{
|
||||
$payload = [];
|
||||
|
||||
try {
|
||||
$response = $callback();
|
||||
} catch (\Throwable $exception) {
|
||||
$this->logAction($serviceId, $action, $payload, [
|
||||
'error' => $exception->getMessage(),
|
||||
], null, $actor);
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$this->logAction($serviceId, $action, $payload, $response, $response['status'] ?? null, $actor);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function logAction(
|
||||
string $serviceId,
|
||||
string $action,
|
||||
array $payload,
|
||||
array $response,
|
||||
?int $status,
|
||||
?Authenticatable $actor = null,
|
||||
): void {
|
||||
CoolifyActionLog::create([
|
||||
'user_id' => $actor?->getAuthIdentifier() ?? auth()->id(),
|
||||
'service_id' => $serviceId,
|
||||
'action' => $action,
|
||||
'payload' => $payload,
|
||||
'response' => $response,
|
||||
'status_code' => $status,
|
||||
]);
|
||||
}
|
||||
}
|
||||
use App\Models\CoolifyActionLog;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
222
app/Services/Dokploy/DokployClient.php
Normal file
222
app/Services/Dokploy/DokployClient.php
Normal file
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Dokploy;
|
||||
|
||||
use App\Models\InfrastructureActionLog;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Http\Client\Factory as HttpFactory;
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DokployClient
|
||||
{
|
||||
public function __construct(private readonly HttpFactory $http) {}
|
||||
|
||||
public function applicationStatus(string $applicationId): array
|
||||
{
|
||||
return $this->cached($this->applicationCacheKey($applicationId), function () use ($applicationId) {
|
||||
$application = $this->get('/application.one', [
|
||||
'applicationId' => $applicationId,
|
||||
]);
|
||||
|
||||
$appName = Arr::get($application, 'appName') ?? Arr::get($application, 'name');
|
||||
$monitoring = [];
|
||||
|
||||
if ($appName) {
|
||||
$monitoring = $this->optionalGet('/application.readAppMonitoring', [
|
||||
'appName' => $appName,
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
'application' => $application,
|
||||
'monitoring' => $monitoring,
|
||||
'appName' => $appName,
|
||||
];
|
||||
}, 30);
|
||||
}
|
||||
|
||||
public function recentDeployments(string $applicationId, int $limit = 5): array
|
||||
{
|
||||
return $this->cached($this->deploymentCacheKey($applicationId), function () use ($applicationId, $limit) {
|
||||
$deployments = $this->get('/deployment.all', [
|
||||
'applicationId' => $applicationId,
|
||||
]);
|
||||
|
||||
if (! is_array($deployments)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_slice($deployments, 0, $limit);
|
||||
}, 60);
|
||||
}
|
||||
|
||||
public function reloadApplication(string $applicationId, ?Authenticatable $actor = null): array
|
||||
{
|
||||
$status = $this->applicationStatus($applicationId);
|
||||
$appName = Arr::get($status, 'appName');
|
||||
|
||||
if (! $appName) {
|
||||
throw new \RuntimeException('Dokploy application name is required to reload the service.');
|
||||
}
|
||||
|
||||
return $this->dispatchAction(
|
||||
$applicationId,
|
||||
'reload',
|
||||
[
|
||||
'applicationId' => $applicationId,
|
||||
'appName' => $appName,
|
||||
],
|
||||
fn (array $payload) => $this->post('/application.reload', $payload),
|
||||
$actor,
|
||||
);
|
||||
}
|
||||
|
||||
public function redeployApplication(string $applicationId, ?Authenticatable $actor = null): array
|
||||
{
|
||||
return $this->dispatchAction(
|
||||
$applicationId,
|
||||
'redeploy',
|
||||
[
|
||||
'applicationId' => $applicationId,
|
||||
],
|
||||
fn (array $payload) => $this->post('/application.redeploy', $payload),
|
||||
$actor,
|
||||
);
|
||||
}
|
||||
|
||||
protected function cached(string $key, callable $callback, int $seconds): mixed
|
||||
{
|
||||
return Cache::remember($key, now()->addSeconds($seconds), $callback);
|
||||
}
|
||||
|
||||
protected function optionalGet(string $path, array $query = []): array
|
||||
{
|
||||
try {
|
||||
return $this->get($path, $query);
|
||||
} catch (\Throwable $exception) {
|
||||
Log::warning('[Dokploy] Optional GET failed', [
|
||||
'path' => $path,
|
||||
'message' => $exception->getMessage(),
|
||||
]);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
protected function get(string $path, array $query = []): array
|
||||
{
|
||||
$response = $this->request()->get($this->normalizePath($path), $query);
|
||||
|
||||
if ($response->failed()) {
|
||||
$this->logFailure('GET', $path, $response);
|
||||
throw new RequestException($response);
|
||||
}
|
||||
|
||||
return $response->json() ?? [];
|
||||
}
|
||||
|
||||
protected function post(string $path, array $payload = []): Response
|
||||
{
|
||||
$response = $this->request()->post($this->normalizePath($path), $payload);
|
||||
|
||||
if ($response->failed()) {
|
||||
$this->logFailure('POST', $path, $response);
|
||||
throw new RequestException($response);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function request(): PendingRequest
|
||||
{
|
||||
$baseUrl = config('dokploy.api.base_url');
|
||||
$token = config('dokploy.api.token');
|
||||
$timeout = config('dokploy.api.timeout', 10);
|
||||
|
||||
if (! $baseUrl || ! $token) {
|
||||
throw new \RuntimeException('Dokploy API is not configured.');
|
||||
}
|
||||
|
||||
return $this->http
|
||||
->baseUrl($baseUrl)
|
||||
->timeout($timeout)
|
||||
->acceptJson()
|
||||
->withHeaders([
|
||||
'x-api-key' => $token,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function normalizePath(string $path): string
|
||||
{
|
||||
return '/'.ltrim($path, '/');
|
||||
}
|
||||
|
||||
protected function logFailure(string $method, string $path, Response $response): void
|
||||
{
|
||||
Log::error('[Dokploy] API request failed', [
|
||||
'method' => $method,
|
||||
'path' => $path,
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function dispatchAction(
|
||||
string $applicationId,
|
||||
string $action,
|
||||
array $payload,
|
||||
callable $callback,
|
||||
?Authenticatable $actor = null,
|
||||
): array {
|
||||
try {
|
||||
$response = $callback($payload);
|
||||
$body = $response->json() ?? [];
|
||||
$status = $response->status();
|
||||
} catch (\Throwable $exception) {
|
||||
$this->logAction($applicationId, $action, $payload, [
|
||||
'error' => $exception->getMessage(),
|
||||
], null, $actor);
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$this->logAction($applicationId, $action, $payload, $body, $status, $actor);
|
||||
Cache::forget($this->applicationCacheKey($applicationId));
|
||||
Cache::forget($this->deploymentCacheKey($applicationId));
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
protected function logAction(
|
||||
string $applicationId,
|
||||
string $action,
|
||||
array $payload,
|
||||
array $response,
|
||||
?int $status,
|
||||
?Authenticatable $actor = null,
|
||||
): void {
|
||||
InfrastructureActionLog::create([
|
||||
'user_id' => $actor?->getAuthIdentifier() ?? auth()->id(),
|
||||
'service_id' => $applicationId,
|
||||
'action' => $action,
|
||||
'payload' => $payload,
|
||||
'response' => $response,
|
||||
'status_code' => $status,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function applicationCacheKey(string $applicationId): string
|
||||
{
|
||||
return "dokploy.application.{$applicationId}";
|
||||
}
|
||||
|
||||
protected function deploymentCacheKey(string $applicationId): string
|
||||
{
|
||||
return "dokploy.deployments.{$applicationId}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user