main #2
@ -45,7 +45,7 @@ function getSiteActionHandler(action) {
|
||||
'使用文件恢复': defineAsyncComponent(() =>
|
||||
import('./SiteDatabaseRestoreDialog.vue')
|
||||
),
|
||||
'从现有站点恢复': defineAsyncComponent(() =>
|
||||
'从指定站点恢复': defineAsyncComponent(() =>
|
||||
import('./site/SiteDatabaseRestoreFromURLDialog.vue')
|
||||
),
|
||||
'管理数据库用户': defineAsyncComponent(() =>
|
||||
|
||||
@ -1,156 +1,188 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:options="{
|
||||
title: '从现有站点恢复'
|
||||
}"
|
||||
v-model="showRestoreDialog"
|
||||
>
|
||||
<template #body-content>
|
||||
<div
|
||||
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" />
|
||||
<div>
|
||||
此操作将用备份中的<b>数据</b>和<b>应用</b>替换您站点中的当前内容
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<FormControl label="站点URL" v-model="siteURL" />
|
||||
<FormControl label="邮箱" v-model="email" />
|
||||
<FormControl label="密码" type="password" v-model="password" />
|
||||
<div class="flex text-base" v-if="$resources.getBackupLinks.data">
|
||||
<GreenCheckIcon class="mr-2 w-4" />
|
||||
找到来自 {{ fetchedBackupFileTimestamp }} 的最新备份
|
||||
</div>
|
||||
<Button
|
||||
v-else
|
||||
@click="$resources.getBackupLinks.submit()"
|
||||
:loading="$resources.getBackupLinks.loading"
|
||||
>
|
||||
获取备份
|
||||
</Button>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<FormControl
|
||||
label="跳过失败的补丁(如果有任何补丁失败)"
|
||||
type="checkbox"
|
||||
v-model="skipFailingPatches"
|
||||
/>
|
||||
</div>
|
||||
<ErrorMessage
|
||||
class="mt-2"
|
||||
:message="
|
||||
$resources.restoreBackup.error || $resources.getBackupLinks.error
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #actions>
|
||||
<Button
|
||||
class="w-full"
|
||||
label="恢复"
|
||||
variant="solid"
|
||||
theme="red"
|
||||
:loading="$resources.restoreBackup.loading"
|
||||
:disabled="!$resources.getBackupLinks.data"
|
||||
@click="$resources.restoreBackup.submit"
|
||||
/>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script>
|
||||
import { date } from '../../utils/format';
|
||||
import { DashboardError } from '../../utils/error';
|
||||
|
||||
export default {
|
||||
name: 'SiteDatabaseRestoreDialog',
|
||||
props: {
|
||||
site: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
siteURL: '',
|
||||
email: '',
|
||||
password: '',
|
||||
selectedFiles: {
|
||||
database: null,
|
||||
public: null,
|
||||
private: null
|
||||
},
|
||||
showRestoreDialog: true,
|
||||
skipFailingPatches: false
|
||||
};
|
||||
},
|
||||
resources: {
|
||||
getBackupLinks() {
|
||||
return {
|
||||
url: 'jcloud.api.site.get_backup_links',
|
||||
params: {
|
||||
url: this.siteURL,
|
||||
email: this.email,
|
||||
password: this.password
|
||||
},
|
||||
validate() {
|
||||
if (!this.siteURL) {
|
||||
throw new DashboardError('站点URL是必填项');
|
||||
}
|
||||
if (!this.email) {
|
||||
throw new DashboardError('邮箱是必填项');
|
||||
}
|
||||
if (!this.password) {
|
||||
throw new DashboardError('密码是必填项');
|
||||
}
|
||||
},
|
||||
onSuccess(remoteFiles) {
|
||||
for (let file of remoteFiles) {
|
||||
this.selectedFiles[file.type] = file.remote_file;
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
restoreBackup() {
|
||||
return {
|
||||
url: 'jcloud.api.site.restore',
|
||||
params: {
|
||||
name: this.site,
|
||||
files: this.selectedFiles,
|
||||
skip_failing_patches: this.skipFailingPatches
|
||||
},
|
||||
validate() {
|
||||
if (!this.selectedFiles.database) {
|
||||
throw new DashboardError(
|
||||
'从站点获取备份时出错'
|
||||
);
|
||||
}
|
||||
},
|
||||
onSuccess() {
|
||||
this.siteURL = '';
|
||||
this.email = '';
|
||||
this.password = '';
|
||||
this.showRestoreDialog = false;
|
||||
|
||||
this.$router.push({
|
||||
name: 'Site Jobs',
|
||||
params: { name: this.site }
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fetchedBackupFileTimestamp() {
|
||||
if (!this.$resources.getBackupLinks.data) return '';
|
||||
|
||||
let backup = this.$resources.getBackupLinks.data[0];
|
||||
let timestamp_string = backup.file_name
|
||||
.split('-')[0]
|
||||
.split('_')
|
||||
.join('T');
|
||||
|
||||
return date(timestamp_string);
|
||||
}
|
||||
}
|
||||
};
|
||||
<template>
|
||||
<Dialog
|
||||
:options="{
|
||||
title: '从指定站点恢复'
|
||||
}"
|
||||
v-model="showRestoreDialog"
|
||||
>
|
||||
<template #body-content>
|
||||
<div
|
||||
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" />
|
||||
<div>
|
||||
此操作将用备份中的<b>数据</b>和<b>应用</b>替换您站点中的当前内容
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<FormControl label="站点URL" v-model="siteURL" />
|
||||
<FormControl label="邮箱" v-model="email" />
|
||||
<FormControl label="密码" type="password" v-model="password" />
|
||||
<div class="flex text-base" v-if="$resources.getBackupLinks.data">
|
||||
<GreenCheckIcon class="mr-2 w-4" />
|
||||
找到来自 {{ fetchedBackupFileTimestamp }} 的最新备份
|
||||
</div>
|
||||
<Button
|
||||
v-else
|
||||
@click="$resources.getBackupLinks.submit()"
|
||||
:loading="$resources.getBackupLinks.loading"
|
||||
>
|
||||
获取备份
|
||||
</Button>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<FormControl
|
||||
label="跳过失败的补丁(如果有任何补丁失败)"
|
||||
type="checkbox"
|
||||
v-model="skipFailingPatches"
|
||||
/>
|
||||
</div>
|
||||
<ErrorMessage
|
||||
class="mt-2"
|
||||
:message="
|
||||
$resources.restoreBackup.error || $resources.getBackupLinks.error
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #actions>
|
||||
<Button
|
||||
class="w-full"
|
||||
label="恢复"
|
||||
variant="solid"
|
||||
theme="red"
|
||||
:loading="$resources.restoreBackup.loading"
|
||||
:disabled="!$resources.getBackupLinks.data"
|
||||
@click="$resources.restoreBackup.submit"
|
||||
/>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script>
|
||||
import { date } from '../../utils/format';
|
||||
import { DashboardError } from '../../utils/error';
|
||||
|
||||
export default {
|
||||
name: 'SiteDatabaseRestoreDialog',
|
||||
props: {
|
||||
site: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
siteURL: '',
|
||||
email: '',
|
||||
password: '',
|
||||
selectedFiles: {
|
||||
database: null,
|
||||
public: null,
|
||||
private: null
|
||||
},
|
||||
showRestoreDialog: true,
|
||||
skipFailingPatches: false
|
||||
};
|
||||
},
|
||||
resources: {
|
||||
getBackupLinks() {
|
||||
return {
|
||||
url: 'jcloud.api.site.get_backup_links',
|
||||
params: {
|
||||
url: this.siteURL,
|
||||
email: this.email,
|
||||
password: this.password
|
||||
},
|
||||
validate() {
|
||||
if (!this.siteURL) {
|
||||
throw new DashboardError('站点URL是必填项');
|
||||
}
|
||||
if (!this.email) {
|
||||
throw new DashboardError('邮箱是必填项');
|
||||
}
|
||||
if (!this.password) {
|
||||
throw new DashboardError('密码是必填项');
|
||||
}
|
||||
},
|
||||
onSuccess(remoteFiles) {
|
||||
for (let file of remoteFiles) {
|
||||
this.selectedFiles[file.type] = file.remote_file;
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
restoreBackup() {
|
||||
return {
|
||||
url: 'jcloud.api.site.restore',
|
||||
params: {
|
||||
name: this.site,
|
||||
files: this.selectedFiles,
|
||||
skip_failing_patches: this.skipFailingPatches
|
||||
},
|
||||
validate() {
|
||||
if (!this.selectedFiles.database) {
|
||||
throw new DashboardError(
|
||||
'从站点获取备份时出错'
|
||||
);
|
||||
}
|
||||
},
|
||||
onSuccess() {
|
||||
this.siteURL = '';
|
||||
this.email = '';
|
||||
this.password = '';
|
||||
this.showRestoreDialog = false;
|
||||
|
||||
this.$router.push({
|
||||
name: 'Site Jobs',
|
||||
params: { name: this.site }
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fetchedBackupFileTimestamp() {
|
||||
if (!this.$resources.getBackupLinks.data) return '';
|
||||
|
||||
let backup = this.$resources.getBackupLinks.data[0];
|
||||
if (!backup || !backup.file_name) return '';
|
||||
|
||||
let timestamp_string = '';
|
||||
|
||||
// 尝试匹配 YYYYMMDD_HHMMSS 格式(实际格式)
|
||||
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>
|
||||
@ -2783,8 +2783,8 @@ class Site(Page, TagHelpers):
|
||||
"group": "危险操作",
|
||||
},
|
||||
{
|
||||
"action": "从现有站点恢复",
|
||||
"description": "从另一个站点恢复数据库、公共和私有文件",
|
||||
"action": "从指定站点恢复",
|
||||
"description": "从指定站点的备份文件恢复数据库、公共和私有文件",
|
||||
"button_label": "恢复",
|
||||
"pg_method": "restore_site_from_files",
|
||||
"group": "危险操作",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user