added a help system, replaced the words "tenant" and "Pwa" with better alternatives. corrected and implemented cron jobs. prepared going live on a coolify-powered system.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\CoolifyActionLogs;
|
||||
|
||||
use App\Filament\Resources\CoolifyActionLogs\Pages\ManageCoolifyActionLogs;
|
||||
use App\Models\CoolifyActionLog;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use UnitEnum;
|
||||
|
||||
class CoolifyActionLogResource extends Resource
|
||||
{
|
||||
protected static ?string $model = CoolifyActionLog::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
protected static string|UnitEnum|null $navigationGroup = 'Platform';
|
||||
|
||||
protected static ?int $navigationSort = 90;
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->label('Timestamp')
|
||||
->sortable()
|
||||
->dateTime(),
|
||||
Tables\Columns\TextColumn::make('user.name')
|
||||
->label('User')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('service_id')
|
||||
->label('Service')
|
||||
->searchable()
|
||||
->copyable()
|
||||
->limit(30),
|
||||
Tables\Columns\BadgeColumn::make('action')
|
||||
->label('Action')
|
||||
->colors([
|
||||
'warning' => 'restart',
|
||||
'info' => 'redeploy',
|
||||
'gray' => 'logs',
|
||||
])
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('status_code')
|
||||
->label('HTTP')
|
||||
->sortable(),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
Tables\Actions\ViewAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ManageCoolifyActionLogs::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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 [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PhotoboothSettings\Pages;
|
||||
|
||||
use App\Filament\Resources\PhotoboothSettings\PhotoboothSettingResource;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPhotoboothSetting extends EditRecord
|
||||
{
|
||||
protected static string $resource = PhotoboothSettingResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getRedirectUrl(): string
|
||||
{
|
||||
return $this->getResource()::getUrl('index');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PhotoboothSettings\Pages;
|
||||
|
||||
use App\Filament\Resources\PhotoboothSettings\PhotoboothSettingResource;
|
||||
use App\Models\PhotoboothSetting;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListPhotoboothSettings extends ListRecords
|
||||
{
|
||||
protected static string $resource = PhotoboothSettingResource::class;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
parent::mount();
|
||||
|
||||
PhotoboothSetting::current();
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PhotoboothSettings;
|
||||
|
||||
use App\Filament\Resources\PhotoboothSettings\Pages\EditPhotoboothSetting;
|
||||
use App\Filament\Resources\PhotoboothSettings\Pages\ListPhotoboothSettings;
|
||||
use App\Filament\Resources\PhotoboothSettings\Schemas\PhotoboothSettingForm;
|
||||
use App\Filament\Resources\PhotoboothSettings\Schemas\PhotoboothSettingInfolist;
|
||||
use App\Filament\Resources\PhotoboothSettings\Tables\PhotoboothSettingsTable;
|
||||
use App\Models\PhotoboothSetting;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use UnitEnum;
|
||||
|
||||
class PhotoboothSettingResource extends Resource
|
||||
{
|
||||
protected static ?string $model = PhotoboothSetting::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
protected static UnitEnum|string|null $navigationGroup = null;
|
||||
|
||||
protected static ?int $navigationSort = 95;
|
||||
|
||||
public static function getNavigationGroup(): UnitEnum|string|null
|
||||
{
|
||||
return __('admin.nav.platform_management');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return PhotoboothSettingForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function infolist(Schema $schema): Schema
|
||||
{
|
||||
return PhotoboothSettingInfolist::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return PhotoboothSettingsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListPhotoboothSettings::route('/'),
|
||||
'edit' => EditPhotoboothSetting::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function canCreate(?Model $record = null): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function canDelete(?Model $record = null): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function canDeleteAny(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PhotoboothSettings\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class PhotoboothSettingForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Section::make(__('FTP-Verbindung'))
|
||||
->description(__('Globale Parameter für den vsftpd-Container.'))
|
||||
->schema([
|
||||
TextInput::make('ftp_port')
|
||||
->numeric()
|
||||
->required()
|
||||
->minValue(1)
|
||||
->maxValue(65535)
|
||||
->helperText(__('Standard: Port 2121 innerhalb des internen Netzwerks.')),
|
||||
TextInput::make('rate_limit_per_minute')
|
||||
->label(__('Uploads pro Minute'))
|
||||
->numeric()
|
||||
->required()
|
||||
->minValue(1)
|
||||
->maxValue(200)
|
||||
->helperText(__('Harte Rate-Limits für Photobooth-Clients.')),
|
||||
TextInput::make('expiry_grace_days')
|
||||
->label(__('Ablauf (Tage nach Eventende)'))
|
||||
->numeric()
|
||||
->required()
|
||||
->minValue(0)
|
||||
->maxValue(14),
|
||||
])->columns(3),
|
||||
Section::make(__('Sicherheit & Steuerung'))
|
||||
->schema([
|
||||
Toggle::make('require_ftps')
|
||||
->label(__('FTPS erzwingen'))
|
||||
->helperText(__('Aktivieren, wenn nur verschlüsselte FTP-Verbindungen erlaubt sein sollen.')),
|
||||
TagsInput::make('allowed_ip_ranges')
|
||||
->label(__('Erlaubte IP-Ranges (optional)'))
|
||||
->placeholder('10.0.0.0/24')
|
||||
->helperText(__('Liste optionaler CIDR-Ranges für Control-Service Allowlisting.')),
|
||||
TextInput::make('control_service_base_url')
|
||||
->label(__('Control-Service URL'))
|
||||
->url()
|
||||
->maxLength(191)
|
||||
->helperText(__('REST-Endpunkt des Provisioning-Sidecars (z. B. http://control:8080).')),
|
||||
TextInput::make('control_service_token_identifier')
|
||||
->label(__('Token Referenz'))
|
||||
->maxLength(191)
|
||||
->helperText(__('Bezeichner des Secrets im Secrets-Store (keine Klartext-Tokens speichern).')),
|
||||
])->columns(2),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PhotoboothSettings\Schemas;
|
||||
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class PhotoboothSettingInfolist
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
//
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PhotoboothSettings\Tables;
|
||||
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class PhotoboothSettingsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('ftp_port')
|
||||
->label(__('Port'))
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('rate_limit_per_minute')
|
||||
->label(__('Uploads/Minute'))
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('expiry_grace_days')
|
||||
->label(__('Ablauf +Tage'))
|
||||
->sortable(),
|
||||
Tables\Columns\IconColumn::make('require_ftps')
|
||||
->label(__('FTPS'))
|
||||
->boolean(),
|
||||
Tables\Columns\TextColumn::make('updated_at')
|
||||
->since()
|
||||
->label(__('Aktualisiert')),
|
||||
])
|
||||
->recordActions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->headerActions([])
|
||||
->bulkActions([]);
|
||||
}
|
||||
}
|
||||
127
app/Filament/SuperAdmin/Pages/CoolifyDeployments.php
Normal file
127
app/Filament/SuperAdmin/Pages/CoolifyDeployments.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
62
app/Filament/Widgets/CoolifyPlatformHealth.php
Normal file
62
app/Filament/Widgets/CoolifyPlatformHealth.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user