@@ -0,0 +1,224 @@
|
||||
import React, { useState } from "react";
|
||||
import { SSHAgentForm } from "../modules/agent/ui/SSHAgentForm";
|
||||
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 {
|
||||
// TODO: Реальный API вызов для развертывания агентов
|
||||
console.log("Deploying agents:", agents);
|
||||
|
||||
// Имитация задержки API
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
|
||||
setSubmitMessage(
|
||||
`Успешно отправлено ${agents.length} сервер(ов) на развертывание`,
|
||||
);
|
||||
setAgents([createEmptyAgentConfig()]);
|
||||
} catch (error) {
|
||||
setSubmitError("Ошибка при развертывании на серверах");
|
||||
} 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user