From 0b7dd1732b79f87e3e8a7e9d734edffd2a0120e9 Mon Sep 17 00:00:00 2001 From: Saeed Vaziry Date: Sat, 31 May 2025 12:39:46 +0200 Subject: [PATCH] #591 - console --- app/Http/Controllers/ConsoleController.php | 18 +- app/Http/Middleware/HandleInertiaRequests.php | 1 + resources/js/components/log-output.tsx | 12 +- resources/js/layouts/server/layout.tsx | 16 +- resources/js/pages/console/index.tsx | 258 ++++++++++++++++++ tests/Feature/ConsoleTest.php | 12 +- 6 files changed, 297 insertions(+), 20 deletions(-) create mode 100644 resources/js/pages/console/index.tsx diff --git a/app/Http/Controllers/ConsoleController.php b/app/Http/Controllers/ConsoleController.php index 510a1fde..96728e62 100644 --- a/app/Http/Controllers/ConsoleController.php +++ b/app/Http/Controllers/ConsoleController.php @@ -7,15 +7,27 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Validation\Rule; +use Inertia\Inertia; +use Inertia\Response; use Spatie\RouteAttributes\Attributes\Get; use Spatie\RouteAttributes\Attributes\Middleware; use Spatie\RouteAttributes\Attributes\Post; +use Spatie\RouteAttributes\Attributes\Prefix; use Symfony\Component\HttpFoundation\StreamedResponse; -#[Middleware('auth')] +#[Prefix('servers/{server}/console')] +#[Middleware(['auth', 'has-project'])] class ConsoleController extends Controller { - #[Post('servers/{server}/console/run', name: 'servers.console.run')] + #[Get('/', name: 'console')] + public function index(Server $server): Response + { + $this->authorize('update', $server); + + return Inertia::render('console/index'); + } + + #[Post('/run', name: 'console.run')] public function run(Server $server, Request $request): StreamedResponse { $this->authorize('update', $server); @@ -61,7 +73,7 @@ function () use ($server, $request, $ssh, $log, $currentDir): void { ); } - #[Get('servers/{server}/console/working-dir', name: 'servers.console.working-dir')] + #[Get('/working-dir', name: 'console.working-dir')] public function workingDir(Server $server): JsonResponse { return response()->json([ diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 0c345185..1aa5ee2d 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -89,6 +89,7 @@ public function share(Request $request): array ...(new Ziggy)->toArray(), 'location' => $request->url(), ], + 'csrf_token' => csrf_token(), 'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true', 'flash' => [ 'success' => fn () => $request->session()->get('success'), diff --git a/resources/js/components/log-output.tsx b/resources/js/components/log-output.tsx index 84d782a0..5ebf02a8 100644 --- a/resources/js/components/log-output.tsx +++ b/resources/js/components/log-output.tsx @@ -3,8 +3,9 @@ import { ReactNode, useRef, useEffect, useState } from 'react'; import { Button } from '@/components/ui/button'; import { ArrowDown, ClockArrowDownIcon } from 'lucide-react'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; +import { cn } from '@/lib/utils'; -export default function LogOutput({ children }: { children: ReactNode }) { +export default function LogOutput({ className, children }: { className?: string; children: ReactNode }) { const scrollRef = useRef(null); const endRef = useRef(null); const [autoScroll, setAutoScroll] = useState(false); @@ -23,16 +24,19 @@ export default function LogOutput({ children }: { children: ReactNode }) {
{children}
-
+
+ + + {!running && ( + + )} + {running && ( + + )} +
+ + +
+
+ {output} +
+ +
+ {!running ? ( +
+ {shellPrefix} + setCommand(e.target.value)} + className="ml-2 h-auto flex-grow border-0 bg-transparent! p-0 shadow-none ring-0 outline-none focus:ring-0 focus:outline-none focus-visible:ring-0" + autoComplete="off" + autoFocus + /> +
+
+ + + ); +} diff --git a/tests/Feature/ConsoleTest.php b/tests/Feature/ConsoleTest.php index ea2836d1..5b5c3e86 100644 --- a/tests/Feature/ConsoleTest.php +++ b/tests/Feature/ConsoleTest.php @@ -3,8 +3,8 @@ namespace Tests\Feature; use App\Facades\SSH; -use App\Web\Pages\Servers\Console\Index; use Illuminate\Foundation\Testing\RefreshDatabase; +use Inertia\Testing\AssertableInertia; use Tests\TestCase; class ConsoleTest extends TestCase @@ -15,9 +15,9 @@ public function test_see_console(): void { $this->actingAs($this->user); - $this->get(Index::getUrl(['server' => $this->server])) + $this->get(route('console', $this->server)) ->assertSuccessful() - ->assertSeeText('Headless Console'); + ->assertInertia(fn (AssertableInertia $page) => $page->component('console/index')); } public function test_run(): void @@ -26,7 +26,7 @@ public function test_run(): void $this->actingAs($this->user); - $this->post(route('servers.console.run', $this->server), [ + $this->post(route('console.run', $this->server), [ 'user' => 'vito', 'command' => 'ls -la', ])->assertStreamedContent('fake output'); @@ -36,11 +36,11 @@ public function test_run_validation_error(): void { $this->actingAs($this->user); - $this->post(route('servers.console.run', $this->server), [ + $this->post(route('console.run', $this->server), [ 'user' => 'vito', ])->assertSessionHasErrors('command'); - $this->post(route('servers.console.run', $this->server), [ + $this->post(route('console.run', $this->server), [ 'command' => 'ls -la', ])->assertSessionHasErrors('user'); }