mirror of
https://gitlab.w-hs.de/an14051/backerup-website.git
synced 2026-07-27 17:35:29 +00:00
feat: update links to Gitea and enhance TerminalTyper for docker-compose support
This commit is contained in:
@@ -6,24 +6,59 @@ import { LINKS } from "@/lib/links";
|
||||
interface Line {
|
||||
type: "comment" | "cmd" | "cmd-cont" | "output" | "success" | "error";
|
||||
text: string;
|
||||
copyable?: boolean;
|
||||
}
|
||||
|
||||
function buildLines(): Line[] {
|
||||
function buildDockerRunLines(): Line[] {
|
||||
return [
|
||||
{ type: "comment", text: "# Pull the latest Backerup image" },
|
||||
{ type: "cmd", text: `docker pull ${LINKS.dockerImage}` },
|
||||
{ type: "output", text: `latest: Pulling from ${LINKS.dockerImage.split(":")[0]}` },
|
||||
{ type: "output", text: "Digest: sha256:a3f8b1c2d9e4f7g6h5..." },
|
||||
{ type: "success", text: "✓ Status: Image is up to date" },
|
||||
{ type: "comment", text: "# Start Backerup with Docker socket access" },
|
||||
{ type: "cmd", text: "docker run -d --name backerup \\" },
|
||||
{ type: "cmd-cont", text: ` -p ${LINKS.uiPort}:${LINKS.uiPort} \\` },
|
||||
{ type: "cmd-cont", text: " -v /var/run/docker.sock:/var/run/docker.sock \\" },
|
||||
{ type: "cmd-cont", text: ` ${LINKS.dockerImage}` },
|
||||
{ type: "success", text: "✓ Container started · a7f82c3d1b09" },
|
||||
{ type: "comment", text: "# Quick start: Run Backerup directly" },
|
||||
{ type: "cmd", text: "docker run -d --name backerup \\", copyable: true },
|
||||
{ type: "cmd-cont", text: ` -p ${LINKS.uiPort}:80 \\`, copyable: true },
|
||||
{ type: "cmd-cont", text: " -v /var/run/docker.sock:/var/run/docker.sock \\", copyable: true },
|
||||
{ type: "cmd-cont", text: " -v ${BACKUP_DIR:-/backups}:/backups \\", copyable: true },
|
||||
{ type: "cmd-cont", text: " -v backerup-config:/app/config \\", copyable: true },
|
||||
{ type: "cmd-cont", text: " -e BACKUP_DIR=/backups \\", copyable: true },
|
||||
{ type: "cmd-cont", text: " -e BACKUP_CONFIG_DIR=/app/config \\", copyable: true },
|
||||
{ type: "cmd-cont", text: ` -e HOST_BACKUP_DIR=/backups \\`, copyable: true },
|
||||
{ type: "cmd-cont", text: ` ${LINKS.dockerImage}`, copyable: true },
|
||||
{ type: "output", text: "a7f82c3d1b09" },
|
||||
{ type: "success", text: "✓ Container started" },
|
||||
{ type: "success", text: "✓ Ready at http://localhost:8080" },
|
||||
];
|
||||
}
|
||||
|
||||
function buildDockerComposeLines(): Line[] {
|
||||
return [
|
||||
{ type: "comment", text: "# Using docker-compose for easier management" },
|
||||
{ type: "cmd", text: "cat > docker-compose.yml << 'EOF'", copyable: false },
|
||||
{ type: "output", text: "services:" },
|
||||
{ type: "output", text: " backerup:" },
|
||||
{ type: "output", text: ` image: ${LINKS.dockerImage}` },
|
||||
{ type: "output", text: " ports:" },
|
||||
{ type: "output", text: ` - \"${LINKS.uiPort}:80\"` },
|
||||
{ type: "output", text: " volumes:" },
|
||||
{ type: "output", text: " - /var/run/docker.sock:/var/run/docker.sock" },
|
||||
{ type: "output", text: " - ${BACKUP_DIR:-/backups}:/backups" },
|
||||
{ type: "output", text: " - backerup-config:/app/config" },
|
||||
{ type: "output", text: " environment:" },
|
||||
{ type: "output", text: " - BACKUP_DIR=/backups" },
|
||||
{ type: "output", text: " - BACKUP_CONFIG_DIR=/app/config" },
|
||||
{ type: "output", text: " - HOST_BACKUP_DIR=${BACKUP_DIR:-/backups}" },
|
||||
{ type: "output", text: " restart: unless-stopped" },
|
||||
{ type: "output", text: "volumes:" },
|
||||
{ type: "output", text: " backerup-config:" },
|
||||
{ type: "output", text: "EOF" },
|
||||
{ type: "success", text: "✓ docker-compose.yml created" },
|
||||
{ type: "cmd", text: "docker compose up -d", copyable: false },
|
||||
{ type: "output", text: "[+] Building 2.3s (12/12) FINISHED" },
|
||||
{ type: "success", text: "✓ Ready at http://localhost:8080" },
|
||||
];
|
||||
}
|
||||
|
||||
function buildLines(mode: "docker-run" | "docker-compose" = "docker-run"): Line[] {
|
||||
return mode === "docker-run" ? buildDockerRunLines() : buildDockerComposeLines();
|
||||
}
|
||||
|
||||
function lineSpeed(line: Line) {
|
||||
if (line.type === "output" || line.type === "success" || line.type === "error") return 12;
|
||||
if (line.type === "comment") return 22;
|
||||
@@ -47,16 +82,44 @@ function lineColor(line: Line) {
|
||||
}
|
||||
}
|
||||
|
||||
function linePrompt(line: Line) {
|
||||
function linePrompt(line: Line): string {
|
||||
if (line.type === "cmd") return "$ ";
|
||||
if (line.type === "cmd-cont") return " ";
|
||||
return "";
|
||||
}
|
||||
|
||||
export default function TerminalTyper() {
|
||||
const LINES = buildLines();
|
||||
export default function TerminalTyper({ mode = "docker-run" }: { mode?: "docker-run" | "docker-compose" } = {}) {
|
||||
const LINES = buildLines(mode);
|
||||
const [doneLines, setDoneLines] = useState<number[]>([]);
|
||||
const [current, setCurrent] = useState<{ idx: number; chars: number } | null>({ idx: 0, chars: 0 });
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copyToClipboard = () => {
|
||||
let textToCopy = "";
|
||||
|
||||
if (mode === "docker-compose") {
|
||||
// For docker-compose, copy the YAML content only
|
||||
const startIdx = LINES.findIndex(line => line.type === "output" && line.text === "services:");
|
||||
const endIdx = LINES.findIndex((line, i) => i > startIdx && line.type === "output" && line.text === "EOF");
|
||||
|
||||
if (startIdx !== -1 && endIdx !== -1) {
|
||||
// Copy the YAML content
|
||||
textToCopy = LINES.slice(startIdx, endIdx)
|
||||
.map(line => line.text)
|
||||
.join("\n");
|
||||
}
|
||||
} else {
|
||||
// For docker-run, copy all cmd and cmd-cont lines
|
||||
textToCopy = LINES.filter(line => line.type === "cmd" || line.type === "cmd-cont")
|
||||
.map(line => line.text.trim())
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(textToCopy).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!current) return;
|
||||
@@ -86,30 +149,41 @@ export default function TerminalTyper() {
|
||||
}, [current]);
|
||||
|
||||
return (
|
||||
<div className="font-mono text-xs sm:text-sm leading-[1.85] select-none">
|
||||
{doneLines.map((i) => {
|
||||
const line = LINES[i];
|
||||
const prompt = linePrompt(line);
|
||||
return (
|
||||
<div key={i} className={`flex ${lineColor(line)}`}>
|
||||
{prompt && <span className="text-cyan-500 shrink-0 select-none">{prompt}</span>}
|
||||
<span>{line.text}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="space-y-3">
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs font-medium text-slate-300 bg-slate-800/50 hover:bg-slate-700/50 transition-colors border border-slate-700/50"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
{copied ? "Copied!" : "Copy command"}
|
||||
</button>
|
||||
<div className="font-mono text-xs sm:text-sm leading-[1.85] select-none">
|
||||
{doneLines.map((i) => {
|
||||
const line = LINES[i];
|
||||
const prompt = linePrompt(line);
|
||||
return (
|
||||
<div key={i} className={`flex ${lineColor(line)}`}>
|
||||
{prompt && <span className="text-cyan-500 shrink-0 select-none">{prompt}</span>}
|
||||
<span>{line.text}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{current && current.idx < LINES.length && (() => {
|
||||
const line = LINES[current.idx];
|
||||
const prompt = linePrompt(line);
|
||||
const partial = line.text.slice(0, current.chars);
|
||||
return (
|
||||
<div className={`flex ${lineColor(line)}`}>
|
||||
{prompt && <span className="text-cyan-500 shrink-0 select-none">{prompt}</span>}
|
||||
<span>{partial}</span>
|
||||
<span className="inline-block w-[7px] h-[0.9em] bg-cyan-400 ml-px animate-blink self-center" />
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
{current && current.idx < LINES.length && (() => {
|
||||
const line = LINES[current.idx];
|
||||
const prompt = linePrompt(line);
|
||||
const partial = line.text.slice(0, current.chars);
|
||||
return (
|
||||
<div className={`flex ${lineColor(line)}`}>
|
||||
{prompt && <span className="text-cyan-500 shrink-0 select-none">{prompt}</span>}
|
||||
<span>{partial}</span>
|
||||
<span className="inline-block w-[7px] h-[0.9em] bg-cyan-400 ml-px animate-blink self-center" />
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user