= ({
diff --git a/frontend/src/pages/add-agents.page.tsx b/frontend/src/pages/add-agents.page.tsx
index ca439d1..94e7525 100644
--- a/frontend/src/pages/add-agents.page.tsx
+++ b/frontend/src/pages/add-agents.page.tsx
@@ -17,6 +17,7 @@ import {
const createEmptyAgentConfig = (): SSHAgentConfig => ({
user: "",
ip: "",
+ port: 22,
authMethod: "key",
sshKey: "",
password: "",
@@ -52,7 +53,8 @@ export const AddAgentsPage: React.FC = () => {
// Валидация
const isValid = agents.every((agent) => {
- if (!agent.user || !agent.ip) return false;
+ if (!agent.user || !agent.ip || !agent.port) return false;
+ if (agent.port < 1 || agent.port > 65535) return false;
if (agent.authMethod === "key" && !agent.sshKey) return false;
if (agent.authMethod === "password" && !agent.password) return false;
return true;
@@ -74,11 +76,11 @@ export const AddAgentsPage: React.FC = () => {
agentLabel: `${agent.ip}-${agent.user}`,
ip: agent.ip,
user: agent.user,
+ port: agent.port,
authMethod: agent.authMethod as "key" | "password",
deployType: (agent.deployType === "deploy"
? "docker"
: agent.deployType) as "docker" | "binary",
- port: 22,
...(agent.authMethod === "key"
? { sshKey: agent.sshKey }
: { password: agent.password }),
From ed439656f87d08f0ffc4e729e036a62fef63cf14 Mon Sep 17 00:00:00 2001
From: NikitaTorbenko <2015nekitciti@gmail.com>
Date: Sat, 4 Apr 2026 05:52:43 +0300
Subject: [PATCH 3/4] feat: add registration token
---
.../layout/navigation/navigation.tsx | 2 +
.../src/app/providers/routing/routing.tsx | 2 +
.../src/modules/agent/ui/SSHAgentForm.tsx | 23 +-
frontend/src/pages/add-agents.page.tsx | 6 +-
frontend/src/pages/registration.page.tsx | 423 ++++++++++++++++++
5 files changed, 453 insertions(+), 3 deletions(-)
create mode 100644 frontend/src/pages/registration.page.tsx
diff --git a/frontend/src/app/providers/layout/navigation/navigation.tsx b/frontend/src/app/providers/layout/navigation/navigation.tsx
index 14a83ed..01f4887 100644
--- a/frontend/src/app/providers/layout/navigation/navigation.tsx
+++ b/frontend/src/app/providers/layout/navigation/navigation.tsx
@@ -6,6 +6,7 @@ import {
FaUser,
FaUsers,
FaRocket,
+ FaKey,
} from "react-icons/fa";
import { useAuthStore } from "@/modules/auth/store/useAuthStore";
@@ -17,6 +18,7 @@ export const Navigation = () => {
const navItems = [
{ path: "/", label: "Главная", icon: FaHome },
{ path: "/add-agents", label: "Деплой", icon: FaRocket },
+ { path: "/registration", label: "Регистрация", icon: FaKey },
{ path: "/admin", label: "Админка", icon: FaUsers, adminOnly: true },
{ path: "/themes", label: "Темы", icon: FaPalette },
];
diff --git a/frontend/src/app/providers/routing/routing.tsx b/frontend/src/app/providers/routing/routing.tsx
index c9b4e6d..3b436c4 100644
--- a/frontend/src/app/providers/routing/routing.tsx
+++ b/frontend/src/app/providers/routing/routing.tsx
@@ -10,6 +10,7 @@ import { DefaultLayout } from "@/shared/layouts/DefaultLayout";
import { AddAgentsPage } from "@/pages/add-agents.page";
import { IDEPage } from "@/pages/ide.page";
import { AdminPage } from "@/pages/admin.page";
+import { RegistrationTokenPage } from "@/pages/registration.page";
export const mockGraphData: GraphData = {
nodes: [
@@ -120,6 +121,7 @@ export const Routing = () => {
} />
} />
} />
+ } />
} />
} />
diff --git a/frontend/src/modules/agent/ui/SSHAgentForm.tsx b/frontend/src/modules/agent/ui/SSHAgentForm.tsx
index d41fe1c..032dce4 100644
--- a/frontend/src/modules/agent/ui/SSHAgentForm.tsx
+++ b/frontend/src/modules/agent/ui/SSHAgentForm.tsx
@@ -21,6 +21,7 @@ interface ExtraField {
}
export interface SSHAgentConfig {
+ agentLabel: string;
user: string;
ip: string;
port: number;
@@ -191,7 +192,27 @@ export const SSHAgentForm: React.FC = ({
- {/* User и IP */}
+ {/* Agent Label */}
+
+
+ handleChange("agentLabel", e.target.value)}
+ required
+ style={inputBaseStyle}
+ onFocus={handleFocus}
+ onBlur={handleBlur}
+ placeholder="production-server-1"
+ />
+
+
+ {/* User, IP и Port */}
({
+ agentLabel: "",
user: "",
ip: "",
port: 22,
@@ -53,7 +54,8 @@ export const AddAgentsPage: React.FC = () => {
// Валидация
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.authMethod === "key" && !agent.sshKey) return false;
if (agent.authMethod === "password" && !agent.password) return false;
@@ -73,7 +75,7 @@ export const AddAgentsPage: React.FC = () => {
// Преобразуем данные из формы в формат API
const deployData: DeployAgentsRequest = {
servers: agents.map((agent) => ({
- agentLabel: `${agent.ip}-${agent.user}`,
+ agentLabel: agent.agentLabel,
ip: agent.ip,
user: agent.user,
port: agent.port,
diff --git a/frontend/src/pages/registration.page.tsx b/frontend/src/pages/registration.page.tsx
new file mode 100644
index 0000000..ecf464a
--- /dev/null
+++ b/frontend/src/pages/registration.page.tsx
@@ -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
([
+ { label: "" },
+ ]);
+ const [results, setResults] = useState([]);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [error, setError] = useState(null);
+ const [successMessage, setSuccessMessage] = useState(null);
+ const [copiedIndex, setCopiedIndex] = useState(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 (
+
+
+ {/* Header */}
+
+
+
+
+
+
+
+ Регистрация токенов для агентов
+
+
+ Создайте токены для регистрации новых агентов
+
+
+
+
+
+
+
+
+ );
+};
From 96f82b4162c31cebb2ea56359a2169d4736c99b2 Mon Sep 17 00:00:00 2001
From: NikitaTorbenko <2015nekitciti@gmail.com>
Date: Sat, 4 Apr 2026 05:57:34 +0300
Subject: [PATCH 4/4] feat: create register
---
.../src/modules/auth/store/useAuthStore.ts | 17 ++++--
frontend/src/modules/auth/types/auth.types.ts | 4 ++
frontend/src/pages/register.page.tsx | 55 +++++++++++++++----
3 files changed, 59 insertions(+), 17 deletions(-)
diff --git a/frontend/src/modules/auth/store/useAuthStore.ts b/frontend/src/modules/auth/store/useAuthStore.ts
index 523cea3..081f837 100644
--- a/frontend/src/modules/auth/store/useAuthStore.ts
+++ b/frontend/src/modules/auth/store/useAuthStore.ts
@@ -17,12 +17,18 @@ const login = async (credentials: LoginCredentials): Promise => {
return response.data;
};
-const register = async (data: RegisterData): Promise => {
- const response = await apiClient.post("/auth/register", {
+const register = async (
+ data: RegisterData,
+): Promise> => {
+ const response = await apiClient.post>("/auth/token", {
login: data.login,
password: data.password,
name: data.firstName,
last_name: data.lastName,
+ is_active: data.is_active,
+ permission_admin: data.permission_admin,
+ permission_manage_agent: data.permission_manage_agent,
+ permission_view: data.permission_view,
});
return response.data;
};
@@ -62,9 +68,10 @@ export const useAuthStore = create()(
register: async (data: RegisterData) => {
set({ isLoading: true, error: null });
try {
- const response = await register(data);
- const user = mapResponseToUser(response);
- set({ user, token: response.token, isLoading: false });
+ await register(data);
+ // После регистрации пользователь не авторизуется автоматически
+ // Нужно войти через /auth/login
+ set({ isLoading: false });
} catch (error) {
set({
error:
diff --git a/frontend/src/modules/auth/types/auth.types.ts b/frontend/src/modules/auth/types/auth.types.ts
index ca4e6d8..ff289a8 100644
--- a/frontend/src/modules/auth/types/auth.types.ts
+++ b/frontend/src/modules/auth/types/auth.types.ts
@@ -8,6 +8,10 @@ export interface RegisterData {
password: string;
firstName: string;
lastName: string;
+ is_active?: boolean;
+ permission_admin?: boolean;
+ permission_manage_agent?: boolean;
+ permission_view?: boolean;
}
export interface LoginResponse {
diff --git a/frontend/src/pages/register.page.tsx b/frontend/src/pages/register.page.tsx
index 98d8843..7406899 100644
--- a/frontend/src/pages/register.page.tsx
+++ b/frontend/src/pages/register.page.tsx
@@ -5,7 +5,7 @@ import { useAuthStore } from "@/modules/auth/store/useAuthStore";
export const RegisterPage: React.FC = () => {
const navigate = useNavigate();
- const { register, isLoading, error, clearError, token } = useAuthStore();
+ const { register, isLoading, error, clearError } = useAuthStore();
const [formData, setFormData] = useState({
login: "",
password: "",
@@ -14,12 +14,7 @@ export const RegisterPage: React.FC = () => {
lastName: "",
});
const [passwordError, setPasswordError] = useState(null);
-
- useEffect(() => {
- if (token) {
- navigate("/");
- }
- }, [token, navigate]);
+ const [successMessage, setSuccessMessage] = useState(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -38,7 +33,17 @@ export const RegisterPage: React.FC = () => {
firstName: formData.firstName,
lastName: formData.lastName,
});
- navigate("/");
+ setSuccessMessage("Аккаунт успешно создан! Теперь вы можете войти.");
+ setFormData({
+ login: "",
+ password: "",
+ confirmPassword: "",
+ firstName: "",
+ lastName: "",
+ });
+ setTimeout(() => {
+ navigate("/auth");
+ }, 2000);
} catch (err) {
// Error is handled by store
}
@@ -82,7 +87,10 @@ export const RegisterPage: React.FC = () => {
className="w-16 h-16 mx-auto mb-4 rounded-full flex items-center justify-center"
style={{ backgroundColor: "var(--bg-secondary)" }}
>
-
+
{
)}
+ {/* Success Message */}
+ {successMessage && (
+
+ {successMessage}
+
+ )}
+
{/* Form */}