1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-13 17:24:44 +08:00

feat: 快照支持设置排除目录 (#4154)

Refs #4076
This commit is contained in:
ssongliu 2024-03-12 11:04:08 +08:00 committed by GitHub
parent 8ca6b9b6fa
commit 3b697c7520
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 215 additions and 1 deletions

View File

@ -52,6 +52,8 @@ type SettingInfo struct {
AppStoreSyncStatus string `json:"appStoreSyncStatus"`
FileRecycleBin string `json:"fileRecycleBin"`
SnapshotIgnore string `json:"snapshotIgnore"`
}
type SettingUpdate struct {

View File

@ -138,7 +138,14 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
if strings.Contains(localDir, dataDir) {
exclusionRules += ("." + strings.ReplaceAll(localDir, dataDir, "") + ";")
}
ignoreVal, _ := settingRepo.Get(settingRepo.WithByKey("SnapshotIgnore"))
rules := strings.Split(ignoreVal.Value, ",")
for _, ignore := range rules {
if len(ignore) == 0 || cmd.CheckIllegal(ignore) {
continue
}
exclusionRules += ("." + strings.ReplaceAll(ignore, dataDir, "") + ";")
}
_ = snapshotRepo.Update(snap.SnapID, map[string]interface{}{"status": "OnSaveData"})
sysIP, _ := settingRepo.Get(settingRepo.WithByKey("SystemIP"))
_ = settingRepo.Update("SystemIP", "")
@ -213,6 +220,7 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
}
}
exMap := make(map[string]struct{})
exStr := ""
excludes := strings.Split(exclusionRules, ";")
excludes = append(excludes, "*.sock")
@ -220,8 +228,12 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
if len(exclude) == 0 {
continue
}
if _, ok := exMap[exclude]; ok {
continue
}
exStr += " --exclude "
exStr += exclude
exMap[exclude] = struct{}{}
}
commands := fmt.Sprintf("tar --warning=no-file-changed --ignore-failed-read -zcf %s %s -C %s .", targetDir+"/"+name, exStr, sourceDir)

View File

@ -72,6 +72,8 @@ func Init() {
migrations.UpdateSnapshotRecords,
migrations.UpdateWebDavConf,
migrations.AddSnapshotIgnore,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View File

@ -0,0 +1,17 @@
package migrations
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
var AddSnapshotIgnore = &gormigrate.Migration{
ID: "20240311-add-snapshot-ignore",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "SnapshotIgnore", Value: "*.sock"}).Error; err != nil {
return err
}
return nil
},
}

View File

