From 8fb8d97dcf1e42e562b816aa69f3a0e6b46e6e29 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Thu, 30 Apr 2026 11:05:55 +0800 Subject: [PATCH] feat: add offline environment setting (#12637) --- agent/app/service/app.go | 2 +- agent/app/service/mcp_server.go | 2 +- agent/global/config.go | 2 +- agent/init/business/business.go | 2 +- core/app/api/v2/auth.go | 2 +- core/app/dto/auth.go | 23 +++++++++++++ core/app/dto/setting.go | 28 ++-------------- core/app/service/script_library.go | 4 +-- core/app/service/setting.go | 7 ++++ core/app/service/upgrade.go | 2 +- core/global/config.go | 2 +- core/init/migration/migrate.go | 1 + core/init/migration/migrations/init.go | 10 ++++++ core/init/run/run.go | 2 +- core/init/validator/validator.go | 32 +++++++++++++++++++ core/init/viper/viper.go | 2 +- frontend/src/api/interface/auth.ts | 2 +- frontend/src/api/interface/setting.ts | 1 + .../src/components/system-upgrade/index.vue | 9 ++++-- frontend/src/lang/modules/en.ts | 1 + frontend/src/lang/modules/es-es.ts | 1 + frontend/src/lang/modules/ja.ts | 1 + frontend/src/lang/modules/ko.ts | 1 + frontend/src/lang/modules/ms.ts | 1 + frontend/src/lang/modules/pt-br.ts | 1 + frontend/src/lang/modules/ru.ts | 1 + frontend/src/lang/modules/tr.ts | 1 + frontend/src/lang/modules/zh-Hant.ts | 1 + frontend/src/lang/modules/zh.ts | 1 + .../Sidebar/components/user-info/index.vue | 21 +++++------- frontend/src/store/interface/index.ts | 2 +- frontend/src/store/modules/global.ts | 2 +- frontend/src/views/app-store/apps/index.vue | 8 ++--- .../src/views/app-store/detail/form/index.vue | 2 +- frontend/src/views/home/index.vue | 13 +++++++- .../src/views/login/components/login-form.vue | 2 +- frontend/src/views/setting/index.vue | 4 +-- .../views/setting/license-required/index.vue | 2 +- frontend/src/views/setting/panel/index.vue | 18 +++++++++++ .../src/views/website/runtime/app/index.vue | 4 +-- .../website/runtime/php/create/index.vue | 4 +-- 41 files changed, 159 insertions(+), 68 deletions(-) diff --git a/agent/app/service/app.go b/agent/app/service/app.go index e96a5a51f..409679e38 100644 --- a/agent/app/service/app.go +++ b/agent/app/service/app.go @@ -126,7 +126,7 @@ func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (*response. lang := strings.ToLower(common.GetLang(ctx)) for _, ap := range apps { if req.Type == "php" { - if !global.CONF.Base.IsOffLine && (ap.RequiredPanelVersion == 0 || !common.CompareAppVersion(common.GetSystemVersion(info.SystemVersion), fmt.Sprintf("%f", ap.RequiredPanelVersion))) { + if !global.CONF.Base.IsOffline && (ap.RequiredPanelVersion == 0 || !common.CompareAppVersion(common.GetSystemVersion(info.SystemVersion), fmt.Sprintf("%f", ap.RequiredPanelVersion))) { continue } } diff --git a/agent/app/service/mcp_server.go b/agent/app/service/mcp_server.go index 615d3bd14..47e0c1717 100644 --- a/agent/app/service/mcp_server.go +++ b/agent/app/service/mcp_server.go @@ -681,7 +681,7 @@ func GetWebsiteID() uint { } func pullImage(imageType string) { - if global.CONF.Base.IsOffLine { + if global.CONF.Base.IsOffline { return } if imageType == "npx" { diff --git a/agent/global/config.go b/agent/global/config.go index 057c48443..9f877fe1f 100644 --- a/agent/global/config.go +++ b/agent/global/config.go @@ -15,7 +15,7 @@ type Base struct { Mode string `mapstructure:"mode"` // xpack [ Enable / Disable ] IsDemo bool `mapstructure:"is_demo"` InstallDir string `mapstructure:"install_dir"` - IsOffLine bool `mapstructure:"is_offline"` + IsOffline bool `mapstructure:"is_offline"` } type SystemDir struct { diff --git a/agent/init/business/business.go b/agent/init/business/business.go index 2e9e8576b..d6d922aa2 100644 --- a/agent/init/business/business.go +++ b/agent/init/business/business.go @@ -20,7 +20,7 @@ func Init() { } func syncApp() { - if global.CONF.Base.IsOffLine { + if global.CONF.Base.IsOffline { return } setting, err := service.NewISettingService().GetSettingInfo() diff --git a/core/app/api/v2/auth.go b/core/app/api/v2/auth.go index 943f0dce4..7f4e9de99 100644 --- a/core/app/api/v2/auth.go +++ b/core/app/api/v2/auth.go @@ -241,7 +241,7 @@ func (b *BaseApi) GetLoginSetting(c *gin.Context) { IsDemo: global.CONF.Base.IsDemo, IsIntl: global.CONF.Base.Edition == "intl", IsFxplay: global.CONF.Base.IsFxplay, - IsOffLine: global.CONF.Base.IsOffLine, + IsOffline: global.CONF.Base.IsOffline || settingInfo.IsOffline == constant.StatusEnable, IsEnterprise: global.CONF.Base.IsEnterprise, Language: settingInfo.Language, MenuTabs: settingInfo.MenuTabs, diff --git a/core/app/dto/auth.go b/core/app/dto/auth.go index 84772df16..b081f5172 100644 --- a/core/app/dto/auth.go +++ b/core/app/dto/auth.go @@ -51,3 +51,26 @@ type SystemSetting struct { type PasskeyID struct { ID string `json:"id" validate:"required"` } + +type CurrentUserInfo struct { + Name string `json:"name"` + SessionTimeout int `json:"sessionTimeout"` + MFAStatus string `json:"mfaStatus"` + MFAInterval string `json:"mfaInterval"` + ExpirationDays int `json:"expirationDays"` + ExpirationTime string `json:"expirationTime"` + ComplexitySetting string `json:"complexitySetting"` + + ApiInterfaceStatus string `json:"apiInterfaceStatus"` + ApiKey string `json:"apiKey"` + IpWhiteList string `json:"ipWhiteList"` + ApiKeyValidityTime string `json:"apiKeyValidityTime"` +} +type CurrentUserUpdate struct { + Name string `json:"name" validate:"required"` + Password string `json:"password"` + OldPassword string `json:"oldPassword"` + SessionTimeout int `json:"sessionTimeout" validate:"required,min=300,max=864000"` + ExpirationDays int `json:"expirationDays" validate:"min=0,max=60"` + ExpirationTime string `json:"expirationTime"` +} diff --git a/core/app/dto/setting.go b/core/app/dto/setting.go index e2084f161..087913a44 100644 --- a/core/app/dto/setting.go +++ b/core/app/dto/setting.go @@ -18,6 +18,7 @@ type SettingInfo struct { MenuTabs string `json:"menuTabs"` Language string `json:"language"` DocSource string `json:"docSource"` + IsOffline string `json:"isOffline"` ServerPort string `json:"serverPort"` SSL string `json:"ssl"` @@ -73,35 +74,12 @@ type SettingBaseInfo struct { DashboardSimpleNodeVisible string `json:"dashboardSimpleNodeVisible"` } -type CurrentUserInfo struct { - Name string `json:"name"` - SessionTimeout int `json:"sessionTimeout"` - MFAStatus string `json:"mfaStatus"` - MFAInterval string `json:"mfaInterval"` - ExpirationDays int `json:"expirationDays"` - ExpirationTime string `json:"expirationTime"` - ComplexitySetting string `json:"complexitySetting"` - - ApiInterfaceStatus string `json:"apiInterfaceStatus"` - ApiKey string `json:"apiKey"` - IpWhiteList string `json:"ipWhiteList"` - ApiKeyValidityTime string `json:"apiKeyValidityTime"` -} -type CurrentUserUpdate struct { - Name string `json:"name" validate:"required"` - Password string `json:"password"` - OldPassword string `json:"oldPassword"` - SessionTimeout int `json:"sessionTimeout" validate:"required,min=300,max=864000"` - ExpirationDays int `json:"expirationDays" validate:"min=0,max=60"` - ExpirationTime string `json:"expirationTime"` -} - type SettingKey struct { Key string `json:"key" validate:"required,oneof=ScriptSync"` } type SettingUpdate struct { - Key string `json:"key" validate:"required"` + Key string `json:"key" validate:"required,base_setting_key"` Value string `json:"value"` } @@ -291,7 +269,7 @@ type AppstoreConfig struct { type LoginSetting struct { IsDemo bool `json:"isDemo"` IsIntl bool `json:"isIntl"` - IsOffLine bool `json:"isOffLine"` + IsOffline bool `json:"isOffline"` IsFxplay bool `json:"isFxplay"` IsEnterprise bool `json:"isEnterprise"` Language string `json:"language"` diff --git a/core/app/service/script_library.go b/core/app/service/script_library.go index 3fb3914d0..239bea660 100644 --- a/core/app/service/script_library.go +++ b/core/app/service/script_library.go @@ -172,7 +172,7 @@ func StartSync() { } service := NewIScriptService() scriptSync, _ := repo.NewISettingRepo().GetValueByKey("ScriptSync") - if !global.CONF.Base.IsOffLine && scriptSync == constant.StatusEnable { + if !global.CONF.Base.IsOffline && scriptSync == constant.StatusEnable { minuteRand, err := rand.Int(rand.Reader, big.NewInt(60)) if err != nil { global.LOG.Errorf("generate random minute failed: %v", err) @@ -202,7 +202,7 @@ func LoadScriptInfo(id uint) (model.ScriptLibrary, error) { } func (u *ScriptService) Sync(req dto.OperateByTaskID) error { - if global.CONF.Base.IsOffLine { + if global.CONF.Base.IsOffline { return nil } syncTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("RemoteScriptLibrary"), task.TaskSync, task.TaskScopeScript, req.TaskID, 0) diff --git a/core/app/service/setting.go b/core/app/service/setting.go index 8cc7577f8..040f52459 100644 --- a/core/app/service/setting.go +++ b/core/app/service/setting.go @@ -106,6 +106,9 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) { } else { info.ProxyPasswd, _ = encrypt.StringDecrypt(info.ProxyPasswd) } + if !global.CONF.Base.IsEnterprise { + info.IsOffline = constant.StatusDisable + } return &info, err } @@ -167,6 +170,10 @@ func (u *SettingService) Update(c *gin.Context, key, value string) error { return nil } switch key { + case "IsOffline": + if !global.CONF.Base.IsEnterprise { + return buserr.New("ErrNotSupportInEnterpriseEdition") + } case "AppStoreLastModified": exist, _ := settingRepo.Get(repo.WithByKey("AppStoreLastModified")) if exist.ID == 0 { diff --git a/core/app/service/upgrade.go b/core/app/service/upgrade.go index 8f44006cb..5bfa914e6 100644 --- a/core/app/service/upgrade.go +++ b/core/app/service/upgrade.go @@ -87,7 +87,7 @@ func NewIUpgradeService() IUpgradeService { } func (u *UpgradeService) SearchUpgrade() (*dto.UpgradeInfo, error) { - if global.CONF.Base.IsOffLine { + if global.CONF.Base.IsOffline { return &dto.UpgradeInfo{}, nil } var upgrade dto.UpgradeInfo diff --git a/core/global/config.go b/core/global/config.go index f6c2903d6..c1a0e34af 100644 --- a/core/global/config.go +++ b/core/global/config.go @@ -12,7 +12,7 @@ type Base struct { Password string `mapstructure:"password"` Language string `mapstructure:"language"` IsDemo bool `mapstructure:"is_demo"` - IsOffLine bool `mapstructure:"is_offline"` + IsOffline bool `mapstructure:"is_offline"` IsFxplay bool `mapstructure:"is_fxplay"` Edition string `mapstructure:"edition"` IsEnterprise bool `mapstructure:"is_enterprise"` diff --git a/core/init/migration/migrate.go b/core/init/migration/migrate.go index 53f2bb5eb..b46c99708 100644 --- a/core/init/migration/migrate.go +++ b/core/init/migration/migrate.go @@ -40,6 +40,7 @@ func Init() { migrations.AddAppStoreInstallAllowPortSetting, migrations.AddUserManagementMenu, migrations.AddOperationLogUser, + migrations.AddIsOfflineSetting, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/core/init/migration/migrations/init.go b/core/init/migration/migrations/init.go index e0cbe8e89..e2b323fdf 100644 --- a/core/init/migration/migrations/init.go +++ b/core/init/migration/migrations/init.go @@ -1048,3 +1048,13 @@ var AddOperationLogUser = &gormigrate.Migration{ return tx.AutoMigrate(&model.OperationLog{}) }, } + +var AddIsOfflineSetting = &gormigrate.Migration{ + ID: "20260429-add-is-offline-setting", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Setting{Key: "IsOffline", Value: constant.StatusDisable}).Error; err != nil { + return err + } + return nil + }, +} diff --git a/core/init/run/run.go b/core/init/run/run.go index 56cb18cda..687785e35 100644 --- a/core/init/run/run.go +++ b/core/init/run/run.go @@ -10,7 +10,7 @@ import ( func Init() { scriptSync, _ := repo.NewISettingRepo().GetValueByKey("ScriptSync") - if !global.CONF.Base.IsOffLine && scriptSync == constant.StatusEnable { + if !global.CONF.Base.IsOffline && scriptSync == constant.StatusEnable { if err := service.NewIScriptService().Sync(dto.OperateByTaskID{}); err != nil { global.LOG.Errorf("sync scripts from remote failed, err: %v", err) } diff --git a/core/init/validator/validator.go b/core/init/validator/validator.go index 06cb69432..a9c5883e7 100644 --- a/core/init/validator/validator.go +++ b/core/init/validator/validator.go @@ -20,9 +20,36 @@ func Init() { if err := validator.RegisterValidation("password", checkPasswordPattern); err != nil { panic(err) } + if err := validator.RegisterValidation("base_setting_key", checkBaseSettingKey); err != nil { + panic(err) + } global.VALID = validator } +var baseSettingKeys = map[string]struct{}{ + "PanelName": {}, + "Theme": {}, + "MenuTabs": {}, + "Language": {}, + "DeveloperMode": {}, + "UpgradeBackupCopies": {}, + "SecurityEntrance": {}, + "BindDomain": {}, + "AllowIPs": {}, + "PasskeyTrustedProxies": {}, + "ComplexityVerification": {}, + "NoAuthSetting": {}, + "DashboardMemoVisible": {}, + "DashboardSimpleNodeVisible": {}, + "MFAStatus": {}, + "Edition": {}, + "DocSource": {}, + "IsOffline": {}, + "AppStoreLastModified": {}, + "ScriptSync": {}, + "HideMenu": {}, +} + func checkNamePattern(fl validator.FieldLevel) bool { value := fl.Field().String() result, err := regexp.MatchString("^[a-zA-Z\u4e00-\u9fa5]{1}[a-zA-Z0-9_\u4e00-\u9fa5]{0,30}$", value) @@ -63,3 +90,8 @@ func checkPasswordPattern(fl validator.FieldLevel) bool { return false } + +func checkBaseSettingKey(fl validator.FieldLevel) bool { + _, ok := baseSettingKeys[fl.Field().String()] + return ok +} diff --git a/core/init/viper/viper.go b/core/init/viper/viper.go index 1762dad7a..9baf3b1e0 100644 --- a/core/init/viper/viper.go +++ b/core/init/viper/viper.go @@ -87,7 +87,7 @@ func Init() { global.CONF.Base.InstallDir = baseDir global.CONF.Base.IsDemo = v.GetBool("base.is_demo") global.CONF.Base.IsFxplay = v.GetBool("base.is_fxplay") - global.CONF.Base.IsOffLine = v.GetBool("base.is_offline") + global.CONF.Base.IsOffline = v.GetBool("base.is_offline") if edition == "intl" { global.CONF.Base.Edition = "intl" } else { diff --git a/frontend/src/api/interface/auth.ts b/frontend/src/api/interface/auth.ts index 05da35846..63a87c2d5 100644 --- a/frontend/src/api/interface/auth.ts +++ b/frontend/src/api/interface/auth.ts @@ -39,7 +39,7 @@ export namespace Login { menuTabs: string; panelName: string; theme: string; - isOffLine: boolean; + isOffline: boolean; isEnterprise: boolean; needCaptcha: boolean; passkeySetting: boolean; diff --git a/frontend/src/api/interface/setting.ts b/frontend/src/api/interface/setting.ts index 27b3fce81..868664b5b 100644 --- a/frontend/src/api/interface/setting.ts +++ b/frontend/src/api/interface/setting.ts @@ -39,6 +39,7 @@ export namespace Setting { menuTabs: string; language: string; docSource: string; + isOffline: string; serverPort: number; ipv6: string; diff --git a/frontend/src/components/system-upgrade/index.vue b/frontend/src/components/system-upgrade/index.vue index ff0a90a80..f3d462b28 100644 --- a/frontend/src/components/system-upgrade/index.vue +++ b/frontend/src/components/system-upgrade/index.vue @@ -28,6 +28,7 @@ {{ $t('license.pro') }} + @@ -44,7 +45,7 @@ @@ -73,7 +74,7 @@ import { GlobalStore } from '@/store'; import { storeToRefs } from 'pinia'; const globalStore = GlobalStore(); -const { docsUrl, isOffLine, isFxplay } = storeToRefs(globalStore); +const { docsUrl, isOffline, isFxplay } = storeToRefs(globalStore); const upgradeRef = ref(); const releasesRef = ref(); const isMasterPro = computed(() => { @@ -107,6 +108,10 @@ const getVersionLog = () => { }; const toLxware = () => { + if (isOffline.value) { + to1Panel(); + return; + } if (!globalStore.isIntl) { window.open('https://www.lxware.cn/1panel' + '', '_blank', 'noopener,noreferrer'); } else { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 77d3b55dd..bb5011a86 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -2230,6 +2230,7 @@ const message = { auto: 'Follow System', language: 'Language', runtimeEnv: 'Runtime environment', + offlineEnv: 'Offline environment', docSource: 'Documentation Source', withByRegion: 'Match Region Setting (Default)', withByLang: 'Match System Language', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 581f31040..88698e744 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -2270,6 +2270,7 @@ const message = { auto: 'Seguir sistema', language: 'Idioma', runtimeEnv: 'Entorno de ejecución', + offlineEnv: 'Entorno sin conexión', docSource: 'Fuente de documentación', withByRegion: 'Seguir región de operación (Predeterminado)', withByLang: 'Seguir idioma del sistema', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index bbf69a732..2d6f686f6 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -2235,6 +2235,7 @@ const message = { auto: 'システムをフォローします', language: '言語', runtimeEnv: '実行環境', + offlineEnv: 'オフライン環境', docSource: 'ドキュメントの参照先', withByRegion: '運用リージョンに従う(デフォルト)', withByLang: 'システム言語に従う', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 106b2c5ca..94ead14d9 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -2195,6 +2195,7 @@ const message = { auto: '시스템 따라가기', language: '언어', runtimeEnv: '실행 환경', + offlineEnv: '오프라인 환경', docSource: '문서 출처', withByRegion: '운영 지역 따름(기본값)', withByLang: '시스템 언어 따름', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index a9b59021c..7ccaa75ed 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -2275,6 +2275,7 @@ const message = { auto: 'Ikut Sistem', language: 'Bahasa', runtimeEnv: 'Persekitaran operasi', + offlineEnv: 'Persekitaran luar talian', docSource: 'Sumber dokumentasi', withByRegion: 'Ikut wilayah operasi (Lalai)', withByLang: 'Ikut bahasa sistem', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 188b33c20..f4293baae 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -2387,6 +2387,7 @@ const message = { auto: 'Seguir o sistema', language: 'Idioma', runtimeEnv: 'Ambiente de execução', + offlineEnv: 'Ambiente offline', docSource: 'Fonte da documentação', withByRegion: 'Seguir região de operação (Padrão)', withByLang: 'Seguir idioma do sistema', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 6c3066121..4285a5383 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -2252,6 +2252,7 @@ const message = { auto: 'Как в системе', language: 'Язык', runtimeEnv: 'Среда выполнения', + offlineEnv: 'Офлайн-среда', docSource: 'Источник документации', withByRegion: 'Следовать региону работы (по умолчанию)', withByLang: 'Следовать языку системы', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 8bef79c99..ea07ee84a 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -2266,6 +2266,7 @@ const message = { auto: 'Sistemi takip et', language: 'Dil', runtimeEnv: 'Çalışma ortamı', + offlineEnv: 'Çevrimdışı ortam', docSource: 'Dokümantasyon kaynağı', withByRegion: 'Çalışma bölgesini takip et (Varsayılan)', withByLang: 'Sistem dilini takip et', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 8f991c7e4..4ba38e000 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -2093,6 +2093,7 @@ const message = { auto: '跟隨系統', language: '系統語言', runtimeEnv: '運行環境', + offlineEnv: '離線環境', docSource: '文件來源', withByRegion: '跟隨運行區域(預設)', withByLang: '跟隨系統語言', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 547b8e56d..1f58f65ac 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -2093,6 +2093,7 @@ const message = { withByRegion: '跟随运行区域(默认)', withByLang: '跟随系统语言', runtimeEnv: '运行环境', + offlineEnv: '离线环境', region: '运行区域', cn: '中国大陆', intl: '全球', diff --git a/frontend/src/layout/components/Sidebar/components/user-info/index.vue b/frontend/src/layout/components/Sidebar/components/user-info/index.vue index b43454124..c431e4eaf 100644 --- a/frontend/src/layout/components/Sidebar/components/user-info/index.vue +++ b/frontend/src/layout/components/Sidebar/components/user-info/index.vue @@ -88,7 +88,6 @@