import { html, reactive } from "/assets/vendor/arrow-core.js" import { Modal } from "/assets/components/modal.js" const state = reactive({ keyName: "", selectedKeyName: "", keyValue: "", keys: [], saved: false, editMode: true, message: "", error: "", visible: false, confirmDelete: { visible: false, keyName: "" } }) let clearTimer = null let fadeTimer = null function showMessage(type, text) { clearTimeout(clearTimer) clearTimeout(fadeTimer) state.error = type === "error" ? text : "" state.message = type === "message" ? text : "" state.visible = true clearTimer = setTimeout(() => { state.message = "" state.error = "" }, 5000) fadeTimer = setTimeout(() => { state.visible = false }, 5000) } function isDuplicateName() { return state.keys.some(k => k.name === state.keyName && k.name !== state.selectedKeyName ) } function canSave() { const name = state.keyName const value = state.keyValue if ( value.length < 10 || /\s/.test(name) || /\s/.test(value) ) { return false } if (isDuplicateName()) return false const selected = state.keys.find(k => k.name === state.selectedKeyName) if (!selected) return true const nameChanged = name !== selected.name const valueChanged = value !== selected.value return nameChanged || valueChanged } function selectKey(name) { const selected = state.keys.find(k => k.name === name) if (selected) { state.selectedKeyName = selected.name state.keyName = selected.name state.keyValue = selected.value state.saved = true state.editMode = false } } const util = { req: async (url, opts) => { const response = await fetch(url, opts) return { ok: response.ok, data: await response.json().catch(() => ({})) } } } const api = { path: "/api/tsk_keys", load: async () => { const { ok, data } = await util.req(api.path, { method: "GET" }) if (ok) { state.keys = data } else { showMessage("error", "Failed to load keys...") } }, save: async () => { const name = state.keyName.trim() const value = state.keyValue.trim() if (!canSave()) { showMessage("error", "Invalid input or duplicate name.") return } const updatedKeys = state.keys.filter(k => k.name !== state.selectedKeyName) updatedKeys.push({ name, value }) const { ok, data } = await util.req(api.path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(updatedKeys) }) if (!ok) { showMessage("error", data.error || "Save failed...") return } state.keys = data selectKey(name) showMessage("message", "Saved key!") }, delete: async (name) => { const url = `${api.path}?name=${encodeURIComponent(name)}` const { ok, data } = await util.req(url, { method: "DELETE" }) state.confirmDelete.visible = false; if (!ok) { showMessage("error", "Delete failed...") return } state.keys = data state.keyName = "" state.keyValue = "" state.selectedKeyName = "" showMessage("message", "Deleted key!") }, applyKey: async (name, value) => { const payload = { name, value } const { ok } = await util.req("/api/tsk_key_set", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }) if (!ok) { showMessage("error", "Apply failed...") return } showMessage("message", "Key applied!") } } let hasLoaded = false export function TSKManager() { if (!hasLoaded) { hasLoaded = true api.load() } return html`