mirror of
https://github.com/pelican-dev/panel.git
synced 2025-05-20 00:34:44 +02:00
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<color>" 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 <Boy132@users.noreply.github.com> * Use filament::icon instead of raw svg * Update resources/views/filament/components/server-console.blade.php Co-authored-by: Boy132 <Boy132@users.noreply.github.com> --------- Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com> Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
This commit is contained in:
parent
5317f97870
commit
d3da1b0a58
@ -57,6 +57,11 @@ class ServerConsole extends Widget
|
|||||||
return $socket;
|
return $socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function canSendCommand(): bool
|
||||||
|
{
|
||||||
|
return !$this->server->isInConflictState() && $this->server->retrieveStatus() === 'running';
|
||||||
|
}
|
||||||
|
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
$this->historyIndex = min($this->historyIndex + 1, count($this->history) - 1);
|
$this->historyIndex = min($this->historyIndex + 1, count($this->history) - 1);
|
||||||
@ -73,7 +78,7 @@ class ServerConsole extends Widget
|
|||||||
|
|
||||||
public function enter(): void
|
public function enter(): void
|
||||||
{
|
{
|
||||||
if (!empty($this->input)) {
|
if (!empty($this->input) && $this->canSendCommand()) {
|
||||||
$this->dispatch('sendServerCommand', command: $this->input);
|
$this->dispatch('sendServerCommand', command: $this->input);
|
||||||
|
|
||||||
$this->history = Arr::prepend($this->history, $this->input);
|
$this->history = Arr::prepend($this->history, $this->input);
|
||||||
|
@ -18,9 +18,15 @@ class ServerOverview extends StatsOverviewWidget
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
Stat::make('Name', $this->server->name)
|
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('Status', $this->status()),
|
||||||
Stat::make('Address', $this->server->allocation->address),
|
Stat::make('Address', $this->server->allocation->address)
|
||||||
|
->extraAttributes([
|
||||||
|
'class' => 'overflow-x-auto',
|
||||||
|
]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,37 @@
|
|||||||
<x-filament::widget>
|
<x-filament::widget>
|
||||||
@assets
|
@assets
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xterm/5.5.0/xterm.js"
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm/css/xterm.min.css">
|
||||||
integrity="sha512-Gujw5GajF5is3nMoGv9X+tCMqePLL/60qvAv1LofUZTV9jK8ENbM9L+maGmOsNzuZaiuyc/fpph1KT9uR5w3CQ=="
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm/lib/xterm.min.js"></script>
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit/lib/addon-fit.min.js"></script>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/xterm/5.5.0/xterm.css"
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links/lib/addon-web-links.min.js"></script>
|
||||||
integrity="sha512-AbNrj/oSHJaILgcdnkYm+DQ08SqVbZ8jlkJbFyyS1WDcAaXAcAfxJnCH69el7oVgTwVwyA5u5T+RdFyUykrV3Q=="
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-search/lib/addon-search.min.js"></script>
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-search-bar/lib/xterm-addon-search-bar.min.js"></script>
|
||||||
|
<style>
|
||||||
|
#terminal {
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-rows > div {
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-top: 2px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@endassets
|
@endassets
|
||||||
|
|
||||||
<div id="terminal" wire:ignore></div>
|
<div id="terminal" wire:ignore></div>
|
||||||
|
|
||||||
<div>
|
<div class="flex items-center w-full bg-transparent border rounded">
|
||||||
|
<x-filament::icon
|
||||||
|
icon="tabler-chevrons-right"
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
class="w-full bg-transparent"
|
class="w-full bg-transparent p-2 focus:outline-none focus:ring-0 border-none"
|
||||||
type="text"
|
type="text"
|
||||||
|
title="{{ $this->canSendCommand() ? '' : 'Can\'t send command when the server is Offline' }}"
|
||||||
|
:readonly="{{ !$this->canSendCommand() }}"
|
||||||
placeholder="Type a command..."
|
placeholder="Type a command..."
|
||||||
wire:model="input"
|
wire:model="input"
|
||||||
wire:keydown.enter="enter"
|
wire:keydown.enter="enter"
|
||||||
@ -24,39 +42,85 @@
|
|||||||
|
|
||||||
@script
|
@script
|
||||||
<script>
|
<script>
|
||||||
|
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 = {
|
let options = {
|
||||||
fontSize: 18,
|
fontSize: 16,
|
||||||
// fontFamily: th('fontFamily.mono'),
|
|
||||||
disableStdin: true,
|
disableStdin: true,
|
||||||
cursorStyle: 'underline',
|
cursorStyle: 'underline',
|
||||||
|
cursorInactiveStyle: 'none',
|
||||||
allowTransparency: true,
|
allowTransparency: true,
|
||||||
|
letterSpacing: 0.75,
|
||||||
|
lineHeight: 1,
|
||||||
rows: 35,
|
rows: 35,
|
||||||
cols: 110
|
cols: 110,
|
||||||
// theme: theme,
|
theme: theme
|
||||||
};
|
};
|
||||||
|
|
||||||
const terminal = new Terminal(options);
|
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'));
|
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 TERMINAL_PRELUDE = '\u001b[1m\u001b[33mpelican@' + '{{ \Filament\Facades\Filament::getTenant()->name }}' + ' ~ \u001b[0m';
|
||||||
|
|
||||||
const handleConsoleOutput = (line, prelude = false) =>
|
const handleConsoleOutput = (line, prelude = false) =>
|
||||||
terminal.writeln((prelude ? TERMINAL_PRELUDE : '') + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m');
|
terminal.writeln((prelude ? TERMINAL_PRELUDE : '') + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m');
|
||||||
|
|
||||||
const handleTransferStatus = (status) => {
|
const handleTransferStatus = (status) =>
|
||||||
switch (status) {
|
status === 'failure' && terminal.writeln(TERMINAL_PRELUDE + 'Transfer has failed.\u001b[0m');
|
||||||
// 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 handleDaemonErrorOutput = (line) =>
|
const handleDaemonErrorOutput = (line) =>
|
||||||
terminal.writeln(
|
terminal.writeln(TERMINAL_PRELUDE + '\u001b[1m\u001b[41m' + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m');
|
||||||
TERMINAL_PRELUDE + '\u001b[1m\u001b[41m' + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m'
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePowerChangeEvent = (state) =>
|
const handlePowerChangeEvent = (state) =>
|
||||||
terminal.writeln(TERMINAL_PRELUDE + 'Server marked as ' + state + '...\u001b[0m');
|
terminal.writeln(TERMINAL_PRELUDE + 'Server marked as ' + state + '...\u001b[0m');
|
||||||
@ -65,38 +129,40 @@
|
|||||||
let token = '{{ $this->getToken() }}';
|
let token = '{{ $this->getToken() }}';
|
||||||
|
|
||||||
socket.onmessage = function(websocketMessageEvent) {
|
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') {
|
switch (event) {
|
||||||
handleConsoleOutput(eventData.args[0]);
|
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') {
|
socket.send(JSON.stringify({
|
||||||
handlePowerChangeEvent(eventData.args[0]);
|
'event': 'auth',
|
||||||
}
|
'args': [token]
|
||||||
|
}));
|
||||||
if (eventData.event === 'daemon error') {
|
break;
|
||||||
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]
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user