583682f330
TicketsPage.tsx — список open/closed тикетов + тред сообщений + ответ + закрытие (owner/moderator/support). ObservabilityPage.tsx — вкладки дашбордов Grafana через iframe за auth-гейтом Caddy, URL берётся из GET /api/v1/config (grafana_url), страница просто не показывает дашборды, пока GRAFANA_PUBLIC_URL не задан на сервере. StaffPage.tsx — форма создания support-аккаунта (только Owner). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
67 lines
2.3 KiB
TypeScript
67 lines
2.3 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { getPublicConfig } from "./api";
|
|
|
|
const DASHBOARDS = [
|
|
{ uid: "netrunner-backend", title: "Backend" },
|
|
{ uid: "netrunner-landing", title: "Лендинг" },
|
|
{ uid: "netrunner-proxy-nodes", title: "Прокси-ноды (ядро)" },
|
|
] as const;
|
|
|
|
export default function ObservabilityPage() {
|
|
const [grafanaUrl, setGrafanaUrl] = useState<string | null | undefined>(undefined);
|
|
const [active, setActive] = useState<(typeof DASHBOARDS)[number]["uid"]>(DASHBOARDS[0].uid);
|
|
|
|
useEffect(() => {
|
|
getPublicConfig().then((cfg) => setGrafanaUrl(cfg.grafana_url));
|
|
}, []);
|
|
|
|
if (grafanaUrl === undefined) {
|
|
return null;
|
|
}
|
|
|
|
if (!grafanaUrl) {
|
|
return (
|
|
<div className="p-6 sm:p-10 max-w-4xl mx-auto">
|
|
<p className="text-center py-20 text-muted-foreground text-sm uppercase tracking-widest">
|
|
Grafana ещё не настроена (GRAFANA_PUBLIC_URL не задан на сервере).
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="p-6 sm:p-10 max-w-6xl mx-auto space-y-6">
|
|
<div className="flex items-center justify-between flex-wrap gap-4">
|
|
<h1 className="text-xl font-black tracking-widest text-primary uppercase">
|
|
Мониторинг
|
|
</h1>
|
|
<div className="flex gap-2 flex-wrap">
|
|
{DASHBOARDS.map((d) => (
|
|
<button
|
|
key={d.uid}
|
|
onClick={() => setActive(d.uid)}
|
|
className={`px-3 py-1.5 rounded-lg text-xs font-bold uppercase tracking-widest transition-colors ${
|
|
active === d.uid
|
|
? "bg-primary/10 text-primary border border-primary/30"
|
|
: "bg-white/5 text-muted-foreground hover:bg-white/10"
|
|
}`}
|
|
>
|
|
{d.title}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Ключ на iframe меняет DOM-узел при смене дашборда — проще, чем
|
|
городить логику перезагрузки src внутри одного и того же iframe. */}
|
|
<iframe
|
|
key={active}
|
|
title={`Grafana: ${active}`}
|
|
src={`${grafanaUrl}/d/${active}/?kiosk&theme=dark`}
|
|
className="w-full rounded-2xl border border-white/10"
|
|
style={{ height: "80vh" }}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|