331 lines
11 KiB
PHP
331 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\Admin\Pages;
|
|
|
|
use App\Models\DnsSetting;
|
|
use App\Services\Agent\AgentClient;
|
|
use BackedEnum;
|
|
use Exception;
|
|
use Filament\Actions\Action;
|
|
use Filament\Actions\Concerns\InteractsWithActions;
|
|
use Filament\Actions\Contracts\HasActions;
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Filament\Tables\Columns\IconColumn;
|
|
use Filament\Tables\Columns\TextColumn;
|
|
use Filament\Tables\Concerns\InteractsWithTable;
|
|
use Filament\Tables\Contracts\HasTable;
|
|
use Filament\Tables\Table;
|
|
use Illuminate\Contracts\Support\Htmlable;
|
|
|
|
class IpAddresses extends Page implements HasActions, HasTable
|
|
{
|
|
use InteractsWithActions;
|
|
use InteractsWithTable;
|
|
|
|
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-signal';
|
|
|
|
protected static ?int $navigationSort = 9;
|
|
|
|
protected string $view = 'filament.admin.pages.ip-addresses';
|
|
|
|
public array $addresses = [];
|
|
|
|
public array $interfaces = [];
|
|
|
|
public ?string $defaultIp = null;
|
|
|
|
public ?string $defaultIpv6 = null;
|
|
|
|
protected ?AgentClient $agent = null;
|
|
|
|
public static function getNavigationLabel(): string
|
|
{
|
|
return __('IP Addresses');
|
|
}
|
|
|
|
public function getTitle(): string|Htmlable
|
|
{
|
|
return __('IP Address Manager');
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->loadAddresses();
|
|
}
|
|
|
|
protected function getAgent(): AgentClient
|
|
{
|
|
return $this->agent ??= new AgentClient;
|
|
}
|
|
|
|
protected function loadAddresses(): void
|
|
{
|
|
try {
|
|
$result = $this->getAgent()->ipList();
|
|
$this->addresses = $result['addresses'] ?? [];
|
|
$this->interfaces = $result['interfaces'] ?? [];
|
|
} catch (Exception $e) {
|
|
$this->addresses = [];
|
|
$this->interfaces = [];
|
|
}
|
|
|
|
$settings = DnsSetting::getAll();
|
|
$this->defaultIp = $settings['default_ip'] ?? null;
|
|
$this->defaultIpv6 = $settings['default_ipv6'] ?? null;
|
|
|
|
$this->flushCachedTableRecords();
|
|
}
|
|
|
|
protected function getHeaderActions(): array
|
|
{
|
|
return [
|
|
Action::make('refresh')
|
|
->label(__('Refresh'))
|
|
->icon('heroicon-o-arrow-path')
|
|
->color('gray')
|
|
->action(fn () => $this->loadAddresses()),
|
|
Action::make('addIp')
|
|
->label(__('Add IP'))
|
|
->icon('heroicon-o-plus-circle')
|
|
->color('primary')
|
|
->modalHeading(__('Add IP Address'))
|
|
->modalDescription(__('Assign a new IPv4 or IPv6 address to a network interface.'))
|
|
->modalSubmitActionLabel(__('Add IP'))
|
|
->form([
|
|
TextInput::make('ip')
|
|
->label(__('IP Address'))
|
|
->placeholder('203.0.113.10')
|
|
->live()
|
|
->afterStateUpdated(function (?string $state, callable $set): void {
|
|
if (! $state) {
|
|
return;
|
|
}
|
|
|
|
if (str_contains($state, ':')) {
|
|
$set('cidr', 64);
|
|
|
|
return;
|
|
}
|
|
|
|
$set('cidr', 24);
|
|
})
|
|
->rule('ip')
|
|
->required(),
|
|
TextInput::make('cidr')
|
|
->label(__('CIDR'))
|
|
->numeric()
|
|
->minValue(1)
|
|
->maxValue(128)
|
|
->default(24)
|
|
->required(),
|
|
Select::make('interface')
|
|
->label(__('Interface'))
|
|
->options(fn () => $this->getInterfaceOptions())
|
|
->searchable()
|
|
->required(),
|
|
])
|
|
->action(function (array $data): void {
|
|
try {
|
|
$result = $this->getAgent()->ipAdd($data['ip'], (int) $data['cidr'], $data['interface']);
|
|
|
|
if ($result['success'] ?? false) {
|
|
$message = $result['message'] ?? __('IP added successfully');
|
|
if (! ($result['persistent'] ?? true)) {
|
|
$message .= ' '.__('(Persistence not configured for this system)');
|
|
}
|
|
|
|
Notification::make()
|
|
->title(__('IP address added'))
|
|
->body($message)
|
|
->success()
|
|
->send();
|
|
$this->dispatch('notificationsSent');
|
|
|
|
$this->loadAddresses();
|
|
$this->dispatch('ip-defaults-updated');
|
|
} else {
|
|
throw new Exception($result['error'] ?? __('Failed to add IP address'));
|
|
}
|
|
} catch (Exception $e) {
|
|
Notification::make()
|
|
->title(__('Failed to add IP'))
|
|
->body($e->getMessage())
|
|
->danger()
|
|
->send();
|
|
$this->dispatch('notificationsSent');
|
|
}
|
|
}),
|
|
];
|
|
}
|
|
|
|
public function table(Table $table): Table
|
|
{
|
|
return $table
|
|
->records(fn () => $this->addresses)
|
|
->columns([
|
|
TextColumn::make('ip')
|
|
->label(__('IP Address'))
|
|
->fontFamily('mono')
|
|
->copyable()
|
|
->searchable(),
|
|
TextColumn::make('version')
|
|
->label(__('Version'))
|
|
->badge()
|
|
->formatStateUsing(fn ($state): string => (int) $state === 6 ? 'IPv6' : 'IPv4')
|
|
->color(fn ($state): string => (int) $state === 6 ? 'primary' : 'info'),
|
|
TextColumn::make('interface')
|
|
->label(__('Interface'))
|
|
->badge()
|
|
->color('gray'),
|
|
TextColumn::make('cidr')
|
|
->label(__('CIDR'))
|
|
->alignCenter(),
|
|
TextColumn::make('scope')
|
|
->label(__('Scope'))
|
|
->badge()
|
|
->color(fn (?string $state): string => $state === 'global' ? 'success' : 'gray')
|
|
->formatStateUsing(fn (?string $state): string => $state ? ucfirst($state) : '-'),
|
|
IconColumn::make('is_primary')
|
|
->label(__('Primary'))
|
|
->boolean(),
|
|
TextColumn::make('default')
|
|
->label(__('Default'))
|
|
->getStateUsing(fn (array $record): ?string => $this->getDefaultLabel($record))
|
|
->badge()
|
|
->color('success')
|
|
->placeholder('-'),
|
|
])
|
|
->recordActions([
|
|
Action::make('setDefault')
|
|
->label(fn (array $record): string => ($record['version'] ?? 4) === 6 ? __('Set Default IPv6') : __('Set Default IPv4'))
|
|
->icon('heroicon-o-star')
|
|
->color('primary')
|
|
->visible(fn (array $record): bool => ! $this->isDefaultIp($record))
|
|
->action(fn (array $record) => $this->setDefaultIp($record)),
|
|
Action::make('remove')
|
|
->label(__('Remove'))
|
|
->icon('heroicon-o-trash')
|
|
->color('danger')
|
|
->visible(fn (array $record): bool => ! $this->isDefaultIp($record))
|
|
->requiresConfirmation()
|
|
->modalHeading(__('Remove IP Address'))
|
|
->modalDescription(fn (array $record): string => __('Remove :ip from :iface?', ['ip' => $record['ip'] ?? '-', 'iface' => $record['interface'] ?? '-']))
|
|
->modalSubmitActionLabel(__('Remove IP'))
|
|
->action(fn (array $record) => $this->removeIp($record)),
|
|
])
|
|
->striped()
|
|
->paginated(false)
|
|
->emptyStateHeading(__('No IP addresses found'))
|
|
->emptyStateDescription(__('Add an IP address to begin managing assignments.'))
|
|
->emptyStateIcon('heroicon-o-signal');
|
|
}
|
|
|
|
protected function getInterfaceOptions(): array
|
|
{
|
|
if (empty($this->interfaces)) {
|
|
$this->loadAddresses();
|
|
}
|
|
|
|
$options = [];
|
|
foreach ($this->interfaces as $interface) {
|
|
$options[$interface] = $interface;
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
protected function getDefaultLabel(array $record): ?string
|
|
{
|
|
$ip = $record['ip'] ?? null;
|
|
if (! $ip) {
|
|
return null;
|
|
}
|
|
|
|
if ($this->defaultIp === $ip) {
|
|
return __('Default IPv4');
|
|
}
|
|
|
|
if ($this->defaultIpv6 === $ip) {
|
|
return __('Default IPv6');
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected function isDefaultIp(array $record): bool
|
|
{
|
|
$ip = $record['ip'] ?? null;
|
|
if (! $ip) {
|
|
return false;
|
|
}
|
|
|
|
return $ip === $this->defaultIp || $ip === $this->defaultIpv6;
|
|
}
|
|
|
|
protected function setDefaultIp(array $record): void
|
|
{
|
|
$ip = $record['ip'] ?? null;
|
|
$version = (int) ($record['version'] ?? 4);
|
|
|
|
if (! $ip) {
|
|
return;
|
|
}
|
|
|
|
if ($version === 6) {
|
|
DnsSetting::set('default_ipv6', $ip);
|
|
$this->defaultIpv6 = $ip;
|
|
} else {
|
|
DnsSetting::set('default_ip', $ip);
|
|
$this->defaultIp = $ip;
|
|
}
|
|
|
|
DnsSetting::clearCache();
|
|
|
|
Notification::make()
|
|
->title(__('Default IP updated'))
|
|
->success()
|
|
->send();
|
|
|
|
$this->dispatch('ip-defaults-updated');
|
|
}
|
|
|
|
protected function removeIp(array $record): void
|
|
{
|
|
$ip = $record['ip'] ?? null;
|
|
$cidr = $record['cidr'] ?? null;
|
|
$interface = $record['interface'] ?? null;
|
|
|
|
if (! $ip || ! $cidr || ! $interface) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$result = $this->getAgent()->ipRemove($ip, (int) $cidr, $interface);
|
|
if (! ($result['success'] ?? false)) {
|
|
throw new Exception($result['error'] ?? __('Failed to remove IP address'));
|
|
}
|
|
|
|
Notification::make()
|
|
->title(__('IP address removed'))
|
|
->success()
|
|
->send();
|
|
$this->dispatch('notificationsSent');
|
|
|
|
$this->loadAddresses();
|
|
$this->dispatch('ip-defaults-updated');
|
|
} catch (Exception $e) {
|
|
Notification::make()
|
|
->title(__('Failed to remove IP'))
|
|
->body($e->getMessage())
|
|
->danger()
|
|
->send();
|
|
$this->dispatch('notificationsSent');
|
|
}
|
|
}
|
|
}
|