mirror of
https://github.com/pelican-dev/panel.git
synced 2026-05-04 18:00:48 +03:00
Add “reachable” column for Client -> Wings connections for Nodes (#2200)
This commit is contained in:
@@ -4,6 +4,7 @@ namespace App\Filament\Admin\Resources\Nodes\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Filament\Admin\Resources\Nodes\NodeResource;
|
||||
use App\Filament\Components\Tables\Columns\NodeClientHealthColumn;
|
||||
use App\Filament\Components\Tables\Columns\NodeHealthColumn;
|
||||
use App\Filament\Components\Tables\Filters\TagsFilter;
|
||||
use App\Models\Node;
|
||||
@@ -34,6 +35,7 @@ class ListNodes extends ListRecords
|
||||
->searchable()
|
||||
->hidden(),
|
||||
NodeHealthColumn::make('health'),
|
||||
NodeClientHealthColumn::make('reachable'),
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/node.table.name'))
|
||||
->sortable()
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Components\Tables\Columns;
|
||||
|
||||
use Filament\Support\Enums\Alignment;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
|
||||
class NodeClientHealthColumn extends IconColumn
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label(trans('admin/node.table.reachable'));
|
||||
|
||||
$this->alignCenter();
|
||||
}
|
||||
|
||||
public function toEmbeddedHtml(): string
|
||||
{
|
||||
$alignment = $this->getAlignment();
|
||||
|
||||
$attributes = $this->getExtraAttributeBag()
|
||||
->class([
|
||||
'fi-ta-icon',
|
||||
'fi-inline' => $this->isInline(),
|
||||
'fi-ta-icon-has-line-breaks' => $this->isListWithLineBreaks(),
|
||||
'fi-wrapped' => $this->canWrap(),
|
||||
($alignment instanceof Alignment) ? "fi-align-{$alignment->value}" : (is_string($alignment) ? $alignment : ''),
|
||||
])
|
||||
->toHtml();
|
||||
|
||||
return Blade::render(<<<'BLADE'
|
||||
<div <?= $attributes ?>>
|
||||
@livewire('node-client-connectivity', ['node' => $record, 'lazy' => true])
|
||||
</div>
|
||||
BLADE, [
|
||||
'attributes' => $attributes,
|
||||
'record' => $this->getRecord(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
94
app/Livewire/NodeClientConnectivity.php
Normal file
94
app/Livewire/NodeClientConnectivity.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Models\Node;
|
||||
use App\Services\Nodes\NodeJWTService;
|
||||
use App\Services\Servers\GetUserPermissionsService;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Filament\Tables\View\Components\Columns\IconColumnComponent\IconComponent;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Component;
|
||||
|
||||
use function Filament\Support\generate_icon_html;
|
||||
|
||||
class NodeClientConnectivity extends Component
|
||||
{
|
||||
#[Locked]
|
||||
public Node $node;
|
||||
|
||||
private GetUserPermissionsService $getUserPermissionsService;
|
||||
|
||||
private NodeJWTService $nodeJWTService;
|
||||
|
||||
public function boot(GetUserPermissionsService $getUserPermissionsService, NodeJWTService $nodeJWTService): void
|
||||
{
|
||||
$this->getUserPermissionsService = $getUserPermissionsService;
|
||||
$this->nodeJWTService = $nodeJWTService;
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\Contracts\View\View
|
||||
{
|
||||
$httpUrl = $this->node->getConnectionAddress();
|
||||
|
||||
$wsUrl = null;
|
||||
$wsToken = null;
|
||||
|
||||
$server = $this->node->servers()->first();
|
||||
|
||||
if ($server) {
|
||||
$user = Auth::user();
|
||||
|
||||
$permissions = $this->getUserPermissionsService->handle($server, $user);
|
||||
|
||||
$wsToken = $this->nodeJWTService
|
||||
->setExpiresAt(now()->addMinute()->toImmutable())
|
||||
->setUser($user)
|
||||
->setClaims([
|
||||
'server_uuid' => $server->uuid,
|
||||
'permissions' => $permissions,
|
||||
])
|
||||
->handle($this->node, $user->id . $server->uuid)->toString();
|
||||
|
||||
$wsUrl = str_replace(['https://', 'http://'], ['wss://', 'ws://'], $this->node->getConnectionAddress());
|
||||
$wsUrl .= sprintf('/api/servers/%s/ws', $server->uuid);
|
||||
}
|
||||
|
||||
return view('livewire.node-client-connectivity', [
|
||||
'httpUrl' => $httpUrl,
|
||||
'wsUrl' => $wsUrl,
|
||||
'wsToken' => $wsToken,
|
||||
'loadingIcon' => $this->makeIcon(TablerIcon::WorldQuestion, 'warning', 'Checking...'),
|
||||
'offlineIcon' => $this->makeIcon(TablerIcon::WorldX, 'danger', 'Node is not reachable from your browser'),
|
||||
'onlineIcon' => $this->makeIcon(TablerIcon::WorldCheck, 'success', 'Node is reachable'),
|
||||
'warningIcon' => $this->makeIcon(TablerIcon::WorldExclamation, 'warning', 'Node is reachable, but WebSocket failed. Check reverse proxy config.'),
|
||||
'onlineNoWsIcon' => $this->makeIcon(TablerIcon::WorldCheck, 'success', 'Node is reachable (WebSocket not tested — no servers)'),
|
||||
]);
|
||||
}
|
||||
|
||||
private function makeIcon(TablerIcon $icon, string $color, string $tooltip): string
|
||||
{
|
||||
return generate_icon_html($icon, attributes: (new ComponentAttributeBag())
|
||||
->merge([
|
||||
'x-tooltip' => '{
|
||||
content: "' . $tooltip . '",
|
||||
theme: $store.theme,
|
||||
allowHTML: true,
|
||||
placement: "bottom",
|
||||
}',
|
||||
'style' => 'color: var(--dark-text, var(--text))',
|
||||
], escape: false)
|
||||
->color(IconComponent::class, $color), size: IconSize::Large)
|
||||
->toHtml();
|
||||
}
|
||||
|
||||
public function placeholder(): string
|
||||
{
|
||||
return generate_icon_html(TablerIcon::WorldQuestion, attributes: (new ComponentAttributeBag())
|
||||
->color(IconComponent::class, 'warning'), size: IconSize::Large)
|
||||
->toHtml();
|
||||
}
|
||||
}
|
||||
@@ -255,6 +255,8 @@ class Node extends Model implements Validatable
|
||||
|
||||
/**
|
||||
* Gets the servers associated with a node.
|
||||
*
|
||||
* @return HasMany<Server, $this>
|
||||
*/
|
||||
public function servers(): HasMany
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ return [
|
||||
],
|
||||
'table' => [
|
||||
'health' => 'Health',
|
||||
'reachable' => 'Reachable',
|
||||
'name' => 'Name',
|
||||
'address' => 'Address',
|
||||
'public' => 'Public',
|
||||
|
||||
67
resources/views/livewire/node-client-connectivity.blade.php
Normal file
67
resources/views/livewire/node-client-connectivity.blade.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<div
|
||||
x-data="{
|
||||
status: 'loading',
|
||||
async check() {
|
||||
try {
|
||||
await fetch('{{ $httpUrl }}', { mode: 'no-cors', signal: AbortSignal.timeout(5000) });
|
||||
} catch (e) {
|
||||
this.status = 'offline';
|
||||
return;
|
||||
}
|
||||
|
||||
@if ($wsUrl && $wsToken)
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
ws.close();
|
||||
reject(new Error('timeout'));
|
||||
}, 10000);
|
||||
|
||||
const ws = new WebSocket('{{ $wsUrl }}');
|
||||
|
||||
ws.onerror = () => {
|
||||
clearTimeout(timeout);
|
||||
ws.close();
|
||||
reject(new Error('ws_error'));
|
||||
};
|
||||
|
||||
ws.onopen = () => {
|
||||
ws.send(JSON.stringify({ event: 'auth', args: ['{{ $wsToken }}'] }));
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.event === 'auth success') {
|
||||
clearTimeout(timeout);
|
||||
ws.close();
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
});
|
||||
this.status = 'online';
|
||||
} catch (e) {
|
||||
this.status = 'warning';
|
||||
}
|
||||
@else
|
||||
this.status = 'online-no-ws';
|
||||
@endif
|
||||
}
|
||||
}"
|
||||
x-init="check()"
|
||||
>
|
||||
<div x-show="status === 'loading'" x-cloak>
|
||||
{!! $loadingIcon !!}
|
||||
</div>
|
||||
<div x-show="status === 'offline'" x-cloak>
|
||||
{!! $offlineIcon !!}
|
||||
</div>
|
||||
<div x-show="status === 'online'" x-cloak>
|
||||
{!! $onlineIcon !!}
|
||||
</div>
|
||||
<div x-show="status === 'warning'" x-cloak>
|
||||
{!! $warningIcon !!}
|
||||
</div>
|
||||
<div x-show="status === 'online-no-ws'" x-cloak>
|
||||
{!! $onlineNoWsIcon !!}
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user