123 lines
4.6 KiB
TypeScript
123 lines
4.6 KiB
TypeScript
import type { BackupPolicy } from '../soteria-types';
|
|
import { formatTimestamp } from '../soteria-ui-helpers';
|
|
|
|
interface BackupPoliciesPanelProps {
|
|
policies: BackupPolicy[];
|
|
policyError: string;
|
|
namespaceOptions: string[];
|
|
policyNamespace: string;
|
|
policyPVC: string;
|
|
policyIntervalHours: number;
|
|
policyEnabled: boolean;
|
|
policyDedupe: boolean;
|
|
policyKeepLast: number;
|
|
busy: boolean;
|
|
onPolicyNamespaceChange: (value: string) => void;
|
|
onPolicyPVCChange: (value: string) => void;
|
|
onPolicyIntervalHoursChange: (value: number) => void;
|
|
onPolicyEnabledChange: (value: boolean) => void;
|
|
onPolicyDedupeChange: (value: boolean) => void;
|
|
onPolicyKeepLastChange: (value: number) => void;
|
|
onSavePolicy: () => void | Promise<void>;
|
|
onDeletePolicy: (policyID: string) => void | Promise<void>;
|
|
onLoadPolicy: (policy: BackupPolicy) => void;
|
|
}
|
|
|
|
export function BackupPoliciesPanel({
|
|
policies,
|
|
policyError,
|
|
namespaceOptions,
|
|
policyNamespace,
|
|
policyPVC,
|
|
policyIntervalHours,
|
|
policyEnabled,
|
|
policyDedupe,
|
|
policyKeepLast,
|
|
busy,
|
|
onPolicyNamespaceChange,
|
|
onPolicyPVCChange,
|
|
onPolicyIntervalHoursChange,
|
|
onPolicyEnabledChange,
|
|
onPolicyDedupeChange,
|
|
onPolicyKeepLastChange,
|
|
onSavePolicy,
|
|
onDeletePolicy,
|
|
onLoadPolicy
|
|
}: BackupPoliciesPanelProps) {
|
|
return (
|
|
<section className="panel scroll-panel">
|
|
<h2>Backup Policies</h2>
|
|
<p className="subtle tiny">
|
|
Policy backups create new restic snapshots. `Keep last` controls version retention per PVC: 1 means only newest copy
|
|
remains after each run. With dedupe on, unchanged blocks are reused in the shared repository. With dedupe off,
|
|
Soteria isolates each PVC to its own repository path.
|
|
</p>
|
|
<div className="stack">
|
|
<label>
|
|
Namespace
|
|
<select value={policyNamespace} onChange={(event) => onPolicyNamespaceChange(event.target.value)}>
|
|
{namespaceOptions.map((item) => <option key={item} value={item}>{item}</option>)}
|
|
</select>
|
|
</label>
|
|
<label>
|
|
PVC (optional)
|
|
<input value={policyPVC} onChange={(event) => onPolicyPVCChange(event.target.value)} placeholder="blank means all PVCs in namespace" />
|
|
</label>
|
|
<label>
|
|
Interval hours
|
|
<input
|
|
type="number"
|
|
min={1}
|
|
value={policyIntervalHours}
|
|
onChange={(event) => onPolicyIntervalHoursChange(Math.max(1, Number(event.target.value || 1)))}
|
|
/>
|
|
</label>
|
|
<label className="checkbox-row">
|
|
<input type="checkbox" checked={policyEnabled} onChange={(event) => onPolicyEnabledChange(event.target.checked)} />
|
|
Enabled
|
|
</label>
|
|
<label className="checkbox-row">
|
|
<input type="checkbox" checked={policyDedupe} onChange={(event) => onPolicyDedupeChange(event.target.checked)} />
|
|
Dedupe unchanged blocks
|
|
</label>
|
|
<label>
|
|
Keep last snapshots per PVC (0 = keep all)
|
|
<input
|
|
type="number"
|
|
min={0}
|
|
max={1000}
|
|
value={policyKeepLast}
|
|
onChange={(event) => onPolicyKeepLastChange(Math.max(0, Math.min(1000, Number(event.target.value || 0))))}
|
|
/>
|
|
</label>
|
|
<button type="button" onClick={() => void onSavePolicy()} disabled={busy || !policyNamespace}>Save policy</button>
|
|
</div>
|
|
|
|
{policyError && <p className="error">{policyError}</p>}
|
|
{!policyError && policies.length === 0 && <p className="subtle">No policies yet.</p>}
|
|
<div className="policy-list">
|
|
{policies.map((policy) => (
|
|
<article key={policy.id} className="policy-item">
|
|
<div className="policy-head">
|
|
<strong>{policy.namespace}/{policy.pvc || '*'}</strong>
|
|
<span className={`chip ${policy.enabled ? 'good' : 'bad'}`}>{policy.enabled ? 'Enabled' : 'Disabled'}</span>
|
|
</div>
|
|
<p className="subtle tiny">
|
|
Every {policy.interval_hours}h | Dedupe: {policy.dedupe === false ? 'off' : 'on'} | Keep last: {policy.keep_last ?? 0}
|
|
{' '}| Updated {formatTimestamp(policy.updated_at || policy.created_at)}
|
|
</p>
|
|
<div className="actions">
|
|
<button type="button" className="secondary" onClick={() => onLoadPolicy(policy)}>
|
|
Load
|
|
</button>
|
|
<button type="button" className="secondary" onClick={() => void onDeletePolicy(policy.id)} disabled={busy}>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</article>
|
|
))}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|