feat: show agent token, revoke, regenerate
Some checks failed
Deploy Web Client / deploy (push) Failing after 35s
Some checks failed
Deploy Web Client / deploy (push) Failing after 35s
This commit is contained in:
parent
652a863706
commit
621db2e29c
@ -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 ---
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -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" });
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user