From d3da1b0a58733c416afe04113ff119e17f38a51b Mon Sep 17 00:00:00 2001 From: Charles Date: Fri, 6 Dec 2024 09:46:10 -0500 Subject: [PATCH] Update Server Console, Address Overflows (#764) * Update Console Updates console to be more better <3. Light Mode still needs some love, haven't figured that out with filaments light/dark options yet as it does not use the "bright" colors... * Add overflow to... Everything? * Oops, Add Name label back * Actually handle Transfer Status & remove useless switch * Use switch case * Readonly command input if server can't receive one * lint * Update app/Filament/Server/Widgets/ServerConsole.php Co-authored-by: Boy132 * Use filament::icon instead of raw svg * Update resources/views/filament/components/server-console.blade.php Co-authored-by: Boy132 --------- Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com> Co-authored-by: Boy132 --- app/Filament/Server/Widgets/ServerConsole.php | 7 +- .../Server/Widgets/ServerOverview.php | 10 +- .../components/server-console.blade.php | 174 ++++++++++++------ 3 files changed, 134 insertions(+), 57 deletions(-) diff --git a/app/Filament/Server/Widgets/ServerConsole.php b/app/Filament/Server/Widgets/ServerConsole.php index 2ac5bb9b4..786a5aa5a 100644 --- a/app/Filament/Server/Widgets/ServerConsole.php +++ b/app/Filament/Server/Widgets/ServerConsole.php @@ -57,6 +57,11 @@ class ServerConsole extends Widget return $socket; } + protected function canSendCommand(): bool + { + return !$this->server->isInConflictState() && $this->server->retrieveStatus() === 'running'; + } + public function up(): void { $this->historyIndex = min($this->historyIndex + 1, count($this->history) - 1); @@ -73,7 +78,7 @@ class ServerConsole extends Widget public function enter(): void { - if (!empty($this->input)) { + if (!empty($this->input) && $this->canSendCommand()) { $this->dispatch('sendServerCommand', command: $this->input); $this->history = Arr::prepend($this->history, $this->input); diff --git a/app/Filament/Server/Widgets/ServerOverview.php b/app/Filament/Server/Widgets/ServerOverview.php index 0d23404e8..3a9c0b80e 100644 --- a/app/Filament/Server/Widgets/ServerOverview.php +++ b/app/Filament/Server/Widgets/ServerOverview.php @@ -18,9 +18,15 @@ class ServerOverview extends StatsOverviewWidget { return [ Stat::make('Name', $this->server->name) - ->description($this->server->description), + ->description($this->server->description) + ->extraAttributes([ + 'class' => 'overflow-x-auto', + ]), Stat::make('Status', $this->status()), - Stat::make('Address', $this->server->allocation->address), + Stat::make('Address', $this->server->allocation->address) + ->extraAttributes([ + 'class' => 'overflow-x-auto', + ]), ]; } diff --git a/resources/views/filament/components/server-console.blade.php b/resources/views/filament/components/server-console.blade.php index cd1e0dedf..a1febe9ed 100644 --- a/resources/views/filament/components/server-console.blade.php +++ b/resources/views/filament/components/server-console.blade.php @@ -1,19 +1,37 @@ @assets - - + + + + + + + @endassets
-
+
+ + let theme = { + background: 'rgba(19,26,32,0.7)', + cursor: 'transparent', + black: '#000000', + red: '#E54B4B', + green: '#9ECE58', + yellow: '#FAED70', + blue: '#396FE2', + magenta: '#BB80B3', + cyan: '#2DDAFD', + white: '#d0d0d0', + brightBlack: 'rgba(255, 255, 255, 0.2)', + brightRed: '#FF5370', + brightGreen: '#C3E88D', + brightYellow: '#FFCB6B', + brightBlue: '#82AAFF', + brightMagenta: '#C792EA', + brightCyan: '#89DDFF', + brightWhite: '#ffffff', + selection: '#FAF089' + }; + let options = { - fontSize: 18, - // fontFamily: th('fontFamily.mono'), + fontSize: 16, disableStdin: true, cursorStyle: 'underline', + cursorInactiveStyle: 'none', allowTransparency: true, + letterSpacing: 0.75, + lineHeight: 1, rows: 35, - cols: 110 - // theme: theme, + cols: 110, + theme: theme }; const terminal = new Terminal(options); - // TODO: load addons + const fitAddon = new FitAddon.FitAddon(); + const webLinksAddon = new WebLinksAddon.WebLinksAddon(); + const searchAddon = new SearchAddon.SearchAddon(); + const searchAddonBar = new SearchBarAddon.SearchBarAddon({ searchAddon }); + + terminal.loadAddon(fitAddon); + terminal.loadAddon(webLinksAddon); + terminal.loadAddon(searchAddon); + terminal.loadAddon(searchAddonBar); + + terminal.open(document.getElementById('terminal')); + fitAddon.fit(); //Fit on first load + + window.addEventListener('resize', () => { + fitAddon.fit(); + }); + + terminal.attachCustomKeyEventHandler((event) => { + if ((event.ctrlKey || event.metaKey) && event.key === 'c') { + document.execCommand('copy'); // navigator.clipboard.writeText() only works on ssl.. + return false; + } else if ((event.ctrlKey || event.metaKey) && event.key === 'f') { + event.preventDefault(); + searchAddonBar.show(); + return false; + } else if (event.key === 'Escape') { + searchAddonBar.hidden(); + } + return true; + }); + const TERMINAL_PRELUDE = '\u001b[1m\u001b[33mpelican@' + '{{ \Filament\Facades\Filament::getTenant()->name }}' + ' ~ \u001b[0m'; const handleConsoleOutput = (line, prelude = false) => terminal.writeln((prelude ? TERMINAL_PRELUDE : '') + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m'); - const handleTransferStatus = (status) => { - switch (status) { - // Sent by either the source or target node if a failure occurs. - case 'failure': - terminal.writeln(TERMINAL_PRELUDE + 'Transfer has failed.\u001b[0m'); - return; - } - }; + const handleTransferStatus = (status) => + status === 'failure' && terminal.writeln(TERMINAL_PRELUDE + 'Transfer has failed.\u001b[0m'); const handleDaemonErrorOutput = (line) => - terminal.writeln( - TERMINAL_PRELUDE + '\u001b[1m\u001b[41m' + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m' - ); + terminal.writeln(TERMINAL_PRELUDE + '\u001b[1m\u001b[41m' + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m'); const handlePowerChangeEvent = (state) => terminal.writeln(TERMINAL_PRELUDE + 'Server marked as ' + state + '...\u001b[0m'); @@ -65,38 +129,40 @@ let token = '{{ $this->getToken() }}'; socket.onmessage = function(websocketMessageEvent) { - let eventData = JSON.parse(websocketMessageEvent.data); + let {event, args} = JSON.parse(websocketMessageEvent.data); - if (eventData.event === 'console output' || eventData.event === 'install output') { - handleConsoleOutput(eventData.args[0]); - } + switch (event) { + case 'console output': + case 'install output': + handleConsoleOutput(args[0]); + break; + case 'status': + handlePowerChangeEvent(args[0]); + break; + case 'transfer status': + handleTransferStatus(args[0]); + break; + case 'daemon error': + handleDaemonErrorOutput(args[0]); + break; + case 'stats': + $wire.dispatchSelf('storeStats', { data: args[0] }); + break; + case 'auth success': + socket.send(JSON.stringify({ + 'event': 'send logs', + 'args': [null] + })); + break; + case 'token expiring': + case 'token expired': + token = '{{ $this->getToken() }}'; - if (eventData.event === 'status') { - handlePowerChangeEvent(eventData.args[0]); - } - - if (eventData.event === 'daemon error') { - handleDaemonErrorOutput(eventData.args[0]); - } - - if (eventData.event === 'stats') { - $wire.dispatchSelf('storeStats', { data: eventData.args[0] }); - } - - if (eventData.event === 'auth success') { - socket.send(JSON.stringify({ - 'event': 'send logs', - 'args': [null] - })); - } - - if (eventData.event === 'token expiring' || eventData.event === 'token expired') { - token = '{{ $this->getToken() }}'; - - socket.send(JSON.stringify({ - 'event': 'auth', - 'args': [token] - })); + socket.send(JSON.stringify({ + 'event': 'auth', + 'args': [token] + })); + break; } };