318 lines
9.6 KiB
JavaScript
318 lines
9.6 KiB
JavaScript
const state = {
|
|
config: null,
|
|
selected: 0,
|
|
view: "servers",
|
|
query: "",
|
|
capabilities: null,
|
|
};
|
|
|
|
const els = {
|
|
navItems: document.querySelectorAll(".nav-item"),
|
|
views: {
|
|
servers: document.querySelector("#serversView"),
|
|
commands: document.querySelector("#commandsView"),
|
|
settings: document.querySelector("#settingsView"),
|
|
},
|
|
viewTitle: document.querySelector("#viewTitle"),
|
|
terminalStatus: document.querySelector("#terminalStatus"),
|
|
searchInput: document.querySelector("#searchInput"),
|
|
serverGrid: document.querySelector("#serverGrid"),
|
|
serverForm: document.querySelector("#serverForm"),
|
|
formTitle: document.querySelector("#formTitle"),
|
|
deleteButton: document.querySelector("#deleteButton"),
|
|
connectButton: document.querySelector("#connectButton"),
|
|
addButton: document.querySelector("#addButton"),
|
|
reloadButton: document.querySelector("#reloadButton"),
|
|
sshButton: document.querySelector("#sshButton"),
|
|
sshCommand: document.querySelector("#sshCommand"),
|
|
commandsList: document.querySelector("#commandsList"),
|
|
settingsForm: document.querySelector("#settingsForm"),
|
|
toast: document.querySelector("#toast"),
|
|
};
|
|
|
|
async function loadConfig() {
|
|
const [configResponse, capabilitiesResponse] = await Promise.all([
|
|
fetch("/api/config"),
|
|
fetch("/api/capabilities"),
|
|
]);
|
|
|
|
const response = configResponse;
|
|
if (!response.ok) throw new Error("Config konnte nicht geladen werden");
|
|
state.config = await response.json();
|
|
state.capabilities = capabilitiesResponse.ok ? await capabilitiesResponse.json() : null;
|
|
state.selected = Math.min(state.selected, Math.max(0, state.config.servers.length - 1));
|
|
render();
|
|
}
|
|
|
|
async function saveConfig(message = "Gespeichert") {
|
|
const response = await fetch("/api/config", {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(state.config),
|
|
});
|
|
|
|
if (!response.ok) throw new Error("Config konnte nicht gespeichert werden");
|
|
state.config = await response.json();
|
|
showToast(message);
|
|
render();
|
|
}
|
|
|
|
function render() {
|
|
renderNavigation();
|
|
renderServers();
|
|
renderServerForm();
|
|
renderCommands();
|
|
renderSettings();
|
|
}
|
|
|
|
function renderNavigation() {
|
|
els.navItems.forEach((item) => {
|
|
item.classList.toggle("active", item.dataset.view === state.view);
|
|
});
|
|
|
|
Object.entries(els.views).forEach(([name, node]) => {
|
|
node.classList.toggle("active", name === state.view);
|
|
});
|
|
|
|
const titles = {
|
|
servers: "Server",
|
|
commands: "Quick Commands",
|
|
settings: "Settings",
|
|
};
|
|
els.viewTitle.textContent = titles[state.view];
|
|
|
|
if (state.capabilities?.terminal_available) {
|
|
els.terminalStatus.textContent = `Terminal: ${state.capabilities.terminal}`;
|
|
} else {
|
|
els.terminalStatus.textContent = "Kein unterstützter Terminal-Emulator gefunden";
|
|
}
|
|
}
|
|
|
|
function filteredServers() {
|
|
const query = state.query.trim().toLowerCase();
|
|
const servers = state.config?.servers ?? [];
|
|
if (!query) return servers.map((server, index) => ({ server, index }));
|
|
|
|
return servers
|
|
.map((server, index) => ({ server, index }))
|
|
.filter(({ server }) => {
|
|
return [server.name, server.host, server.user, server.group, server.auth]
|
|
.join(" ")
|
|
.toLowerCase()
|
|
.includes(query);
|
|
});
|
|
}
|
|
|
|
function renderServers() {
|
|
const items = filteredServers();
|
|
els.serverGrid.innerHTML = "";
|
|
|
|
if (items.length === 0) {
|
|
els.serverGrid.innerHTML = `<p class="server-meta">Keine Server gefunden.</p>`;
|
|
return;
|
|
}
|
|
|
|
for (const { server, index } of items) {
|
|
const card = document.createElement("article");
|
|
card.className = "server-card";
|
|
card.classList.toggle("active", index === state.selected);
|
|
card.innerHTML = `
|
|
<h3>${escapeHTML(server.name || "Unbenannt")}</h3>
|
|
<p class="server-meta">${escapeHTML(server.user || "")}@${escapeHTML(server.host || "")}:${server.port || 22}</p>
|
|
<div class="pill-row">
|
|
<span class="pill">${escapeHTML(server.group || "Keine Gruppe")}</span>
|
|
<span class="pill">${escapeHTML(server.auth || "key")}</span>
|
|
${server.kitty_fix ? `<span class="pill">kitty</span>` : ""}
|
|
</div>
|
|
`;
|
|
card.addEventListener("click", () => {
|
|
state.selected = index;
|
|
els.sshCommand.hidden = true;
|
|
render();
|
|
});
|
|
els.serverGrid.append(card);
|
|
}
|
|
}
|
|
|
|
function renderServerForm() {
|
|
const server = state.config?.servers?.[state.selected];
|
|
const disabled = !server;
|
|
els.serverForm.querySelectorAll("input, select, button").forEach((input) => {
|
|
input.disabled = disabled;
|
|
});
|
|
els.connectButton.disabled = disabled || !state.capabilities?.terminal_available;
|
|
|
|
if (!server) {
|
|
els.formTitle.textContent = "Kein Server ausgewählt";
|
|
els.serverForm.reset();
|
|
return;
|
|
}
|
|
|
|
els.formTitle.textContent = server.name ? `${server.name} bearbeiten` : "Server bearbeiten";
|
|
setFormValue("name", server.name);
|
|
setFormValue("host", server.host);
|
|
setFormValue("user", server.user);
|
|
setFormValue("port", server.port || 22);
|
|
setFormValue("group", server.group);
|
|
setFormValue("auth", server.auth || "key");
|
|
setFormValue("key", server.key);
|
|
setFormValue("password_id", server.password_id);
|
|
els.serverForm.elements.kitty_fix.checked = Boolean(server.kitty_fix);
|
|
}
|
|
|
|
function renderCommands() {
|
|
const commands = state.config?.quick_commands ?? [];
|
|
els.commandsList.innerHTML = "";
|
|
|
|
if (commands.length === 0) {
|
|
els.commandsList.innerHTML = `<p class="server-meta">Keine Quick Commands konfiguriert.</p>`;
|
|
return;
|
|
}
|
|
|
|
for (const command of commands) {
|
|
const item = document.createElement("article");
|
|
item.className = "command-item";
|
|
item.innerHTML = `
|
|
<h3>${escapeHTML(command.name)}</h3>
|
|
<code>${escapeHTML(command.command)}</code>
|
|
`;
|
|
els.commandsList.append(item);
|
|
}
|
|
}
|
|
|
|
function renderSettings() {
|
|
const settings = state.config?.settings;
|
|
if (!settings) return;
|
|
|
|
els.settingsForm.elements.theme.value = settings.theme || "neon-green";
|
|
els.settingsForm.elements.term.value = settings.terminal?.term || "xterm-256color";
|
|
els.settingsForm.elements.enable_kitty_fix.checked = Boolean(settings.terminal?.enable_kitty_fix);
|
|
}
|
|
|
|
function addServer() {
|
|
state.config.servers.push({
|
|
name: "Neuer Server",
|
|
host: "",
|
|
user: "root",
|
|
port: 22,
|
|
group: "Homelab",
|
|
auth: "key",
|
|
key: "",
|
|
password_id: "",
|
|
kitty_fix: true,
|
|
});
|
|
state.selected = state.config.servers.length - 1;
|
|
state.view = "servers";
|
|
render();
|
|
}
|
|
|
|
function collectServerForm() {
|
|
const form = els.serverForm.elements;
|
|
return {
|
|
name: form.name.value.trim(),
|
|
host: form.host.value.trim(),
|
|
user: form.user.value.trim(),
|
|
port: Number(form.port.value) || 22,
|
|
group: form.group.value.trim(),
|
|
auth: form.auth.value,
|
|
key: form.key.value.trim(),
|
|
password_id: form.password_id.value.trim(),
|
|
kitty_fix: form.kitty_fix.checked,
|
|
};
|
|
}
|
|
|
|
function setFormValue(name, value) {
|
|
els.serverForm.elements[name].value = value ?? "";
|
|
}
|
|
|
|
async function showSSHCommand() {
|
|
const response = await fetch(`/api/ssh-command/${state.selected}`, { method: "POST" });
|
|
if (!response.ok) throw new Error("SSH Befehl konnte nicht erzeugt werden");
|
|
|
|
const data = await response.json();
|
|
els.sshCommand.textContent = data.command;
|
|
els.sshCommand.hidden = false;
|
|
}
|
|
|
|
async function connectSSH() {
|
|
const response = await fetch(`/api/connect/${state.selected}`, { method: "POST" });
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.error || "SSH Verbindung konnte nicht gestartet werden");
|
|
}
|
|
|
|
els.sshCommand.textContent = data.command;
|
|
els.sshCommand.hidden = false;
|
|
showToast("SSH Terminal gestartet");
|
|
}
|
|
|
|
function showToast(message) {
|
|
els.toast.textContent = message;
|
|
els.toast.hidden = false;
|
|
window.setTimeout(() => {
|
|
els.toast.hidden = true;
|
|
}, 2200);
|
|
}
|
|
|
|
function escapeHTML(value) {
|
|
return String(value ?? "")
|
|
.replaceAll("&", "&")
|
|
.replaceAll("<", "<")
|
|
.replaceAll(">", ">")
|
|
.replaceAll('"', """)
|
|
.replaceAll("'", "'");
|
|
}
|
|
|
|
els.navItems.forEach((item) => {
|
|
item.addEventListener("click", () => {
|
|
state.view = item.dataset.view;
|
|
render();
|
|
});
|
|
});
|
|
|
|
els.searchInput.addEventListener("input", (event) => {
|
|
state.query = event.target.value;
|
|
renderServers();
|
|
});
|
|
|
|
els.addButton.addEventListener("click", addServer);
|
|
els.reloadButton.addEventListener("click", () => loadConfig().then(() => showToast("Neu geladen")).catch((error) => showToast(error.message)));
|
|
els.sshButton.addEventListener("click", () => showSSHCommand().catch((error) => showToast(error.message)));
|
|
els.connectButton.addEventListener("click", () => connectSSH().catch((error) => showToast(error.message)));
|
|
|
|
els.deleteButton.addEventListener("click", async () => {
|
|
if (!state.config.servers[state.selected]) return;
|
|
state.config.servers.splice(state.selected, 1);
|
|
state.selected = Math.max(0, state.selected - 1);
|
|
await saveConfig("Server gelöscht");
|
|
});
|
|
|
|
els.serverForm.addEventListener("submit", async (event) => {
|
|
event.preventDefault();
|
|
const server = collectServerForm();
|
|
|
|
if (!server.name || !server.host || !server.user) {
|
|
showToast("Name, Host und User sind Pflichtfelder");
|
|
return;
|
|
}
|
|
|
|
state.config.servers[state.selected] = server;
|
|
await saveConfig("Server gespeichert");
|
|
});
|
|
|
|
els.settingsForm.addEventListener("submit", async (event) => {
|
|
event.preventDefault();
|
|
const form = els.settingsForm.elements;
|
|
state.config.settings = {
|
|
theme: form.theme.value.trim() || "neon-green",
|
|
terminal: {
|
|
term: form.term.value.trim() || "xterm-256color",
|
|
enable_kitty_fix: form.enable_kitty_fix.checked,
|
|
},
|
|
};
|
|
await saveConfig("Settings gespeichert");
|
|
});
|
|
|
|
loadConfig().catch((error) => showToast(error.message));
|