Frontend basics

This commit is contained in:
Piotr Dec 2025-10-16 23:07:06 +02:00
parent 8565ce19fe
commit e310930d9e
Signed by: stawros
GPG key ID: 74B18A3F0F1E99C0
10 changed files with 607 additions and 58 deletions

187
app/static/js/app.js Normal file
View file

@ -0,0 +1,187 @@
let selectedNode = { type: "group", path: "/" }; // type: "group" | "entry"
let currentKind = "simple";
const treeDiv = document.getElementById("tree");
const currentPathEl = document.getElementById("current-path");
const editorSimple = document.getElementById("editor-simple");
const editorComplex = document.getElementById("editor-complex");
const kindSelect = document.getElementById("entry-kind");
function showEditor(kind) {
editorSimple.style.display = (kind === "simple") ? "block" : "none";
editorComplex.style.display = (kind === "complex") ? "block" : "none";
}
kindSelect.addEventListener("change", () => {
currentKind = kindSelect.value;
showEditor(currentKind);
});
async function api(path, opts = {}) {
const resp = await fetch(path, opts);
if (!resp.ok) {
const txt = await resp.text();
throw new Error("API error: " + txt);
}
if (resp.headers.get("Content-Type")?.includes("application/json")) {
return resp.json();
}
return null;
}
function renderTree(node) {
const ul = document.createElement("ul");
const mkEntryLi = (e) => {
const li = document.createElement("li");
li.textContent = "🔑 " + e.title;
li.className = "node";
li.onclick = () => {
selectedNode = { type: "entry", path: e.path, kind: e.kind };
currentPathEl.textContent = e.path;
kindSelect.value = e.kind;
showEditor(e.kind);
if (e.kind === "simple") {
document.getElementById("s-key").value = e.title;
document.getElementById("s-value").value = "";
} else {
document.getElementById("c-title").value = e.title;
document.getElementById("c-username").value = "";
document.getElementById("c-password").value = "";
document.getElementById("c-url").value = "";
document.getElementById("c-notes").value = "";
}
};
return li;
};
const mkGroupLi = (g) => {
const li = document.createElement("li");
const header = document.createElement("div");
header.textContent = "📂 " + (g.name || "/");
header.className = "node";
header.onclick = () => {
selectedNode = { type: "group", path: g.path };
currentPathEl.textContent = g.path;
};
li.appendChild(header);
const inner = renderTreeChildren(g);
li.appendChild(inner);
return li;
};
function renderTreeChildren(node) {
const wrap = document.createElement("ul");
node.groups.forEach(sg => wrap.appendChild(mkGroupLi(sg)));
node.entries.forEach(e => wrap.appendChild(mkEntryLi(e)));
return wrap;
}
ul.appendChild(mkGroupLi(node));
treeDiv.innerHTML = "";
treeDiv.appendChild(ul);
}
async function refreshTree() {
const data = await api("/api/v1/tree");
renderTree(data);
}
// Toolbar lewej kolumny
document.getElementById("btn-refresh").onclick = refreshTree;
document.getElementById("btn-add-group").onclick = async () => {
const name = prompt("Nazwa nowej grupy:");
if (!name) return;
await api("/api/v1/group", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ name, parent_path: selectedNode.type === "group" ? selectedNode.path : "/" })
});
await refreshTree();
};
document.getElementById("btn-add-entry").onclick = async () => {
const parent_path = selectedNode.type === "group" ? selectedNode.path : "/";
const kind = kindSelect.value;
if (kind === "simple") {
const key = prompt("Klucz (title):"); if (!key) return;
const value = prompt("Wartość (password):"); if (value === null) return;
await api("/api/v1/entry", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ kind, parent_path, data: { key, value } })
});
} else {
const title = prompt("Tytuł:"); if (!title) return;
await api("/api/v1/entry", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ kind, parent_path, data: { title, username:"", password:"", url:"", notes:"" } })
});
}
await refreshTree();
};
// Edytory
document.getElementById("s-save").onclick = async () => {
if (selectedNode.type !== "entry") { alert("Wybierz wpis"); return; }
const data = { key: document.getElementById("s-key").value, value: document.getElementById("s-value").value };
await api("/api/v1/entry", {
method: "PATCH",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ path: selectedNode.path, kind: "simple", data })
});
await refreshTree();
};
document.getElementById("s-delete").onclick = async () => {
if (selectedNode.type !== "entry") return;
if (!confirm("Usunąć wpis?")) return;
await api("/api/v1/entry?path=" + encodeURIComponent(selectedNode.path), { method: "DELETE" });
await refreshTree();
};
document.getElementById("c-save").onclick = async () => {
if (selectedNode.type !== "entry") { alert("Wybierz wpis"); return; }
const data = {
title: document.getElementById("c-title").value,
username: document.getElementById("c-username").value,
password: document.getElementById("c-password").value,
url: document.getElementById("c-url").value,
notes: document.getElementById("c-notes").value
};
await api("/api/v1/entry", {
method: "PATCH",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ path: selectedNode.path, kind: "complex", data })
});
await refreshTree();
};
document.getElementById("c-delete").onclick = async () => {
if (selectedNode.type !== "entry") return;
if (!confirm("Usunąć wpis?")) return;
await api("/api/v1/entry?path=" + encodeURIComponent(selectedNode.path), { method: "DELETE" });
await refreshTree();
};
document.getElementById("btn-move-entry").onclick = async () => {
if (selectedNode.type !== "entry") { alert("Wybierz wpis"); return; }
const target = document.getElementById("target-group").value || "/";
await api("/api/v1/entry/move", {
method: "PATCH",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ path: selectedNode.path, target_group_path: target })
});
await refreshTree();
};
document.getElementById("btn-save-all").onclick = async () => {
await api("/api/v1/save", { method: "POST" });
alert("Zapisano do bazy");
};
// Start
showEditor(currentKind);
refreshTree().catch(err => console.error(err));