177 lines
7.1 KiB
TypeScript
Executable File
177 lines
7.1 KiB
TypeScript
Executable File
/**
|
||
* Machine save confirmation modal
|
||
*/
|
||
|
||
import React, { useState } from 'react';
|
||
import { Save, X } from 'lucide-react';
|
||
import { useMachineStore } from '../store/machineStore';
|
||
import { log } from '../utils/logger';
|
||
import { Button } from './shared/Button';
|
||
|
||
interface SaveConfirmationModalProps {
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
onSuccess?: () => void;
|
||
}
|
||
|
||
export const SaveConfirmationModal: React.FC<SaveConfirmationModalProps> = ({
|
||
isOpen,
|
||
onClose,
|
||
onSuccess
|
||
}) => {
|
||
const {
|
||
machineToSave,
|
||
createSavedMachine,
|
||
setMachineToSave,
|
||
setShowSaveConfirmModal
|
||
} = useMachineStore();
|
||
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
const handleConfirm = async () => {
|
||
if (!machineToSave) {
|
||
setError('Нет данных для сохранения');
|
||
return;
|
||
}
|
||
|
||
setLoading(true);
|
||
setError(null);
|
||
|
||
try {
|
||
log.info('SaveConfirmationModal', 'Saving machine', { name: machineToSave.name });
|
||
|
||
await createSavedMachine(machineToSave);
|
||
|
||
log.info('SaveConfirmationModal', 'Machine saved successfully');
|
||
|
||
// Сбрасываем состояние
|
||
setMachineToSave(null);
|
||
setShowSaveConfirmModal(false);
|
||
|
||
if (onSuccess) {
|
||
onSuccess();
|
||
}
|
||
|
||
onClose();
|
||
} catch (err) {
|
||
const errorMessage = err instanceof Error ? err.message : 'Не удалось сохранить машину';
|
||
log.error('SaveConfirmationModal', 'Failed to save machine', { error: errorMessage });
|
||
setError(errorMessage);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleCancel = () => {
|
||
setMachineToSave(null);
|
||
setShowSaveConfirmModal(false);
|
||
setError(null);
|
||
onClose();
|
||
};
|
||
|
||
if (!isOpen || !machineToSave) return null;
|
||
|
||
return (
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||
<div className="bg-kaspersky-bg-card rounded-lg shadow-2xl border border-kaspersky-border max-w-md w-full select-none">
|
||
<div className="p-6">
|
||
{/* Header */}
|
||
<div className="flex items-center gap-3 mb-6">
|
||
<div className="flex-shrink-0 w-12 h-12 bg-kaspersky-primary bg-opacity-10 rounded-full flex items-center justify-center border-2 border-kaspersky-primary border-opacity-30">
|
||
<Save className="w-6 h-6 text-kaspersky-primary" />
|
||
</div>
|
||
<div>
|
||
<h2 className="text-xl font-bold text-kaspersky-text-dark">Сохранить машину?</h2>
|
||
<p className="text-sm text-kaspersky-text-light">Подтвердите сохранение в профиль</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Error */}
|
||
{error && (
|
||
<div className="mb-4 p-3 bg-kaspersky-danger bg-opacity-10 border border-kaspersky-danger rounded text-kaspersky-danger text-sm">
|
||
⚠️ {error}
|
||
</div>
|
||
)}
|
||
|
||
{/* Machine Info */}
|
||
<div className="mb-6 p-4 bg-kaspersky-bg rounded-lg border border-kaspersky-border space-y-3">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-kaspersky-text-light text-sm font-medium">Название:</span>
|
||
<span className="text-kaspersky-text-dark font-semibold">{machineToSave.name}</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-kaspersky-text-light text-sm font-medium">Хост:</span>
|
||
<span className="text-kaspersky-text-dark font-mono text-sm">{machineToSave.hostname}:{machineToSave.port}</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-kaspersky-text-light text-sm font-medium">Протокол:</span>
|
||
<span className="text-kaspersky-primary uppercase text-sm font-bold">{machineToSave.protocol}</span>
|
||
</div>
|
||
{machineToSave.description && (
|
||
<div className="pt-3 border-t border-kaspersky-border">
|
||
<span className="text-kaspersky-text-light text-sm font-medium block mb-1">Описание:</span>
|
||
<span className="text-kaspersky-text text-sm">{machineToSave.description}</span>
|
||
</div>
|
||
)}
|
||
{machineToSave.tags && machineToSave.tags.length > 0 && (
|
||
<div className="pt-3 border-t border-kaspersky-border">
|
||
<span className="text-kaspersky-text-light text-sm font-medium block mb-2">Теги:</span>
|
||
<div className="flex flex-wrap gap-1.5">
|
||
{machineToSave.tags.map((tag, index) => (
|
||
<span
|
||
key={index}
|
||
className="px-2.5 py-1 bg-kaspersky-primary bg-opacity-20 text-kaspersky-primary rounded-md text-xs font-medium border border-kaspersky-primary border-opacity-30"
|
||
>
|
||
{tag}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
{machineToSave.is_favorite && (
|
||
<div className="pt-3 border-t border-kaspersky-border flex items-center gap-2">
|
||
<svg className="w-5 h-5 text-kaspersky-warning" fill="currentColor" viewBox="0 0 20 20">
|
||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||
</svg>
|
||
<span className="text-kaspersky-warning text-sm font-semibold">В избранном</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Description */}
|
||
<div className="mb-6 p-3 bg-kaspersky-primary bg-opacity-5 rounded-lg border border-kaspersky-primary border-opacity-20">
|
||
<p className="text-kaspersky-text text-sm">
|
||
💾 Машина будет сохранена в вашем профиле и доступна из любой сессии.
|
||
Учетные данные для подключения будут запрашиваться отдельно при каждом подключении.
|
||
</p>
|
||
</div>
|
||
|
||
{/* Actions */}
|
||
<div className="flex gap-3">
|
||
<Button
|
||
onClick={handleConfirm}
|
||
disabled={loading}
|
||
variant="primary"
|
||
fullWidth
|
||
isLoading={loading}
|
||
leftIcon={!loading ? <Save size={18} /> : undefined}
|
||
>
|
||
{loading ? 'Сохранение...' : 'Сохранить'}
|
||
</Button>
|
||
<Button
|
||
onClick={handleCancel}
|
||
disabled={loading}
|
||
variant="secondary"
|
||
leftIcon={<X size={18} />}
|
||
>
|
||
Отмена
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|