2.x - console (wip)

This commit is contained in:
Saeed Vaziry 2024-10-02 23:46:53 +02:00
parent 906ddc38de
commit 63e7cfd8f7
9 changed files with 181 additions and 9 deletions

View File

@ -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)

View File

@ -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 '';

View File

@ -97,8 +97,8 @@ public function panel(Panel $panel): Panel
->authMiddleware([
Authenticate::class,
])
->login()
->spa()
->login()
->globalSearchKeyBindings(['command+k', 'ctrl+k'])
->sidebarCollapsibleOnDesktop()
->globalSearchFieldKeyBindingSuffix();

View File

@ -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([

View File

@ -0,0 +1,36 @@
<?php
namespace App\Web\Pages\Servers\Console;
use App\Models\Server;
use App\Web\Components\Page;
use App\Web\Traits\PageHasServer;
class Index extends Page
{
use PageHasServer;
protected ?string $live = '';
protected $listeners = [];
protected static ?string $slug = 'servers/{server}/console';
protected static bool $shouldRegisterNavigation = false;
protected static ?string $title = 'Console';
public Server $server;
public static function canAccess(): bool
{
return auth()->user()?->can('update', static::getServerFromRoute()) ?? false;
}
public function getWidgets(): array
{
return [
[Widgets\Console::class, ['server' => $this->server]],
];
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace App\Web\Pages\Servers\Console\Widgets;
use App\Models\Server;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Form;
use Livewire\Component;
class Console extends Component implements HasForms
{
use InteractsWithForms;
public Server $server;
protected $listeners = ['$refresh'];
public bool $running = false;
public string $output = '';
public ?array $data = [];
public function mount(): void
{
$this->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'
<div>
<x-console-view wire:stream="output">
{{ $this->output }}
</x-console-view>
<div class="mt-5">
{{ $this->form }}
</div>
</div>
BLADE;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Web\Pages\Servers\Console\Widgets;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Illuminate\Support\Facades\Cache;
use Livewire\Component;
class StopCommand extends Component implements HasForms
{
use InteractsWithForms;
public function stop(): void
{
Cache::put('console', 0);
}
public function render(): string
{
return <<<'BLADE'
<div>
<x-filament::icon
icon="heroicon-o-stop"
wire:click="stop"
class="h-5 w-5 text-danger-400 cursor-pointer"
/>
</div>
BLADE;
}
}

View File

@ -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')

View File

@ -1,5 +1,5 @@
<div
{{ $attributes->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 }}
</div>