Add manual cloud backup controls and manager modal

This commit is contained in:
Hao19911125
2026-04-10 17:26:57 +08:00
parent a6b3137511
commit 09b6e1e566
11 changed files with 3016 additions and 925 deletions

View File

@@ -1072,6 +1072,74 @@
</label>
</div>
<div class="bme-config-card" style="margin-top: 20px;">
<div class="bme-config-card-head">
<div>
<div class="bme-config-card-title">云端存储模式</div>
<div class="bme-config-card-subtitle">
自动模式沿用当前镜像同步;手动模式停止自动云端写入,改为按当前聊天手动备份与恢复。
</div>
</div>
</div>
<div class="bme-config-row">
<label for="bme-setting-cloud-storage-mode">存储模式</label>
<select
id="bme-setting-cloud-storage-mode"
class="bme-config-input"
>
<option value="automatic">自动储存</option>
<option value="manual">手动储存</option>
</select>
</div>
<div class="bme-config-help" id="bme-cloud-storage-mode-help">
自动储存会继续按当前镜像逻辑同步;手动储存只保留本地写入,需要你主动备份和恢复。
</div>
<div
class="bme-config-help"
id="bme-cloud-storage-mode-status"
style="display: none; margin-top: 8px;"
></div>
<div
id="bme-cloud-backup-manual-actions"
class="bme-config-actions"
style="display: none; margin-top: 12px; flex-wrap: wrap;"
>
<button
class="bme-config-secondary-btn"
id="bme-act-backup-to-cloud"
type="button"
>
<i class="fa-solid fa-cloud-arrow-up"></i>
<span>备份到云端</span>
</button>
<button
class="bme-config-secondary-btn"
id="bme-act-restore-from-cloud"
type="button"
>
<i class="fa-solid fa-cloud-arrow-down"></i>
<span>从云端获取备份</span>
</button>
<button
class="bme-config-secondary-btn"
id="bme-act-manage-server-backups"
type="button"
>
<i class="fa-solid fa-box-archive"></i>
<span>管理服务器备份</span>
</button>
<button
class="bme-config-secondary-btn"
id="bme-act-rollback-last-restore"
type="button"
disabled
>
<i class="fa-solid fa-rotate-left"></i>
<span>回滚上次恢复</span>
</button>
</div>
</div>
<div class="bme-config-grid bme-config-grid-2" style="margin-top: 20px;">
<div class="bme-config-card">
<div class="bme-config-card-head">

File diff suppressed because it is too large Load Diff

View File

@@ -1123,6 +1123,7 @@ export async function onDeleteCurrentIdbController(runtime) {
}
const dbName = runtime.buildBmeDbName(chatId);
const restoreSafetyDbName = runtime.buildRestoreSafetyDbName?.(chatId) || "";
if (
!runtime.confirm(
`确定要删除当前聊天的本地缓存数据库?\n\n目标: ${dbName}\n操作不可撤销。`,
@@ -1139,6 +1140,14 @@ export async function onDeleteCurrentIdbController(runtime) {
req.onerror = () => reject(req.error);
req.onblocked = () => resolve();
});
if (restoreSafetyDbName) {
await new Promise((resolve, reject) => {
const req = indexedDB.deleteDatabase(restoreSafetyDbName);
req.onsuccess = () => resolve();
req.onerror = () => reject(req.error);
req.onblocked = () => resolve();
});
}
runtime.toastr.success(`已删除数据库 ${dbName}`);
} catch (error) {
runtime.toastr.error(`删除失败: ${error?.message || error}`);

View File

@@ -61,6 +61,12 @@ export function createGraphPersistenceState() {
lastSyncUploadedAt: 0,
lastSyncDownloadedAt: 0,
lastSyncedRevision: 0,
lastBackupUploadedAt: 0,
lastBackupRestoredAt: 0,
lastBackupRollbackAt: 0,
lastBackupFilename: "",
syncDirty: false,
syncDirtyReason: "",
lastSyncError: "",
dualWriteLastResult: null,
updatedAt: new Date().toISOString(),