diff --git a/app/main.py b/app/main.py index c0e0c74..4850b5d 100644 --- a/app/main.py +++ b/app/main.py @@ -1,4 +1,7 @@ +from pathlib import Path + from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles from app.auth.router import router as auth_router from app.config import settings @@ -8,3 +11,8 @@ app = FastAPI(title=settings.app_name) app.include_router(auth_router) app.mount("/graphql", graphql_app) +app.mount( + "/admin", + StaticFiles(directory=str(Path(__file__).parent / "static"), html=True), + name="admin", +) diff --git a/app/static/app.js b/app/static/app.js new file mode 100644 index 0000000..236ff8d --- /dev/null +++ b/app/static/app.js @@ -0,0 +1,133 @@ +async function api(path, body) { + const res = await fetch(path, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + const json = await res.json().catch(() => ({})); + if (!res.ok) { + throw new Error(json.detail || res.statusText); + } + return json; +} + +async function gql(query, variables) { + const headers = { "Content-Type": "application/json" }; + const token = localStorage.getItem("access_token"); + if (token) { + headers.Authorization = `Bearer ${token}`; + } + const res = await fetch("/graphql", { + method: "POST", + headers, + body: JSON.stringify({ query, variables }), + }); + const json = await res.json(); + if (json.errors && json.errors.length) { + throw new Error(json.errors[0].message); + } + return json.data; +} + +const CLIENTS_QUERY = ` + query { clients { id clientId name isConfidential redirectUris createdAt } } +`; + +document.addEventListener("DOMContentLoaded", () => { + const loginForm = document.getElementById("login-form"); + const panel = document.getElementById("panel"); + const statusEl = document.getElementById("status"); + const clientsBody = document.getElementById("clients-body"); + const createBtn = document.getElementById("create-client"); + const logoutBtn = document.getElementById("logout"); + + function setStatus(message, ok = false) { + statusEl.textContent = message || ""; + statusEl.classList.toggle("ok", ok); + } + + function setLoggedIn(isLoggedIn) { + loginForm.classList.toggle("hidden", isLoggedIn); + panel.classList.toggle("hidden", !isLoggedIn); + } + + function renderClients(clients) { + clientsBody.textContent = ""; + for (const client of clients) { + const row = document.createElement("tr"); + const cells = [ + client.id, + client.clientId, + client.name, + client.isConfidential ? "yes" : "no", + (client.redirectUris || []).join(", "), + client.createdAt, + ]; + for (const value of cells) { + const td = document.createElement("td"); + td.textContent = value; + row.appendChild(td); + } + clientsBody.appendChild(row); + } + } + + async function loadClients() { + try { + const data = await gql(CLIENTS_QUERY); + renderClients(data.clients); + setStatus(""); + } catch (err) { + renderClients([]); + setStatus(err.message); + } + } + + async function handleLogin(event) { + event.preventDefault(); + setStatus(""); + const email = document.getElementById("email").value; + const password = document.getElementById("password").value; + try { + const { access_token } = await api("/auth/login", { email, password }); + localStorage.setItem("access_token", access_token); + setLoggedIn(true); + await loadClients(); + } catch (err) { + setStatus(err.message); + } + } + + async function handleCreateClient() { + const name = prompt("Название клиента"); + if (!name) { + return; + } + try { + await gql( + "mutation ($name: String!) { createClient(name: $name) { id clientId name } }", + { name } + ); + setStatus(""); + await loadClients(); + } catch (err) { + setStatus(err.message); + } + } + + function handleLogout() { + localStorage.removeItem("access_token"); + setLoggedIn(false); + setStatus(""); + loginForm.reset(); + } + + loginForm.addEventListener("submit", handleLogin); + createBtn.addEventListener("click", handleCreateClient); + logoutBtn.addEventListener("click", handleLogout); + + if (localStorage.getItem("access_token")) { + setLoggedIn(true); + loadClients(); + } +}); diff --git a/app/static/index.html b/app/static/index.html new file mode 100644 index 0000000..023fcd0 --- /dev/null +++ b/app/static/index.html @@ -0,0 +1,142 @@ + + +
+ + +