mirror of
				https://github.com/1Panel-dev/1Panel.git
				synced 2025-10-31 11:15:58 +08:00 
			
		
		
		
	
							parent
							
								
									8ca6b9b6fa
								
							
						
					
					
						commit
						3b697c7520
					
				
					 11 changed files with 215 additions and 1 deletions
				
			
		|  | @ -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…
	
	Add table
		
		Reference in a new issue