Skip to content

Commit 8aa295a

Browse files
committed
fix: Enhance file history settings with default values and validation
1 parent 254ed95 commit 8aa295a

3 files changed

Lines changed: 102 additions & 17 deletions

File tree

agent/app/dto/request/file_history.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@ type FileHistoryRestoreReq struct {
4747

4848
type FileHistorySettingUpdate struct {
4949
Enable string `json:"enable" validate:"required,oneof=Enable Disable"`
50-
MaxPerPath int `json:"maxPerPath" validate:"required,min=1,max=1000"`
51-
DiskQuotaMB int `json:"diskQuotaMB" validate:"required,min=1,max=1048576"`
50+
MaxPerPath int `json:"maxPerPath" validate:"min=0,max=1000"`
51+
DiskQuotaMB int `json:"diskQuotaMB" validate:"min=0,max=1048576"`
5252
}

agent/app/service/file_history.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,12 @@ const (
3030
fileHistorySettingMaxPerPath = "FileHistoryMaxPerPath"
3131
fileHistorySettingDiskQuotaMB = "FileHistoryDiskQuotaMB"
3232
fileHistoryOpSave = "save"
33+
fileHistoryOpRestore = "restore"
3334
fileHistoryOpRename = "rename"
3435
fileHistoryOpMove = "move"
3536
fileHistoryRootDirName = "file-history"
37+
defaultFileHistoryMaxPerPath = 20
38+
defaultFileHistoryDiskQuotaMB = 1024
3639
)
3740

3841
var historyService = NewIFileHistoryService()
@@ -62,8 +65,8 @@ func NewIFileHistoryService() IFileHistoryService {
6265
func (s *FileHistoryService) GetSettingInfo() (*response.FileHistorySettingInfo, error) {
6366
info := &response.FileHistorySettingInfo{
6467
Enable: constant.StatusEnable,
65-
MaxPerPath: 20,
66-
DiskQuotaMB: 1024,
68+
MaxPerPath: defaultFileHistoryMaxPerPath,
69+
DiskQuotaMB: defaultFileHistoryDiskQuotaMB,
6770
}
6871
if value, err := settingRepo.GetValueByKey(fileHistorySettingEnable); err == nil && value != "" {
6972
info.Enable = value
@@ -82,6 +85,12 @@ func (s *FileHistoryService) GetSettingInfo() (*response.FileHistorySettingInfo,
8285
}
8386

8487
func (s *FileHistoryService) UpdateSetting(req request.FileHistorySettingUpdate) error {
88+
if req.MaxPerPath <= 0 {
89+
req.MaxPerPath = defaultFileHistoryMaxPerPath
90+
}
91+
if req.DiskQuotaMB <= 0 {
92+
req.DiskQuotaMB = defaultFileHistoryDiskQuotaMB
93+
}
8594
if err := settingRepo.UpdateOrCreate(fileHistorySettingEnable, req.Enable); err != nil {
8695
return err
8796
}
@@ -161,13 +170,20 @@ func (s *FileHistoryService) RecordOperation(operation string, filePath string,
161170
}
162171

163172
previousID := uint(0)
164-
if s.isVersionOperation(operation) {
165-
contentHash := sha256HexBytes(content)
166-
if latestVersion, err := s.getLatestVersionByFileID(fileID); err == nil {
173+
if latestVersion, err := s.getLatestVersionByFileID(fileID); err == nil {
174+
switch operation {
175+
case fileHistoryOpSave:
176+
contentHash := sha256HexBytes(content)
167177
if latestVersion.ContentSHA == contentHash {
168178
return nil
169179
}
170180
previousID = latestVersion.ID
181+
case fileHistoryOpRestore:
182+
previousID = latestVersion.ID
183+
default:
184+
if latestChainRecord.ID != 0 {
185+
previousID = latestChainRecord.ID
186+
}
171187
}
172188
} else if latestChainRecord.ID != 0 {
173189
previousID = latestChainRecord.ID
@@ -414,6 +430,9 @@ func (s *FileHistoryService) Restore(req request.FileHistoryRestoreReq) (respons
414430
_ = s.rollbackRestore(currentPath, rollbackContent, rollbackMode, existedBefore)
415431
return response.FileInfo{}, err
416432
}
433+
if err := historyService.RecordOperation(fileHistoryOpRestore, currentPath, rollbackContent, rollbackMode, "", ""); err != nil {
434+
global.LOG.Warnf("record file restore history failed for %s: %v", currentPath, err)
435+
}
417436

418437
info, err := files.NewFileInfo(files.FileOption{
419438
Path: currentPath,
@@ -571,7 +590,7 @@ func (s *FileHistoryService) getLatestActiveRelatedByPath(absPath string) (model
571590

572591
func (s *FileHistoryService) getLatestVersionByFileID(fileID string) (model.FileHistory, error) {
573592
var item model.FileHistory
574-
db := global.DB.Model(&model.FileHistory{}).Where("file_id = ? AND operation = ?", fileID, fileHistoryOpSave).Order("created_at desc")
593+
db := global.DB.Model(&model.FileHistory{}).Where("file_id = ? AND operation IN ?", fileID, []string{fileHistoryOpSave, fileHistoryOpRestore}).Order("created_at desc")
575594
err := db.First(&item).Error
576595
return item, err
577596
}
@@ -609,7 +628,7 @@ func (s *FileHistoryService) resolveFileChain(paths ...string) (string, model.Fi
609628
}
610629

611630
func (s *FileHistoryService) isVersionOperation(operation string) bool {
612-
return operation == fileHistoryOpSave
631+
return operation == fileHistoryOpSave || operation == fileHistoryOpRestore
613632
}
614633

615634
func (s *FileHistoryService) totalSize() (int64, error) {

frontend/src/views/host/file-management/code-editor/history/index.vue

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,29 @@
1717
<el-card shadow="never" class="history-setting">
1818
<el-collapse v-model="activeCollapse" :accordion="true" class="!border-0">
1919
<el-collapse-item :title="$t('file.historySettingTitle')" name="history" class="!border-0">
20-
<el-form label-position="top" class="flex items-end gap-6">
21-
<el-form-item :label="$t('file.historyEnable')">
20+
<el-form
21+
ref="historySettingFormRef"
22+
:model="historySetting"
23+
:rules="historySettingRules"
24+
label-position="top"
25+
class="flex items-end gap-6"
26+
>
27+
<el-form-item :label="$t('file.historyEnable')" prop="enable">
2228
<el-switch
2329
v-model="historySetting.enable"
2430
active-value="Enable"
2531
inactive-value="Disable"
2632
/>
2733
</el-form-item>
28-
<el-form-item :label="$t('file.historyMaxPerPath')">
34+
<el-form-item :label="$t('file.historyMaxPerPath')" prop="maxPerPath">
2935
<el-input-number
3036
v-model="historySetting.maxPerPath"
3137
:min="1"
3238
:max="1000"
3339
class="!w-52"
3440
/>
3541
</el-form-item>
36-
<el-form-item :label="$t('file.historyDiskQuota')">
42+
<el-form-item :label="$t('file.historyDiskQuota')" prop="diskQuotaMB">
3743
<el-input-number
3844
v-model="historySetting.diskQuotaMB"
3945
:min="1"
@@ -212,7 +218,7 @@
212218
</template>
213219

214220
<script setup lang="ts">
215-
import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref } from 'vue';
221+
import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, reactive, ref } from 'vue';
216222
import { dateFormatSimpleWithSecond } from '@/utils/date';
217223
import { computeSize } from '@/utils/size';
218224
import { MsgError, MsgSuccess } from '@/utils/message';
@@ -221,7 +227,7 @@ import { getAgentFileHistoryInfo, updateAgentFileHistoryInfo } from '@/api/modul
221227
import { File } from '@/api/interface/file';
222228
import { Setting } from '@/api/interface/setting';
223229
import { loadMonacoLanguageSupport, setupMonacoEnvironment } from '@/utils/monaco';
224-
import { ElMessageBox } from 'element-plus';
230+
import { ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
225231
import { Languages } from '@/global/mimetype';
226232
import i18n from '@/lang';
227233
@@ -241,6 +247,7 @@ const historyLoading = ref(false);
241247
const deleteLoading = ref(false);
242248
const settingSaving = ref(false);
243249
const restoreLoading = ref(false);
250+
const historySettingFormRef = ref<FormInstance>();
244251
const scope = ref<'current' | 'all'>('current');
245252
const operationFilter = ref('');
246253
const activeCollapse = ref([]);
@@ -256,6 +263,51 @@ const historySetting = ref<Setting.FileHistoryInfo>({
256263
maxPerPath: 20,
257264
diskQuotaMB: 1024,
258265
});
266+
const historySettingRules = reactive<FormRules<Setting.FileHistoryInfo>>({
267+
enable: [
268+
{
269+
required: true,
270+
message: i18n.global.t('commons.rule.requiredInput'),
271+
trigger: 'change',
272+
},
273+
],
274+
maxPerPath: [
275+
{
276+
required: true,
277+
message: i18n.global.t('commons.rule.requiredInput'),
278+
trigger: 'change',
279+
},
280+
{
281+
validator: (_, value, callback) => {
282+
const parsedValue = Number(value);
283+
if (!Number.isInteger(parsedValue) || parsedValue <= 0) {
284+
callback(new Error(i18n.global.t('commons.rule.integer')));
285+
return;
286+
}
287+
callback();
288+
},
289+
trigger: 'change',
290+
},
291+
],
292+
diskQuotaMB: [
293+
{
294+
required: true,
295+
message: i18n.global.t('commons.rule.requiredInput'),
296+
trigger: 'change',
297+
},
298+
{
299+
validator: (_, value, callback) => {
300+
const parsedValue = Number(value);
301+
if (!Number.isInteger(parsedValue) || parsedValue <= 0) {
302+
callback(new Error(i18n.global.t('commons.rule.integer')));
303+
return;
304+
}
305+
callback();
306+
},
307+
trigger: 'change',
308+
},
309+
],
310+
});
259311
const historyItems = ref<File.FileHistoryInfo[]>([]);
260312
const selected = ref<File.FileHistoryInfo[]>([]);
261313
const selectedHistory = ref<File.FileHistoryInfo | null>(null);
@@ -267,6 +319,7 @@ const windowWidth = ref(window.innerWidth);
267319
const operationMap = {
268320
'': i18n.global.t('app.all'),
269321
save: i18n.global.t('commons.button.save'),
322+
restore: i18n.global.t('commons.button.recover'),
270323
move: i18n.global.t('file.move'),
271324
rename: i18n.global.t('file.rename'),
272325
} as const;
@@ -279,7 +332,7 @@ const operationOptions = Object.entries(operationMap).map(([value, label]) => ({
279332
const getOperationLabel = (operation: string) => {
280333
return operationMap[operation as keyof typeof operationMap] || operation;
281334
};
282-
const isVersionRecord = (operation: string) => operation === 'save';
335+
const isVersionRecord = (operation: string) => operation === 'save' || operation === 'restore';
283336
const canRestoreSelected = computed(() => isVersionRecord(selectedHistory.value?.operation || ''));
284337
const isRestoringCurrentFile = computed(() => selectedHistory.value?.path === currentFile.value.path);
285338
const getCurrentTargetPath = (row: File.FileHistoryInfo) => row.currentPath || row.path;
@@ -412,10 +465,23 @@ const loadHistorySetting = async () => {
412465
historySetting.value = res.data;
413466
};
414467
468+
const normalizeHistorySettingNumber = (value: number, defaultValue: number) => {
469+
const parsedValue = Number(value);
470+
return parsedValue > 0 ? parsedValue : defaultValue;
471+
};
472+
415473
const saveHistorySetting = async () => {
416474
settingSaving.value = true;
417475
try {
418-
await updateAgentFileHistoryInfo(historySetting.value);
476+
const valid = await historySettingFormRef.value?.validate().catch(() => false);
477+
if (!valid) {
478+
return;
479+
}
480+
await updateAgentFileHistoryInfo({
481+
...historySetting.value,
482+
maxPerPath: normalizeHistorySettingNumber(historySetting.value.maxPerPath, 20),
483+
diskQuotaMB: normalizeHistorySettingNumber(historySetting.value.diskQuotaMB, 1024),
484+
});
419485
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
420486
} catch (error) {
421487
MsgError(String(error));

0 commit comments

Comments
 (0)