import { api } from "./api/client.js"; import { renderAppShell } from "./components/shell.js"; import { renderLoginPage } from "./pages/login.js"; import "./styles/main.css"; const state = { user: null, error: "", tables: [], activeTable: "", activeTab: "data", page: "overview", rows: { rows: [], total: 0 }, tableDetails: { columns: [], foreignKeys: [] }, users: [], roles: [], auditLogs: [], postgresLogs: [], sqlDraft: "", sqlResult: null, search: "" }; const app = document.querySelector("#app"); bootstrap(); async function bootstrap() { try { const { user } = await api.me(); state.user = user; if (user) { await hydrateDashboard(); } } catch { state.user = null; } render(); } async function hydrateDashboard() { const [tablesPayload, usersPayload, rolesPayload, auditPayload, logsPayload] = await Promise.all([ api.tables(), api.users(), api.roles(), api.audit(), api.postgresLogs() ]); state.tables = tablesPayload.tables; state.users = usersPayload.users; state.roles = rolesPayload.roles; state.auditLogs = auditPayload.logs; state.postgresLogs = logsPayload.logs; if (!state.activeTable && state.tables[0]) { state.activeTable = state.tables[0].table_name; } if (state.activeTable) { await Promise.all([loadRows(), loadTableDetails()]); } } async function loadRows() { if (!state.activeTable) { return; } state.rows = await api.rows(state.activeTable, { page: 1, pageSize: 25, search: state.search }); } async function loadTableDetails() { if (!state.activeTable) { return; } state.tableDetails = await api.tableDetails(state.activeTable); } function render() { app.innerHTML = state.user ? renderAppShell(state) : renderLoginPage(state); bindEvents(); } function bindEvents() { const loginForm = document.querySelector("#loginForm"); if (loginForm) { loginForm.addEventListener("submit", handleLogin); } const logoutButton = document.querySelector("#logoutButton"); if (logoutButton) { logoutButton.addEventListener("click", handleLogout); } document.querySelectorAll("[data-page]").forEach((element) => { element.addEventListener("click", async (event) => { state.page = event.currentTarget.dataset.page; render(); }); }); document.querySelectorAll("[data-tab]").forEach((element) => { element.addEventListener("click", async (event) => { state.activeTab = event.currentTarget.dataset.tab; render(); }); }); document.querySelectorAll("[data-table]").forEach((element) => { element.addEventListener("click", async (event) => { state.activeTable = event.currentTarget.dataset.table; state.page = "overview"; state.sqlDraft = `select * from ${state.activeTable} limit 20;`; await Promise.all([loadRows(), loadTableDetails()]); render(); }); }); document.querySelectorAll("[data-action]").forEach((element) => { element.addEventListener("click", handleAction); }); } async function handleLogin(event) { event.preventDefault(); const formData = new FormData(event.currentTarget); state.error = ""; try { const payload = await api.login({ username: formData.get("username"), password: formData.get("password") }); state.user = payload.user; await hydrateDashboard(); } catch (error) { state.error = error.message; } render(); } async function handleLogout() { await api.logout(); state.user = null; state.error = ""; render(); } async function handleAction(event) { const action = event.currentTarget.dataset.action; if (action === "refresh") { await hydrateDashboard(); } if (action === "search") { state.search = document.querySelector("#tableSearchInput")?.value || ""; await loadRows(); } if (action === "run-sql") { state.sqlDraft = document.querySelector("#sqlEditor")?.value || ""; state.sqlResult = await api.executeSql(state.sqlDraft); } render(); }