1Panel/frontend/src/views/setting/snapshot/create/index.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>