import React, { useEffect, useState } from 'react' import { Plus, Pencil, Trash2, RefreshCw } from 'lucide-react' import { api } from '../api/client' interface Package { id: number; name: string; diskMb: number; bandwidthMb: number maxDatabases: number; maxEmailAccounts: number; maxSubdomains: number phpVersion: string; sslEnabled: boolean _count?: { accounts: number } } const blank = { name: '', diskMb: 1024, bandwidthMb: 10240, maxDatabases: 5, maxEmailAccounts: 10, maxSubdomains: 10, maxAddonDomains: 5, maxParkedDomains: 5, maxFtpAccounts: 3, maxCronJobs: 5, phpVersion: '8.3', sslEnabled: true, backupEnabled: true, } export default function Packages() { const [packages, setPackages] = useState([]) const [loading, setLoading] = useState(false) const [showModal, setShowModal] = useState(false) const [editing, setEditing] = useState(null) const [form, setForm] = useState(blank) const [saving, setSaving] = useState(false) const [error, setError] = useState('') const fetch = async () => { setLoading(true) try { const r = await api.get('/nixserver/packages'); setPackages(r.data.data) } finally { setLoading(false) } } useEffect(() => { fetch() }, []) const openCreate = () => { setEditing(null); setForm(blank); setError(''); setShowModal(true) } const openEdit = (p: Package) => { setEditing(p) setForm({ ...blank, ...p }) setError('') setShowModal(true) } const save = async (e: React.FormEvent) => { e.preventDefault(); setSaving(true); setError('') try { if (editing) await api.put(`/nixserver/packages/${editing.id}`, form) else await api.post('/nixserver/packages', form) setShowModal(false); fetch() } catch (err: unknown) { setError((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Save failed') } finally { setSaving(false) } } const del = async (p: Package) => { if (!confirm(`Delete package "${p.name}"?`)) return try { await api.delete(`/nixserver/packages/${p.id}`); fetch() } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Delete failed') } } const field = (k: keyof typeof form) => (e: React.ChangeEvent) => setForm(f => ({ ...f, [k]: e.target.type === 'number' ? Number(e.target.value) : e.target.type === 'checkbox' ? (e.target as HTMLInputElement).checked : e.target.value })) return (

Packages

{packages.length} hosting plans

{packages.map(p => (

{p.name}

{p._count?.accounts ?? 0} accounts

{[ ['Disk', p.diskMb === 0 ? 'Unlimited' : `${(p.diskMb / 1024).toFixed(0)} GB`], ['BW', p.bandwidthMb === 0 ? 'Unlimited' : `${(p.bandwidthMb / 1024).toFixed(0)} GB`], ['Databases', p.maxDatabases === 0 ? '∞' : p.maxDatabases], ['Emails', p.maxEmailAccounts === 0 ? '∞' : p.maxEmailAccounts], ['Subdomains', p.maxSubdomains === 0 ? '∞' : p.maxSubdomains], ['PHP', p.phpVersion], ].map(([l, v]) => (
{l} {v}
))}
{p.sslEnabled && SSL}
))}
{/* Modal */} {showModal && (

{editing ? 'Edit Package' : 'New Package'}

{error &&
{error}
}
{[ ['Disk (MB)', 'diskMb'], ['Bandwidth (MB)', 'bandwidthMb'], ['Max Databases', 'maxDatabases'], ['Max Email Accounts', 'maxEmailAccounts'], ['Max Subdomains', 'maxSubdomains'], ['Max Addon Domains', 'maxAddonDomains'], ['Max FTP Accounts', 'maxFtpAccounts'], ['Max Cron Jobs', 'maxCronJobs'], ].map(([label, key]) => (
))}
)}
) }