From b718c11f3a9c5358b925e8b2cb26004c8cdcc03a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Tue, 15 Oct 2024 17:44:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A6=BB=E7=BA=BF=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/setting.go | 2 + internal/data/app.go | 2 +- internal/data/setting.go | 110 ++++++++++++++++++-------- internal/http/request/setting.go | 1 + internal/job/panel_task.go | 16 ++-- internal/service/app.go | 11 ++- internal/service/info.go | 24 ++++-- web/src/i18n/en.json | 7 +- web/src/i18n/zh_CN.json | 21 ++--- web/src/views/setting/SettingBase.vue | 6 +- web/src/views/setting/types.ts | 1 + 11 files changed, 139 insertions(+), 62 deletions(-) diff --git a/internal/biz/setting.go b/internal/biz/setting.go index 7996f638..8f447358 100644 --- a/internal/biz/setting.go +++ b/internal/biz/setting.go @@ -21,6 +21,7 @@ const ( SettingKeySshPort SettingKey = "ssh_port" SettingKeySshUser SettingKey = "ssh_user" SettingKeySshPassword SettingKey = "ssh_password" + SettingKeyOfflineMode SettingKey = "offline_mode" ) type Setting struct { @@ -33,6 +34,7 @@ type Setting struct { type SettingRepo interface { Get(key SettingKey, defaultValue ...string) (string, error) + GetBool(key SettingKey, defaultValue ...bool) (bool, error) Set(key SettingKey, value string) error Delete(key SettingKey) error GetPanelSetting(ctx context.Context) (*request.PanelSetting, error) diff --git a/internal/data/app.go b/internal/data/app.go index 2268b818..cb175a03 100644 --- a/internal/data/app.go +++ b/internal/data/app.go @@ -288,7 +288,7 @@ func (r *appRepo) Update(slug string) error { } task := new(biz.Task) - task.Name = "更新应用 " + item.Name + task.Name = "升级应用 " + item.Name task.Status = biz.TaskStatusWaiting task.Shell = fmt.Sprintf(`curl -fsLm 10 --retry 3 "%s" | bash -s -- "%s" "%s" >> /tmp/%s.log 2>&1`, shellUrl, shellChannel, shellVersion, item.Slug) task.Log = "/tmp/" + item.Slug + ".log" diff --git a/internal/data/setting.go b/internal/data/setting.go index 59f49c2f..96ce50ea 100644 --- a/internal/data/setting.go +++ b/internal/data/setting.go @@ -4,10 +4,10 @@ import ( "context" "errors" "fmt" + "github.com/go-rat/utils/hash" "path/filepath" "slices" - "github.com/go-rat/utils/hash" "github.com/goccy/go-yaml" "github.com/gookit/color" "github.com/spf13/cast" @@ -22,10 +22,14 @@ import ( "github.com/TheTNB/panel/pkg/types" ) -type settingRepo struct{} +type settingRepo struct { + taskRepo biz.TaskRepo +} func NewSettingRepo() biz.SettingRepo { - return &settingRepo{} + return &settingRepo{ + taskRepo: NewTaskRepo(), + } } func (r *settingRepo) Get(key biz.SettingKey, defaultValue ...string) (string, error) { @@ -43,6 +47,21 @@ func (r *settingRepo) Get(key biz.SettingKey, defaultValue ...string) (string, e return setting.Value, nil } +func (r *settingRepo) GetBool(key biz.SettingKey, defaultValue ...bool) (bool, error) { + setting := new(biz.Setting) + if err := app.Orm.Where("key = ?", key).First(setting).Error; err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return false, err + } + } + + if setting.Value == "" && len(defaultValue) > 0 { + return defaultValue[0], nil + } + + return cast.ToBool(setting.Value), nil +} + func (r *settingRepo) Set(key biz.SettingKey, value string) error { setting := new(biz.Setting) if err := app.Orm.Where("key = ?", key).First(setting).Error; err != nil { @@ -66,16 +85,20 @@ func (r *settingRepo) Delete(key biz.SettingKey) error { } func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSetting, error) { - name := new(biz.Setting) - if err := app.Orm.Where("key = ?", biz.SettingKeyName).First(name).Error; err != nil { + name, err := r.Get(biz.SettingKeyName) + if err != nil { return nil, err } - websitePath := new(biz.Setting) - if err := app.Orm.Where("key = ?", biz.SettingKeyWebsitePath).First(websitePath).Error; err != nil { + offlineMode, err := r.Get(biz.SettingKeyOfflineMode) + if err != nil { return nil, err } - backupPath := new(biz.Setting) - if err := app.Orm.Where("key = ?", biz.SettingKeyBackupPath).First(backupPath).Error; err != nil { + websitePath, err := r.Get(biz.SettingKeyWebsitePath) + if err != nil { + return nil, err + } + backupPath, err := r.Get(biz.SettingKeyBackupPath) + if err != nil { return nil, err } @@ -95,11 +118,12 @@ func (r *settingRepo) GetPanelSetting(ctx context.Context) (*request.PanelSettin } return &request.PanelSetting{ - Name: name.Value, + Name: name, Locale: app.Conf.String("app.locale"), Entrance: app.Conf.String("http.entrance"), - WebsitePath: websitePath.Value, - BackupPath: backupPath.Value, + OfflineMode: cast.ToBool(offlineMode), + WebsitePath: websitePath, + BackupPath: backupPath, Username: user.Username, Email: user.Email, Port: app.Conf.Int("http.port"), @@ -113,6 +137,9 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P if err := r.Set(biz.SettingKeyName, setting.Name); err != nil { return false, err } + if err := r.Set(biz.SettingKeyOfflineMode, cast.ToString(setting.OfflineMode)); err != nil { + return false, err + } if err := r.Set(biz.SettingKeyWebsitePath, setting.WebsitePath); err != nil { return false, err } @@ -120,6 +147,37 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P return false, err } + // 用户 + user := new(biz.User) + userID := cast.ToUint(ctx.Value("user_id")) + if err := app.Orm.Where("id = ?", userID).First(user).Error; err != nil { + return false, err + } + + user.Username = setting.Username + user.Email = setting.Email + if setting.Password != "" { + value, err := hash.NewArgon2id().Make(setting.Password) + if err != nil { + return false, err + } + user.Password = value + } + if err := app.Orm.Save(user).Error; err != nil { + return false, err + } + + // 下面是需要需要重启的设置 + // 面板HTTPS + restartFlag := false + oldCert, _ := io.Read(filepath.Join(app.Root, "panel/storage/cert.pem")) + oldKey, _ := io.Read(filepath.Join(app.Root, "panel/storage/cert.key")) + if oldCert != setting.Cert || oldKey != setting.Key { + if r.taskRepo.HasRunningTask() { + return false, errors.New("后台任务正在运行,禁止修改部分设置,请稍后再试") + } + restartFlag = true + } if err := io.Write(filepath.Join(app.Root, "panel/storage/cert.pem"), setting.Cert, 0644); err != nil { return false, err } @@ -127,7 +185,7 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P return false, err } - restartFlag := false + // 面板主配置 config := new(types.PanelConfig) cm := yaml.CommentMap{} raw, err := io.Read("/usr/local/etc/panel/config.yml") @@ -147,29 +205,13 @@ func (r *settingRepo) UpdatePanelSetting(ctx context.Context, setting *request.P if err != nil { return false, err } - if err = io.Write("/usr/local/etc/panel/config.yml", string(encoded), 0644); err != nil { - return false, err - } if raw != string(encoded) { + if r.taskRepo.HasRunningTask() { + return false, errors.New("后台任务正在运行,禁止修改部分设置,请稍后再试") + } restartFlag = true } - - user := new(biz.User) - userID := cast.ToUint(ctx.Value("user_id")) - if err = app.Orm.Where("id = ?", userID).First(user).Error; err != nil { - return false, err - } - - user.Username = setting.Username - user.Email = setting.Email - if setting.Password != "" { - value, err := hash.NewArgon2id().Make(setting.Password) - if err != nil { - return false, err - } - user.Password = value - } - if err = app.Orm.Save(user).Error; err != nil { + if err = io.Write("/usr/local/etc/panel/config.yml", string(encoded), 0644); err != nil { return false, err } @@ -222,7 +264,7 @@ func (r *settingRepo) UpdatePanel(version, url, checksum string) error { color.Greenln("|-前置检查...") } if io.Exists("/tmp/panel-storage.zip") { - return errors.New("检测到 /tmp 存在临时文件,可能是上次更新失败所致,请运行 panel-cli fix 修复后重试") + return errors.New("检测到 /tmp 存在临时文件,可能是上次升级失败所致,请运行 panel-cli fix 修复后重试") } if app.IsCli { diff --git a/internal/http/request/setting.go b/internal/http/request/setting.go index 103e62c5..7ceadd8d 100644 --- a/internal/http/request/setting.go +++ b/internal/http/request/setting.go @@ -4,6 +4,7 @@ type PanelSetting struct { Name string `json:"name" validate:"required"` Locale string `json:"locale" validate:"required"` Entrance string `json:"entrance" validate:"required"` + OfflineMode bool `json:"offline_mode"` WebsitePath string `json:"website_path" validate:"required"` BackupPath string `json:"backup_path" validate:"required"` Username string `json:"username" validate:"required"` diff --git a/internal/job/panel_task.go b/internal/job/panel_task.go index a0289039..474e209a 100644 --- a/internal/job/panel_task.go +++ b/internal/job/panel_task.go @@ -14,14 +14,16 @@ import ( // PanelTask 面板每日任务 type PanelTask struct { - appRepo biz.AppRepo - backupRepo biz.BackupRepo + appRepo biz.AppRepo + backupRepo biz.BackupRepo + settingRepo biz.SettingRepo } func NewPanelTask() *PanelTask { return &PanelTask{ - appRepo: data.NewAppRepo(), - backupRepo: data.NewBackupRepo(), + appRepo: data.NewAppRepo(), + backupRepo: data.NewBackupRepo(), + settingRepo: data.NewSettingRepo(), } } @@ -52,8 +54,10 @@ func (receiver *PanelTask) Run() { } // 更新商店缓存 - if err = receiver.appRepo.UpdateCache(); err != nil { - app.Logger.Error("更新商店缓存失败", zap.Error(err)) + if offline, err := receiver.settingRepo.GetBool(biz.SettingKeyOfflineMode); err == nil && !offline { + if err = receiver.appRepo.UpdateCache(); err != nil { + app.Logger.Error("更新商店缓存失败", zap.Error(err)) + } } // 回收内存 diff --git a/internal/service/app.go b/internal/service/app.go index 8aaa39fb..52594303 100644 --- a/internal/service/app.go +++ b/internal/service/app.go @@ -12,12 +12,14 @@ import ( ) type AppService struct { - appRepo biz.AppRepo + appRepo biz.AppRepo + settingRepo biz.SettingRepo } func NewAppService() *AppService { return &AppService{ - appRepo: data.NewAppRepo(), + appRepo: data.NewAppRepo(), + settingRepo: data.NewSettingRepo(), } } @@ -162,6 +164,11 @@ func (s *AppService) IsInstalled(w http.ResponseWriter, r *http.Request) { } func (s *AppService) UpdateCache(w http.ResponseWriter, r *http.Request) { + if offline, _ := s.settingRepo.GetBool(biz.SettingKeyOfflineMode); offline { + Error(w, http.StatusForbidden, "离线模式下无法更新应用列表缓存") + return + } + if err := s.appRepo.UpdateCache(); err != nil { Error(w, http.StatusInternalServerError, "%v", err) return diff --git a/internal/service/info.go b/internal/service/info.go index 76879c09..b48da222 100644 --- a/internal/service/info.go +++ b/internal/service/info.go @@ -207,6 +207,11 @@ func (s *InfoService) InstalledDbAndPhp(w http.ResponseWriter, r *http.Request) } func (s *InfoService) CheckUpdate(w http.ResponseWriter, r *http.Request) { + if offline, _ := s.settingRepo.GetBool(biz.SettingKeyOfflineMode); offline { + Error(w, http.StatusForbidden, "离线模式下无法检查更新") + return + } + current := app.Version latest, err := s.api.LatestVersion() if err != nil { @@ -237,6 +242,11 @@ func (s *InfoService) CheckUpdate(w http.ResponseWriter, r *http.Request) { } func (s *InfoService) UpdateInfo(w http.ResponseWriter, r *http.Request) { + if offline, _ := s.settingRepo.GetBool(biz.SettingKeyOfflineMode); offline { + Error(w, http.StatusForbidden, "离线模式下无法检查更新") + return + } + current := app.Version latest, err := s.api.LatestVersion() if err != nil { @@ -261,7 +271,7 @@ func (s *InfoService) UpdateInfo(w http.ResponseWriter, r *http.Request) { versions, err := s.api.IntermediateVersions() if err != nil { - Error(w, http.StatusInternalServerError, "获取更新信息失败:%v", err) + Error(w, http.StatusInternalServerError, "获取升级信息失败:%v", err) return } @@ -269,13 +279,13 @@ func (s *InfoService) UpdateInfo(w http.ResponseWriter, r *http.Request) { } func (s *InfoService) Update(w http.ResponseWriter, r *http.Request) { - if s.taskRepo.HasRunningTask() { - Error(w, http.StatusInternalServerError, "当前有任务正在执行,禁止更新") + if offline, _ := s.settingRepo.GetBool(biz.SettingKeyOfflineMode); offline { + Error(w, http.StatusForbidden, "离线模式下无法升级") return } - if err := app.Orm.Exec("PRAGMA wal_checkpoint(TRUNCATE)").Error; err != nil { - types.Status = types.StatusFailed - Error(w, http.StatusInternalServerError, "面板数据库异常,已终止操作:%v", err) + + if s.taskRepo.HasRunningTask() { + Error(w, http.StatusInternalServerError, "后台任务正在运行,禁止升级,请稍后再试") return } @@ -306,7 +316,7 @@ func (s *InfoService) Update(w http.ResponseWriter, r *http.Request) { func (s *InfoService) Restart(w http.ResponseWriter, r *http.Request) { if s.taskRepo.HasRunningTask() { - Error(w, http.StatusInternalServerError, "当前有任务正在执行,禁止重启") + Error(w, http.StatusInternalServerError, "后台任务正在运行,禁止重启,请稍后再试") return } diff --git a/web/src/i18n/en.json b/web/src/i18n/en.json index 55bd9a17..567aacef 100644 --- a/web/src/i18n/en.json +++ b/web/src/i18n/en.json @@ -226,13 +226,16 @@ "placeholder": "admin{'@'}example.com" }, "port": { - "label": "Port (After saving, restart the panel and modify the browser address bar's port to the new port to access the panel)", + "label": "Port", "placeholder": "8888" }, "entrance": { - "label": "Security entrance (After saving, restart the panel and clear the browser Cookies to take effect)", + "label": "Security entrance", "placeholder": "admin" }, + "offline": { + "label": "Offline mode" + }, "https": { "label": "Panel HTTPS" }, diff --git a/web/src/i18n/zh_CN.json b/web/src/i18n/zh_CN.json index 9da8a2c2..fa958f79 100644 --- a/web/src/i18n/zh_CN.json +++ b/web/src/i18n/zh_CN.json @@ -88,22 +88,22 @@ } }, "homeUpdate": { - "title": "更新面板", + "title": "升级面板", "loading": "正在加载更新信息,稍等片刻", "alerts": { - "success": "面板更新成功", - "info": "取消更新" + "success": "面板升级成功", + "info": "取消升级" }, "button": { - "update": "立即更新" + "update": "立即升级" }, "confirm": { "update": { - "title": "更新面板", - "content": "确定更新面板吗?", + "title": "升级面板", + "content": "确定升级面板吗?", "positiveText": "确定", "negativeText": "取消", - "loading": "面板更新中..." + "loading": "面板升级中..." } } }, @@ -226,13 +226,16 @@ "placeholder": "admin{'@'}example.com" }, "port": { - "label": "端口(保存后需重启面板并修改浏览器地址栏的端口为新端口以访问面板)", + "label": "端口", "placeholder": "8888" }, "entrance": { - "label": "安全入口(保存后需重启面板并清除浏览器 Cookies 方可生效)", + "label": "安全入口", "placeholder": "admin" }, + "offline": { + "label": "离线模式" + }, "https": { "label": "面板 HTTPS" }, diff --git a/web/src/views/setting/SettingBase.vue b/web/src/views/setting/SettingBase.vue index f289ece7..bf42e350 100644 --- a/web/src/views/setting/SettingBase.vue +++ b/web/src/views/setting/SettingBase.vue @@ -16,6 +16,7 @@ const model = ref({ email: '', port: 8888, entrance: '', + offline_mode: false, website_path: '', backup_path: '', https: false, @@ -66,7 +67,7 @@ onMounted(() => { :placeholder="$t('settingIndex.edit.fields.name.placeholder')" /> - + @@ -99,6 +100,9 @@ onMounted(() => { :placeholder="$t('settingIndex.edit.fields.entrance.placeholder')" /> + + +