Files
testpf/frontend/src/main.js
2026-03-19 16:07:35 +07:00

177 lines
4.0 KiB
JavaScript

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();
}