230 lines
7.5 KiB
TypeScript
230 lines
7.5 KiB
TypeScript
import React, { useState } from "react";
|
||
import { SSHAgentForm } from "../modules/agent/ui/SSHAgentForm";
|
||
import { agentApiService } from "../modules/agent/api/agent.api.service";
|
||
import { FiPlusCircle, FiSend } from "react-icons/fi";
|
||
|
||
interface SSHAgentConfig {
|
||
user: string;
|
||
ip: string;
|
||
authMethod: string;
|
||
sshKey?: string;
|
||
password?: string;
|
||
extraFields: { key: string; value: string }[];
|
||
deployType: string;
|
||
}
|
||
|
||
const createEmptyAgentConfig = (): SSHAgentConfig => ({
|
||
user: "",
|
||
ip: "",
|
||
authMethod: "key",
|
||
sshKey: "",
|
||
password: "",
|
||
extraFields: [],
|
||
deployType: "docker",
|
||
});
|
||
|
||
export const AddAgentsPage: React.FC = () => {
|
||
const [agents, setAgents] = useState<SSHAgentConfig[]>([
|
||
createEmptyAgentConfig(),
|
||
]);
|
||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||
const [submitMessage, setSubmitMessage] = useState<string | null>(null);
|
||
const [submitError, setSubmitError] = useState<string | null>(null);
|
||
|
||
const handleAgentChange = (index: number, config: SSHAgentConfig) => {
|
||
const newAgents = [...agents];
|
||
newAgents[index] = config;
|
||
setAgents(newAgents);
|
||
};
|
||
|
||
const handleAgentRemove = (index: number) => {
|
||
const newAgents = agents.filter((_, i) => i !== index);
|
||
setAgents(newAgents);
|
||
};
|
||
|
||
const handleAddAgent = () => {
|
||
setAgents([...agents, createEmptyAgentConfig()]);
|
||
};
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
|
||
// Валидация
|
||
const isValid = agents.every((agent) => {
|
||
if (!agent.user || !agent.ip) return false;
|
||
if (agent.authMethod === "key" && !agent.sshKey) return false;
|
||
if (agent.authMethod === "password" && !agent.password) return false;
|
||
return true;
|
||
});
|
||
|
||
if (!isValid) {
|
||
setSubmitError("Пожалуйста, заполните все обязательные поля");
|
||
return;
|
||
}
|
||
|
||
setIsSubmitting(true);
|
||
setSubmitMessage(null);
|
||
setSubmitError(null);
|
||
|
||
try {
|
||
// Получаем текущих агентов для проверки подключения
|
||
const currentAgents = await agentApiService.getAgents();
|
||
console.log("Current agents:", currentAgents);
|
||
|
||
// TODO: Реальный API вызов для развертывания агентов
|
||
// Пока выводим список подключенных агентов
|
||
setSubmitMessage(
|
||
`Успешно подключено ${currentAgents.length} агент(ов). Серверы: ${agents.length}`,
|
||
);
|
||
setAgents([createEmptyAgentConfig()]);
|
||
} catch (error) {
|
||
setSubmitError(
|
||
error instanceof Error
|
||
? error.message
|
||
: "Ошибка при подключении к серверам",
|
||
);
|
||
} finally {
|
||
setIsSubmitting(false);
|
||
}
|
||
};
|
||
|
||
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)" }}
|
||
>
|
||
<FiSend className="w-7 h-7" style={{ color: "var(--accent)" }} />
|
||
</div>
|
||
<div>
|
||
<h1
|
||
className="text-3xl font-bold mb-1"
|
||
style={{ color: "var(--text-primary)" }}
|
||
>
|
||
Развертывание агентов по SSH
|
||
</h1>
|
||
<p style={{ color: "var(--text-secondary)", fontSize: "16px" }}>
|
||
Настройте SSH-серверы и разверните агенты
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<form onSubmit={handleSubmit}>
|
||
{/* Agent Forms */}
|
||
<div className="space-y-5">
|
||
{agents.map((agent, index) => (
|
||
<SSHAgentForm
|
||
key={index}
|
||
index={index}
|
||
config={agent}
|
||
onChange={handleAgentChange}
|
||
onRemove={handleAgentRemove}
|
||
canRemove={agents.length > 1}
|
||
/>
|
||
))}
|
||
</div>
|
||
|
||
{/* Add Agent Button */}
|
||
<button
|
||
type="button"
|
||
onClick={handleAddAgent}
|
||
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";
|
||
}}
|
||
>
|
||
<FiPlusCircle size={18} />
|
||
Добавить ещё один сервер
|
||
</button>
|
||
|
||
{/* Messages */}
|
||
{submitMessage && (
|
||
<div
|
||
className="mb-6 p-4 rounded-lg border text-sm"
|
||
style={{
|
||
backgroundColor: "var(--success-bg)",
|
||
borderColor: "var(--success-border)",
|
||
color: "var(--success-text)",
|
||
}}
|
||
>
|
||
{submitMessage}
|
||
</div>
|
||
)}
|
||
|
||
{submitError && (
|
||
<div
|
||
className="mb-6 p-4 rounded-lg border text-sm"
|
||
style={{
|
||
backgroundColor: "var(--error-bg)",
|
||
borderColor: "var(--error-border)",
|
||
color: "var(--error-text)",
|
||
}}
|
||
>
|
||
{submitError}
|
||
</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"
|
||
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" />
|
||
Подключение к серверам...
|
||
</>
|
||
) : (
|
||
<>
|
||
<FiSend size={18} />
|
||
Развернуть на {agents.length} сервер(ах)
|
||
</>
|
||
)}
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|