fix: sidebar & admin.api
ci-front / build (push) Successful in 2m1s

This commit is contained in:
nikita
2026-04-04 22:58:09 +03:00
parent c73035019f
commit 6b82c99d50
2 changed files with 155 additions and 33 deletions
@@ -15,6 +15,7 @@ import {
import { useAgentStore } from "@/app/providers/layout/store/agent.store";
import { useAuthStore } from "@/modules/auth/store/useAuthStore";
import { Graph, type GraphData } from "@/modules/graph";
import { adminApi } from "@/modules/admin/api/admin.api";
interface SidebarProps {
isOpen?: boolean;
@@ -36,6 +37,12 @@ export const Sidebar: React.FC<SidebarProps> = ({
new Set(agents.map((a) => a.label)),
);
// Token generation state
const [tokenLabel, setTokenLabel] = useState("");
const [generatedToken, setGeneratedToken] = useState<string | null>(null);
const [tokenGenerating, setTokenGenerating] = useState(false);
const [tokenError, setTokenError] = useState<string | null>(null);
const toggleAgent = (label: string) => {
setExpandedAgents((prev) => {
const next = new Set(prev);
@@ -90,13 +97,38 @@ export const Sidebar: React.FC<SidebarProps> = ({
}, [agents]);
const handleCopyToken = () => {
if (token) {
navigator.clipboard.writeText(token);
const tokenToCopy = generatedToken || token;
if (tokenToCopy) {
navigator.clipboard.writeText(tokenToCopy);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};
const handleGenerateToken = async () => {
if (!tokenLabel.trim()) return;
setTokenGenerating(true);
setTokenError(null);
try {
const newToken = await adminApi.generateToken(tokenLabel.trim());
setGeneratedToken(newToken);
} catch (e) {
setTokenError(
e instanceof Error ? e.message : "Failed to generate token",
);
} finally {
setTokenGenerating(false);
}
};
const handleCloseTokenModal = () => {
setShowTokenModal(false);
setTokenLabel("");
setGeneratedToken(null);
setTokenError(null);
setCopied(false);
};
if (!isOpen) {
return (
<button
@@ -439,7 +471,7 @@ export const Sidebar: React.FC<SidebarProps> = ({
<div
className="fixed inset-0 z-[60] flex items-center justify-center p-4"
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
onClick={() => setShowTokenModal(false)}
onClick={handleCloseTokenModal}
>
<div
className="w-full max-w-md rounded-xl shadow-2xl border"
@@ -459,11 +491,11 @@ export const Sidebar: React.FC<SidebarProps> = ({
className="text-sm font-semibold"
style={{ color: "var(--text-primary)" }}
>
Ваш токен доступа
Генерация токена
</h2>
</div>
<button
onClick={() => setShowTokenModal(false)}
onClick={handleCloseTokenModal}
className="p-1 rounded transition-colors"
style={{ color: "var(--text-secondary)" }}
>
@@ -472,27 +504,72 @@ export const Sidebar: React.FC<SidebarProps> = ({
</div>
<div className="p-4 space-y-3">
<div>
<label
className="block text-xs font-medium mb-2"
style={{ color: "var(--text-secondary)" }}
>
Токен
</label>
{/* Error */}
{tokenError && (
<div
className="flex items-center gap-2 rounded-lg p-3 border"
className="text-xs p-2 rounded"
style={{
backgroundColor: "var(--bg-secondary)",
borderColor: "var(--border)",
backgroundColor: "rgba(239,68,68,0.1)",
border: "1px solid rgba(239,68,68,0.3)",
color: "var(--error-text, #ef4444)",
}}
>
<code
className="flex-1 text-xs font-mono break-all"
style={{ color: "var(--text-primary)" }}
{tokenError}
</div>
)}
{/* Label input */}
{!generatedToken && (
<div>
<label
className="block text-xs font-medium mb-2"
style={{ color: "var(--text-secondary)" }}
>
{token || "Токен не найден"}
</code>
{token && (
Имя токена
</label>
<input
type="text"
value={tokenLabel}
onChange={(e) => setTokenLabel(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && tokenLabel.trim()) {
handleGenerateToken();
}
}}
placeholder="Введите имя..."
autoFocus
className="w-full px-3 py-2 rounded-lg border text-sm focus:outline-none transition-all"
style={{
backgroundColor: "var(--input-bg)",
borderColor: "var(--border)",
color: "var(--text-primary)",
}}
/>
</div>
)}
{/* Generated token */}
{generatedToken && (
<div>
<label
className="block text-xs font-medium mb-2"
style={{ color: "var(--text-secondary)" }}
>
Токен
</label>
<div
className="flex items-center gap-2 rounded-lg p-3 border"
style={{
backgroundColor: "var(--bg-secondary)",
borderColor: "var(--border)",
}}
>
<code
className="flex-1 text-xs font-mono break-all"
style={{ color: "var(--text-primary)" }}
>
{generatedToken}
</code>
<button
onClick={handleCopyToken}
className="p-1.5 rounded transition-colors"
@@ -507,20 +584,56 @@ export const Sidebar: React.FC<SidebarProps> = ({
<FaCopy size={12} />
)}
</button>
)}
</div>
</div>
</div>
)}
<button
onClick={() => setShowTokenModal(false)}
className="w-full py-2 rounded-lg text-xs font-medium transition-colors"
style={{
backgroundColor: "var(--accent)",
color: "var(--accent-text)",
}}
>
Закрыть
</button>
{/* Buttons */}
<div className="flex gap-2">
{generatedToken && (
<button
onClick={() => {
setGeneratedToken(null);
setTokenLabel("");
}}
className="flex-1 py-2 rounded-lg text-xs font-medium transition-colors"
style={{
backgroundColor: "var(--bg-secondary)",
color: "var(--text-primary)",
border: "1px solid var(--border)",
}}
>
Новый токен
</button>
)}
<button
onClick={
generatedToken ? handleCloseTokenModal : handleGenerateToken
}
disabled={tokenGenerating || !tokenLabel.trim()}
className="flex-1 py-2 rounded-lg text-xs font-medium transition-colors"
style={{
backgroundColor:
tokenGenerating || (!generatedToken && !tokenLabel.trim())
? "var(--bg-secondary)"
: "var(--accent)",
color:
tokenGenerating || (!generatedToken && !tokenLabel.trim())
? "var(--text-muted)"
: "var(--accent-text)",
cursor:
tokenGenerating || (!generatedToken && !tokenLabel.trim())
? "default"
: "pointer",
}}
>
{tokenGenerating
? "Генерация..."
: generatedToken
? "Готово"
: "Создать"}
</button>
</div>
</div>
</div>
</div>
@@ -85,4 +85,13 @@ export const adminApi = {
headers: { Authorization: getAuthHeader() },
});
},
generateToken: async (label: string): Promise<string> => {
const res = await apiClient.post<{ token: string }>(
"/agents/register-token",
{ label },
{ headers: { Authorization: getAuthHeader() } },
);
return res.data.token;
},
};