Админка: страницы Тикеты + Мониторинг, роль support в навигации

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>
This commit is contained in:
2026-07-09 23:20:59 +07:00
parent 91e214dad2
commit 583682f330
6 changed files with 443 additions and 8 deletions
+52
View File
@@ -4,6 +4,7 @@ import { UserPlus, RefreshCw } from "lucide-react";
import {
createModerator,
createPartner,
createSupport,
fetchStaffUsers,
setNodePermission,
type StaffRole,
@@ -23,6 +24,9 @@ export default function StaffPage() {
const [partnerPassword, setPartnerPassword] = useState("");
const [partnerPercent, setPartnerPercent] = useState("10");
const [supportUsername, setSupportUsername] = useState("");
const [supportPassword, setSupportPassword] = useState("");
const [error, setError] = useState("");
const load = async () => {
@@ -68,6 +72,19 @@ export default function StaffPage() {
}
};
const handleCreateSupport = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
try {
await createSupport(supportUsername, supportPassword);
setSupportUsername("");
setSupportPassword("");
await load();
} catch {
setError("Не удалось создать аккаунт саппорта (логин уже занят?).");
}
};
const handleTogglePermission = async (userId: string, current: boolean) => {
try {
await setNodePermission(userId, !current);
@@ -135,6 +152,41 @@ export default function StaffPage() {
</form>
)}
{role.role === "owner" && (
<form
onSubmit={handleCreateSupport}
className="bg-black/40 border border-white/10 rounded-2xl p-6 space-y-3"
>
<h2 className="text-xs font-bold uppercase tracking-widest text-secondary flex items-center gap-2">
<UserPlus className="w-4 h-4" /> Новый саппорт
</h2>
<input
required
placeholder="Логин"
value={supportUsername}
onChange={(e) => setSupportUsername(e.target.value)}
className="w-full bg-white/5 border border-white/10 rounded-lg p-2.5 text-sm outline-none focus:border-secondary"
/>
<input
required
type="password"
placeholder="Пароль"
value={supportPassword}
onChange={(e) => setSupportPassword(e.target.value)}
className="w-full bg-white/5 border border-white/10 rounded-lg p-2.5 text-sm outline-none focus:border-secondary"
/>
<p className="text-[10px] text-muted-foreground">
Видит только раздел "Тикеты" доступа к нодам/staff/выводам нет.
</p>
<button
type="submit"
className="w-full bg-secondary/10 hover:bg-secondary/20 text-secondary border border-secondary/30 font-bold uppercase tracking-widest text-xs py-2.5 rounded-lg transition-all"
>
Создать
</button>
</form>
)}
<form
onSubmit={handleCreatePartner}
className="bg-black/40 border border-white/10 rounded-2xl p-6 space-y-3"