// Copyright (c) 2026 Ivan Vasilev // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. import { useState, useEffect, useCallback } from 'react'; import { apiFetch, copyText, timeAgo, getServerId, type ApiKey, type PageProps } from '../../lib/shared'; import { IcoPlus, IcoTrash, IcoCopy } from '../../components/icons'; import './apikeys.css'; // Вкладка «API-ключи»: ключи внешнего API (/api/v1). Открытый ключ показывается // один раз при создании (в БД хранится только хэш). Сервер-бар сверху рендерит App. // TODO multi-server: сейчас сервер один (server_id = 0). Когда появится список // серверов — ключ будет привязываться к выбранному в сервер-баре. export default function ApiKeysPage({ token, showMsg }: PageProps) { const [keys, setKeys] = useState([]); const [label, setLabel] = useState(''); const [created, setCreated] = useState<{ label: string; key: string } | null>(null); const load = useCallback(async () => { try { const data = await apiFetch('GET', '/ui/apikeys', token); setKeys(data.keys ?? []); } catch { showMsg('Ошибка загрузки ключей'); } }, [token, showMsg]); const createKey = useCallback(async () => { if (!label.trim()) return; const r = await apiFetch('POST', '/ui/apikeys', token, { label: label.trim(), server_id: getServerId() ?? 0 }); if (r.error) { showMsg(r.error); return; } setLabel(''); setCreated({ label: r.label, key: r.key }); await load(); }, [label, token, load, showMsg]); const deleteKey = useCallback(async (id: number, lbl: string) => { if (!confirm('Удалить ключ «' + lbl + '»? Клиенты с ним потеряют доступ.')) return; await apiFetch('DELETE', '/ui/apikeys/' + id, token); showMsg('Ключ удалён: ' + lbl); await load(); }, [token, load, showMsg]); useEffect(() => { if (token) load(); }, [token, load]); return ( <> {/* Сервер-бар сверху рендерит App; к выбранному в нём серверу и привязывается ключ. TODO multi-server: server_id берётся из выбранной карточки (пока 0). */}
setLabel(e.target.value)} onKeyDown={e => e.key === 'Enter' && createKey()} />
{keys.length === 0 ? ( ) : keys.map((k, i) => ( ))}
# Метка Ключ Создан Последний раз
Нет ключей
{i + 1} {k.label} {k.prefix} {new Date(k.created_at * 1000).toLocaleDateString()} {k.last_used ? timeAgo(k.last_used) + ' назад' : 'никогда'}
{/* Мобильный вид: таблица скрыта (.table-card), ключи — карточками */}

{keys.length} ключей

{keys.length === 0 ? (

Нет ключей

) : keys.map((k, i) => (
#{i + 1} {k.label}
{k.prefix}
Создан {new Date(k.created_at * 1000).toLocaleDateString()} {k.last_used ? timeAgo(k.last_used) + ' назад' : 'не использован'}
))}
{created && (
setCreated(null)}>
e.stopPropagation()}>

Ключ «{created.label}» создан

Скопируй ключ сейчас — он показывается один раз и больше не будет доступен.

{created.key}
)} ); }