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"`
|
AppStoreSyncStatus string `json:"appStoreSyncStatus"`
|
||||||
|
|
||||||
FileRecycleBin string `json:"fileRecycleBin"`
|
FileRecycleBin string `json:"fileRecycleBin"`
|
||||||
|
|
||||||
|
SnapshotIgnore string `json:"snapshotIgnore"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingUpdate struct {
|
type SettingUpdate struct {
|
||||||
|
@ -138,7 +138,14 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
|
|||||||
if strings.Contains(localDir, dataDir) {
|
if strings.Contains(localDir, dataDir) {
|
||||||
exclusionRules += ("." + strings.ReplaceAll(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"})
|
_ = snapshotRepo.Update(snap.SnapID, map[string]interface{}{"status": "OnSaveData"})
|
||||||
sysIP, _ := settingRepo.Get(settingRepo.WithByKey("SystemIP"))
|
sysIP, _ := settingRepo.Get(settingRepo.WithByKey("SystemIP"))
|
||||||
_ = settingRepo.Update("SystemIP", "")
|
_ = settingRepo.Update("SystemIP", "")
|
||||||
@ -213,6 +220,7 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exMap := make(map[string]struct{})
|
||||||
exStr := ""
|
exStr := ""
|
||||||
excludes := strings.Split(exclusionRules, ";")
|
excludes := strings.Split(exclusionRules, ";")
|
||||||
excludes = append(excludes, "*.sock")
|
excludes = append(excludes, "*.sock")
|
||||||
@ -220,8 +228,12 @@ func handleSnapTar(sourceDir, targetDir, name, exclusionRules string) error {
|
|||||||
if len(exclude) == 0 {
|
if len(exclude) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if _, ok := exMap[exclude]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
exStr += " --exclude "
|
exStr += " --exclude "
|
||||||
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)
|
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.UpdateSnapshotRecords,
|
||||||
|
|
||||||
migrations.UpdateWebDavConf,
|
migrations.UpdateWebDavConf,
|
||||||
|
|
||||||
|
migrations.AddSnapshotIgnore,
|
||||||
})
|
})
|
||||||
if err := m.Migrate(); err != nil {
|
if err := m.Migrate(); err != nil {
|
||||||
global.LOG.Error(err)
|
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;
|
emailVars: string;
|
||||||
weChatVars: string;
|
weChatVars: string;
|
||||||
dingVars: string;
|
dingVars: string;
|
||||||
|
snapshotIgnore: string;
|
||||||
}
|
}
|
||||||
export interface SettingUpdate {
|
export interface SettingUpdate {
|
||||||
key: string;
|
key: string;
|
||||||
|
@ -116,6 +116,10 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
isAll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@ -142,6 +146,9 @@ const closePage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const disabledDir = (row: File.File) => {
|
const disabledDir = (row: File.File) => {
|
||||||
|
if (props.isAll) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!props.dir) {
|
if (!props.dir) {
|
||||||
return row.isDir;
|
return row.isDir;
|
||||||
}
|
}
|
||||||
|
@ -1396,6 +1396,10 @@ const message = {
|
|||||||
|
|
||||||
snapshot: 'Snapshot',
|
snapshot: 'Snapshot',
|
||||||
status: 'Snapshot status',
|
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',
|
panelInfo: 'Write 1Panel basic information',
|
||||||
panelBin: 'Backup 1Panel system files',
|
panelBin: 'Backup 1Panel system files',
|
||||||
daemonJson: 'Backup Docker configuration file',
|
daemonJson: 'Backup Docker configuration file',
|
||||||
|
@ -1237,6 +1237,9 @@ const message = {
|
|||||||
|
|
||||||
snapshot: '快照',
|
snapshot: '快照',
|
||||||
status: '快照狀態',
|
status: '快照狀態',
|
||||||
|
ignoreRule: '排除規則',
|
||||||
|
ignoreHelper: '快照時將使用該規則對 1Panel 數據目錄進行壓縮備份,請謹慎修改。',
|
||||||
|
ignoreHelper1: '一行一個,例: \n*.log\n/opt/1panel/cache',
|
||||||
panelInfo: '寫入 1Panel 基礎信息',
|
panelInfo: '寫入 1Panel 基礎信息',
|
||||||
panelBin: '備份 1Panel 系統文件',
|
panelBin: '備份 1Panel 系統文件',
|
||||||
daemonJson: '備份 Docker 配置文件',
|
daemonJson: '備份 Docker 配置文件',
|
||||||
|
@ -1237,6 +1237,9 @@ const message = {
|
|||||||
path: '路径',
|
path: '路径',
|
||||||
|
|
||||||
snapshot: '快照',
|
snapshot: '快照',
|
||||||
|
ignoreRule: '排除规则',
|
||||||
|
ignoreHelper: '快照时将使用该规则对 1Panel 数据目录进行压缩备份,请谨慎修改。',
|
||||||
|
ignoreHelper1: '一行一个,例: \n*.log\n/opt/1panel/cache',
|
||||||
status: '快照状态',
|
status: '快照状态',
|
||||||
panelInfo: '写入 1Panel 基础信息',
|
panelInfo: '写入 1Panel 基础信息',
|
||||||
panelBin: '备份 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()">
|
<el-button type="primary" @click="onCreate()">
|
||||||
{{ $t('setting.createSnapshot') }}
|
{{ $t('setting.createSnapshot') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button type="primary" plain @click="onIgnore()">
|
||||||
|
{{ $t('setting.ignoreRule') }}
|
||||||
|
</el-button>
|
||||||
<el-button type="primary" plain @click="onImport()">
|
<el-button type="primary" plain @click="onImport()">
|
||||||
{{ $t('setting.importSnapshot') }}
|
{{ $t('setting.importSnapshot') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -163,6 +166,7 @@
|
|||||||
|
|
||||||
<OpDialog ref="opRef" @search="search" />
|
<OpDialog ref="opRef" @search="search" />
|
||||||
<SnapStatus ref="snapStatusRef" @search="search" />
|
<SnapStatus ref="snapStatusRef" @search="search" />
|
||||||
|
<IgnoreRule ref="ignoreRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -175,6 +179,7 @@ import { ElForm } from 'element-plus';
|
|||||||
import { Rules } from '@/global/form-rules';
|
import { Rules } from '@/global/form-rules';
|
||||||
import i18n from '@/lang';
|
import i18n from '@/lang';
|
||||||
import { Setting } from '@/api/interface/setting';
|
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 SnapStatus from '@/views/setting/snapshot/snap_status/index.vue';
|
||||||
import RecoverStatus from '@/views/setting/snapshot/status/index.vue';
|
import RecoverStatus from '@/views/setting/snapshot/status/index.vue';
|
||||||
import SnapshotImport from '@/views/setting/snapshot/import/index.vue';
|
import SnapshotImport from '@/views/setting/snapshot/import/index.vue';
|
||||||
@ -193,6 +198,7 @@ const paginationConfig = reactive({
|
|||||||
const searchName = ref();
|
const searchName = ref();
|
||||||
|
|
||||||
const opRef = ref();
|
const opRef = ref();
|
||||||
|
const ignoreRef = ref();
|
||||||
|
|
||||||
const snapStatusRef = ref();
|
const snapStatusRef = ref();
|
||||||
const recoverStatusRef = ref();
|
const recoverStatusRef = ref();
|
||||||
@ -231,6 +237,10 @@ const onImport = () => {
|
|||||||
importRef.value.acceptParams({ names: names });
|
importRef.value.acceptParams({ names: names });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onIgnore = () => {
|
||||||
|
ignoreRef.value.acceptParams();
|
||||||
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
drawerVisible.value = false;
|
drawerVisible.value = false;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user