Pulse/frontend-modern/src/components/UpdateConfirmationModal.tsx
2025-10-11 23:29:47 +00:00

164 lines
8 KiB
TypeScript

import { createSignal, Show, For } from 'solid-js';
import type { UpdatePlan } from '@/api/updates';
interface UpdateConfirmationModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
currentVersion: string;
latestVersion: string;
plan: UpdatePlan;
isApplying: boolean;
}
export function UpdateConfirmationModal(props: UpdateConfirmationModalProps) {
const [acknowledged, setAcknowledged] = createSignal(false);
const handleConfirm = () => {
if (acknowledged() && !props.isApplying) {
props.onConfirm();
}
};
return (
<Show when={props.isOpen}>
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
{/* Header */}
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-between">
<h2 class="text-xl font-semibold text-gray-900 dark:text-gray-100">
Confirm Update
</h2>
<button
onClick={props.onClose}
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
disabled={props.isApplying}
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
{/* Body */}
<div class="px-6 py-4 space-y-4">
{/* Version Jump */}
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
<div class="text-sm font-medium text-blue-900 dark:text-blue-100 mb-2">
Version Update
</div>
<div class="flex items-center gap-3 text-blue-800 dark:text-blue-200">
<span class="font-mono text-sm">{props.currentVersion}</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>
<span class="font-mono text-sm font-semibold">{props.latestVersion}</span>
</div>
</div>
{/* Estimated Time */}
<Show when={props.plan.estimatedTime}>
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>Estimated time: {props.plan.estimatedTime}</span>
</div>
</Show>
{/* Prerequisites */}
<Show when={props.plan.prerequisites && props.plan.prerequisites.length > 0}>
<div>
<div class="text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">
Prerequisites
</div>
<ul class="space-y-2">
<For each={props.plan.prerequisites}>
{(prerequisite) => (
<li class="flex items-start gap-2 text-sm text-gray-700 dark:text-gray-300">
<svg class="w-4 h-4 mt-0.5 flex-shrink-0 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<span>{prerequisite}</span>
</li>
)}
</For>
</ul>
</div>
</Show>
{/* Root Required Warning */}
<Show when={props.plan.requiresRoot}>
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-3">
<div class="flex items-start gap-2">
<svg class="w-5 h-5 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
<div class="text-sm text-yellow-800 dark:text-yellow-200">
<div class="font-medium">Root access required</div>
<div class="text-yellow-700 dark:text-yellow-300 mt-1">
This update requires elevated privileges to modify system files.
</div>
</div>
</div>
</div>
</Show>
{/* Rollback Support */}
<Show when={props.plan.rollbackSupport}>
<div class="flex items-center gap-2 text-sm text-green-600 dark:text-green-400">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>Automatic backup will be created</span>
</div>
</Show>
{/* Acknowledgement Checkbox */}
<div class="pt-4 border-t border-gray-200 dark:border-gray-700">
<label class="flex items-start gap-3 cursor-pointer">
<input
type="checkbox"
checked={acknowledged()}
onChange={(e) => setAcknowledged(e.currentTarget.checked)}
class="mt-1 w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 focus:ring-2"
disabled={props.isApplying}
/>
<span class="text-sm text-gray-700 dark:text-gray-300">
I understand that Pulse will be temporarily unavailable during the update process.
{props.plan.rollbackSupport && ' A backup will be created automatically.'}
</span>
</label>
</div>
</div>
{/* Footer */}
<div class="px-6 py-4 bg-gray-50 dark:bg-gray-900/50 border-t border-gray-200 dark:border-gray-700 flex items-center justify-end gap-3">
<button
onClick={props.onClose}
disabled={props.isApplying}
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
Cancel
</button>
<button
onClick={handleConfirm}
disabled={!acknowledged() || props.isApplying}
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
<Show when={props.isApplying}>
<svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</Show>
<span>{props.isApplying ? 'Starting...' : 'Start Update'}</span>
</button>
</div>
</div>
</div>
</Show>
);
}