This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
|||||||
FaUser,
|
FaUser,
|
||||||
FaUsers,
|
FaUsers,
|
||||||
FaRocket,
|
FaRocket,
|
||||||
|
FaKey,
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
import { useAuthStore } from "@/modules/auth/store/useAuthStore";
|
import { useAuthStore } from "@/modules/auth/store/useAuthStore";
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ export const Navigation = () => {
|
|||||||
const navItems = [
|
const navItems = [
|
||||||
{ path: "/", label: "Главная", icon: FaHome },
|
{ path: "/", label: "Главная", icon: FaHome },
|
||||||
{ path: "/add-agents", label: "Деплой", icon: FaRocket },
|
{ path: "/add-agents", label: "Деплой", icon: FaRocket },
|
||||||
|
{ path: "/registration", label: "Регистрация", icon: FaKey },
|
||||||
{ path: "/admin", label: "Админка", icon: FaUsers, adminOnly: true },
|
{ path: "/admin", label: "Админка", icon: FaUsers, adminOnly: true },
|
||||||
{ path: "/themes", label: "Темы", icon: FaPalette },
|
{ path: "/themes", label: "Темы", icon: FaPalette },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { DefaultLayout } from "@/shared/layouts/DefaultLayout";
|
|||||||
import { AddAgentsPage } from "@/pages/add-agents.page";
|
import { AddAgentsPage } from "@/pages/add-agents.page";
|
||||||
import { IDEPage } from "@/pages/ide.page";
|
import { IDEPage } from "@/pages/ide.page";
|
||||||
import { AdminPage } from "@/pages/admin.page";
|
import { AdminPage } from "@/pages/admin.page";
|
||||||
|
import { RegistrationTokenPage } from "@/pages/registration.page";
|
||||||
|
|
||||||
export const mockGraphData: GraphData = {
|
export const mockGraphData: GraphData = {
|
||||||
nodes: [
|
nodes: [
|
||||||
@@ -120,6 +121,7 @@ export const Routing = () => {
|
|||||||
<Route path="/" element={<HomePage />} />
|
<Route path="/" element={<HomePage />} />
|
||||||
<Route path="/themes" element={<ThemesPage />} />
|
<Route path="/themes" element={<ThemesPage />} />
|
||||||
<Route path="/add-agents" element={<AddAgentsPage />} />
|
<Route path="/add-agents" element={<AddAgentsPage />} />
|
||||||
|
<Route path="/registration" element={<RegistrationTokenPage />} />
|
||||||
<Route path="/admin" element={<AdminPage />} />
|
<Route path="/admin" element={<AdminPage />} />
|
||||||
<Route path="/IDE" element={<IDEPage />} />
|
<Route path="/IDE" element={<IDEPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ interface ExtraField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SSHAgentConfig {
|
export interface SSHAgentConfig {
|
||||||
|
agentLabel: string;
|
||||||
user: string;
|
user: string;
|
||||||
ip: string;
|
ip: string;
|
||||||
port: number;
|
port: number;
|
||||||
@@ -191,7 +192,27 @@ export const SSHAgentForm: React.FC<SSHAgentFormProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: "grid", gap: "20px" }}>
|
<div style={{ display: "grid", gap: "20px" }}>
|
||||||
{/* User и IP */}
|
{/* Agent Label */}
|
||||||
|
<div>
|
||||||
|
<label style={labelStyle}>
|
||||||
|
<span style={{ display: "flex", alignItems: "center", gap: "6px" }}>
|
||||||
|
<FiServer size={14} />
|
||||||
|
Метка агента *
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={config.agentLabel}
|
||||||
|
onChange={(e) => handleChange("agentLabel", e.target.value)}
|
||||||
|
required
|
||||||
|
style={inputBaseStyle}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
placeholder="production-server-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* User, IP и Port */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "grid",
|
display: "grid",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
} from "react-icons/fi";
|
} from "react-icons/fi";
|
||||||
|
|
||||||
const createEmptyAgentConfig = (): SSHAgentConfig => ({
|
const createEmptyAgentConfig = (): SSHAgentConfig => ({
|
||||||
|
agentLabel: "",
|
||||||
user: "",
|
user: "",
|
||||||
ip: "",
|
ip: "",
|
||||||
port: 22,
|
port: 22,
|
||||||
@@ -53,7 +54,8 @@ export const AddAgentsPage: React.FC = () => {
|
|||||||
|
|
||||||
// Валидация
|
// Валидация
|
||||||
const isValid = agents.every((agent) => {
|
const isValid = agents.every((agent) => {
|
||||||
if (!agent.user || !agent.ip || !agent.port) return false;
|
if (!agent.agentLabel || !agent.user || !agent.ip || !agent.port)
|
||||||
|
return false;
|
||||||
if (agent.port < 1 || agent.port > 65535) return false;
|
if (agent.port < 1 || agent.port > 65535) return false;
|
||||||
if (agent.authMethod === "key" && !agent.sshKey) return false;
|
if (agent.authMethod === "key" && !agent.sshKey) return false;
|
||||||
if (agent.authMethod === "password" && !agent.password) return false;
|
if (agent.authMethod === "password" && !agent.password) return false;
|
||||||
@@ -73,7 +75,7 @@ export const AddAgentsPage: React.FC = () => {
|
|||||||
// Преобразуем данные из формы в формат API
|
// Преобразуем данные из формы в формат API
|
||||||
const deployData: DeployAgentsRequest = {
|
const deployData: DeployAgentsRequest = {
|
||||||
servers: agents.map((agent) => ({
|
servers: agents.map((agent) => ({
|
||||||
agentLabel: `${agent.ip}-${agent.user}`,
|
agentLabel: agent.agentLabel,
|
||||||
ip: agent.ip,
|
ip: agent.ip,
|
||||||
user: agent.user,
|
user: agent.user,
|
||||||
port: agent.port,
|
port: agent.port,
|
||||||
|
|||||||
@@ -0,0 +1,423 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { agentApiService } from "@/modules/agent/api/agent.api.service";
|
||||||
|
import { FiKey, FiPlus, FiTrash2, FiCopy, FiCheck, FiX } from "react-icons/fi";
|
||||||
|
|
||||||
|
interface RegistrationTokenForm {
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RegistrationResult {
|
||||||
|
label: string;
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RegistrationTokenPage: React.FC = () => {
|
||||||
|
const [tokens, setTokens] = useState<RegistrationTokenForm[]>([
|
||||||
|
{ label: "" },
|
||||||
|
]);
|
||||||
|
const [results, setResults] = useState<RegistrationResult[]>([]);
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||||
|
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const handleTokenChange = (index: number, label: string) => {
|
||||||
|
const newTokens = [...tokens];
|
||||||
|
newTokens[index] = { label };
|
||||||
|
setTokens(newTokens);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddToken = () => {
|
||||||
|
setTokens([...tokens, { label: "" }]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveToken = (index: number) => {
|
||||||
|
const newTokens = tokens.filter((_, i) => i !== index);
|
||||||
|
setTokens(newTokens);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCopyToken = async (token: string, index: number) => {
|
||||||
|
await navigator.clipboard.writeText(token);
|
||||||
|
setCopiedIndex(index);
|
||||||
|
setTimeout(() => setCopiedIndex(null), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Валидация
|
||||||
|
const validTokens = tokens.filter((t) => t.label.trim());
|
||||||
|
if (validTokens.length === 0) {
|
||||||
|
setError("Введите хотя бы одну метку для токена");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccessMessage(null);
|
||||||
|
setResults([]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const createdTokens: RegistrationResult[] = [];
|
||||||
|
|
||||||
|
for (const tokenData of validTokens) {
|
||||||
|
const response = await agentApiService.createRegistrationToken({
|
||||||
|
label: tokenData.label,
|
||||||
|
});
|
||||||
|
|
||||||
|
// API возвращает объект с токеном
|
||||||
|
const token = response.token || Object.values(response)[0] as string;
|
||||||
|
|
||||||
|
createdTokens.push({
|
||||||
|
label: tokenData.label,
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setResults(createdTokens);
|
||||||
|
setSuccessMessage(
|
||||||
|
`Успешно создано ${createdTokens.length} токен(ов)`
|
||||||
|
);
|
||||||
|
setTokens([{ label: "" }]);
|
||||||
|
} catch (err) {
|
||||||
|
setError(
|
||||||
|
err instanceof Error
|
||||||
|
? err.message
|
||||||
|
: "Ошибка при создании токенов"
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputStyle: React.CSSProperties = {
|
||||||
|
width: "100%",
|
||||||
|
padding: "10px 12px",
|
||||||
|
border: "1px solid var(--border)",
|
||||||
|
borderRadius: "8px",
|
||||||
|
backgroundColor: "var(--input-bg)",
|
||||||
|
color: "var(--text-primary)",
|
||||||
|
fontSize: "14px",
|
||||||
|
transition: "border-color 0.2s, box-shadow 0.2s",
|
||||||
|
};
|
||||||
|
|
||||||
|
const labelStyle: React.CSSProperties = {
|
||||||
|
display: "block",
|
||||||
|
marginBottom: "8px",
|
||||||
|
color: "var(--text-secondary)",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: 500,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="min-h-screen py-8 px-4"
|
||||||
|
style={{ backgroundColor: "var(--bg-primary)" }}
|
||||||
|
>
|
||||||
|
<div style={{ maxWidth: "900px", margin: "0 auto" }}>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="flex items-center gap-4 mb-4">
|
||||||
|
<div
|
||||||
|
className="w-14 h-14 rounded-xl flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: "var(--bg-secondary)" }}
|
||||||
|
>
|
||||||
|
<FiKey className="w-7 h-7" style={{ color: "var(--accent)" }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1
|
||||||
|
className="text-3xl font-bold mb-1"
|
||||||
|
style={{ color: "var(--text-primary)" }}
|
||||||
|
>
|
||||||
|
Регистрация токенов для агентов
|
||||||
|
</h1>
|
||||||
|
<p style={{ color: "var(--text-secondary)", fontSize: "16px" }}>
|
||||||
|
Создайте токены для регистрации новых агентов
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{/* Token Forms */}
|
||||||
|
<div className="space-y-5">
|
||||||
|
{tokens.map((token, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="rounded-2xl shadow-lg border"
|
||||||
|
style={{
|
||||||
|
backgroundColor: "var(--card-bg)",
|
||||||
|
borderColor: "var(--border)",
|
||||||
|
padding: "24px",
|
||||||
|
marginBottom: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: "20px",
|
||||||
|
paddingBottom: "16px",
|
||||||
|
borderBottom: "1px solid var(--border)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
|
||||||
|
<div
|
||||||
|
className="w-10 h-10 rounded-lg flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: "var(--bg-secondary)" }}
|
||||||
|
>
|
||||||
|
<FiKey style={{ color: "var(--accent)", fontSize: "20px" }} />
|
||||||
|
</div>
|
||||||
|
<h3
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
color: "var(--text-primary)",
|
||||||
|
fontSize: "18px",
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Токен #{index + 1}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
{tokens.length > 1 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleRemoveToken(index)}
|
||||||
|
className="flex items-center gap-2 px-4 py-2 rounded-lg transition-all"
|
||||||
|
style={{
|
||||||
|
background: "var(--error-bg)",
|
||||||
|
color: "var(--error-text)",
|
||||||
|
border: "1px solid var(--error-border)",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: 500,
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.background = "var(--error-text)";
|
||||||
|
e.currentTarget.style.color = "#fff";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.background = "var(--error-bg)";
|
||||||
|
e.currentTarget.style.color = "var(--error-text)";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FiTrash2 size={14} />
|
||||||
|
Удалить
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Label Input */}
|
||||||
|
<div>
|
||||||
|
<label style={labelStyle}>
|
||||||
|
<span
|
||||||
|
style={{ display: "flex", alignItems: "center", gap: "6px" }}
|
||||||
|
>
|
||||||
|
<FiKey size={14} />
|
||||||
|
Метка токена *
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={token.label}
|
||||||
|
onChange={(e) => handleTokenChange(index, e.target.value)}
|
||||||
|
required
|
||||||
|
style={inputStyle}
|
||||||
|
onFocus={(e) => {
|
||||||
|
e.currentTarget.style.borderColor = "var(--border-focus)";
|
||||||
|
e.currentTarget.style.boxShadow = `0 0 0 3px var(--border-focus)30`;
|
||||||
|
}}
|
||||||
|
onBlur={(e) => {
|
||||||
|
e.currentTarget.style.borderColor = "var(--border)";
|
||||||
|
e.currentTarget.style.boxShadow = "none";
|
||||||
|
}}
|
||||||
|
placeholder="agent-production-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Add Token Button */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleAddToken}
|
||||||
|
className="w-full flex items-center justify-center gap-2 py-3.5 px-4 rounded-xl border-2 border-dashed transition-all mb-6 font-medium"
|
||||||
|
style={{
|
||||||
|
borderColor: "var(--border)",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
color: "var(--accent)",
|
||||||
|
fontSize: "15px",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.borderColor = "var(--accent)";
|
||||||
|
e.currentTarget.style.backgroundColor = "var(--accent)10";
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.borderColor = "var(--border)";
|
||||||
|
e.currentTarget.style.backgroundColor = "transparent";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FiPlus size={18} />
|
||||||
|
Добавить ещё один токен
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Messages */}
|
||||||
|
{successMessage && (
|
||||||
|
<div
|
||||||
|
className="mb-6 p-4 rounded-lg border text-sm"
|
||||||
|
style={{
|
||||||
|
backgroundColor: "var(--success-bg)",
|
||||||
|
borderColor: "var(--success-border)",
|
||||||
|
color: "var(--success-text)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span>{successMessage}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => setSuccessMessage(null)}
|
||||||
|
style={{ background: "none", border: "none", cursor: "pointer", color: "inherit" }}
|
||||||
|
>
|
||||||
|
<FiX size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div
|
||||||
|
className="mb-6 p-4 rounded-lg border text-sm"
|
||||||
|
style={{
|
||||||
|
backgroundColor: "var(--error-bg)",
|
||||||
|
borderColor: "var(--error-border)",
|
||||||
|
color: "var(--error-text)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span>{error}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => setError(null)}
|
||||||
|
style={{ background: "none", border: "none", cursor: "pointer", color: "inherit" }}
|
||||||
|
>
|
||||||
|
<FiX size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className="w-full flex items-center justify-center gap-2 px-4 py-3.5 rounded-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed font-medium text-base mb-8"
|
||||||
|
style={{
|
||||||
|
backgroundColor: isSubmitting
|
||||||
|
? "var(--bg-secondary)"
|
||||||
|
: "var(--button-primary)",
|
||||||
|
color: "var(--button-primary-text)",
|
||||||
|
boxShadow: isSubmitting
|
||||||
|
? "none"
|
||||||
|
: "0 4px 14px var(--shadow-color)",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
if (!isSubmitting) {
|
||||||
|
e.currentTarget.style.backgroundColor =
|
||||||
|
"var(--button-primary-hover)";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = isSubmitting
|
||||||
|
? "var(--bg-secondary)"
|
||||||
|
: "var(--button-primary)";
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isSubmitting ? (
|
||||||
|
<>
|
||||||
|
<div className="w-5 h-5 border-2 border-current border-t-transparent rounded-full animate-spin" />
|
||||||
|
Создание токенов...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FiKey size={18} />
|
||||||
|
Создать {tokens.filter((t) => t.label.trim()).length || 1} токен(ов)
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Results */}
|
||||||
|
{results.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<h2
|
||||||
|
className="text-xl font-bold mb-4"
|
||||||
|
style={{ color: "var(--text-primary)" }}
|
||||||
|
>
|
||||||
|
Созданные токены
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{results.map((result, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="rounded-xl border p-4"
|
||||||
|
style={{
|
||||||
|
backgroundColor: "var(--card-bg)",
|
||||||
|
borderColor: "var(--border)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<span
|
||||||
|
className="font-medium"
|
||||||
|
style={{ color: "var(--text-primary)" }}
|
||||||
|
>
|
||||||
|
{result.label}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCopyToken(result.token, index)}
|
||||||
|
className="flex items-center gap-2 px-3 py-1.5 rounded-lg transition-all text-xs font-medium"
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
copiedIndex === index
|
||||||
|
? "var(--success-text)"
|
||||||
|
: "var(--accent)",
|
||||||
|
color:
|
||||||
|
copiedIndex === index
|
||||||
|
? "#fff"
|
||||||
|
: "var(--accent-text)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{copiedIndex === index ? (
|
||||||
|
<>
|
||||||
|
<FiCheck size={12} />
|
||||||
|
Скопировано
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FiCopy size={12} />
|
||||||
|
Копировать
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<code
|
||||||
|
className="block p-3 rounded-lg text-xs font-mono break-all"
|
||||||
|
style={{
|
||||||
|
backgroundColor: "var(--bg-secondary)",
|
||||||
|
color: "var(--accent)",
|
||||||
|
border: "1px solid var(--border)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{result.token}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user