All checks were successful
PostgreSQL Compatibility Matrix / PG14 smoke (push) Successful in 7s
PostgreSQL Compatibility Matrix / PG15 smoke (push) Successful in 6s
PostgreSQL Compatibility Matrix / PG16 smoke (push) Successful in 6s
PostgreSQL Compatibility Matrix / PG17 smoke (push) Successful in 7s
PostgreSQL Compatibility Matrix / PG18 smoke (push) Successful in 7s
Introduced a spacer above the admin navigation link to enhance sidebar organization. Updated the styles for the admin navigation button, including hover and active states, to improve clarity and visual feedback when interacting with the button.
162 lines
6.6 KiB
JavaScript
162 lines
6.6 KiB
JavaScript
import React from "react";
|
|
import { NavLink, Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom";
|
|
import { useAuth } from "./state";
|
|
import { LoginPage } from "./pages/LoginPage";
|
|
import { DashboardPage } from "./pages/DashboardPage";
|
|
import { TargetsPage } from "./pages/TargetsPage";
|
|
import { TargetDetailPage } from "./pages/TargetDetailPage";
|
|
import { QueryInsightsPage } from "./pages/QueryInsightsPage";
|
|
import { AlertsPage } from "./pages/AlertsPage";
|
|
import { AdminUsersPage } from "./pages/AdminUsersPage";
|
|
|
|
function Protected({ children }) {
|
|
const { tokens } = useAuth();
|
|
const location = useLocation();
|
|
if (!tokens?.accessToken) return <Navigate to="/login" state={{ from: location.pathname }} replace />;
|
|
return children;
|
|
}
|
|
|
|
function Layout({ children }) {
|
|
const { me, logout, uiMode, setUiMode, alertToasts, dismissAlertToast } = useAuth();
|
|
const navigate = useNavigate();
|
|
const navClass = ({ isActive }) => `nav-btn${isActive ? " active" : ""}`;
|
|
|
|
return (
|
|
<div className="shell">
|
|
<aside className="sidebar">
|
|
<div className="brand">
|
|
<img src="/nexapg-logo.svg" alt="NexaPG" className="brand-logo" />
|
|
<h1>NexaPG</h1>
|
|
</div>
|
|
<nav className="sidebar-nav">
|
|
<NavLink to="/" end className={navClass}>
|
|
<span className="nav-icon" aria-hidden="true">
|
|
<svg viewBox="0 0 24 24">
|
|
<path d="M4 6c0-1.7 3.6-3 8-3s8 1.3 8 3-3.6 3-8 3-8-1.3-8-3zm0 6c0 1.7 3.6 3 8 3s8-1.3 8-3M4 18c0 1.7 3.6 3 8 3s8-1.3 8-3" />
|
|
</svg>
|
|
</span>
|
|
<span className="nav-label">Dashboard</span>
|
|
</NavLink>
|
|
<NavLink to="/targets" className={navClass}>
|
|
<span className="nav-icon" aria-hidden="true">
|
|
<svg viewBox="0 0 24 24">
|
|
<path d="M12 3l8 4.5v9L12 21l-8-4.5v-9L12 3zM12 12l8-4.5M12 12L4 7.5M12 12v9" />
|
|
</svg>
|
|
</span>
|
|
<span className="nav-label">Targets</span>
|
|
</NavLink>
|
|
<NavLink to="/query-insights" className={navClass}>
|
|
<span className="nav-icon" aria-hidden="true">
|
|
<svg viewBox="0 0 24 24">
|
|
<path d="M4 19h16M7 15l3-3 3 2 4-5M18 8h.01" />
|
|
</svg>
|
|
</span>
|
|
<span className="nav-label">Query Insights</span>
|
|
</NavLink>
|
|
<NavLink to="/alerts" className={navClass}>
|
|
<span className="nav-icon" aria-hidden="true">
|
|
<svg viewBox="0 0 24 24">
|
|
<path d="M15 17h5l-1.4-1.4A2 2 0 0 1 18 14.2V10a6 6 0 0 0-12 0v4.2a2 2 0 0 1-.6 1.4L4 17h5m6 0a3 3 0 0 1-6 0" />
|
|
</svg>
|
|
</span>
|
|
<span className="nav-label">Alerts</span>
|
|
</NavLink>
|
|
{me?.role === "admin" && (
|
|
<>
|
|
<div className="sidebar-nav-spacer" aria-hidden="true" />
|
|
<NavLink to="/admin/users" className={({ isActive }) => `nav-btn admin-nav${isActive ? " active" : ""}`}>
|
|
<span className="nav-icon" aria-hidden="true">
|
|
<svg viewBox="0 0 24 24">
|
|
<path d="M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zm-7 8a7 7 0 0 1 14 0" />
|
|
</svg>
|
|
</span>
|
|
<span className="nav-label">Admin</span>
|
|
</NavLink>
|
|
</>
|
|
)}
|
|
</nav>
|
|
<div className="profile">
|
|
<div className="mode-switch-block">
|
|
<div className="mode-switch-label">View Mode</div>
|
|
<button
|
|
className={`mode-toggle ${uiMode === "easy" ? "easy" : "dba"}`}
|
|
onClick={() => setUiMode(uiMode === "easy" ? "dba" : "easy")}
|
|
type="button"
|
|
>
|
|
<span className="mode-pill">Easy</span>
|
|
<span className="mode-pill">DBA</span>
|
|
</button>
|
|
<small>{uiMode === "easy" ? "Simple health guidance" : "Advanced DBA metrics"}</small>
|
|
</div>
|
|
<div>{me?.email}</div>
|
|
<div className="role">{me?.role}</div>
|
|
<button className="logout-btn" onClick={logout}>Logout</button>
|
|
</div>
|
|
</aside>
|
|
<main className="main">
|
|
{children}
|
|
<div className="toast-stack" aria-live="polite" aria-atomic="true">
|
|
{alertToasts.map((toast) => (
|
|
<div key={toast.id} className={`alert-toast ${toast.severity || "warning"}${toast.closing ? " closing" : ""}`}>
|
|
<div className="alert-toast-head">
|
|
<strong>{toast.severity === "alert" ? "New Alert" : "New Warning"}</strong>
|
|
<div className="toast-actions">
|
|
<button
|
|
type="button"
|
|
className="toast-view"
|
|
title="Open in Alerts"
|
|
onClick={() => {
|
|
navigate(`/alerts?open=${encodeURIComponent(toast.alertKey || "")}`);
|
|
dismissAlertToast(toast.id);
|
|
}}
|
|
>
|
|
<span aria-hidden="true">
|
|
<svg viewBox="0 0 24 24" width="13" height="13">
|
|
<path
|
|
d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6zm10 3a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"
|
|
fill="currentColor"
|
|
/>
|
|
</svg>
|
|
</span>
|
|
</button>
|
|
<button type="button" className="toast-close" onClick={() => dismissAlertToast(toast.id)}>
|
|
x
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="alert-toast-title">{toast.title}</div>
|
|
<div className="alert-toast-target">{toast.target}</div>
|
|
<div className="alert-toast-message">{toast.message}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function App() {
|
|
return (
|
|
<Routes>
|
|
<Route path="/login" element={<LoginPage />} />
|
|
<Route
|
|
path="*"
|
|
element={
|
|
<Protected>
|
|
<Layout>
|
|
<Routes>
|
|
<Route path="/" element={<DashboardPage />} />
|
|
<Route path="/targets" element={<TargetsPage />} />
|
|
<Route path="/targets/:id" element={<TargetDetailPage />} />
|
|
<Route path="/query-insights" element={<QueryInsightsPage />} />
|
|
<Route path="/alerts" element={<AlertsPage />} />
|
|
<Route path="/admin/users" element={<AdminUsersPage />} />
|
|
</Routes>
|
|
</Layout>
|
|
</Protected>
|
|
}
|
|
/>
|
|
</Routes>
|
|
);
|
|
}
|