foundation 102
This commit is contained in:
@@ -0,0 +1,694 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Debian 13 Bootstrap Configurator</title>
|
||||||
|
|
||||||
|
<!-- Tailwind CSS -->
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
|
||||||
|
<!-- React & ReactDOM -->
|
||||||
|
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
||||||
|
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
||||||
|
|
||||||
|
<!-- Babel for JSX -->
|
||||||
|
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body class="antialiased h-screen flex flex-col font-mono text-sm overflow-hidden">
|
||||||
|
<div id="root" class="flex-1 flex flex-col h-full"></div>
|
||||||
|
|
||||||
|
<script type="text/babel">
|
||||||
|
const { useState, useEffect } = React;
|
||||||
|
|
||||||
|
// ==============================================================================
|
||||||
|
// 1. UI & THEME CONFIGURATION (Texte, Farben, Bilder)
|
||||||
|
// ==============================================================================
|
||||||
|
const uiConfigJSON = {
|
||||||
|
text: {
|
||||||
|
appTitle: "Debian 13 // JSON Configurator",
|
||||||
|
logoLetter: "D",
|
||||||
|
navTitle: "Nav-Gruppe: Checkliste",
|
||||||
|
pipelineTitle: "Pipeline: Script Output",
|
||||||
|
btnCopyDefault: "[ SICHTBARE MODULE KOPIEREN ]",
|
||||||
|
btnCopySuccess: "[ KOMPLETTES SKRIPT KOPIERT ]",
|
||||||
|
emptyStateTitle: "[ KEINE AKTIVEN/SICHTBAREN MODULE ]",
|
||||||
|
emptyStateSub: "Bitte wähle links Einträge aus oder klappe die Navigation auf.",
|
||||||
|
logMessage: ">> LOG: Generiere {{count}} sichtbare Pipeline-Schritte..."
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
accent: "#10b981", // Haupt-Akzent (Emerald Green)
|
||||||
|
accentHover: "#34d399",
|
||||||
|
accentDark: "#064e3b",
|
||||||
|
bgApp: "#05080f", // Tiefster Hintergrund
|
||||||
|
bgPanelDark: "#0f172a", // Dunkler Kasten-Hintergrund
|
||||||
|
bgPanelLight: "#1e293b", // Heller Kasten-Hintergrund
|
||||||
|
textMain: "#f8fafc", // Helle Schrift
|
||||||
|
textMuted: "#94a3b8", // Abgedunkelte Schrift
|
||||||
|
wmBorderLight: "#94a3b8", // Wmaker 3D Rand (Hell)
|
||||||
|
wmBorderDark: "#0f172a", // Wmaker 3D Rand (Dunkel)
|
||||||
|
wmBorderMedium: "#64748b" // Wmaker Rahmenfarbe
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
// Ein cyber-artiges Terminal-Bild für den Header (Unsplash)
|
||||||
|
headerBg: "https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?q=80&w=1200&auto=format&fit=crop"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==============================================================================
|
||||||
|
// 2. MASTER SETUP CONFIGURATION (Inhalte, Pakete, Befehle)
|
||||||
|
// ==============================================================================
|
||||||
|
const setupConfigJSON = [
|
||||||
|
{
|
||||||
|
id: "basis",
|
||||||
|
title: "Basis & System",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "baseUpdate", type: "checkbox", label: "apt update & upgrade", defaultChecked: true,
|
||||||
|
commands: [ { text: "Paketquellen aktualisieren und System upgraden", code: "apt update && apt upgrade -y" } ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "baseDeps", type: "checkbox", label: "Curl, Wget, Git, GnuPG", defaultChecked: true,
|
||||||
|
commands: [
|
||||||
|
{ text: "Curl, Git, GnuPG und Zertifikate installieren", code: "apt install -y curl wget git build-essential apt-transport-https ca-certificates gnupg software-properties-common" },
|
||||||
|
{ text: "Apt Keyring-Verzeichnis sicher anlegen", code: "install -m 0755 -d /etc/apt/keyrings" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "terminal",
|
||||||
|
title: "Terminal & UI",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "keyboardUsIntl", type: "checkbox", label: "US Tastatur mit Umlauten", defaultChecked: true,
|
||||||
|
commands: [
|
||||||
|
{ text: "Konfiguration schreiben", code: "cat << 'EOF' > /etc/default/keyboard\nXKBMODEL=\"pc105\"\nXKBLAYOUT=\"us\"\nXKBVARIANT=\"altgr-intl\"\nXKBOPTIONS=\"\"\nBACKSPACE=\"guess\"\nEOF" },
|
||||||
|
{ text: "System-Dienste neu laden", code: "dpkg-reconfigure -f noninteractive keyboard-configuration\nsetupcon || true" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "promptColor", type: "select", label: "Bash Prompt (PS1)", defaultSelect: "green",
|
||||||
|
options: [
|
||||||
|
{ value: "none", label: "STANDARD" },
|
||||||
|
{ value: "green", label: "GRÜN (HACKER STYLE)", meta: "\\[\\033[01;32m\\]" },
|
||||||
|
{ value: "blue", label: "BLAU (CLEAN)", meta: "\\[\\033[01;34m\\]" },
|
||||||
|
{ value: "cyan", label: "CYAN (MODERN)", meta: "\\[\\033[01;36m\\]" },
|
||||||
|
{ value: "red", label: "ROT (WARNING/ROOT)", meta: "\\[\\033[01;31m\\]" }
|
||||||
|
],
|
||||||
|
commandTemplate: [
|
||||||
|
{ text: "PS1 Variable in ~/.bashrc anhängen", code: "TARGET_USER=\"${SUDO_USER:-$USER}\"\nUSER_HOME=$(getent passwd \"$TARGET_USER\" | cut -d: -f6)\nif ! grep -q \"# BOOTSTRAP_PS1\" \"$USER_HOME/.bashrc\"; then\n echo -e \"\\n# BOOTSTRAP_PS1\\nPS1='{{meta}}\\u@\\h\\[\\033[00m\\]:\\[\\033[01;34m\\]\\w\\[\\033[00m\\]\\\\$ '\" >> \"$USER_HOME/.bashrc\"\nfi" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "dev",
|
||||||
|
title: "Sprachen & Editoren",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "neovim", type: "checkbox_with_select", label: "Neovim", defaultChecked: false, selectId: "neovimSrc", selectLabel: "QUELLE:", selectDefault: "apt",
|
||||||
|
options: [ { value: "apt", label: "Debian Repo (Stable)" }, { value: "web", label: "Website (Latest)" } ],
|
||||||
|
commandsByValue: {
|
||||||
|
"apt": [ { text: "Installation von: neovim", code: "apt install -y neovim" } ],
|
||||||
|
"web": [
|
||||||
|
{ text: "Archiv laden & entpacken", code: "curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz\nrm -rf /opt/nvim\ntar -C /opt -xzf nvim-linux64.tar.gz" },
|
||||||
|
{ text: "Globale Symlinks setzen", code: "ln -sf /opt/nvim-linux64/bin/nvim /usr/local/bin/nvim\nrm nvim-linux64.tar.gz" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "golang", type: "checkbox_with_select", label: "Golang", defaultChecked: false, selectId: "golangSrc", selectLabel: "QUELLE:", selectDefault: "apt",
|
||||||
|
options: [ { value: "apt", label: "Debian Repo (Stable)" }, { value: "web", label: "Website (Latest)" } ],
|
||||||
|
commandsByValue: {
|
||||||
|
"apt": [ { text: "Installation von: golang", code: "apt install -y golang" } ],
|
||||||
|
"web": [
|
||||||
|
{ text: "Neueste Version ermitteln", code: "GO_LATEST=$(curl -sL https://go.dev/VERSION?m=text | head -n 1)\necho \"Lade ${GO_LATEST} herunter...\"" },
|
||||||
|
{ text: "Archiv laden & nach /usr/local entpacken", code: "curl -LO \"https://go.dev/dl/${GO_LATEST}.linux-amd64.tar.gz\"\nrm -rf /usr/local/go\ntar -C /usr/local -xzf \"${GO_LATEST}.linux-amd64.tar.gz\"" },
|
||||||
|
{ text: "Globale Symlinks setzen", code: "ln -sf /usr/local/go/bin/go /usr/local/bin/go\nln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt\nrm \"${GO_LATEST}.linux-amd64.tar.gz\"" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "elixir", type: "checkbox_with_select", label: "Elixir & Erlang", defaultChecked: false, selectId: "elixirSrc", selectLabel: "QUELLE:", selectDefault: "apt",
|
||||||
|
options: [ { value: "apt", label: "Debian Repo (Stable)" }, { value: "web", label: "Website (Latest)" } ],
|
||||||
|
commandsByValue: {
|
||||||
|
"apt": [ { text: "Installation von: elixir erlang", code: "apt install -y elixir erlang" } ],
|
||||||
|
"web": [
|
||||||
|
{ text: "Erlang & Build-Tools vorbereiten", code: "apt install -y erlang make git" },
|
||||||
|
{ text: "Elixir Quellcode klonen & kompilieren", code: "rm -rf /usr/local/src/elixir\ngit clone https://github.com/elixir-lang/elixir.git /usr/local/src/elixir\ncd /usr/local/src/elixir\nmake install clean" },
|
||||||
|
{ text: "Globale Symlinks setzen", code: "ln -sf /usr/local/src/elixir/bin/elixir /usr/local/bin/elixir\nln -sf /usr/local/src/elixir/bin/elixirc /usr/local/bin/elixirc\nln -sf /usr/local/src/elixir/bin/iex /usr/local/bin/iex\nln -sf /usr/local/src/elixir/bin/mix /usr/local/bin/mix\ncd -" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "thirdparty",
|
||||||
|
title: "Externe Quellen (GPG/Curl)",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "docker", type: "checkbox", label: "Docker (CE, CLI, Compose)", defaultChecked: false,
|
||||||
|
commands: [
|
||||||
|
{ text: "Docker GPG-Schlüssel speichern", code: "curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg\nchmod a+r /etc/apt/keyrings/docker.gpg" },
|
||||||
|
{ text: "Apt-Repository eintragen", code: "echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo \"$VERSION_CODENAME\") stable\" | tee /etc/apt/sources.list.d/docker.list > /dev/null" },
|
||||||
|
{ text: "Docker installieren", code: "apt update\napt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin" },
|
||||||
|
{ text: "User in Docker-Gruppe aufnehmen", code: "TARGET_USER=\"${SUDO_USER:-$USER}\"\nusermod -aG docker \"$TARGET_USER\"" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "nodejs", type: "checkbox_with_select", label: "Node.js", defaultChecked: false, selectId: "nodeVersion", selectLabel: "VER:", selectDefault: "20.x",
|
||||||
|
options: [ { value: "22.x", label: "22.x (Current)" }, { value: "20.x", label: "20.x (LTS)" }, { value: "18.x", label: "18.x (Legacy)" } ],
|
||||||
|
commandTemplate: [
|
||||||
|
{ text: "NodeSource GPG-Schlüssel importieren", code: "curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg" },
|
||||||
|
{ text: "Repository-Eintrag erstellen", code: "echo \"deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_{{selectValue}} nodistro main\" | tee /etc/apt/sources.list.d/nodesource.list > /dev/null" },
|
||||||
|
{ text: "Node.js installieren", code: "apt update\napt install -y nodejs" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "syncthing", type: "checkbox", label: "Syncthing", defaultChecked: false,
|
||||||
|
commands: [
|
||||||
|
{ text: "Syncthing Release Key importieren", code: "curl -fsSL https://syncthing.net/release-key.txt | gpg --dearmor -o /etc/apt/keyrings/syncthing-archive-keyring.gpg" },
|
||||||
|
{ text: "Repository hinzufügen", code: "echo \"deb [signed-by=/etc/apt/keyrings/syncthing-archive-keyring.gpg] https://apt.syncthing.net/ syncthing stable\" | tee /etc/apt/sources.list.d/syncthing.list > /dev/null" },
|
||||||
|
{ text: "Syncthing installieren", code: "apt update\napt install -y syncthing" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// ==============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
// --- Dynamsiche CSS Injection basierend auf uiConfigJSON ---
|
||||||
|
const DynamicStyles = () => (
|
||||||
|
<style>{`
|
||||||
|
:root {
|
||||||
|
--c-accent: ${uiConfigJSON.theme.accent};
|
||||||
|
--c-accent-hover: ${uiConfigJSON.theme.accentHover};
|
||||||
|
--c-accent-dark: ${uiConfigJSON.theme.accentDark};
|
||||||
|
--c-bg-app: ${uiConfigJSON.theme.bgApp};
|
||||||
|
--c-bg-panel-dark: ${uiConfigJSON.theme.bgPanelDark};
|
||||||
|
--c-bg-panel-light: ${uiConfigJSON.theme.bgPanelLight};
|
||||||
|
--c-text-main: ${uiConfigJSON.theme.textMain};
|
||||||
|
--c-text-muted: ${uiConfigJSON.theme.textMuted};
|
||||||
|
--wm-light: ${uiConfigJSON.theme.wmBorderLight};
|
||||||
|
--wm-dark: ${uiConfigJSON.theme.wmBorderDark};
|
||||||
|
--wm-med: ${uiConfigJSON.theme.wmBorderMedium};
|
||||||
|
}
|
||||||
|
|
||||||
|
body { background-color: var(--c-bg-panel-dark); color: var(--c-text-main); }
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar { width: 12px; }
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-track { background: var(--c-bg-panel-dark); border-left: 2px solid var(--c-bg-panel-light); }
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb { background: var(--wm-med); border: 2px solid; border-top-color: var(--wm-light); border-left-color: var(--wm-light); border-bottom-color: var(--wm-dark); border-right-color: var(--wm-dark); }
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover { background: var(--c-text-muted); }
|
||||||
|
|
||||||
|
/* Wmaker Components */
|
||||||
|
.wm-btn {
|
||||||
|
background-color: var(--wm-med);
|
||||||
|
color: var(--c-text-main);
|
||||||
|
border: 2px solid;
|
||||||
|
border-top-color: var(--wm-light);
|
||||||
|
border-left-color: var(--wm-light);
|
||||||
|
border-bottom-color: var(--wm-dark);
|
||||||
|
border-right-color: var(--wm-dark);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 4px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.wm-btn:active, .wm-btn-active-state {
|
||||||
|
border-top-color: var(--wm-dark);
|
||||||
|
border-left-color: var(--wm-dark);
|
||||||
|
border-bottom-color: var(--wm-light);
|
||||||
|
border-right-color: var(--wm-light);
|
||||||
|
background-color: var(--c-bg-panel-light);
|
||||||
|
}
|
||||||
|
.wm-btn-success {
|
||||||
|
background-color: var(--c-accent-dark);
|
||||||
|
border-top-color: var(--c-accent-hover);
|
||||||
|
border-left-color: var(--c-accent-hover);
|
||||||
|
border-bottom-color: var(--wm-dark);
|
||||||
|
border-right-color: var(--wm-dark);
|
||||||
|
}
|
||||||
|
.wm-btn-success:active {
|
||||||
|
border-top-color: var(--wm-dark);
|
||||||
|
border-left-color: var(--wm-dark);
|
||||||
|
border-bottom-color: var(--c-accent-hover);
|
||||||
|
border-right-color: var(--c-accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wm-block {
|
||||||
|
border: 2px solid var(--wm-med);
|
||||||
|
background-color: var(--c-bg-panel-light);
|
||||||
|
box-shadow: 4px 4px 0px rgba(0,0,0,0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wm-titlebar {
|
||||||
|
background: linear-gradient(to right, var(--c-bg-panel-light), var(--c-bg-panel-dark));
|
||||||
|
border-bottom: 2px solid var(--wm-med);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wm-header-bg {
|
||||||
|
background-image: linear-gradient(to right, rgba(15, 23, 42, 0.95), rgba(15, 23, 42, 0.7)), url('${uiConfigJSON.images.headerBg}');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wm-checkbox-inner {
|
||||||
|
border: 2px solid;
|
||||||
|
border-top-color: var(--wm-dark);
|
||||||
|
border-left-color: var(--wm-dark);
|
||||||
|
border-bottom-color: var(--wm-light);
|
||||||
|
border-right-color: var(--wm-light);
|
||||||
|
background-color: var(--c-bg-panel-light);
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.wm-checkbox-inner.checked { background-color: var(--c-accent); }
|
||||||
|
|
||||||
|
.wm-input {
|
||||||
|
border: 2px solid;
|
||||||
|
border-top-color: var(--wm-dark);
|
||||||
|
border-left-color: var(--wm-dark);
|
||||||
|
border-bottom-color: var(--wm-light);
|
||||||
|
border-right-color: var(--wm-light);
|
||||||
|
background-color: var(--wm-dark);
|
||||||
|
color: var(--c-text-main);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wm-toggle-box {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 2px solid;
|
||||||
|
border-top-color: var(--c-text-muted);
|
||||||
|
border-left-color: var(--c-text-muted);
|
||||||
|
border-bottom-color: var(--wm-dark);
|
||||||
|
border-right-color: var(--wm-dark);
|
||||||
|
background-color: var(--wm-med);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.wm-toggle-box:active {
|
||||||
|
border-top-color: var(--wm-dark);
|
||||||
|
border-left-color: var(--wm-dark);
|
||||||
|
border-bottom-color: var(--c-text-muted);
|
||||||
|
border-right-color: var(--c-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Helfer-Klassen für dynamische Farben */
|
||||||
|
.text-accent { color: var(--c-accent); }
|
||||||
|
.bg-accent { background-color: var(--c-accent); }
|
||||||
|
`}</style>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// --- UI Komponenten ---
|
||||||
|
const CommandItem = ({ command }) => {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const copyCode = () => {
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
navigator.clipboard.writeText(command.code).then(() => {
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const textArea = document.createElement("textarea");
|
||||||
|
textArea.value = command.code;
|
||||||
|
textArea.style.position = "fixed";
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
} catch (err) { }
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-4 last:mb-0">
|
||||||
|
<div className="flex items-center justify-between mb-1" style={{ backgroundColor: 'var(--c-bg-panel-light)', border: '1px solid var(--wm-med)', padding: '4px 8px' }}>
|
||||||
|
<p className="text-xs flex items-center gap-2" style={{ color: 'var(--c-text-muted)' }}>
|
||||||
|
<span className="w-2 h-2 bg-accent inline-block"></span>
|
||||||
|
<span dangerouslySetInnerHTML={{ __html: command.text }}></span>
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={copyCode}
|
||||||
|
className={`wm-btn text-[10px] py-0 px-2 ml-4 ${copied ? 'wm-btn-success' : ''}`}
|
||||||
|
>
|
||||||
|
{copied ? 'KOPIERT' : 'COPY'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 overflow-x-auto custom-scrollbar" style={{ backgroundColor: 'black', border: '2px solid var(--wm-med)' }}>
|
||||||
|
<pre className="text-xs font-mono text-accent">
|
||||||
|
<code>{command.code}</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Checkbox = ({ label, checked, onChange }) => (
|
||||||
|
<label className="flex items-start space-x-3 cursor-pointer group mb-3">
|
||||||
|
<div className="mt-0.5">
|
||||||
|
<input type="checkbox" checked={checked} onChange={onChange} className="hidden" />
|
||||||
|
<div className={`wm-checkbox-inner ${checked ? 'checked' : ''}`}>
|
||||||
|
{checked && (
|
||||||
|
<svg className="w-3.5 h-3.5" style={{color: '#fff'}} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="none" strokeLinejoin="miter" strokeWidth="3" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-sm font-bold uppercase" style={{ color: 'var(--c-text-main)' }}>{label}</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
|
||||||
|
const CollapsibleNavGroup = ({ title, sectionId, collapsed, onToggle, children }) => (
|
||||||
|
<div className="mb-4 wm-block" style={{ backgroundColor: 'var(--c-bg-panel-light)' }}>
|
||||||
|
<div
|
||||||
|
className="wm-titlebar px-3 py-2 flex items-center justify-between cursor-pointer select-none"
|
||||||
|
onClick={() => onToggle(sectionId)}
|
||||||
|
>
|
||||||
|
<h4 className="text-xs font-bold uppercase tracking-widest text-accent">{title}</h4>
|
||||||
|
<div className={`wm-toggle-box ${collapsed ? '' : 'wm-btn-active-state'}`}>
|
||||||
|
<span className="text-[10px] text-black font-bold leading-none mt-0.5">{collapsed ? '+' : '-'}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!collapsed && (
|
||||||
|
<div className="p-3" style={{ borderTop: '1px solid var(--wm-med)' }}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const buildInitialConfig = () => {
|
||||||
|
const initConf = {};
|
||||||
|
setupConfigJSON.forEach(section => {
|
||||||
|
section.items.forEach(item => {
|
||||||
|
if (item.type === 'checkbox' || item.type === 'checkbox_with_select') {
|
||||||
|
initConf[item.id] = item.defaultChecked;
|
||||||
|
if (item.type === 'checkbox_with_select') {
|
||||||
|
initConf[item.selectId] = item.selectDefault;
|
||||||
|
}
|
||||||
|
} else if (item.type === 'select') {
|
||||||
|
initConf[item.id] = item.defaultSelect;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return initConf;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildInitialCollapsed = () => {
|
||||||
|
const initColl = {};
|
||||||
|
setupConfigJSON.forEach(section => { initColl[section.id] = false; });
|
||||||
|
return initColl;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [config, setConfig] = useState(() => {
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem('wmaker-config-state');
|
||||||
|
if (saved) return JSON.parse(saved);
|
||||||
|
} catch (e) {}
|
||||||
|
return buildInitialConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
const [collapsed, setCollapsed] = useState(() => {
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem('wmaker-nav-collapsed');
|
||||||
|
if (saved) return JSON.parse(saved);
|
||||||
|
} catch (e) {}
|
||||||
|
return buildInitialCollapsed();
|
||||||
|
});
|
||||||
|
|
||||||
|
const [copiedAll, setCopiedAll] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => { localStorage.setItem('wmaker-nav-collapsed', JSON.stringify(collapsed)); }, [collapsed]);
|
||||||
|
useEffect(() => { localStorage.setItem('wmaker-config-state', JSON.stringify(config)); }, [config]);
|
||||||
|
|
||||||
|
const handleConfigToggle = (key) => setConfig(prev => ({ ...prev, [key]: !prev[key] }));
|
||||||
|
const handleConfigSelect = (key, val) => setConfig(prev => ({ ...prev, [key]: val }));
|
||||||
|
const handleCollapseToggle = (sectionId) => setCollapsed(prev => ({ ...prev, [sectionId]: !prev[sectionId] }));
|
||||||
|
|
||||||
|
// ==============================================================================
|
||||||
|
// PIPELINE ENGINE
|
||||||
|
// ==============================================================================
|
||||||
|
const getVisibleSteps = () => {
|
||||||
|
const steps = [];
|
||||||
|
|
||||||
|
setupConfigJSON.forEach(section => {
|
||||||
|
if (collapsed[section.id]) return;
|
||||||
|
|
||||||
|
section.items.forEach(item => {
|
||||||
|
let finalCommands = [];
|
||||||
|
let displayTitle = item.label;
|
||||||
|
|
||||||
|
if (item.type === 'checkbox' && config[item.id]) {
|
||||||
|
finalCommands = item.commands;
|
||||||
|
}
|
||||||
|
else if (item.type === 'select' && config[item.id] !== 'none') {
|
||||||
|
const activeOption = item.options.find(o => o.value === config[item.id]);
|
||||||
|
displayTitle = `${item.label} (${activeOption.label})`;
|
||||||
|
finalCommands = item.commandTemplate.map(c => ({
|
||||||
|
text: c.text, code: c.code.replaceAll('{{meta}}', activeOption.meta || '')
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else if (item.type === 'checkbox_with_select' && config[item.id]) {
|
||||||
|
const selVal = config[item.selectId];
|
||||||
|
const activeOption = item.options.find(o => o.value === selVal);
|
||||||
|
displayTitle = `${item.label} (${activeOption.label})`;
|
||||||
|
|
||||||
|
if (item.commandsByValue) {
|
||||||
|
finalCommands = item.commandsByValue[selVal];
|
||||||
|
} else if (item.commandTemplate) {
|
||||||
|
finalCommands = item.commandTemplate.map(c => ({
|
||||||
|
text: c.text, code: c.code.replaceAll('{{selectValue}}', selVal)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalCommands && finalCommands.length > 0) {
|
||||||
|
steps.push({
|
||||||
|
title: displayTitle,
|
||||||
|
commands: finalCommands
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return steps;
|
||||||
|
};
|
||||||
|
|
||||||
|
const visibleSteps = getVisibleSteps();
|
||||||
|
|
||||||
|
const copyFullScript = () => {
|
||||||
|
if(visibleSteps.length === 0) return;
|
||||||
|
|
||||||
|
let text = `#!/bin/bash\n# Debian 13 Bootstrap Skript\n# Generiert durch den JSON Setup-Builder\n\n`;
|
||||||
|
|
||||||
|
text += `# ==========================================\n`;
|
||||||
|
text += `# Init & Root-Check\n`;
|
||||||
|
text += `# ==========================================\n`;
|
||||||
|
text += `set -e\n`;
|
||||||
|
text += `if [ "$EUID" -ne 0 ]; then\n echo "Bitte als root ausführen (z.B. sudo ./bootstrap.sh)"\n exit 1\nfi\n\n`;
|
||||||
|
|
||||||
|
visibleSteps.forEach(s => {
|
||||||
|
text += `# ==========================================\n`;
|
||||||
|
text += `# ${s.title}\n`;
|
||||||
|
text += `# ==========================================\n`;
|
||||||
|
s.commands.forEach(c => {
|
||||||
|
text += `# ${c.text}\n${c.code}\n\n`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
navigator.clipboard.writeText(text).then(() => showCopiedAll());
|
||||||
|
} else {
|
||||||
|
const textArea = document.createElement("textarea");
|
||||||
|
textArea.value = text;
|
||||||
|
textArea.style.position = "fixed";
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
showCopiedAll();
|
||||||
|
} catch (err) { }
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const showCopiedAll = () => {
|
||||||
|
setCopiedAll(true);
|
||||||
|
setTimeout(() => setCopiedAll(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderNavItem = (item) => {
|
||||||
|
if (item.type === 'checkbox') {
|
||||||
|
return <Checkbox key={item.id} label={item.label} checked={config[item.id]} onChange={() => handleConfigToggle(item.id)} />;
|
||||||
|
}
|
||||||
|
else if (item.type === 'select') {
|
||||||
|
return (
|
||||||
|
<div key={item.id} className="mt-3 mb-3 ml-8 pl-3" style={{ borderLeft: '2px solid var(--wm-med)' }}>
|
||||||
|
<label className="text-[10px] font-bold block mb-1 uppercase" style={{ color: 'var(--c-text-muted)' }}>{item.label}</label>
|
||||||
|
<select
|
||||||
|
value={config[item.id]}
|
||||||
|
onChange={(e) => handleConfigSelect(item.id, e.target.value)}
|
||||||
|
className="wm-input w-full p-1"
|
||||||
|
>
|
||||||
|
{item.options.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (item.type === 'checkbox_with_select') {
|
||||||
|
return (
|
||||||
|
<div key={item.id}>
|
||||||
|
<Checkbox label={item.label} checked={config[item.id]} onChange={() => handleConfigToggle(item.id)} />
|
||||||
|
{config[item.id] && (
|
||||||
|
<div className="ml-8 mt-1 mb-3 flex items-center gap-2 pl-3" style={{ borderLeft: '2px solid var(--wm-med)' }}>
|
||||||
|
<label className="text-[10px] uppercase font-bold" style={{ color: 'var(--c-text-muted)' }}>{item.selectLabel}</label>
|
||||||
|
<select
|
||||||
|
value={config[item.selectId]}
|
||||||
|
onChange={(e) => handleConfigSelect(item.selectId, e.target.value)}
|
||||||
|
className="wm-input flex-1 p-1"
|
||||||
|
>
|
||||||
|
{item.options.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DynamicStyles />
|
||||||
|
<div className="flex flex-col h-full w-full p-4 gap-4" style={{ backgroundColor: 'var(--c-bg-app)' }}>
|
||||||
|
{/* Header mit dynamischem Image Background */}
|
||||||
|
<header className="wm-block wm-titlebar wm-header-bg p-3 flex justify-between items-center shrink-0 z-20">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-6 h-6 bg-accent flex items-center justify-center" style={{ border: '2px solid', borderTopColor: 'var(--c-accent-hover)', borderLeftColor: 'var(--c-accent-hover)', borderBottomColor: 'var(--wm-dark)', borderRightColor: 'var(--wm-dark)' }}>
|
||||||
|
<span className="text-black font-bold text-xs">{uiConfigJSON.text.logoLetter}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-lg font-bold tracking-wider" style={{ color: 'var(--c-text-main)', textShadow: '2px 2px 0px rgba(0,0,0,0.8)' }}>
|
||||||
|
{uiConfigJSON.text.appTitle}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={copyFullScript}
|
||||||
|
className={`wm-btn ${copiedAll ? 'wm-btn-success' : ''} ${visibleSteps.length === 0 ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||||
|
disabled={visibleSteps.length === 0}
|
||||||
|
>
|
||||||
|
{copiedAll ? uiConfigJSON.text.btnCopySuccess : uiConfigJSON.text.btnCopyDefault}
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Main Content Area */}
|
||||||
|
<div className="flex flex-1 overflow-hidden gap-4">
|
||||||
|
|
||||||
|
{/* Sidebar: Nav-Gruppe */}
|
||||||
|
<div className="w-1/3 flex flex-col shrink-0">
|
||||||
|
<div className="wm-block flex flex-col h-full">
|
||||||
|
<div className="wm-titlebar px-3 py-2 flex items-center justify-between shrink-0">
|
||||||
|
<h3 className="font-bold uppercase tracking-wide" style={{ color: 'var(--c-text-main)' }}>{uiConfigJSON.text.navTitle}</h3>
|
||||||
|
<div className="w-4 h-4" style={{ border: '2px solid', borderTopColor: 'var(--wm-light)', borderLeftColor: 'var(--wm-light)', borderBottomColor: 'var(--wm-dark)', borderRightColor: 'var(--wm-dark)', backgroundColor: 'var(--wm-med)' }}></div>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 flex-1 overflow-y-auto custom-scrollbar" style={{ backgroundColor: 'var(--c-bg-panel-dark)' }}>
|
||||||
|
|
||||||
|
{setupConfigJSON.map(section => (
|
||||||
|
<CollapsibleNavGroup
|
||||||
|
key={section.id}
|
||||||
|
title={section.title}
|
||||||
|
sectionId={section.id}
|
||||||
|
collapsed={collapsed[section.id]}
|
||||||
|
onToggle={handleCollapseToggle}
|
||||||
|
>
|
||||||
|
{section.items.map(item => renderNavItem(item))}
|
||||||
|
</CollapsibleNavGroup>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Preview Panel: Pipeline */}
|
||||||
|
<div className="flex-1 flex flex-col min-w-0">
|
||||||
|
<div className="wm-block flex flex-col h-full">
|
||||||
|
<div className="wm-titlebar px-3 py-2 flex items-center justify-between shrink-0">
|
||||||
|
<h3 className="font-bold uppercase tracking-wide" style={{ color: 'var(--c-text-main)' }}>{uiConfigJSON.text.pipelineTitle}</h3>
|
||||||
|
<div className="w-4 h-4" style={{ border: '2px solid', borderTopColor: 'var(--wm-light)', borderLeftColor: 'var(--wm-light)', borderBottomColor: 'var(--wm-dark)', borderRightColor: 'var(--wm-dark)', backgroundColor: 'var(--wm-med)' }}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto custom-scrollbar p-6" style={{ backgroundColor: 'var(--c-bg-app)' }}>
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
|
||||||
|
{visibleSteps.length === 0 ? (
|
||||||
|
<div className="flex flex-col items-center justify-center h-[50vh] space-y-4" style={{ color: 'var(--wm-med)' }}>
|
||||||
|
<div className="w-16 h-16 border-4 border-dashed flex items-center justify-center" style={{ borderColor: 'var(--wm-med)' }}>
|
||||||
|
<span className="text-2xl font-bold">?</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-bold uppercase tracking-widest text-center">
|
||||||
|
{uiConfigJSON.text.emptyStateTitle}<br/>
|
||||||
|
<span className="text-xs mt-2 block" style={{ color: 'var(--c-text-muted)' }}>{uiConfigJSON.text.emptyStateSub}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p className="text-xs mb-8 uppercase tracking-widest pb-2 text-accent" style={{ borderBottom: '1px solid var(--c-accent-dark)' }}>
|
||||||
|
{uiConfigJSON.text.logMessage.replace('{{count}}', visibleSteps.length)}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{visibleSteps.map((step, index) => (
|
||||||
|
<div key={index} className="mb-10 last:mb-0">
|
||||||
|
<div className="flex items-center gap-3 mb-3">
|
||||||
|
<span className="text-accent w-6 h-6 flex items-center justify-center font-bold text-xs" style={{ backgroundColor: 'var(--c-bg-panel-dark)', border: '1px solid var(--wm-med)' }}>
|
||||||
|
{index + 1}
|
||||||
|
</span>
|
||||||
|
<h3 className="font-bold uppercase tracking-wide text-base" style={{ color: 'var(--c-text-main)' }}>{step.title}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pl-9">
|
||||||
|
{step.commands.map((cmd, idx) => (
|
||||||
|
<CommandItem key={idx} command={cmd} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
|
root.render(<App />);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+18
-4
@@ -2,9 +2,23 @@
|
|||||||
:toc:
|
:toc:
|
||||||
|
|
||||||
// 1. Grundlagen
|
// 1. Grundlagen
|
||||||
include::01_Grundlagen/01_ressourcen_paradoxon.adoc[]
|
include::01_Grundlagen/01_warum_nur_los.adoc[]
|
||||||
include::01_Grundlagen/02_himmelsmechanik.adoc[]
|
|
||||||
|
|
||||||
// 2. Strukturen
|
// 2. Strukturen
|
||||||
include::02_Strukturen/01_hauptmaschinen.adoc[]
|
include::02_Strukturen/01_der_innere_kern.adoc[]
|
||||||
include::02_Strukturen/02_ernte_schwarm.adoc[]
|
|
||||||
|
// 03. Charaktere
|
||||||
|
include::03_Charaktere/01_miner_klasse.adoc[]
|
||||||
|
|
||||||
|
// 4. Kult
|
||||||
|
include::04_Kult/01_fantasy_tech.adoc[]
|
||||||
|
|
||||||
|
// 5. Tech
|
||||||
|
include::05_Tech/01_plasma_trichter.adoc[]
|
||||||
|
|
||||||
|
// 6. Strukturen
|
||||||
|
include::06_Regel/01_nina_protokoll.adoc[]
|
||||||
|
|
||||||
|
// Nerds reding to the end also like to do this next
|
||||||
|
include::00_aMeta/rawDocZ/01_neovim_setup.adoc[]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user