Import source: src/components/common/terminal-panel
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,100 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { Download, Maximize2, RefreshCcw, Search, Trash2 } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
|
||||||
|
export function TerminalPanel() {
|
||||||
|
const targetRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const [connected, setConnected] = useState(true);
|
||||||
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let disposed = false;
|
||||||
|
async function boot() {
|
||||||
|
const [{ Terminal }, { FitAddon }] = await Promise.all([
|
||||||
|
import("xterm"),
|
||||||
|
import("xterm-addon-fit"),
|
||||||
|
]);
|
||||||
|
if (disposed || !targetRef.current) return;
|
||||||
|
const term = new Terminal({
|
||||||
|
cursorBlink: true,
|
||||||
|
rows: 14,
|
||||||
|
convertEol: true,
|
||||||
|
disableStdin: true,
|
||||||
|
theme: {
|
||||||
|
background: "#0A0F1E",
|
||||||
|
foreground: "#E4E7EC",
|
||||||
|
cursor: "#FF8A00",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const fit = new FitAddon();
|
||||||
|
term.loadAddon(fit);
|
||||||
|
term.open(targetRef.current);
|
||||||
|
fit.fit();
|
||||||
|
term.writeln("CoaxysPanel mock terminal");
|
||||||
|
term.writeln("Connected to srv-dal-01.coaxys.net as coaxys");
|
||||||
|
term.writeln("");
|
||||||
|
term.writeln("$ php -v");
|
||||||
|
term.writeln("PHP 8.4.7 (mock output)");
|
||||||
|
term.writeln("$ systemctl status nginx");
|
||||||
|
term.writeln("active (running) - no command was executed on the host");
|
||||||
|
setLoaded(true);
|
||||||
|
}
|
||||||
|
void boot();
|
||||||
|
return () => {
|
||||||
|
disposed = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex-row flex-wrap items-center justify-between gap-2 space-y-0">
|
||||||
|
<CardTitle>Terminal</CardTitle>
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
<span className="inline-flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
connected
|
||||||
|
? "h-1.5 w-1.5 animate-pulse-soft rounded-full bg-success"
|
||||||
|
: "h-1.5 w-1.5 rounded-full bg-destructive"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{connected ? "Connected" : "Disconnected"}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setConnected(true)}
|
||||||
|
aria-label="Reconnect"
|
||||||
|
>
|
||||||
|
<RefreshCcw className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button size="icon" variant="outline" aria-label="Search terminal">
|
||||||
|
<Search className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button size="icon" variant="outline" aria-label="Download transcript">
|
||||||
|
<Download className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button size="icon" variant="outline" aria-label="Clear terminal">
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button size="icon" variant="outline" aria-label="Fullscreen">
|
||||||
|
<Maximize2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div
|
||||||
|
ref={targetRef}
|
||||||
|
className="min-h-[320px] overflow-hidden rounded-md border border-border/60 bg-[#0A0F1E] p-2"
|
||||||
|
aria-label="Mock terminal session"
|
||||||
|
/>
|
||||||
|
{!loaded ? (
|
||||||
|
<p className="mt-2 text-xs text-muted-foreground">Loading terminal emulator...</p>
|
||||||
|
) : null}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user