From 63e7cfd8f762f86ef9b7893f205e0555f3c2134c Mon Sep 17 00:00:00 2001 From: Saeed Vaziry Date: Wed, 2 Oct 2024 23:46:53 +0200 Subject: [PATCH] 2.x - console (wip) --- app/Facades/SSH.php | 2 +- app/Helpers/SSH.php | 9 +- app/Providers/WebServiceProvider.php | 2 +- app/Support/Testing/SSHFake.php | 2 +- app/Web/Pages/Servers/Console/Index.php | 36 +++++++ .../Pages/Servers/Console/Widgets/Console.php | 98 +++++++++++++++++++ .../Servers/Console/Widgets/StopCommand.php | 31 ++++++ app/Web/Traits/PageHasServer.php | 8 ++ .../views/components/console-view.blade.php | 2 +- 9 files changed, 181 insertions(+), 9 deletions(-) create mode 100644 app/Web/Pages/Servers/Console/Index.php create mode 100644 app/Web/Pages/Servers/Console/Widgets/Console.php create mode 100644 app/Web/Pages/Servers/Console/Widgets/StopCommand.php diff --git a/app/Facades/SSH.php b/app/Facades/SSH.php index 74c0eaa..5cbb0a6 100644 --- a/app/Facades/SSH.php +++ b/app/Facades/SSH.php @@ -13,7 +13,7 @@ * @method static init(Server $server, string $asUser = null) * @method static setLog(?ServerLog $log) * @method static connect() - * @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false) + * @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false, callable $streamCallback = null) * @method static string assertExecuted(array|string $commands) * @method static string assertExecutedContains(string $command) * @method static string assertFileUploaded(string $toPath, ?string $content = null) diff --git a/app/Helpers/SSH.php b/app/Helpers/SSH.php index e281f0e..067c5dd 100755 --- a/app/Helpers/SSH.php +++ b/app/Helpers/SSH.php @@ -91,7 +91,7 @@ public function connect(bool $sftp = false): void /** * @throws SSHError */ - public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string + public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false, ?callable $streamCallback = null): string { if (! $this->log && $log) { $this->log = ServerLog::make($this->server, $log); @@ -116,11 +116,10 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo $this->connection->setTimeout(0); if ($stream) { - $this->connection->exec($command, function ($output) { + $this->connection->exec($command, function ($output) use ($streamCallback) { $this->log?->write($output); - echo $output; - ob_flush(); - flush(); + + return $streamCallback($output); }); return ''; diff --git a/app/Providers/WebServiceProvider.php b/app/Providers/WebServiceProvider.php index 5ae2629..5c6e7dd 100644 --- a/app/Providers/WebServiceProvider.php +++ b/app/Providers/WebServiceProvider.php @@ -97,8 +97,8 @@ public function panel(Panel $panel): Panel ->authMiddleware([ Authenticate::class, ]) - ->login() ->spa() + ->login() ->globalSearchKeyBindings(['command+k', 'ctrl+k']) ->sidebarCollapsibleOnDesktop() ->globalSearchFieldKeyBindingSuffix(); diff --git a/app/Support/Testing/SSHFake.php b/app/Support/Testing/SSHFake.php index 2551462..01c0711 100644 --- a/app/Support/Testing/SSHFake.php +++ b/app/Support/Testing/SSHFake.php @@ -40,7 +40,7 @@ public function connect(bool $sftp = false): void } } - public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string + public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false, ?callable $streamCallback = null): string { if (! $this->log && $log) { $this->log = $this->server->logs()->create([ diff --git a/app/Web/Pages/Servers/Console/Index.php b/app/Web/Pages/Servers/Console/Index.php new file mode 100644 index 0000000..b1bb5e2 --- /dev/null +++ b/app/Web/Pages/Servers/Console/Index.php @@ -0,0 +1,36 @@ +user()?->can('update', static::getServerFromRoute()) ?? false; + } + + public function getWidgets(): array + { + return [ + [Widgets\Console::class, ['server' => $this->server]], + ]; + } +} diff --git a/app/Web/Pages/Servers/Console/Widgets/Console.php b/app/Web/Pages/Servers/Console/Widgets/Console.php new file mode 100644 index 0000000..f9b1fc5 --- /dev/null +++ b/app/Web/Pages/Servers/Console/Widgets/Console.php @@ -0,0 +1,98 @@ +data['user'] = $this->server->ssh_user; + } + + public function form(Form $form): Form + { + return $form + ->statePath('data') + ->schema([ + Grid::make() + ->columns(6) + ->schema([ + Select::make('user') + ->options([ + 'root' => 'root', + 'vito' => 'vito', + ]) + ->maxWidth('sm') + ->hiddenLabel(), + TextInput::make('command') + ->hiddenLabel() + ->placeholder('Command') + ->columnSpan(5) + ->suffixActions( + [ + Action::make('run') + ->label('Run') + ->icon('heroicon-o-play') + ->color('primary') + ->visible(! $this->running) + ->action(function () { + $this->running = true; + $ssh = $this->server->ssh($this->data['user']); + $log = 'console-'.time(); + defer(function () use ($ssh, $log) { + $ssh->exec(command: $this->data['command'], log: $log, stream: true, streamCallback: function ($output) { + $this->output .= $output; + $this->stream( + to: 'output', + content: $output, + ); + }); + })->name($log); + }), + Action::make('stop') + ->view('web.components.dynamic-widget', [ + 'widget' => StopCommand::class, + 'params' => [], + ]), + ], + ), + ]), + ]); + } + + public function render(): string + { + return <<<'BLADE' +
+ + {{ $this->output }} + +
+ {{ $this->form }} +
+
+ BLADE; + } +} diff --git a/app/Web/Pages/Servers/Console/Widgets/StopCommand.php b/app/Web/Pages/Servers/Console/Widgets/StopCommand.php new file mode 100644 index 0000000..e77f4a2 --- /dev/null +++ b/app/Web/Pages/Servers/Console/Widgets/StopCommand.php @@ -0,0 +1,31 @@ + + + + BLADE; + } +} diff --git a/app/Web/Traits/PageHasServer.php b/app/Web/Traits/PageHasServer.php index eec15ef..eec35c0 100644 --- a/app/Web/Traits/PageHasServer.php +++ b/app/Web/Traits/PageHasServer.php @@ -3,6 +3,7 @@ namespace App\Web\Traits; use App\Models\Server; +use App\Web\Pages\Servers\Console\Index as ConsoleIndex; use App\Web\Pages\Servers\CronJobs\Index as CronJobsIndex; use App\Web\Pages\Servers\Databases\Index as DatabasesIndex; use App\Web\Pages\Servers\Firewall\Index as FirewallIndex; @@ -88,6 +89,13 @@ public function getSubNavigation(): array ->url(MetricsIndex::getUrl(parameters: ['server' => $this->server])); } + if (ConsoleIndex::canAccess()) { + $items[] = NavigationItem::make(ConsoleIndex::getNavigationLabel()) + ->icon('heroicon-o-command-line') + ->isActiveWhen(fn () => request()->routeIs(ConsoleIndex::getRouteName().'*')) + ->url(ConsoleIndex::getUrl(parameters: ['server' => $this->server])); + } + if (LogsIndex::canAccess()) { $items[] = NavigationItem::make(LogsIndex::getNavigationLabel()) ->icon('heroicon-o-square-3-stack-3d') diff --git a/resources/views/components/console-view.blade.php b/resources/views/components/console-view.blade.php index 19f5233..8352d3e 100644 --- a/resources/views/components/console-view.blade.php +++ b/resources/views/components/console-view.blade.php @@ -1,5 +1,5 @@
merge(["class" => "font-mono whitespace-pre relative h-[500px] w-full overflow-auto whitespace-pre-line rounded-md border border-gray-200 bg-black p-5 text-gray-50 dark:border-gray-700"]) }} + {{ $attributes->merge(["class" => "font-mono whitespace-pre relative h-[500px] w-full overflow-auto whitespace-pre-line rounded-xl border border-gray-200 bg-black p-5 text-gray-50 dark:border-gray-800"]) }} > {{ $slot }}