Админка: страницы Тикеты + Мониторинг, роль 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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user