fix document 500 error and fix users tabel for bot

This commit is contained in:
lendry
2026-06-26 10:32:04 +03:00
parent ead3155ad8
commit d5e6b58955
17 changed files with 396 additions and 67 deletions

View File

@@ -11,7 +11,9 @@ import { Input } from '@/components/ui/input';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import {
AdminBotMetrics,
AdminUser,
ManagedBot,
fetchAdminBotAccounts,
fetchAdminBotMetrics,
fetchAdminBots,
setAdminBotActive
@@ -23,6 +25,7 @@ export default function AdminBotsPage() {
const { token, user: currentUser } = useAuth();
const { showToast } = useToast();
const [bots, setBots] = useState<ManagedBot[]>([]);
const [botAccounts, setBotAccounts] = useState<AdminUser[]>([]);
const [total, setTotal] = useState(0);
const [metrics, setMetrics] = useState<AdminBotMetrics | null>(null);
const [search, setSearch] = useState('');
@@ -34,13 +37,15 @@ export default function AdminBotsPage() {
if (!token) return;
setLoading(true);
try {
const [botsResponse, metricsResponse] = await Promise.all([
const [botsResponse, metricsResponse, accountsResponse] = await Promise.all([
fetchAdminBots({ search, page, limit: 20 }, token),
fetchAdminBotMetrics(token)
fetchAdminBotMetrics(token),
fetchAdminBotAccounts(search, token)
]);
setBots(botsResponse.bots ?? []);
setTotal(botsResponse.total ?? 0);
setMetrics(metricsResponse);
setBotAccounts(accountsResponse.users ?? []);
} catch (error) {
showToast(error instanceof Error ? error.message : 'Не удалось загрузить ботов');
} finally {
@@ -207,6 +212,66 @@ export default function AdminBotsPage() {
</Button>
</div>
) : null}
<div className="mt-10">
<h3 className="mb-2 text-lg font-medium">Системные учётные записи ботов</h3>
<p className="mb-4 text-sm text-[#667085]">
Пользователи IdP, связанные с Telegram-ботами (BotFather и боты пользователей). Роль: «Бот».
</p>
<div className="overflow-hidden rounded-2xl border border-[#eceef4] bg-white">
<Table>
<TableHeader>
<TableRow>
<TableHead>Учётная запись</TableHead>
<TableHead>Telegram-бот</TableHead>
<TableHead>Роль</TableHead>
<TableHead>Статус</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={4} className="py-10 text-center text-[#667085]">
<Loader2 className="mx-auto h-5 w-5 animate-spin" />
</TableCell>
</TableRow>
) : botAccounts.length ? (
botAccounts.map((account) => (
<TableRow key={account.id}>
<TableCell>
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-[#eef4ff] text-[#3390ec]">
<Bot className="h-5 w-5" />
</div>
<div>
<p className="font-medium">{account.displayName}</p>
<p className="text-sm text-[#667085]">{account.username ? `@${account.username}` : account.id.slice(0, 8)}</p>
</div>
</div>
</TableCell>
<TableCell>
{account.linkedBotUsername ? `@${account.linkedBotUsername}` : '—'}
{account.isSystemBot ? <p className="text-xs text-[#3390ec]">Системный</p> : null}
</TableCell>
<TableCell>Бот</TableCell>
<TableCell>
<span className="inline-flex rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-medium text-emerald-700">
{account.status === 'ACTIVE' ? 'Активен' : account.status}
</span>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={4} className="py-10 text-center text-[#667085]">
Системные учётные записи не найдены
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
</AdminShell>
);
}

View File

@@ -2,7 +2,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useRouter } from 'next/navigation';
import { Ban, BadgeCheck, Crown, FileText, KeyRound, Loader2, Search, ShieldPlus, UserCheck, UserCog } from 'lucide-react';
import { Ban, BadgeCheck, Crown, FileText, KeyRound, Loader2, Search, ShieldOff, ShieldPlus, UserCheck, UserCog } from 'lucide-react';
import { VerificationBadge } from '@/components/id/verification-badge';
import { UserDocumentsDialog } from '@/components/documents/user-documents-dialog';
import { AdminShell } from '@/components/id/admin-shell';
@@ -12,7 +12,7 @@ import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { AdminPermission, AdminRole, AdminUser, apiFetch } from '@/lib/api';
import { AdminPermission, AdminRole, AdminUser, adminDisableUserTotp, apiFetch, fetchUserTotpStatus } from '@/lib/api';
import { getAdminLandingPath } from '@/lib/admin-access';
import { DEFAULT_VERIFICATION_ICON, VERIFICATION_ICON_OPTIONS } from '@/lib/verification-icons';
@@ -24,6 +24,7 @@ const statusLabels: Record<string, string> = {
const roleLabels: Record<string, string> = {
user: 'Пользователь',
bot: 'Бот',
admin: 'Администратор',
moderator: 'Модератор',
manager: 'Менеджер',
@@ -274,9 +275,32 @@ export default function AdminUsersPage() {
setDialog('verification');
}
async function handleAdminDisableTotp(user: AdminUser) {
if (!token || !currentUser?.isSuperAdmin) return;
setActionLoading(true);
try {
const status = await fetchUserTotpStatus(user.id, token);
if (!status.isEnabled) {
showToast('У пользователя не включена двухфакторная аутентификация');
return;
}
const confirmed = window.confirm(`Отключить 2FA для ${user.displayName}? Пользователю не потребуется код из приложения-аутентификатора.`);
if (!confirmed) return;
await adminDisableUserTotp(user.id, token);
showToast('Двухфакторная аутентификация отключена');
} catch (error) {
showToast(error instanceof Error ? error.message : 'Не удалось отключить 2FA');
} finally {
setActionLoading(false);
}
}
function renderRoles(user: AdminUser) {
if (user.isBot) {
return roleLabels.bot;
}
const userRoles = user.roles ?? [];
const extraRoles = userRoles.filter((role) => role !== DEFAULT_USER_ROLE);
const extraRoles = userRoles.filter((role) => role !== DEFAULT_USER_ROLE && role !== 'bot');
const direct = user.directPermissions ?? [];
const labels = user.isSuperAdmin
? ['Супер-админ', ...extraRoles.map((role) => roleLabels[role] ?? role)]
@@ -357,7 +381,7 @@ export default function AdminUsersPage() {
</Button>
</>
) : null}
{currentUser?.canViewUserDocuments ? (
{currentUser?.canViewUserDocuments && !user.isBot ? (
<Button
variant="secondary"
size="icon"
@@ -368,6 +392,17 @@ export default function AdminUsersPage() {
<FileText className="h-4 w-4" />
</Button>
) : null}
{currentUser?.isSuperAdmin && !user.isBot ? (
<Button
variant="secondary"
size="icon"
aria-label="Отключить 2FA"
disabled={actionLoading}
onClick={() => void handleAdminDisableTotp(user)}
>
<ShieldOff className="h-4 w-4" />
</Button>
) : null}
{currentUser?.canVerifyUsers ? (
<Button
variant={user.isVerified ? 'default' : 'secondary'}