mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-10-10 23:47:39 +08:00
558 lines
18 KiB
Vue
558 lines
18 KiB
Vue
<template>
|
|
<DrawerPro v-model="drawerVisible" :header="$t('setting.snapshot')" :back="handleClose" size="large">
|
|
<fu-steps
|
|
v-loading="loading"
|
|
class="steps"
|
|
:space="50"
|
|
ref="stepsRef"
|
|
direction="vertical"
|
|
:isLoading="stepLoading"
|
|
:finishButtonText="$t('commons.button.create')"
|
|
@change="changeStep"
|
|
:beforeLeave="beforeLeave"
|
|
>
|
|
<fu-step id="baseData" :title="$t('setting.stepBaseData')">
|
|
<el-form
|
|
v-loading="loading"
|
|
class="mt-5"
|
|
label-position="top"
|
|
ref="formRef"
|
|
:model="form"
|
|
:rules="rules"
|
|
>
|
|
<el-form-item :label="$t('setting.backupAccount')" prop="fromAccounts">
|
|
<el-select multiple @change="changeAccount" v-model="form.fromAccounts" clearable>
|
|
<div v-for="item in backupOptions" :key="item.id">
|
|
<el-option
|
|
v-if="item.type !== $t('setting.LOCAL')"
|
|
:value="item.id"
|
|
:label="item.type + ' - ' + item.name"
|
|
/>
|
|
<el-option v-else :value="item.id" :label="item.type" />
|
|
</div>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item :label="$t('cronjob.default_download_path')" prop="downloadAccountID">
|
|
<el-select v-model="form.downloadAccountID" clearable>
|
|
<div v-for="item in accountOptions" :key="item.id">
|
|
<el-option
|
|
v-if="item.type !== $t('setting.LOCAL')"
|
|
:value="item.id"
|
|
:label="item.type + ' - ' + item.name"
|
|
/>
|
|
<el-option v-else :value="item.id" :label="item.type" />
|
|
</div>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item :label="$t('setting.compressPassword')" prop="secret">
|
|
<el-input v-model="form.secret"></el-input>
|
|
</el-form-item>
|
|
<el-form-item :label="$t('commons.table.description')" prop="description">
|
|
<el-input type="textarea" clearable v-model="form.description" />
|
|
</el-form-item>
|
|
</el-form>
|
|
</fu-step>
|
|
<fu-step id="appData" :title="$t('setting.stepAppData')">
|
|
<div class="mt-5 mb-5" v-if="!form.appData || form.appData.length === 0">
|
|
<span class="input-help">{{ $t('setting.noAppData') }}</span>
|
|
</div>
|
|
<div v-else>
|
|
<el-checkbox
|
|
class="ml-6"
|
|
v-model="form.backupAllImage"
|
|
@change="selectAllImage"
|
|
:label="$t('setting.selectAllImage')"
|
|
size="large"
|
|
/>
|
|
<el-tree
|
|
style="max-width: 600px"
|
|
ref="appRef"
|
|
node-key="id"
|
|
:data="form.appData"
|
|
:props="defaultProps"
|
|
@check-change="onChangeAppData"
|
|
show-checkbox
|
|
>
|
|
<template #default="{ data }">
|
|
<div class="float-left">
|
|
<span>{{ loadApp18n(data.label) }}</span>
|
|
</div>
|
|
<div class="ml-4 float-left">
|
|
<span v-if="data.size">{{ computeSize(data.size) }}</span>
|
|
</div>
|
|
</template>
|
|
</el-tree>
|
|
</div>
|
|
</fu-step>
|
|
<fu-step id="panelData" :title="$t('setting.stepPanelData')">
|
|
<el-tree
|
|
style="max-width: 600px"
|
|
ref="panelRef"
|
|
node-key="id"
|
|
:data="form.panelData"
|
|
:props="defaultProps"
|
|
show-checkbox
|
|
>
|
|
<template #default="{ node, data }">
|
|
<div class="float-left">
|
|
<span>{{ load18n(node, data.label) }}</span>
|
|
</div>
|
|
<div class="ml-4 float-left">
|
|
<span v-if="data.size">{{ computeSize(data.size) }}</span>
|
|
</div>
|
|
</template>
|
|
</el-tree>
|
|
</fu-step>
|
|
<fu-step id="backupData" :title="$t('setting.stepBackupData')">
|
|
<div class="mt-5 mb-5" v-if="!form.appData || form.appData.length === 0">
|
|
<span class="input-help">{{ $t('setting.noBackupData') }}</span>
|
|
</div>
|
|
<div v-else>
|
|
<el-tree
|
|
style="max-width: 600px"
|
|
ref="backupRef"
|
|
node-key="id"
|
|
:data="form.backupData"
|
|
:props="defaultProps"
|
|
show-checkbox
|
|
>
|
|
<template #default="{ node, data }">
|
|
<div class="float-left">
|
|
<span>{{ load18n(node, data.label) }}</span>
|
|
</div>
|
|
<div class="ml-4 float-left">
|
|
<span v-if="data.size">{{ computeSize(data.size) }}</span>
|
|
</div>
|
|
</template>
|
|
</el-tree>
|
|
</div>
|
|
</fu-step>
|
|
<fu-step id="otherData" :title="$t('setting.stepOtherData')">
|
|
<div class="ml-5">
|
|
<el-checkbox v-model="form.withOperationLog" :label="$t('logs.operation')" size="large" />
|
|
</div>
|
|
<div class="ml-5">
|
|
<el-checkbox v-model="form.withLoginLog" :label="$t('logs.login')" size="large" />
|
|
</div>
|
|
<div class="ml-5">
|
|
<el-checkbox v-model="form.withSystemLog" :label="$t('logs.system')" size="large" />
|
|
</div>
|
|
<div class="ml-5">
|
|
<el-checkbox v-model="form.withTaskLog" :label="$t('logs.task')" size="large" />
|
|
</div>
|
|
<div class="ml-5">
|
|
<el-checkbox v-model="form.withMonitorData" :label="$t('setting.monitorData')" size="large" />
|
|
</div>
|
|
</fu-step>
|
|
<template #footer></template>
|
|
</fu-steps>
|
|
<template #footer>
|
|
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
|
|
<el-button @click="prev" v-if="nowIndex !== 0">{{ $t('commons.button.prev') }}</el-button>
|
|
<el-button type="primary" v-if="nowIndex === 4" @click="submitAddSnapshot">
|
|
{{ $t('commons.button.create') }}
|
|
</el-button>
|
|
<el-button @click="next" v-else>{{ $t('commons.button.next') }}</el-button>
|
|
</template>
|
|
</DrawerPro>
|
|
<TaskLog ref="taskLogRef" width="70%" />
|
|
</template>
|
|
<script lang="ts" setup>
|
|
import { ref } from 'vue';
|
|
import { loadSnapshotInfo, snapshotCreate } from '@/api/modules/setting';
|
|
import { computeSize, newUUID } from '@/utils/util';
|
|
import i18n from '@/lang';
|
|
import TaskLog from '@/components/task-log/index.vue';
|
|
import { listBackupOptions } from '@/api/modules/backup';
|
|
import { Rules } from '@/global/form-rules';
|
|
import { ElForm } from 'element-plus';
|
|
import { MsgSuccess } from '@/utils/message';
|
|
|
|
const loading = ref();
|
|
const stepLoading = ref(false);
|
|
const stepsRef = ref();
|
|
const nowIndex = ref(0);
|
|
|
|
const appRef = ref();
|
|
const panelRef = ref();
|
|
const backupRef = ref();
|
|
const taskLogRef = ref();
|
|
|
|
const backupOptions = ref();
|
|
const accountOptions = ref();
|
|
|
|
type FormInstance = InstanceType<typeof ElForm>;
|
|
const formRef = ref<FormInstance>();
|
|
const form = reactive({
|
|
id: 0,
|
|
taskID: '',
|
|
downloadAccountID: '',
|
|
fromAccounts: [],
|
|
sourceAccountIDs: '',
|
|
description: '',
|
|
secret: '',
|
|
|
|
backupAllImage: false,
|
|
withLoginLog: false,
|
|
withOperationLog: false,
|
|
withSystemLog: false,
|
|
withTaskLog: false,
|
|
withMonitorData: false,
|
|
|
|
panelData: [],
|
|
backupData: [],
|
|
appData: [],
|
|
});
|
|
const rules = reactive({
|
|
fromAccounts: [Rules.requiredSelect],
|
|
downloadAccountID: [Rules.requiredSelect],
|
|
});
|
|
|
|
const defaultProps = {
|
|
children: 'children',
|
|
label: 'label',
|
|
checked: 'isCheck',
|
|
disabled: 'isDisable',
|
|
};
|
|
const drawerVisible = ref();
|
|
|
|
const emit = defineEmits(['search']);
|
|
const acceptParams = (): void => {
|
|
search();
|
|
loadBackups();
|
|
drawerVisible.value = true;
|
|
};
|
|
|
|
const handleClose = () => {
|
|
drawerVisible.value = false;
|
|
};
|
|
|
|
const submitForm = async (formEl: any) => {
|
|
let bool;
|
|
if (!formEl) return;
|
|
await formEl.validate((valid: boolean) => {
|
|
if (valid) {
|
|
bool = true;
|
|
} else {
|
|
bool = false;
|
|
}
|
|
});
|
|
return bool;
|
|
};
|
|
const beforeLeave = async (stepItem: any) => {
|
|
switch (stepItem.id) {
|
|
case 'baseData':
|
|
if (await submitForm(formRef.value)) {
|
|
stepsRef.value.next();
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
case 'appData':
|
|
if (form.appData && form.appData.length !== 0) {
|
|
let appChecks = appRef.value.getCheckedNodes();
|
|
loadCheckForSubmit(appChecks, form.appData);
|
|
}
|
|
return true;
|
|
case 'panelData':
|
|
let panelChecks = panelRef.value.getCheckedNodes();
|
|
loadCheckForSubmit(panelChecks, form.panelData);
|
|
return true;
|
|
case 'backupData':
|
|
if (form.backupData && form.backupData.length !== 0) {
|
|
let backupChecks = backupRef.value.getCheckedNodes();
|
|
loadCheckForSubmit(backupChecks, form.backupData);
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
function next() {
|
|
stepsRef.value.next();
|
|
}
|
|
function prev() {
|
|
stepsRef.value.prev();
|
|
}
|
|
|
|
const loadApp18n = (label: string) => {
|
|
switch (label) {
|
|
case 'appData':
|
|
return i18n.global.t('setting.appDataLabel');
|
|
case 'appImage':
|
|
case 'appBackup':
|
|
return i18n.global.t('setting.' + label);
|
|
default:
|
|
return label;
|
|
}
|
|
};
|
|
|
|
const loadBackups = async () => {
|
|
const res = await listBackupOptions();
|
|
backupOptions.value = [];
|
|
for (const item of res.data) {
|
|
if (item.id !== 0) {
|
|
backupOptions.value.push({ id: item.id, type: i18n.global.t('setting.' + item.type), name: item.name });
|
|
}
|
|
}
|
|
changeAccount();
|
|
};
|
|
|
|
const changeStep = (currentStep: any) => {
|
|
nowIndex.value = currentStep.index;
|
|
switch (currentStep.id) {
|
|
case 'appData':
|
|
if (appRef.value) {
|
|
return;
|
|
}
|
|
nextTick(() => {
|
|
setAppDefaultCheck(form.appData);
|
|
});
|
|
return;
|
|
case 'panelData':
|
|
if (panelRef.value) {
|
|
return;
|
|
}
|
|
nextTick(() => {
|
|
setPanelDefaultCheck(form.panelData);
|
|
return;
|
|
});
|
|
return;
|
|
case 'backupData':
|
|
if (backupRef.value) {
|
|
return;
|
|
}
|
|
nextTick(() => {
|
|
setBackupDefaultCheck(form.backupData);
|
|
return;
|
|
});
|
|
return;
|
|
}
|
|
};
|
|
|
|
const changeAccount = async () => {
|
|
accountOptions.value = [];
|
|
let isInAccounts = false;
|
|
for (const item of backupOptions.value) {
|
|
let exist = false;
|
|
for (const ac of form.fromAccounts) {
|
|
if (item.id == ac) {
|
|
exist = true;
|
|
break;
|
|
}
|
|
}
|
|
if (exist) {
|
|
if (item.id === form.downloadAccountID) {
|
|
isInAccounts = true;
|
|
}
|
|
accountOptions.value.push(item);
|
|
}
|
|
}
|
|
if (!isInAccounts) {
|
|
form.downloadAccountID = form.downloadAccountID ? undefined : form.downloadAccountID;
|
|
}
|
|
};
|
|
|
|
const load18n = (node: any, label: string) => {
|
|
if (node.level === 1) {
|
|
switch (label) {
|
|
case 'agent':
|
|
case 'conf':
|
|
case 'docker':
|
|
case 'log':
|
|
case 'runtime':
|
|
case 'task':
|
|
case 'app':
|
|
case 'database':
|
|
case 'website':
|
|
case 'directory':
|
|
return i18n.global.t('setting.' + label + 'Label');
|
|
case 'system_snapshot':
|
|
return i18n.global.t('setting.snapshotLabel');
|
|
default:
|
|
return label;
|
|
}
|
|
}
|
|
if (node.level === 2) {
|
|
switch (label) {
|
|
case 'App':
|
|
return i18n.global.t('setting.appLabel');
|
|
case 'AppStore':
|
|
return i18n.global.t('setting.appStoreLabel');
|
|
case 'shell':
|
|
return i18n.global.t('setting.shellLabel');
|
|
default:
|
|
return label;
|
|
}
|
|
}
|
|
return label;
|
|
};
|
|
|
|
const submitAddSnapshot = async () => {
|
|
loading.value = true;
|
|
form.taskID = newUUID();
|
|
form.sourceAccountIDs = form.fromAccounts.join(',');
|
|
await snapshotCreate(form)
|
|
.then(() => {
|
|
loading.value = false;
|
|
drawerVisible.value = false;
|
|
emit('search');
|
|
openTaskLog(form.taskID);
|
|
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
|
})
|
|
.catch(() => {
|
|
loading.value = false;
|
|
});
|
|
};
|
|
|
|
const openTaskLog = (taskID: string) => {
|
|
taskLogRef.value.openWithTaskID(taskID);
|
|
};
|
|
|
|
const loadCheckForSubmit = (checks: any, list: any) => {
|
|
for (const item of list) {
|
|
let isCheck = false;
|
|
for (const check of checks) {
|
|
if (item.id == check.id) {
|
|
isCheck = true;
|
|
break;
|
|
}
|
|
}
|
|
item.isCheck = isCheck;
|
|
if (item.children) {
|
|
loadCheckForSubmit(checks, item.children);
|
|
}
|
|
}
|
|
};
|
|
|
|
const selectAllImage = () => {
|
|
for (const item of form.appData) {
|
|
for (const item2 of item.children) {
|
|
if (item2.label === 'appImage') {
|
|
appRef.value.setChecked(item2.id, form.backupAllImage, false);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const search = async () => {
|
|
const res = await loadSnapshotInfo();
|
|
form.panelData = res.data.panelData || [];
|
|
form.backupData = res.data.backupData || [];
|
|
form.appData = res.data.appData || [];
|
|
};
|
|
|
|
function onChangeAppData(data: any, isCheck: boolean) {
|
|
if (data.label !== 'appData' || !data.relationItemID) {
|
|
return;
|
|
}
|
|
data.isCheck = isCheck;
|
|
let isDisable = false;
|
|
for (const item of form.appData) {
|
|
if (!item.children) {
|
|
return;
|
|
}
|
|
for (const itemData of item.children) {
|
|
if (itemData.label === 'appData' && itemData.relationItemID === data.relationItemID && itemData.isCheck) {
|
|
isDisable = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (const item of form.appData) {
|
|
if (!item.children) {
|
|
return;
|
|
}
|
|
for (const relationItem of item.children) {
|
|
if (relationItem.id !== data.relationItemID) {
|
|
continue;
|
|
}
|
|
relationItem.isDisable = isDisable;
|
|
if (isDisable) {
|
|
appRef.value.setChecked(relationItem.id, isDisable, isDisable);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
const setAppDefaultCheck = async (list: any) => {
|
|
for (const item of list) {
|
|
if (item.isCheck) {
|
|
appRef.value.setChecked(item.id, true, true);
|
|
continue;
|
|
}
|
|
if (item.children) {
|
|
setAppDefaultCheck(item.children);
|
|
}
|
|
}
|
|
};
|
|
const setPanelDefaultCheck = async (list: any) => {
|
|
for (const item of list) {
|
|
if (item.isCheck) {
|
|
panelRef.value.setChecked(item.id, true, true);
|
|
continue;
|
|
}
|
|
if (item.children) {
|
|
setPanelDefaultCheck(item.children);
|
|
}
|
|
}
|
|
};
|
|
const setBackupDefaultCheck = async (list: any) => {
|
|
for (const item of list) {
|
|
if (item.isCheck) {
|
|
backupRef.value.setChecked(item.id, true, true);
|
|
continue;
|
|
}
|
|
if (item.children) {
|
|
setBackupDefaultCheck(item.children);
|
|
}
|
|
}
|
|
};
|
|
|
|
defineExpose({
|
|
acceptParams,
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.steps {
|
|
width: 100%;
|
|
margin-top: 20px;
|
|
:deep(.el-step) {
|
|
.el-step__line {
|
|
background-color: var(--el-color-primary-light-5);
|
|
}
|
|
.el-step__head.is-success {
|
|
color: var(--el-color-primary-light-5);
|
|
border-color: var(--el-color-primary-light-5);
|
|
}
|
|
.el-step__icon {
|
|
color: var(--el-color-primary-light-2);
|
|
}
|
|
.el-step__icon.is-text {
|
|
border-radius: 50%;
|
|
border: 2px solid;
|
|
border-color: var(--el-color-primary-light-2);
|
|
}
|
|
|
|
.el-step__title.is-finish {
|
|
color: #717379;
|
|
font-size: 13px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.el-step__description.is-finish {
|
|
color: #606266;
|
|
}
|
|
|
|
.el-step__title.is-success {
|
|
font-weight: bold;
|
|
color: var(--el-color-primary-light-2);
|
|
}
|
|
|
|
.el-step__title.is-process {
|
|
font-weight: bold;
|
|
color: var(--el-text-color-regular);
|
|
}
|
|
}
|
|
}
|
|
</style>
|