mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 11:27:17 +08:00
feat: 离线模式
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
// 回收内存
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ const model = ref<Setting>({
|
||||
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')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('settingIndex.edit.fields.locale.label')">
|
||||
<n-form-item v-show="false" label="$t('settingIndex.edit.fields.locale.label')">
|
||||
<n-select v-model:value="model.locale" :options="locales"> </n-select>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('settingIndex.edit.fields.username.label')">
|
||||
@@ -99,6 +100,9 @@ onMounted(() => {
|
||||
:placeholder="$t('settingIndex.edit.fields.entrance.placeholder')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('settingIndex.edit.fields.offline.label')">
|
||||
<n-switch v-model:value="model.offline_mode" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('settingIndex.edit.fields.path.label')">
|
||||
<n-input
|
||||
v-model:value="model.website_path"
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface Setting {
|
||||
email: string
|
||||
port: number
|
||||
entrance: string
|
||||
offline_mode: boolean
|
||||
website_path: string
|
||||
backup_path: string
|
||||
https: boolean
|
||||
|
||||
Reference in New Issue
Block a user