diff --git a/frontend/.qwen/settings.json b/frontend/.qwen/settings.json index db229fd..627a679 100644 --- a/frontend/.qwen/settings.json +++ b/frontend/.qwen/settings.json @@ -1,7 +1,13 @@ { "permissions": { "allow": [ - "Bash(yarn *)" + "Bash(yarn *)", + "Bash(npx *)", + "Bash(npm run *)", + "Bash(type *)", + "Bash(dir)", + "Bash(move *)", + "Bash(findstr *)" ] }, "$version": 3 diff --git a/frontend/src/app/providers/routing/routing.tsx b/frontend/src/app/providers/routing/routing.tsx index 13d3c47..af4b471 100644 --- a/frontend/src/app/providers/routing/routing.tsx +++ b/frontend/src/app/providers/routing/routing.tsx @@ -4,6 +4,7 @@ import { HomePage } from "@/pages/home.page"; import { ThemesPage } from "@/pages/themes.page"; import { AuthPage } from "@/pages/auth.page"; import { RegisterPage } from "@/pages/register.page"; +import { AddAgentsPage } from "@/pages/add-agents.page"; import { DefaultLayout } from "@/shared/layouts/DefaultLayout"; export const Routing = () => { @@ -21,6 +22,7 @@ export const Routing = () => { } /> } /> } /> + } /> } /> diff --git a/frontend/src/modules/agent/types/agent.types.tsx b/frontend/src/modules/agent/types/agent.types.tsx new file mode 100644 index 0000000..4dae3f0 --- /dev/null +++ b/frontend/src/modules/agent/types/agent.types.tsx @@ -0,0 +1,27 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const DeployType = { + Docker: "docker", + Binary: "binary", + Deploy: "deploy", +} as const; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const AuthMethod = { + Key: "key", + Password: "password", +} as const; + +export interface ExtraField { + key: string; + value: string; +} + +export interface SSHAgentConfig { + user: string; + ip: string; + authMethod: string; + sshKey?: string; + password?: string; + extraFields: ExtraField[]; + deployType: string; +} diff --git a/frontend/src/modules/agent/ui/SSHAgentForm.tsx b/frontend/src/modules/agent/ui/SSHAgentForm.tsx new file mode 100644 index 0000000..d53b187 --- /dev/null +++ b/frontend/src/modules/agent/ui/SSHAgentForm.tsx @@ -0,0 +1,496 @@ +import React from "react"; +import { + FiServer, + FiGlobe, + FiKey, + FiLock, + FiPlus, + FiTrash2, + FiSettings, +} from "react-icons/fi"; +import { SiDocker } from "react-icons/si"; +import { FiPackage, FiUploadCloud } from "react-icons/fi"; + +type DeployType = "docker" | "binary" | "deploy"; +type AuthMethod = "key" | "password"; + +interface ExtraField { + key: string; + value: string; +} + +export interface SSHAgentConfig { + user: string; + ip: string; + authMethod: AuthMethod; + sshKey?: string; + password?: string; + extraFields: ExtraField[]; + deployType: DeployType; +} + +interface SSHAgentFormProps { + index: number; + config: SSHAgentConfig; + onChange: (index: number, config: SSHAgentConfig) => void; + onRemove: (index: number) => void; + canRemove: boolean; +} + +const DEPLOY_OPTIONS: { + value: DeployType; + label: string; + icon: React.ReactNode; +}[] = [ + { value: "docker", label: "Docker", icon: }, + { value: "binary", label: "Binary", icon: }, +]; + +const inputBaseStyle: 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, +}; + +export const SSHAgentForm: React.FC = ({ + index, + config, + onChange, + onRemove, + canRemove, +}) => { + const handleChange = (field: keyof SSHAgentConfig, value: unknown) => { + onChange(index, { ...config, [field]: value }); + }; + + const handleExtraFieldChange = ( + fieldIndex: number, + field: keyof ExtraField, + value: string, + ) => { + const newExtraFields = [...config.extraFields]; + newExtraFields[fieldIndex] = { + ...newExtraFields[fieldIndex], + [field]: value, + }; + handleChange("extraFields", newExtraFields); + }; + + const addExtraField = () => { + handleChange("extraFields", [ + ...config.extraFields, + { key: "", value: "" }, + ]); + }; + + const removeExtraField = (fieldIndex: number) => { + const newExtraFields = config.extraFields.filter( + (_, i) => i !== fieldIndex, + ); + handleChange("extraFields", newExtraFields); + }; + + const handleFocus = ( + e: React.FocusEvent< + HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement + >, + ) => { + e.currentTarget.style.borderColor = "var(--border-focus)"; + e.currentTarget.style.boxShadow = `0 0 0 3px var(--border-focus)30`; + }; + + const handleBlur = ( + e: React.FocusEvent< + HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement + >, + ) => { + e.currentTarget.style.borderColor = "var(--border)"; + e.currentTarget.style.boxShadow = "none"; + }; + + return ( +
+ {/* Header */} +
+
+
+ +
+

+ SSH сервер #{index + 1} +

+
+ {canRemove && ( + + )} +
+ +
+ {/* User и IP */} +
+
+ + handleChange("user", e.target.value)} + required + style={inputBaseStyle} + onFocus={handleFocus} + onBlur={handleBlur} + placeholder="username" + /> +
+ +
+ + handleChange("ip", e.target.value)} + required + style={inputBaseStyle} + onFocus={handleFocus} + onBlur={handleBlur} + placeholder="192.168.1.1" + /> +
+
+ + {/* Метод аутентификации */} +
+ +
+ {(["key", "password"] as const).map((method) => ( + + ))} +
+
+ + {/* SSH Key или Password */} + {config.authMethod === "key" ? ( +
+ +