- Added main application entry point (app.jsx) and bootstrap file for React setup. - Created Wiki index page (Index.jsx) for documentation and file management. - Implemented utility functions for formatting and status handling in utils.js. - Set up Blade template (app.blade.php) for Inertia.js integration. - Defined API routes for metrics and terminal access in api.php. - Established console commands for scheduled tasks in console.php. - Created web routes for various application features including authentication, hypervisors, containers, cloud accounts, security, backups, and wiki management in web.php. - Developed setup scripts for Windows (setup.ps1) and Linux (setup.sh) environments. - Configured Tailwind CSS for styling with custom themes and animations in tailwind.config.js. - Set up Vite for asset bundling and development server in vite.config.js.
115 lines
6.2 KiB
JavaScript
115 lines
6.2 KiB
JavaScript
import AppLayout from '@/Layouts/AppLayout';
|
|
import { Head, router } from '@inertiajs/react';
|
|
import { HardDrive, Plus, Download, RotateCcw, Calendar, CheckCircle, XCircle, Clock } from 'lucide-react';
|
|
import { GlassCard, StatCard } from '@/Components/ui/GlassCard';
|
|
import { StatusBadge } from '@/Components/ui/StatusBadge';
|
|
import { Button } from '@/Components/ui/Button';
|
|
import { DataTable } from '@/Components/ui/DataTable';
|
|
import { formatBytes, timeAgo } from '@/lib/utils';
|
|
|
|
export default function BackupIndex({ backups, policies, stats }) {
|
|
const backupColumns = [
|
|
{ header: 'Target', accessor: 'target_name', render: row => (
|
|
<div>
|
|
<p className="text-sm text-white font-medium">{row.target_name}</p>
|
|
<p className="text-xs text-white/30 capitalize">{row.target_type}</p>
|
|
</div>
|
|
)},
|
|
{ header: 'Size', accessor: 'size_bytes', render: row => (
|
|
<span className="text-sm text-white/70 font-mono">{row.size_bytes ? formatBytes(row.size_bytes) : '—'}</span>
|
|
)},
|
|
{ header: 'Status', accessor: 'status', render: row => <StatusBadge status={row.status} /> },
|
|
{ header: 'Created', accessor: 'created_at', render: row => (
|
|
<span className="text-xs text-white/40">{timeAgo(row.created_at)}</span>
|
|
)},
|
|
{ header: 'Expires', accessor: 'expires_at', render: row => (
|
|
row.expires_at
|
|
? <span className={`text-xs ${new Date(row.expires_at) < new Date() ? 'text-red-400' : 'text-white/40'}`}>{timeAgo(row.expires_at)}</span>
|
|
: <span className="text-xs text-white/20">Never</span>
|
|
)},
|
|
{ header: '', key: 'actions', render: row => (
|
|
<div className="flex gap-1" onClick={e => e.stopPropagation()}>
|
|
{row.status === 'success' && (
|
|
<>
|
|
<Button size="sm" variant="ghost" icon={Download} title="Download" />
|
|
<Button size="sm" variant="ghost" icon={RotateCcw} title="Restore"
|
|
onClick={() => confirm('Restore from this backup?') && router.post(`/backups/${row.id}/restore`)} />
|
|
</>
|
|
)}
|
|
<Button size="sm" variant="ghost" icon={XCircle} className="hover:text-red-400"
|
|
onClick={() => confirm('Delete this backup?') && router.delete(`/backups/${row.id}`)} />
|
|
</div>
|
|
)},
|
|
];
|
|
|
|
const policyColumns = [
|
|
{ header: 'Policy', accessor: 'name', render: row => (
|
|
<div>
|
|
<p className="text-sm text-white font-medium">{row.name}</p>
|
|
<p className="text-xs text-white/30 capitalize">{row.target_type} · {row.schedule}</p>
|
|
</div>
|
|
)},
|
|
{ header: 'Retention', accessor: 'retention_value', render: row => (
|
|
<span className="text-sm text-white/70">{row.retention_value} {row.retention_type === 'count' ? 'backups' : 'days'}</span>
|
|
)},
|
|
{ header: 'Storage', accessor: 'storage_type', render: row => (
|
|
<span className="text-xs px-2 py-0.5 rounded bg-white/5 text-white/60 uppercase">{row.storage_type}</span>
|
|
)},
|
|
{ header: 'Status', accessor: 'is_active', render: row => (
|
|
<StatusBadge status={row.is_active ? 'active' : 'stopped'} />
|
|
)},
|
|
];
|
|
|
|
return (
|
|
<AppLayout title="Backup & DR">
|
|
<Head title="Backup & DR" />
|
|
<div className="space-y-5">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h2 className="text-xl font-bold text-white">Backup & Disaster Recovery</h2>
|
|
<p className="text-sm text-white/40">Manage backups, schedules, and restore points</p>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button variant="secondary" icon={Calendar}>Schedules</Button>
|
|
<Button icon={Plus}>Create Policy</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
|
|
<StatCard title="Total Backups" value={stats?.total ?? 0} icon={HardDrive} color="cyan" />
|
|
<StatCard title="Successful" value={stats?.success ?? 0} icon={CheckCircle} color="green" />
|
|
<StatCard title="Failed" value={stats?.failed ?? 0} icon={XCircle} color="red" />
|
|
<StatCard title="Storage Used" value={stats?.total_size ?? '0 B'} icon={HardDrive} color="purple" />
|
|
</div>
|
|
|
|
{/* DR Status */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
|
{[
|
|
{ label: 'RTO', value: '< 4h', desc: 'Recovery Time Objective', color: 'text-green-400' },
|
|
{ label: 'RPO', value: '< 1h', desc: 'Recovery Point Objective', color: 'text-cyan-400' },
|
|
{ label: 'Last DR Test', value: 'N/A', desc: 'Last tested', color: 'text-white/40' },
|
|
].map(d => (
|
|
<GlassCard key={d.label} className="p-5 text-center">
|
|
<p className="text-xs text-white/40 uppercase tracking-wider mb-1">{d.label}</p>
|
|
<p className={`text-3xl font-bold ${d.color}`}>{d.value}</p>
|
|
<p className="text-xs text-white/30 mt-1">{d.desc}</p>
|
|
</GlassCard>
|
|
))}
|
|
</div>
|
|
|
|
<GlassCard className="p-5">
|
|
<h3 className="text-sm font-semibold text-white mb-4">Backup Policies</h3>
|
|
<DataTable columns={policyColumns} data={policies ?? []}
|
|
emptyMessage="No backup policies. Create one to start protecting your infrastructure." />
|
|
</GlassCard>
|
|
|
|
<GlassCard className="p-5">
|
|
<h3 className="text-sm font-semibold text-white mb-4">Backup History</h3>
|
|
<DataTable columns={backupColumns} data={backups?.data ?? []}
|
|
emptyMessage="No backups found." />
|
|
</GlassCard>
|
|
</div>
|
|
</AppLayout>
|
|
);
|
|
}
|