feat: show agent token, revoke, regenerate
Some checks failed
Deploy Web Client / deploy (push) Failing after 35s

This commit is contained in:
Markov 2026-02-23 12:11:12 +01:00
parent 652a863706
commit 621db2e29c
3 changed files with 78 additions and 35 deletions

View File

@ -237,6 +237,10 @@ async def update_member(slug: str, request: Request, user: dict = Depends(get_cu
async def regenerate_token(slug: str, user: dict = Depends(get_current_user)): async def regenerate_token(slug: str, user: dict = Depends(get_current_user)):
return await tracker_post(f"/api/v1/members/{slug}/regenerate-token") return await tracker_post(f"/api/v1/members/{slug}/regenerate-token")
@app.post("/api/v1/members/{slug}/revoke-token")
async def revoke_token(slug: str, user: dict = Depends(get_current_user)):
return await tracker_post(f"/api/v1/members/{slug}/revoke-token")
# --- Health --- # --- Health ---

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import { Member, updateMember, regenerateToken } from "@/lib/api"; import { Member, updateMember, regenerateToken, revokeToken } from "@/lib/api";
interface Props { interface Props {
agent: Member; agent: Member;
@ -19,9 +19,10 @@ export default function AgentModal({ agent, onClose, onUpdated }: Props) {
const [prompt, setPrompt] = useState(agent.agent_config?.prompt || ""); const [prompt, setPrompt] = useState(agent.agent_config?.prompt || "");
const [model, setModel] = useState(agent.agent_config?.model || ""); const [model, setModel] = useState(agent.agent_config?.model || "");
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [newToken, setNewToken] = useState<string | null>(null); const [currentToken, setCurrentToken] = useState<string | null>(agent.token || null);
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const [confirmRegen, setConfirmRegen] = useState(false); const [confirmRegen, setConfirmRegen] = useState(false);
const [confirmRevoke, setConfirmRevoke] = useState(false);
const handleSave = async () => { const handleSave = async () => {
setSaving(true); setSaving(true);
@ -49,16 +50,26 @@ export default function AgentModal({ agent, onClose, onUpdated }: Props) {
const handleRegenerate = async () => { const handleRegenerate = async () => {
try { try {
const { token } = await regenerateToken(agent.slug); const { token } = await regenerateToken(agent.slug);
setNewToken(token); setCurrentToken(token);
setConfirmRegen(false); setConfirmRegen(false);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
}; };
const handleRevoke = async () => {
try {
await revokeToken(agent.slug);
setCurrentToken(null);
setConfirmRevoke(false);
} catch (e) {
console.error(e);
}
};
const copyToken = () => { const copyToken = () => {
if (newToken) { if (currentToken) {
navigator.clipboard.writeText(newToken); navigator.clipboard.writeText(currentToken);
setCopied(true); setCopied(true);
setTimeout(() => setCopied(false), 2000); setTimeout(() => setCopied(false), 2000);
} }
@ -157,45 +168,68 @@ export default function AgentModal({ agent, onClose, onUpdated }: Props) {
{/* Token section */} {/* Token section */}
<div className="pt-3 border-t border-[var(--border)]"> <div className="pt-3 border-t border-[var(--border)]">
<div className="text-sm text-[var(--muted)] mb-2">Токен доступа</div> <div className="text-sm text-[var(--muted)] mb-2">Токен доступа</div>
{newToken ? ( {currentToken ? (
<div> <div>
<div className="mb-2 p-2 bg-yellow-500/10 border border-yellow-500/30 rounded text-yellow-400 text-xs">
Сохраните токен он больше не будет показан!
</div>
<div className="p-3 bg-[var(--bg)] border border-[var(--border)] rounded-lg font-mono text-xs break-all select-all mb-2"> <div className="p-3 bg-[var(--bg)] border border-[var(--border)] rounded-lg font-mono text-xs break-all select-all mb-2">
{newToken} {currentToken}
</div>
<div className="flex items-center gap-2">
<button
onClick={copyToken}
className="px-3 py-1.5 bg-[var(--accent)] text-white rounded text-xs hover:opacity-90"
>
{copied ? "Скопировано!" : "Скопировать"}
</button>
{confirmRegen ? (
<>
<button
onClick={handleRegenerate}
className="px-3 py-1.5 bg-yellow-500/20 text-yellow-400 rounded text-xs hover:bg-yellow-500/30"
>
Да, новый
</button>
<button onClick={() => setConfirmRegen(false)} className="text-xs text-[var(--muted)]">
Отмена
</button>
</>
) : (
<button
onClick={() => setConfirmRegen(true)}
className="text-xs text-[var(--muted)] hover:text-[var(--fg)]"
>
Перегенерировать
</button>
)}
{confirmRevoke ? (
<>
<button
onClick={handleRevoke}
className="px-3 py-1.5 bg-red-500/20 text-red-400 rounded text-xs hover:bg-red-500/30"
>
Да, отозвать
</button>
<button onClick={() => setConfirmRevoke(false)} className="text-xs text-[var(--muted)]">
Отмена
</button>
</>
) : (
<button
onClick={() => setConfirmRevoke(true)}
className="text-xs text-red-400/60 hover:text-red-400"
>
Отозвать
</button>
)}
</div> </div>
<button
onClick={copyToken}
className="px-3 py-1.5 bg-[var(--accent)] text-white rounded text-xs hover:opacity-90"
>
{copied ? "Скопировано!" : "Скопировать"}
</button>
</div>
) : confirmRegen ? (
<div className="flex items-center gap-2">
<span className="text-xs text-red-400">Старый токен перестанет работать!</span>
<button
onClick={handleRegenerate}
className="px-3 py-1.5 bg-red-500/20 text-red-400 rounded text-xs hover:bg-red-500/30"
>
Да, перегенерировать
</button>
<button
onClick={() => setConfirmRegen(false)}
className="px-3 py-1.5 text-xs text-[var(--muted)]"
>
Отмена
</button>
</div> </div>
) : ( ) : (
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="text-xs text-[var(--muted)]">Токен скрыт</span> <span className="text-xs text-[var(--muted)]">Токен отозван</span>
<button <button
onClick={() => setConfirmRegen(true)} onClick={handleRegenerate}
className="text-xs text-[var(--accent)] hover:underline" className="text-xs text-[var(--accent)] hover:underline"
> >
Перегенерировать Сгенерировать новый
</button> </button>
</div> </div>
)} )}

View File

@ -50,6 +50,7 @@ export interface Member {
status: string; status: string;
avatar_url: string | null; avatar_url: string | null;
agent_config: AgentConfig | null; agent_config: AgentConfig | null;
token?: string | null;
} }
export interface MemberCreateResponse extends Member { export interface MemberCreateResponse extends Member {
@ -238,3 +239,7 @@ export async function updateMember(slug: string, data: {
export async function regenerateToken(slug: string): Promise<{ token: string }> { export async function regenerateToken(slug: string): Promise<{ token: string }> {
return request(`/api/v1/members/${slug}/regenerate-token`, { method: "POST" }); return request(`/api/v1/members/${slug}/regenerate-token`, { method: "POST" });
} }
export async function revokeToken(slug: string): Promise<void> {
await request(`/api/v1/members/${slug}/revoke-token`, { method: "POST" });
}