diff --git a/backend/app/api/v1/setting.go b/backend/app/api/v1/setting.go index 97a3dea5e..30f5a670e 100644 --- a/backend/app/api/v1/setting.go +++ b/backend/app/api/v1/setting.go @@ -205,23 +205,22 @@ func (b *BaseApi) LoadTimeZone(c *gin.Context) { // @Summary Sync system time // @Description 系统时间同步 // @Accept json -// @Param request body dto.SyncTimeZone true "request" +// @Param request body dto.SyncTime true "request" // @Success 200 {string} ntime // @Security ApiKeyAuth // @Router /settings/time/sync [post] -// @x-panel-log {"bodyKeys":["ntpSite", "timeZone"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"系统时间同步[ntpSite]-[timeZone]","formatEN":"sync system time [ntpSite]-[timeZone]"} +// @x-panel-log {"bodyKeys":["ntpSite"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"系统时间同步[ntpSite]","formatEN":"sync system time [ntpSite]"} func (b *BaseApi) SyncTime(c *gin.Context) { - var req dto.SyncTimeZone + var req dto.SyncTime if err := c.ShouldBindJSON(&req); err != nil { helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) return } - ntime, err := settingService.SyncTime(req) - if err != nil { + if err := settingService.SyncTime(req); err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return } - helper.SuccessWithData(c, ntime.Format("2006-01-02 15:04:05 MST -0700")) + helper.SuccessWithData(c, nil) } // @Tags System Setting diff --git a/backend/app/dto/setting.go b/backend/app/dto/setting.go index bc7433925..9aeddbaa3 100644 --- a/backend/app/dto/setting.go +++ b/backend/app/dto/setting.go @@ -111,9 +111,8 @@ type UpgradeInfo struct { ReleaseNote string `json:"releaseNote"` } -type SyncTimeZone struct { - NtpSite string `json:"ntpSite"` - TimeZone string `json:"timeZone"` +type SyncTime struct { + NtpSite string `json:"ntpSite"` } type Upgrade struct { diff --git a/backend/app/service/setting.go b/backend/app/service/setting.go index 339083b6e..5c30cc4d1 100644 --- a/backend/app/service/setting.go +++ b/backend/app/service/setting.go @@ -35,7 +35,7 @@ type ISettingService interface { UpdateSSL(c *gin.Context, req dto.SSLUpdate) error LoadFromCert() (*dto.SSLInfo, error) HandlePasswordExpired(c *gin.Context, old, new string) error - SyncTime(req dto.SyncTimeZone) (time.Time, error) + SyncTime(req dto.SyncTime) error } func NewISettingService() ISettingService { @@ -72,46 +72,49 @@ func (u *SettingService) LoadTimeZone() ([]string, error) { } func (u *SettingService) Update(key, value string) error { - if key == "ExpirationDays" { + if err := settingRepo.Update(key, value); err != nil { + return err + } + switch key { + case "ExpirationDays": timeout, _ := strconv.Atoi(value) if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format("2006-01-02 15:04:05")); err != nil { return err } - } - if key == "BindDomain" { + case "BindDomain": global.CONF.System.BindDomain = value - } - if key == "AllowIPs" { + case "AllowIPs": global.CONF.System.AllowIPs = value - } - if err := settingRepo.Update(key, value); err != nil { - return err - } - if key == "UserName" { + case "TimeZone": + if err := ntp.UpdateSystemTimeZone(value); err != nil { + return err + } + go func() { + _, err := cmd.Exec("systemctl restart 1panel.service") + if err != nil { + global.LOG.Errorf("restart system for new time zone failed, err: %v", err) + } + }() + case "UserName", "Password": _ = global.SESSION.Clean() } + return nil } -func (u *SettingService) SyncTime(req dto.SyncTimeZone) (time.Time, error) { +func (u *SettingService) SyncTime(req dto.SyncTime) error { + if err := settingRepo.Update("NtpSite", req.NtpSite); err != nil { + return err + } ntime, err := ntp.GetRemoteTime(req.NtpSite) if err != nil { - return ntime, err + return err } - ts := ntime.Format("2006-01-02 15:04:05") - if err := ntp.UpdateSystemTime(ts, req.TimeZone); err != nil { - return ntime, err + if err := ntp.UpdateSystemTime(ts); err != nil { + return err } - - if err := settingRepo.Update("TimeZone", req.TimeZone); err != nil { - return ntime, err - } - if err := settingRepo.Update("NtpSite", req.NtpSite); err != nil { - return ntime, err - } - - return ntime, nil + return nil } func (u *SettingService) UpdatePort(port uint) error { diff --git a/backend/init/migration/migrations/init.go b/backend/init/migration/migrations/init.go index 094bff2d9..9931603f4 100644 --- a/backend/init/migration/migrations/init.go +++ b/backend/init/migration/migrations/init.go @@ -348,7 +348,7 @@ var AddBindAndAllowIPs = &gormigrate.Migration{ if err := tx.Create(&model.Setting{Key: "TimeZone", Value: common.LoadTimeZone()}).Error; err != nil { return err } - if err := tx.Create(&model.Setting{Key: "NtpSite", Value: "pool.ntp.org:123"}).Error; err != nil { + if err := tx.Create(&model.Setting{Key: "NtpSite", Value: "pool.ntp.org"}).Error; err != nil { return err } return nil diff --git a/backend/utils/ntp/ntp.go b/backend/utils/ntp/ntp.go index 377f46c44..920e1eae1 100644 --- a/backend/utils/ntp/ntp.go +++ b/backend/utils/ntp/ntp.go @@ -31,7 +31,7 @@ type packet struct { } func GetRemoteTime(site string) (time.Time, error) { - conn, err := net.Dial("udp", site) + conn, err := net.Dial("udp", site+":123") if err != nil { return time.Time{}, fmt.Errorf("failed to connect: %v", err) } @@ -59,15 +59,10 @@ func GetRemoteTime(site string) (time.Time, error) { return showtime, nil } -func UpdateSystemTime(dateTime, timezone string) error { +func UpdateSystemTime(dateTime string) error { system := runtime.GOOS if system == "linux" { - stdout, err := cmd.Execf(`%s timedatectl set-timezone "%s"`, cmd.SudoHandleCmd(), timezone) - if err != nil { - return fmt.Errorf("update system time zone failed, stdout: %s, err: %v", stdout, err) - } - - stdout2, err := cmd.Execf(`%s timedatectl set-time "%s"`, cmd.SudoHandleCmd(), dateTime) + stdout2, err := cmd.Execf(`%s date -s "%s"`, cmd.SudoHandleCmd(), dateTime) if err != nil { return fmt.Errorf("update system time failed,stdout: %s, err: %v", stdout2, err) } @@ -75,3 +70,15 @@ func UpdateSystemTime(dateTime, timezone string) error { } return fmt.Errorf("the current system architecture %v does not support synchronization", system) } + +func UpdateSystemTimeZone(timezone string) error { + system := runtime.GOOS + if system == "linux" { + stdout, err := cmd.Execf(`%s timedatectl set-timezone "%s"`, cmd.SudoHandleCmd(), timezone) + if err != nil { + return fmt.Errorf("update system time zone failed, stdout: %s, err: %v", stdout, err) + } + return nil + } + return fmt.Errorf("the current system architecture %v does not support synchronization", system) +} diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 3f1e4f5b8..84f05a2a0 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -7878,7 +7878,7 @@ var doc = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dto.SyncTimeZone" + "$ref": "#/definitions/dto.SyncTime" } } ], @@ -7893,11 +7893,10 @@ var doc = `{ "x-panel-log": { "BeforeFuntions": [], "bodyKeys": [ - "ntpSite", - "timeZone" + "ntpSite" ], - "formatEN": "sync system time [ntpSite]-[timeZone]", - "formatZH": "系统时间同步[ntpSite]-[timeZone]", + "formatEN": "sync system time [ntpSite]", + "formatZH": "系统时间同步[ntpSite]", "paramKeys": [] } } @@ -12621,14 +12620,11 @@ var doc = `{ } } }, - "dto.SyncTimeZone": { + "dto.SyncTime": { "type": "object", "properties": { "ntpSite": { "type": "string" - }, - "timeZone": { - "type": "string" } } }, diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index 1eddce5ec..c60b0832e 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -7864,7 +7864,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dto.SyncTimeZone" + "$ref": "#/definitions/dto.SyncTime" } } ], @@ -7879,11 +7879,10 @@ "x-panel-log": { "BeforeFuntions": [], "bodyKeys": [ - "ntpSite", - "timeZone" + "ntpSite" ], - "formatEN": "sync system time [ntpSite]-[timeZone]", - "formatZH": "系统时间同步[ntpSite]-[timeZone]", + "formatEN": "sync system time [ntpSite]", + "formatZH": "系统时间同步[ntpSite]", "paramKeys": [] } } @@ -12607,14 +12606,11 @@ } } }, - "dto.SyncTimeZone": { + "dto.SyncTime": { "type": "object", "properties": { "ntpSite": { "type": "string" - }, - "timeZone": { - "type": "string" } } }, diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index a3e8f7aa6..2a407f02f 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -1694,12 +1694,10 @@ definitions: required: - id type: object - dto.SyncTimeZone: + dto.SyncTime: properties: ntpSite: type: string - timeZone: - type: string type: object dto.UpdateDescription: properties: @@ -8276,7 +8274,7 @@ paths: name: request required: true schema: - $ref: '#/definitions/dto.SyncTimeZone' + $ref: '#/definitions/dto.SyncTime' responses: "200": description: OK @@ -8291,9 +8289,8 @@ paths: BeforeFuntions: [] bodyKeys: - ntpSite - - timeZone - formatEN: sync system time [ntpSite]-[timeZone] - formatZH: 系统时间同步[ntpSite]-[timeZone] + formatEN: sync system time [ntpSite] + formatZH: 系统时间同步[ntpSite] paramKeys: [] /settings/update: post: diff --git a/frontend/src/api/modules/setting.ts b/frontend/src/api/modules/setting.ts index dfd7374e3..e544a36f9 100644 --- a/frontend/src/api/modules/setting.ts +++ b/frontend/src/api/modules/setting.ts @@ -38,8 +38,8 @@ export const handleExpired = (param: Setting.PasswordUpdate) => { export const loadTimeZone = () => { return http.get>(`/settings/time/option`); }; -export const syncTime = (timeZone: string, ntpSite: string) => { - return http.post(`/settings/time/sync`, { timeZone: timeZone, ntpSite: ntpSite }); +export const syncTime = (ntpSite: string) => { + return http.post(`/settings/time/sync`, { ntpSite: ntpSite }); }; export const cleanMonitors = () => { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 5444fea12..da5bd9e55 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -907,11 +907,17 @@ const message = { 'If you do not operate the panel for more than {0} seconds, the panel automatically logs out', syncTime: 'Server time', timeZone: 'Time Zone', - timeZoneHelper: 'Timezone modification depends on the system timedatectl service.', + timeZoneChangeHelper: 'Changing the time zone requires restarting the service. Do you want to continue?', + timeZoneHelper: + 'Timezone modification depends on the system timedatectl service. take effect after restart the 1Panel service.', timeZoneCN: 'Bei Jing', timeZoneAM: 'Los Angeles', timeZoneNY: 'New York', + ntpALi: 'Alibaba', + ntpGoogle: 'Google', syncSite: 'Ntp Server', + syncSiteHelper: + 'This operation will use {0} as the source for system time synchronization. Do you want to continue?', changePassword: 'Password change', oldPassword: 'Original password', newPassword: 'New password', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index a2429de97..a30fd742b 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -905,12 +905,16 @@ const message = { sessionTimeoutError: '最小超时时间为 300 秒', sessionTimeoutHelper: '如果用户超过 {0} 秒未操作面板,面板将自动退出登录', syncTime: '服务器时间', - timeZone: '时区', - timeZoneHelper: '时区修改依赖于系统 timedatectl 服务。', + timeZone: '系统时区', + timeZoneChangeHelper: '系统时区修改需要重启服务,是否继续?', + timeZoneHelper: '时区修改依赖于系统 timedatectl 服务,重启 1Panel 服务后生效。', timeZoneCN: '北京', timeZoneAM: '洛杉矶', timeZoneNY: '纽约', - syncSite: '同步地址', + ntpALi: '阿里', + ntpGoogle: '谷歌', + syncSite: 'NTP 服务器', + syncSiteHelper: '该操作将使用 {0} 作为源进行系统时间同步,是否继续?', changePassword: '密码修改', oldPassword: '原密码', newPassword: '新密码', diff --git a/frontend/src/views/setting/panel/index.vue b/frontend/src/views/setting/panel/index.vue index e7e8731ce..38b4822cc 100644 --- a/frontend/src/views/setting/panel/index.vue +++ b/frontend/src/views/setting/panel/index.vue @@ -73,6 +73,15 @@ + + + + + @@ -108,6 +118,7 @@ import Password from '@/views/setting/panel/password/index.vue'; import UserName from '@/views/setting/panel/username/index.vue'; import Timeout from '@/views/setting/panel/timeout/index.vue'; import PanelName from '@/views/setting/panel/name/index.vue'; +import TimeZone from '@/views/setting/panel/timezone/index.vue'; import Ntp from '@/views/setting/panel/ntp/index.vue'; const loading = ref(false); @@ -137,6 +148,7 @@ const passwordRef = ref(); const panelNameRef = ref(); const timeoutRef = ref(); const ntpRef = ref(); +const timezoneRef = ref(); const search = async () => { const res = await getSettingInfo(); @@ -164,8 +176,11 @@ const onChangeTitle = () => { const onChangeTimeout = () => { timeoutRef.value.acceptParams({ sessionTimeout: form.sessionTimeout }); }; +const onChangeTimeZone = () => { + timezoneRef.value.acceptParams({ timeZone: form.timeZone }); +}; const onChangeNtp = () => { - ntpRef.value.acceptParams({ localTime: form.localTime, timeZone: form.timeZone, ntpSite: form.ntpSite }); + ntpRef.value.acceptParams({ localTime: form.localTime, ntpSite: form.ntpSite }); }; const onSave = async (key: string, val: any) => { diff --git a/frontend/src/views/setting/panel/ntp/index.vue b/frontend/src/views/setting/panel/ntp/index.vue index a7765d04d..a54a3a753 100644 --- a/frontend/src/views/setting/panel/ntp/index.vue +++ b/frontend/src/views/setting/panel/ntp/index.vue @@ -4,56 +4,25 @@ - - - - - - - - - {{ $t('setting.timeZoneCN') }} + + + + {{ $t('website.default') }} - - {{ $t('setting.timeZoneAM') }} + + {{ $t('setting.ntpALi') }} - - {{ $t('setting.timeZoneNY') }} + + {{ $t('setting.ntpGoogle') }} - - - - @@ -72,24 +41,20 @@ import { reactive, ref } from 'vue'; import i18n from '@/lang'; import { MsgSuccess } from '@/utils/message'; -import { loadTimeZone, syncTime } from '@/api/modules/setting'; -import { FormInstance } from 'element-plus'; +import { syncTime } from '@/api/modules/setting'; +import { ElMessageBox, FormInstance } from 'element-plus'; import { Rules } from '@/global/form-rules'; const emit = defineEmits<{ (e: 'search'): void }>(); interface DialogProps { - timeZone: string; localTime: string; ntpSite: string; } const drawerVisiable = ref(); const loading = ref(); -const zones = ref>([]); -const oldTimeZone = ref(); const form = reactive({ - timeZone: '', localTime: '', ntpSite: '', }); @@ -97,38 +62,37 @@ const form = reactive({ const formRef = ref(); const acceptParams = (params: DialogProps): void => { - loadTimeZones(); - oldTimeZone.value = params.timeZone; - form.timeZone = params.timeZone; form.localTime = params.localTime; form.ntpSite = params.ntpSite; drawerVisiable.value = true; }; -const canChangeZone = () => { - return zones.value.length === 0; -}; - -const loadTimeZones = async () => { - const res = await loadTimeZone(); - zones.value = res.data; -}; const onSyncTime = async (formEl: FormInstance | undefined) => { if (!formEl) return; formEl.validate(async (valid) => { if (!valid) return; - loading.value = true; - await syncTime(form.timeZone, form.ntpSite) - .then((res) => { - loading.value = false; - form.localTime = res.data; - emit('search'); - handleClose(); - MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); - }) - .catch(() => { - loading.value = false; - }); + ElMessageBox.confirm( + i18n.global.t('setting.syncSiteHelper', [form.ntpSite]), + i18n.global.t('setting.syncSite'), + { + confirmButtonText: i18n.global.t('commons.button.confirm'), + cancelButtonText: i18n.global.t('commons.button.cancel'), + type: 'info', + }, + ).then(async () => { + loading.value = true; + await syncTime(form.ntpSite) + .then((res) => { + loading.value = false; + form.localTime = res.data; + emit('search'); + handleClose(); + MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); + }) + .catch(() => { + loading.value = false; + }); + }); }); }; diff --git a/frontend/src/views/setting/panel/timezone/index.vue b/frontend/src/views/setting/panel/timezone/index.vue new file mode 100644 index 000000000..569ef0608 --- /dev/null +++ b/frontend/src/views/setting/panel/timezone/index.vue @@ -0,0 +1,139 @@ + + +