diff --git a/api/v1/system.go b/api/v1/system.go index bdf88fb7f..38309cb70 100644 --- a/api/v1/system.go +++ b/api/v1/system.go @@ -23,6 +23,8 @@ type SystemStatus struct { DisablePublicMemos bool `json:"disablePublicMemos"` // Max upload size. MaxUploadSizeMiB int `json:"maxUploadSizeMiB"` + // Auto Backup Interval. + AutoBackupInterval int `json:"autoBackupInterval"` // Additional style. AdditionalStyle string `json:"additionalStyle"` // Additional script. @@ -50,6 +52,7 @@ func (s *APIV1Service) registerSystemRoutes(g *echo.Group) { AllowSignUp: false, DisablePublicMemos: false, MaxUploadSizeMiB: 32, + AutoBackupInterval: 0, AdditionalStyle: "", AdditionalScript: "", CustomizedProfile: CustomizedProfile{ @@ -103,6 +106,8 @@ func (s *APIV1Service) registerSystemRoutes(g *echo.Group) { systemStatus.DisablePublicMemos = baseValue.(bool) case SystemSettingMaxUploadSizeMiBName.String(): systemStatus.MaxUploadSizeMiB = int(baseValue.(float64)) + case SystemSettingAutoBackupIntervalName.String(): + systemStatus.AutoBackupInterval = int(baseValue.(float64)) case SystemSettingAdditionalStyleName.String(): systemStatus.AdditionalStyle = baseValue.(string) case SystemSettingAdditionalScriptName.String(): diff --git a/api/v1/system_setting.go b/api/v1/system_setting.go index eb2925e94..37cef7548 100644 --- a/api/v1/system_setting.go +++ b/api/v1/system_setting.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "net/http" - "strconv" "strings" "github.com/labstack/echo/v4" @@ -143,18 +142,12 @@ func (upsert UpsertSystemSettingRequest) Validate() error { return fmt.Errorf(systemSettingUnmarshalError, settingName) } case SystemSettingAutoBackupIntervalName: - var value string + var value int if err := json.Unmarshal([]byte(upsert.Value), &value); err != nil { return fmt.Errorf(systemSettingUnmarshalError, settingName) } - if value != "" { - v, err := strconv.Atoi(value) - if err != nil { - return fmt.Errorf(systemSettingUnmarshalError, settingName) - } - if v < 0 { - return fmt.Errorf("backup interval should > 0") - } + if value < 0 { + return fmt.Errorf("must be positive") } case SystemSettingTelegramBotTokenName: if upsert.Value == "" { diff --git a/web/src/components/Settings/SystemSection.tsx b/web/src/components/Settings/SystemSection.tsx index d88ba11c3..e32ad60a5 100644 --- a/web/src/components/Settings/SystemSection.tsx +++ b/web/src/components/Settings/SystemSection.tsx @@ -17,6 +17,7 @@ interface State { additionalStyle: string; additionalScript: string; maxUploadSizeMiB: number; + autoBackupInterval: number; memoDisplayWithUpdatedTs: boolean; } @@ -31,6 +32,7 @@ const SystemSection = () => { additionalScript: systemStatus.additionalScript, disablePublicMemos: systemStatus.disablePublicMemos, maxUploadSizeMiB: systemStatus.maxUploadSizeMiB, + autoBackupInterval: systemStatus.autoBackupInterval, memoDisplayWithUpdatedTs: systemStatus.memoDisplayWithUpdatedTs, }); const [telegramBotToken, setTelegramBotToken] = useState(""); @@ -57,6 +59,7 @@ const SystemSection = () => { additionalScript: systemStatus.additionalScript, disablePublicMemos: systemStatus.disablePublicMemos, maxUploadSizeMiB: systemStatus.maxUploadSizeMiB, + autoBackupInterval: systemStatus.autoBackupInterval, memoDisplayWithUpdatedTs: systemStatus.memoDisplayWithUpdatedTs, }); }, [systemStatus]); @@ -194,6 +197,30 @@ const SystemSection = () => { event.target.select(); }; + const handleAutoBackupIntervalChanged = async (event: React.FocusEvent) => { + // fixes cursor skipping position on mobile + event.target.selectionEnd = event.target.value.length; + + let num = parseInt(event.target.value); + if (Number.isNaN(num)) { + num = 0; + } + setState({ + ...state, + autoBackupInterval: num, + }); + event.target.value = num.toString(); + globalStore.setSystemStatus({ autoBackupInterval: num }); + await api.upsertSystemSetting({ + name: "auto-backup-interval", + value: JSON.stringify(num), + }); + }; + + const handleAutoBackupIntervalFocus = (event: React.FocusEvent) => { + event.target.select(); + }; + return (

{t("common.basic")}

@@ -239,6 +266,23 @@ const SystemSection = () => { onChange={handleMaxUploadSizeChanged} />
+
+
+ {t("setting.system-section.auto-backup-interval")} + + + +
+ +
diff --git a/web/src/locales/en.json b/web/src/locales/en.json index e5d3871c7..d762efa71 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -260,6 +260,8 @@ "disable-public-memos": "Disable public memos", "max-upload-size": "Maximum upload size (MiB)", "max-upload-size-hint": "Recommended value is 32 MiB.", + "auto-backup-interval": "Auto backup interval (seconds)", + "auto-backup-interval-hint": "Set 0 to disable auto backup. Reboot is required after changing this value.", "additional-style": "Additional style", "additional-script": "Additional script", "additional-style-placeholder": "Additional CSS code", diff --git a/web/src/locales/zh-Hans.json b/web/src/locales/zh-Hans.json index 8e82633a9..2346cda4e 100644 --- a/web/src/locales/zh-Hans.json +++ b/web/src/locales/zh-Hans.json @@ -435,6 +435,8 @@ "server-name": "服务名称", "max-upload-size-hint": "建议值为 32 MiB。", "max-upload-size": "最大上传大小 (MiB)", + "auto-backup-interval": "自动备份间隔(单位:秒)", + "auto-backup-interval-hint": "设置为0禁止自动备份,重启后生效", "display-with-updated-time": "Display with updated time" } }, diff --git a/web/src/store/module/global.ts b/web/src/store/module/global.ts index 5f2118036..d1f5dfc51 100644 --- a/web/src/store/module/global.ts +++ b/web/src/store/module/global.ts @@ -13,6 +13,7 @@ export const initialGlobalState = async () => { allowSignUp: false, disablePublicMemos: false, maxUploadSizeMiB: 0, + autoBackupInterval: 0, additionalStyle: "", additionalScript: "", memoDisplayWithUpdatedTs: false, diff --git a/web/src/types/modules/system.d.ts b/web/src/types/modules/system.d.ts index 80fff1eb1..acde0e481 100644 --- a/web/src/types/modules/system.d.ts +++ b/web/src/types/modules/system.d.ts @@ -20,6 +20,7 @@ interface SystemStatus { allowSignUp: boolean; disablePublicMemos: boolean; maxUploadSizeMiB: number; + autoBackupInterval: number; additionalStyle: string; additionalScript: string; customizedProfile: CustomizedProfile;