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:
arsam
2026-09-18 23:10:42 +03:30
parent d6ce972052
commit 1ac12f3e48
+100
View File
@@ -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>
);
}