从现有站点恢复重命名为从指定站点恢复

This commit is contained in:
jingrow 2025-10-21 05:01:20 +08:00
parent da3f814451
commit 057d8164f5
3 changed files with 190 additions and 158 deletions

View File

@ -45,7 +45,7 @@ function getSiteActionHandler(action) {
'使用文件恢复': defineAsyncComponent(() => '使用文件恢复': defineAsyncComponent(() =>
import('./SiteDatabaseRestoreDialog.vue') import('./SiteDatabaseRestoreDialog.vue')
), ),
'从现有站点恢复': defineAsyncComponent(() => '从指定站点恢复': defineAsyncComponent(() =>
import('./site/SiteDatabaseRestoreFromURLDialog.vue') import('./site/SiteDatabaseRestoreFromURLDialog.vue')
), ),
'管理数据库用户': defineAsyncComponent(() => '管理数据库用户': defineAsyncComponent(() =>

View File

@ -1,156 +1,188 @@
<template> <template>
<Dialog <Dialog
:options="{ :options="{
title: '从现有站点恢复' title: '从指定站点恢复'
}" }"
v-model="showRestoreDialog" v-model="showRestoreDialog"
> >
<template #body-content> <template #body-content>
<div <div
class="mb-6 flex items-center rounded border border-gray-200 bg-gray-100 p-4 text-sm text-gray-600" class="mb-6 flex items-center rounded border border-gray-200 bg-gray-100 p-4 text-sm text-gray-600"
> >
<i-lucide-alert-triangle class="mr-4 inline-block h-6 w-6" /> <i-lucide-alert-triangle class="mr-4 inline-block h-6 w-6" />
<div> <div>
此操作将用备份中的<b>数据</b><b>应用</b>替换您站点中的当前内容 此操作将用备份中的<b>数据</b><b>应用</b>替换您站点中的当前内容
</div> </div>
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
<FormControl label="站点URL" v-model="siteURL" /> <FormControl label="站点URL" v-model="siteURL" />
<FormControl label="邮箱" v-model="email" /> <FormControl label="邮箱" v-model="email" />
<FormControl label="密码" type="password" v-model="password" /> <FormControl label="密码" type="password" v-model="password" />
<div class="flex text-base" v-if="$resources.getBackupLinks.data"> <div class="flex text-base" v-if="$resources.getBackupLinks.data">
<GreenCheckIcon class="mr-2 w-4" /> <GreenCheckIcon class="mr-2 w-4" />
找到来自 {{ fetchedBackupFileTimestamp }} 的最新备份 找到来自 {{ fetchedBackupFileTimestamp }} 的最新备份
</div> </div>
<Button <Button
v-else v-else
@click="$resources.getBackupLinks.submit()" @click="$resources.getBackupLinks.submit()"
:loading="$resources.getBackupLinks.loading" :loading="$resources.getBackupLinks.loading"
> >
获取备份 获取备份
</Button> </Button>
</div> </div>
<div class="mt-3"> <div class="mt-3">
<FormControl <FormControl
label="跳过失败的补丁(如果有任何补丁失败)" label="跳过失败的补丁(如果有任何补丁失败)"
type="checkbox" type="checkbox"
v-model="skipFailingPatches" v-model="skipFailingPatches"
/> />
</div> </div>
<ErrorMessage <ErrorMessage
class="mt-2" class="mt-2"
:message=" :message="
$resources.restoreBackup.error || $resources.getBackupLinks.error $resources.restoreBackup.error || $resources.getBackupLinks.error
" "
/> />
</template> </template>
<template #actions> <template #actions>
<Button <Button
class="w-full" class="w-full"
label="恢复" label="恢复"
variant="solid" variant="solid"
theme="red" theme="red"
:loading="$resources.restoreBackup.loading" :loading="$resources.restoreBackup.loading"
:disabled="!$resources.getBackupLinks.data" :disabled="!$resources.getBackupLinks.data"
@click="$resources.restoreBackup.submit" @click="$resources.restoreBackup.submit"
/> />
</template> </template>
</Dialog> </Dialog>
</template> </template>
<script> <script>
import { date } from '../../utils/format'; import { date } from '../../utils/format';
import { DashboardError } from '../../utils/error'; import { DashboardError } from '../../utils/error';
export default { export default {
name: 'SiteDatabaseRestoreDialog', name: 'SiteDatabaseRestoreDialog',
props: { props: {
site: { site: {
type: String, type: String,
required: true required: true
} }
}, },
data() { data() {
return { return {
siteURL: '', siteURL: '',
email: '', email: '',
password: '', password: '',
selectedFiles: { selectedFiles: {
database: null, database: null,
public: null, public: null,
private: null private: null
}, },
showRestoreDialog: true, showRestoreDialog: true,
skipFailingPatches: false skipFailingPatches: false
}; };
}, },
resources: { resources: {
getBackupLinks() { getBackupLinks() {
return { return {
url: 'jcloud.api.site.get_backup_links', url: 'jcloud.api.site.get_backup_links',
params: { params: {
url: this.siteURL, url: this.siteURL,
email: this.email, email: this.email,
password: this.password password: this.password
}, },
validate() { validate() {
if (!this.siteURL) { if (!this.siteURL) {
throw new DashboardError('站点URL是必填项'); throw new DashboardError('站点URL是必填项');
} }
if (!this.email) { if (!this.email) {
throw new DashboardError('邮箱是必填项'); throw new DashboardError('邮箱是必填项');
} }
if (!this.password) { if (!this.password) {
throw new DashboardError('密码是必填项'); throw new DashboardError('密码是必填项');
} }
}, },
onSuccess(remoteFiles) { onSuccess(remoteFiles) {
for (let file of remoteFiles) { for (let file of remoteFiles) {
this.selectedFiles[file.type] = file.remote_file; this.selectedFiles[file.type] = file.remote_file;
} }
} }
}; };
}, },
restoreBackup() { restoreBackup() {
return { return {
url: 'jcloud.api.site.restore', url: 'jcloud.api.site.restore',
params: { params: {
name: this.site, name: this.site,
files: this.selectedFiles, files: this.selectedFiles,
skip_failing_patches: this.skipFailingPatches skip_failing_patches: this.skipFailingPatches
}, },
validate() { validate() {
if (!this.selectedFiles.database) { if (!this.selectedFiles.database) {
throw new DashboardError( throw new DashboardError(
'从站点获取备份时出错' '从站点获取备份时出错'
); );
} }
}, },
onSuccess() { onSuccess() {
this.siteURL = ''; this.siteURL = '';
this.email = ''; this.email = '';
this.password = ''; this.password = '';
this.showRestoreDialog = false; this.showRestoreDialog = false;
this.$router.push({ this.$router.push({
name: 'Site Jobs', name: 'Site Jobs',
params: { name: this.site } params: { name: this.site }
}); });
} }
}; };
} }
}, },
computed: { computed: {
fetchedBackupFileTimestamp() { fetchedBackupFileTimestamp() {
if (!this.$resources.getBackupLinks.data) return ''; if (!this.$resources.getBackupLinks.data) return '';
let backup = this.$resources.getBackupLinks.data[0]; let backup = this.$resources.getBackupLinks.data[0];
let timestamp_string = backup.file_name if (!backup || !backup.file_name) return '';
.split('-')[0]
.split('_') let timestamp_string = '';
.join('T');
// YYYYMMDD_HHMMSS
return date(timestamp_string); let timestampMatch = backup.file_name.match(/(\d{8}_\d{6})/);
} if (timestampMatch) {
} let match = timestampMatch[1];
}; let year = match.substring(0, 4);
let month = match.substring(4, 6);
let day = match.substring(6, 8);
let hour = match.substring(9, 11);
let minute = match.substring(11, 13);
let second = match.substring(13, 15);
timestamp_string = `${year}-${month}-${day}T${hour}:${minute}:${second}`;
} else {
// YYYY-MM-DD_HH-MM-SS
let oldFormatMatch = backup.file_name.match(/(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})/);
if (oldFormatMatch) {
timestamp_string = oldFormatMatch[1].replace('_', 'T').replace(/-/g, '-');
} else {
// YYYY-MM-DD
let dateMatch = backup.file_name.match(/(\d{4}-\d{2}-\d{2})/);
if (dateMatch) {
timestamp_string = dateMatch[1] + 'T00:00:00';
} else {
//
return backup.file_name;
}
}
}
try {
return date(timestamp_string);
} catch (e) {
//
return backup.file_name;
}
}
}
};
</script> </script>

View File

@ -2783,8 +2783,8 @@ class Site(Page, TagHelpers):
"group": "危险操作", "group": "危险操作",
}, },
{ {
"action": "现有站点恢复", "action": "指定站点恢复",
"description": "另一个站点恢复数据库、公共和私有文件", "description": "指定站点的备份文件恢复数据库、公共和私有文件",
"button_label": "恢复", "button_label": "恢复",
"pg_method": "restore_site_from_files", "pg_method": "restore_site_from_files",
"group": "危险操作", "group": "危险操作",