@ -45,6 +45,7 @@ export namespace Setting {
emailVars: string;
weChatVars: string;
dingVars: string;
snapshotIgnore: string;
}
export interface SettingUpdate {
key: string;

View File

@ -116,6 +116,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
isAll: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
@ -142,6 +146,9 @@ const closePage = () => {
};
const disabledDir = (row: File.File) => {
if (props.isAll) {
return false;
}
if (!props.dir) {
return row.isDir;
}

View File

@ -1396,6 +1396,10 @@ const message = {
snapshot: 'Snapshot',
status: 'Snapshot status',
ignoreRule: 'Ignore Rule',
ignoreHelper:
'This rule will be used to compress and backup the 1Panel data directory during snapshots, please modify with caution.',
ignoreHelper1: 'One item per line, e.g.: \n*.log\n/opt/1panel/cache',
panelInfo: 'Write 1Panel basic information',
panelBin: 'Backup 1Panel system files',
daemonJson: 'Backup Docker configuration file',

View File

@ -1237,6 +1237,9 @@ const message = {
snapshot: '快照',
status: '快照狀態',
ignoreRule: '排除規則',
ignoreHelper: '快照時將使用該規則對 1Panel 數據目錄進行壓縮備份請謹慎修改',
ignoreHelper1: '一行一個 \n*.log\n/opt/1panel/cache',
panelInfo: '寫入 1Panel 基礎信息',
panelBin: '備份 1Panel 系統文件',
daemonJson: '備份 Docker 配置文件',

View File

@ -1237,6 +1237,9 @@ const message = {
path: '路径',
snapshot: '快照',
ignoreRule: '排除规则',
ignoreHelper: '快照时将使用该规则对 1Panel 数据目录进行压缩备份请谨慎修改',
ignoreHelper1: '一行一个 \n*.log\n/opt/1panel/cache',
status: '快照状态',
panelInfo: '写入 1Panel 基础信息',
panelBin: '备份 1Panel 系统文件',

View File

@ -0,0 +1,153 @@
<template>
<div>
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
<template #header>
<DrawerHeader :header="$t('setting.ignoreRule')" :back="handleClose" />
</template>
<el-alert :closable="false" type="warning">{{ $t('setting.ignoreHelper') }}</el-alert>
<el-row type="flex" justify="center" v-loading="loading" class="mt-2">
<el-col :span="22">
<el-form ref="formRef" :model="form" :rules="rules">
<el-form-item prop="tmpRule">
<div class="w-full">
<el-input
v-model="form.tmpRule"
:rows="5"
style="width: calc(100% - 50px)"
type="textarea"
:placeholder="$t('setting.ignoreHelper1')"
/>
<FileList @choose="loadDir" :path="baseDir" :isAll="true"></FileList>
</div>
</el-form-item>
</el-form>
<el-button :disabled="form.tmpRule === ''" @click="handleAdd(formRef)">
{{ $t('xpack.tamper.addRule') }}
</el-button>
<el-table :data="tableList">
<el-table-column prop="value" />
<el-table-column min-width="18">
<template #default="scope">
<el-button link type="primary" @click="handleDelete(scope.$index)">
{{ $t('commons.button.delete') }}
</el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button @click="drawerVisible = false">{{ $t('commons.button.cancel') }}</el-button>
<el-button :disabled="loading" type="primary" @click="onSave()">
{{ $t('commons.button.save') }}
</el-button>
</span>
</template>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import FileList from '@/components/file-list/index.vue';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { FormInstance } from 'element-plus';
import { getSettingInfo, loadBaseDir, updateSetting } from '@/api/modules/setting';
const loading = ref();
const baseDir = ref();
const drawerVisible = ref(false);
const tableList = ref();
const form = reactive({
tmpRule: '',
});
const formRef = ref<FormInstance>();
const rules = reactive({
tmpRule: [{ validator: checkData, trigger: 'blur' }],
});
function checkData(rule: any, value: any, callback: any) {
if (form.tmpRule !== '') {
const reg = /^[^\\\"'|<>?]{1,128}$/;
let items = value.split('\n');
for (const item of items) {
if (item.indexOf(' ') !== -1) {
callback(new Error(i18n.global.t('setting.noSpace')));
}
if (!reg.test(item) && value !== '') {
callback(new Error(i18n.global.t('commons.rule.linuxName', ['\\:?\'"<>|'])));
} else {
callback();
}
}
}
callback();
}
const acceptParams = async (): Promise<void> => {
loadPath();
const res = await getSettingInfo();
tableList.value = [];
let items = res.data.snapshotIgnore.split(',');
for (const item of items) {
tableList.value.push({ value: item });
}
drawerVisible.value = true;
};
const loadPath = async () => {
const pathRes = await loadBaseDir();
baseDir.value = pathRes.data;
};
const loadDir = async (path: string) => {
form.tmpRule += path + '\n';
};
const handleAdd = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
let itemData = form.tmpRule.split('\n');
for (const item of itemData) {
if (item) {
tableList.value.push({ value: item });
}
}
form.tmpRule = '';
});
};
const handleDelete = (index: number) => {
tableList.value.splice(index, 1);
};
const onSave = async () => {
let list = [];
for (const item of tableList.value) {
list.push(item.value);
}
await updateSetting({ key: 'SnapshotIgnore', value: list.join(',') })
.then(async () => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
loading.value = false;
drawerVisible.value = false;
return;
})
.catch(() => {
loading.value = false;
});
};
const handleClose = () => {
drawerVisible.value = false;
};
defineExpose({
acceptParams,
});
</script>

View File

@ -7,6 +7,9 @@
<el-button type="primary" @click="onCreate()">
{{ $t('setting.createSnapshot') }}
</el-button>
<el-button type="primary" plain @click="onIgnore()">
{{ $t('setting.ignoreRule') }}
</el-button>
<el-button type="primary" plain @click="onImport()">
{{ $t('setting.importSnapshot') }}
</el-button>
@ -163,6 +166,7 @@
<OpDialog ref="opRef" @search="search" />
<SnapStatus ref="snapStatusRef" @search="search" />
<IgnoreRule ref="ignoreRef" />
</div>
</template>
@ -175,6 +179,7 @@ import { ElForm } from 'element-plus';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { Setting } from '@/api/interface/setting';
import IgnoreRule from '@/views/setting/snapshot/ignore-rule/index.vue';
import SnapStatus from '@/views/setting/snapshot/snap_status/index.vue';
import RecoverStatus from '@/views/setting/snapshot/status/index.vue';
import SnapshotImport from '@/views/setting/snapshot/import/index.vue';
@ -193,6 +198,7 @@ const paginationConfig = reactive({
const searchName = ref();
const opRef = ref();
const ignoreRef = ref();
const snapStatusRef = ref();
const recoverStatusRef = ref();
@ -231,6 +237,10 @@ const onImport = () => {
importRef.value.acceptParams({ names: names });
};
const onIgnore = () => {
ignoreRef.value.acceptParams();
};
const handleClose = () => {
drawerVisible.value = false;
};