mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-03-14 01:34:47 +08:00
parent
8ca6b9b6fa
commit
3b697c7520
@ -52,6 +52,8 @@ type SettingInfo struct {
|
||||
AppStoreSyncStatus string `json:"appStoreSyncStatus"`
|
||||
|
||||
FileRecycleBin string `json:"fileRecycleBin"`
|
||||
|
||||
SnapshotIgnore string `json:"snapshotIgnore"`
|
||||
}
|
||||
|
||||
type SettingUpdate struct {
|
||||
|
@ -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)
|
||||
|
@ -72,6 +72,8 @@ func Init() {
|
||||
migrations.UpdateSnapshotRecords,
|
||||
|
||||
migrations.UpdateWebDavConf,
|
||||
|
||||
migrations.AddSnapshotIgnore,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
17
backend/init/migration/migrations/v_1_10.go
Normal file
17
backend/init/migration/migrations/v_1_10.go
Normal 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
|
||||
},
|
||||
}
|
@ -45,6 +45,7 @@ export namespace Setting {
|
||||
emailVars: string;
|
||||
weChatVars: string;
|
||||
dingVars: string;
|
||||
snapshotIgnore: string;
|
||||
}
|
||||
export interface SettingUpdate {
|
||||
key: string;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -1237,6 +1237,9 @@ const message = {
|
||||
|
||||
snapshot: '快照',
|
||||
status: '快照狀態',
|
||||
ignoreRule: '排除規則',
|
||||
ignoreHelper: '快照時將使用該規則對 1Panel 數據目錄進行壓縮備份,請謹慎修改。',
|
||||
ignoreHelper1: '一行一個,例: \n*.log\n/opt/1panel/cache',
|
||||
panelInfo: '寫入 1Panel 基礎信息',
|
||||
panelBin: '備份 1Panel 系統文件',
|
||||
daemonJson: '備份 Docker 配置文件',
|
||||
|
@ -1237,6 +1237,9 @@ const message = {
|
||||
path: '路径',
|
||||
|
||||
snapshot: '快照',
|
||||
ignoreRule: '排除规则',
|
||||
ignoreHelper: '快照时将使用该规则对 1Panel 数据目录进行压缩备份,请谨慎修改。',
|
||||
ignoreHelper1: '一行一个,例: \n*.log\n/opt/1panel/cache',
|
||||
status: '快照状态',
|
||||
panelInfo: '写入 1Panel 基础信息',
|
||||
panelBin: '备份 1Panel 系统文件',
|
||||
|
153
frontend/src/views/setting/snapshot/ignore-rule/index.vue
Normal file
153
frontend/src/views/setting/snapshot/ignore-rule/index.vue
Normal 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>
|
@ -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;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user