Initial commit: DSH Web Manager
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { useState } from 'react';
|
||||
import { api, instancePath, type Instance, type Backup } from './api';
|
||||
import { ResourceState, Empty, bytes, date, useAction, useResource } from './ui';
|
||||
|
||||
export function BackupPanel({ instance, refresh }: { instance: Instance; refresh: () => Promise<void> }) {
|
||||
const base = instancePath(instance.id);
|
||||
const backups = useResource(() => api.request<Backup[]>(base + '/backups'));
|
||||
const action = useAction();
|
||||
const [password, setPassword] = useState(''), [originalPassword, setOriginalPassword] = useState(''), [file, setFile] = useState<File>();
|
||||
return <section aria-label="备份与恢复">
|
||||
<p className="callout">操作会按需停止实例并恢复原运行状态。备份使用创建时的登录密码加密;修改登录密码不改变旧包密码。恢复包含插件重建、重启验证和失败回滚。</p>
|
||||
{action.feedback}
|
||||
<form onSubmit={e => { e.preventDefault(); void action.run(async () => { try { await api.request(base + '/backups', 'POST', { password }); await backups.refresh(); } finally { setPassword(''); } }, '加密备份已创建'); }}>
|
||||
<label>当前登录密码<input type="password" autoComplete="current-password" required value={password} onChange={e => setPassword(e.target.value)} /></label>
|
||||
<button disabled={action.busy || !password}>创建加密备份</button>
|
||||
</form>
|
||||
<label>原备份密码(旧平台密钥包留空)<input type="password" autoComplete="off" value={originalPassword} onChange={e => setOriginalPassword(e.target.value)} /></label>
|
||||
<form onSubmit={e => { e.preventDefault(); if (!file) return; void action.run(async () => { try { if (file.size > 44743364) throw new Error('备份包超过大小限制'); const form = new FormData(); form.set('file', file); form.set('password', originalPassword); await api.request(base + '/backups', 'POST', form); await backups.refresh(); } finally { setOriginalPassword(''); } }, '备份已验证并导入;尚未覆盖当前配置'); }}>
|
||||
<label>加密备份文件<input type="file" accept=".dshbak" onChange={e => setFile(e.target.files?.[0])} /></label>
|
||||
<button disabled={action.busy || !file}>验证并导入</button>
|
||||
</form>
|
||||
<ResourceState resource={backups} />
|
||||
{backups.data?.length === 0 && <Empty>暂无配置备份</Empty>}
|
||||
{backups.data?.map(b => <div className="backup-row" key={b.id}><span>{b.id}</span><span>{date(b.created_at)}</span><span>{bytes(b.size)}</span><a className="button" href={'/api' + base + '/backups?download=' + encodeURIComponent(b.id)}>下载</a><button disabled={action.busy} onClick={() => action.run(async () => { try { await api.request(base + '/restore', 'POST', { backup_id: b.id, password: originalPassword }); await refresh(); } finally { setOriginalPassword(''); } }, '配置恢复及插件验证完成,已恢复原运行状态。', '确认覆盖当前配置?服务器将先保留当前 home 快照。')}>恢复</button></div>)}
|
||||
</section>;
|
||||
}
|
||||
Reference in New Issue
Block a user