优化备份后台布局
This commit is contained in:
@@ -17,8 +17,15 @@ const loading = ref(false)
|
|||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const status = ref<BackupStatus | null>(null)
|
const status = ref<BackupStatus | null>(null)
|
||||||
const statusError = ref('')
|
const statusError = ref('')
|
||||||
const canUpdateSchedule = computed(() => adminSession.isSuperAdmin || adminSession.hasPermission('backup:schedule:update'))
|
const canUpdateSchedule = computed(
|
||||||
const schedule = reactive({ enabled: true, full_hour: 4, full_minute: 30, binlog_interval_minutes: 1 })
|
() => adminSession.isSuperAdmin || adminSession.hasPermission('backup:schedule:update')
|
||||||
|
)
|
||||||
|
const schedule = reactive({
|
||||||
|
enabled: true,
|
||||||
|
full_hour: 4,
|
||||||
|
full_minute: 30,
|
||||||
|
binlog_interval_minutes: 1,
|
||||||
|
})
|
||||||
|
|
||||||
const healthLabel = computed(() => (status.value?.health === 'healthy' ? '链路正常' : '需要检查'))
|
const healthLabel = computed(() => (status.value?.health === 'healthy' ? '链路正常' : '需要检查'))
|
||||||
const healthType = computed(() => (status.value?.health === 'healthy' ? 'success' : 'warning'))
|
const healthType = computed(() => (status.value?.health === 'healthy' ? 'success' : 'warning'))
|
||||||
@@ -27,7 +34,10 @@ async function load() {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
statusError.value = ''
|
statusError.value = ''
|
||||||
try {
|
try {
|
||||||
const [nextStatus, nextSchedule] = await Promise.all([fetchBackupStatus(), fetchBackupSchedule()])
|
const [nextStatus, nextSchedule] = await Promise.all([
|
||||||
|
fetchBackupStatus(),
|
||||||
|
fetchBackupSchedule(),
|
||||||
|
])
|
||||||
status.value = nextStatus
|
status.value = nextStatus
|
||||||
Object.assign(schedule, nextSchedule)
|
Object.assign(schedule, nextSchedule)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -64,7 +74,7 @@ onMounted(load)
|
|||||||
<el-alert v-if="statusError" type="warning" :title="statusError" :closable="false" show-icon />
|
<el-alert v-if="statusError" type="warning" :title="statusError" :closable="false" show-icon />
|
||||||
|
|
||||||
<div v-else class="status-grid">
|
<div v-else class="status-grid">
|
||||||
<el-card shadow="never">
|
<el-card shadow="never" class="status-card health-card">
|
||||||
<template #header><span>备份健康状态</span></template>
|
<template #header><span>备份健康状态</span></template>
|
||||||
<el-tag :type="healthType" effect="light">{{ healthLabel }}</el-tag>
|
<el-tag :type="healthType" effect="light">{{ healthLabel }}</el-tag>
|
||||||
<dl>
|
<dl>
|
||||||
@@ -77,7 +87,7 @@ onMounted(load)
|
|||||||
</dl>
|
</dl>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card shadow="never">
|
<el-card shadow="never" class="status-card full-card">
|
||||||
<template #header><span>最新全量备份</span></template>
|
<template #header><span>最新全量备份</span></template>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>本地完成标识</dt>
|
<dt>本地完成标识</dt>
|
||||||
@@ -87,7 +97,7 @@ onMounted(load)
|
|||||||
</dl>
|
</dl>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card shadow="never">
|
<el-card shadow="never" class="status-card binlog-card">
|
||||||
<template #header><span>最新 Binlog 归档</span></template>
|
<template #header><span>最新 Binlog 归档</span></template>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>Binlog 文件</dt>
|
<dt>Binlog 文件</dt>
|
||||||
@@ -101,41 +111,176 @@ onMounted(load)
|
|||||||
<el-card shadow="never" class="schedule-card">
|
<el-card shadow="never" class="schedule-card">
|
||||||
<template #header><span>定时配置</span></template>
|
<template #header><span>定时配置</span></template>
|
||||||
<el-form label-position="top" class="schedule-form">
|
<el-form label-position="top" class="schedule-form">
|
||||||
|
<div class="schedule-fields">
|
||||||
<el-form-item label="启用服务器定时备份">
|
<el-form-item label="启用服务器定时备份">
|
||||||
<el-switch v-model="schedule.enabled" :disabled="!canUpdateSchedule" />
|
<el-switch v-model="schedule.enabled" :disabled="!canUpdateSchedule" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="每日全量备份时间(Asia/Shanghai)">
|
<el-form-item label="每日全量备份时间(Asia/Shanghai)">
|
||||||
<div class="time-inputs">
|
<div class="time-inputs">
|
||||||
<el-input-number v-model="schedule.full_hour" :min="0" :max="23" :disabled="!canUpdateSchedule" controls-position="right" />
|
<el-input-number
|
||||||
|
v-model="schedule.full_hour"
|
||||||
|
:min="0"
|
||||||
|
:max="23"
|
||||||
|
:disabled="!canUpdateSchedule"
|
||||||
|
controls-position="right"
|
||||||
|
/>
|
||||||
<span>:</span>
|
<span>:</span>
|
||||||
<el-input-number v-model="schedule.full_minute" :min="0" :max="59" :disabled="!canUpdateSchedule" controls-position="right" />
|
<el-input-number
|
||||||
|
v-model="schedule.full_minute"
|
||||||
|
:min="0"
|
||||||
|
:max="59"
|
||||||
|
:disabled="!canUpdateSchedule"
|
||||||
|
controls-position="right"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Binlog 归档间隔">
|
<el-form-item label="Binlog 归档间隔">
|
||||||
<el-select v-model="schedule.binlog_interval_minutes" :disabled="!canUpdateSchedule">
|
<el-select
|
||||||
|
v-model="schedule.binlog_interval_minutes"
|
||||||
|
class="binlog-interval-select"
|
||||||
|
:disabled="!canUpdateSchedule"
|
||||||
|
>
|
||||||
<el-option :value="1" label="每 1 分钟" />
|
<el-option :value="1" label="每 1 分钟" />
|
||||||
<el-option :value="2" label="每 2 分钟" />
|
<el-option :value="2" label="每 2 分钟" />
|
||||||
<el-option :value="3" label="每 3 分钟" />
|
<el-option :value="3" label="每 3 分钟" />
|
||||||
<el-option :value="5" label="每 5 分钟" />
|
<el-option :value="5" label="每 5 分钟" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-button v-if="canUpdateSchedule" type="primary" :loading="saving" @click="saveSchedule">保存定时配置</el-button>
|
</div>
|
||||||
|
<div v-if="canUpdateSchedule" class="schedule-actions">
|
||||||
|
<el-button type="primary" :loading="saving" @click="saveSchedule">保存定时配置</el-button>
|
||||||
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.backup-page { padding: 20px; }
|
.backup-page {
|
||||||
.page-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 18px; }
|
padding: 20px;
|
||||||
.page-header h1 { margin: 0; font-size: 22px; font-weight: 600; }
|
}
|
||||||
.status-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
|
|
||||||
dl { margin: 16px 0 0; display: grid; gap: 8px; }
|
.page-header {
|
||||||
dt { color: var(--el-text-color-secondary); font-size: 13px; }
|
display: flex;
|
||||||
dd { margin: 0; overflow-wrap: anywhere; }
|
align-items: flex-start;
|
||||||
.path { color: var(--el-text-color-regular); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
|
justify-content: space-between;
|
||||||
.schedule-card { margin-top: 16px; }
|
margin-bottom: 18px;
|
||||||
.schedule-form { display: grid; grid-template-columns: repeat(3, minmax(180px, 260px)); gap: 0 18px; align-items: end; }
|
}
|
||||||
.time-inputs { display: flex; align-items: center; gap: 8px; }
|
|
||||||
@media (max-width: 900px) { .status-grid, .schedule-form { grid-template-columns: 1fr; } }
|
.page-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.health-card {
|
||||||
|
grid-column: span 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-card {
|
||||||
|
grid-column: span 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.binlog-card {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 16px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
min-width: 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path {
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-card {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-form {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-fields {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 250px 280px 180px;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-fields :deep(.el-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-inputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-inputs :deep(.el-input-number) {
|
||||||
|
width: 112px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.binlog-interval-select {
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-actions {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.health-card {
|
||||||
|
grid-column: span 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-card {
|
||||||
|
grid-column: span 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-fields {
|
||||||
|
grid-template-columns: repeat(2, minmax(220px, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.backup-page {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.health-card,
|
||||||
|
.full-card,
|
||||||
|
.binlog-card {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schedule-fields {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user