187 lines
6.7 KiB
JavaScript
187 lines
6.7 KiB
JavaScript
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));
|