fix document 500 error and fix users tabel for bot
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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'}
|
||||
|
||||
Reference in New Issue
Block a user