From 4e8c5a21f924e3fbad583aacbe5abb6e2485683c Mon Sep 17 00:00:00 2001
From: CityFun <31820853+zhengkunwang223@users.noreply.github.com>
Date: Wed, 17 Sep 2025 17:49:59 +0800
Subject: [PATCH] feat: Add support for offline version. (#10395)
---
agent/global/config.go | 1 +
core/app/api/v2/auth.go | 1 +
core/app/dto/setting.go | 1 +
core/app/service/upgrade.go | 3 +++
core/global/config.go | 1 +
core/init/viper/viper.go | 1 +
frontend/src/components/system-upgrade/index.vue | 12 ++++++++++--
frontend/src/lang/modules/en.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 +
frontend/src/store/interface/index.ts | 1 +
frontend/src/store/modules/global.ts | 1 +
frontend/src/views/app-store/apps/index.vue | 14 +++++++-------
frontend/src/views/app-store/detail/form/index.vue | 5 +++++
frontend/src/views/app-store/setting/index.vue | 8 ++++----
frontend/src/views/login/components/login-form.vue | 1 +
frontend/src/views/setting/index.vue | 8 ++++++++
23 files changed, 54 insertions(+), 13 deletions(-)
diff --git a/agent/global/config.go b/agent/global/config.go
index f2a492510..b2c62b138 100644
--- a/agent/global/config.go
+++ b/agent/global/config.go
@@ -14,6 +14,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"`
}
type RemoteURL struct {
diff --git a/core/app/api/v2/auth.go b/core/app/api/v2/auth.go
index d1eb03a71..79817cd30 100644
--- a/core/app/api/v2/auth.go
+++ b/core/app/api/v2/auth.go
@@ -137,6 +137,7 @@ func (b *BaseApi) GetLoginSetting(c *gin.Context) {
res := &dto.LoginSetting{
IsDemo: global.CONF.Base.IsDemo,
IsIntl: global.CONF.Base.IsIntl,
+ IsOffLine: global.CONF.Base.IsOffLine,
Language: settingInfo.Language,
MenuTabs: settingInfo.MenuTabs,
PanelName: settingInfo.PanelName,
diff --git a/core/app/dto/setting.go b/core/app/dto/setting.go
index 3ab501c4b..1afc810e3 100644
--- a/core/app/dto/setting.go
+++ b/core/app/dto/setting.go
@@ -232,6 +232,7 @@ type AppstoreConfig struct {
type LoginSetting struct {
IsDemo bool `json:"isDemo"`
IsIntl bool `json:"isIntl"`
+ IsOffLine bool `json:"isOffLine"`
Language string `json:"language"`
MenuTabs string `json:"menuTabs"`
PanelName string `json:"panelName"`
diff --git a/core/app/service/upgrade.go b/core/app/service/upgrade.go
index 9e03f7450..5505105e5 100644
--- a/core/app/service/upgrade.go
+++ b/core/app/service/upgrade.go
@@ -38,6 +38,9 @@ func NewIUpgradeService() IUpgradeService {
}
func (u *UpgradeService) SearchUpgrade() (*dto.UpgradeInfo, error) {
+ if global.CONF.Base.IsOffLine {
+ return &dto.UpgradeInfo{}, nil
+ }
var upgrade dto.UpgradeInfo
currentVersion, err := settingRepo.Get(repo.WithByKey("SystemVersion"))
if err != nil {
diff --git a/core/global/config.go b/core/global/config.go
index 5b97e506c..9f0ebb8bd 100644
--- a/core/global/config.go
+++ b/core/global/config.go
@@ -14,6 +14,7 @@ type Base struct {
Language string `mapstructure:"language"`
IsDemo bool `mapstructure:"is_demo"`
IsIntl bool `mapstructure:"is_intl"`
+ IsOffLine bool `mapstructure:"is_offline"`
Version string `mapstructure:"version"`
InstallDir string `mapstructure:"install_dir"`
ChangeUserInfo string `mapstructure:"change_user_info"`
diff --git a/core/init/viper/viper.go b/core/init/viper/viper.go
index 99a697b25..ae872f2a7 100644
--- a/core/init/viper/viper.go
+++ b/core/init/viper/viper.go
@@ -91,6 +91,7 @@ func Init() {
global.CONF.Base.InstallDir = baseDir
global.CONF.Base.IsDemo = v.GetBool("base.is_demo")
global.CONF.Base.IsIntl = v.GetBool("base.is_intl")
+ global.CONF.Base.IsOffLine = v.GetBool("base.is_offline")
global.CONF.Base.Version = version
global.CONF.Base.Username = username
global.CONF.Base.Password = password
diff --git a/frontend/src/components/system-upgrade/index.vue b/frontend/src/components/system-upgrade/index.vue
index 3f19e7cad..3219236e6 100644
--- a/frontend/src/components/system-upgrade/index.vue
+++ b/frontend/src/components/system-upgrade/index.vue
@@ -18,7 +18,15 @@
- {{ $t(!isMasterPro ? 'license.community' : 'license.pro') }}
+
+ {{ $t('license.pro') }}
+
+
+ {{ $t('license.offLine') }}
+
+
+ {{ $t('license.community') }}
+
{
diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts
index 5bfac10f2..bfd07ab80 100644
--- a/frontend/src/lang/modules/en.ts
+++ b/frontend/src/lang/modules/en.ts
@@ -1982,6 +1982,7 @@ const message = {
backupRecoverMessage: 'Please enter the compression or decompression password (leave blank to not set)',
},
license: {
+ offLine: 'Offline',
community: 'OSS',
oss: 'Open Source Software',
pro: 'Pro',
diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts
index ed63e8b2c..667cf265f 100644
--- a/frontend/src/lang/modules/ja.ts
+++ b/frontend/src/lang/modules/ja.ts
@@ -1899,6 +1899,7 @@ const message = {
backupRecoverMessage: '圧縮または減圧パスワードを入力してください(設定しないように空白のままにしてください)',
},
license: {
+ offLine: 'オフライン版',
community: '無料',
oss: '無料',
pro: '専門',
diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts
index 3ac5b6559..260eff08e 100644
--- a/frontend/src/lang/modules/ko.ts
+++ b/frontend/src/lang/modules/ko.ts
@@ -1870,6 +1870,7 @@ const message = {
backupRecoverMessage: '압축 또는 압축 해제 비밀번호를 입력하세요 (설정하지 않으려면 비워 두세요)',
},
license: {
+ offLine: '오프라인 버전',
community: 'OSS',
oss: '오픈 소스 소프트웨어',
pro: 'Pro',
diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts
index f4a05711c..774797bae 100644
--- a/frontend/src/lang/modules/ms.ts
+++ b/frontend/src/lang/modules/ms.ts
@@ -1958,6 +1958,7 @@ const message = {
'Sila masukkan kata laluan mampatan atau nyahmampatan (biarkan kosong jika tidak menetapkan)',
},
license: {
+ offLine: 'Versi Luar Talian',
community: 'OSS',
oss: 'Perisian Sumber Terbuka',
pro: 'Pro',
diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts
index 15f68e8b2..2fd832d4c 100644
--- a/frontend/src/lang/modules/pt-br.ts
+++ b/frontend/src/lang/modules/pt-br.ts
@@ -1946,6 +1946,7 @@ const message = {
'Por favor, insira a senha de compressão ou descompressão (deixe em branco para não definir)',
},
license: {
+ offLine: 'Versão Offline',
community: 'Gratuito',
oss: 'Open Source Software',
pro: 'Pro',
diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts
index c81c61948..a0762b771 100644
--- a/frontend/src/lang/modules/ru.ts
+++ b/frontend/src/lang/modules/ru.ts
@@ -1945,6 +1945,7 @@ const message = {
'Пожалуйста, введите пароль для сжатия или распаковки (оставьте пустым, чтобы не устанавливать)',
},
license: {
+ offLine: 'Офлайн версия',
community: 'OSS',
oss: 'Open Source Software',
pro: 'Pro',
diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts
index 2accc31f9..653cd3050 100644
--- a/frontend/src/lang/modules/tr.ts
+++ b/frontend/src/lang/modules/tr.ts
@@ -1999,6 +1999,7 @@ const message = {
backupRecoverMessage: 'Lütfen sıkıştırma veya sıkıştırma açma parolasını girin (ayarlamamak için boş bırakın)',
},
license: {
+ offLine: 'Çevrimdışı Sürüm',
community: 'OSS',
oss: 'Açık Kaynak Yazılım',
pro: 'Pro',
diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts
index 40151e63e..e4a3d3e17 100644
--- a/frontend/src/lang/modules/zh-Hant.ts
+++ b/frontend/src/lang/modules/zh-Hant.ts
@@ -1855,6 +1855,7 @@ const message = {
backupRecoverMessage: '請輸入壓縮或解壓縮密碼(留空則不設定)',
},
license: {
+ offLine: '離線版',
community: '社區版',
oss: '社區版',
pro: '專業版:',
diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts
index 8327bb701..d646d3221 100644
--- a/frontend/src/lang/modules/zh.ts
+++ b/frontend/src/lang/modules/zh.ts
@@ -1848,6 +1848,7 @@ const message = {
backupRecoverMessage: '请输入压缩或解压缩密码(留空则不设置)',
},
license: {
+ offLine: '离线版',
community: '社区版',
oss: '社区版',
pro: '专业版',
diff --git a/frontend/src/store/interface/index.ts b/frontend/src/store/interface/index.ts
index 3ec452b53..73e39f38a 100644
--- a/frontend/src/store/interface/index.ts
+++ b/frontend/src/store/interface/index.ts
@@ -41,6 +41,7 @@ export interface GlobalState {
isIntl: boolean;
productProExpires: number;
isMasterProductPro: boolean;
+ isOffLine: boolean;
currentNode: string;
currentNodeAddr: string;
diff --git a/frontend/src/store/modules/global.ts b/frontend/src/store/modules/global.ts
index 1c6aa6a83..a46dbc69b 100644
--- a/frontend/src/store/modules/global.ts
+++ b/frontend/src/store/modules/global.ts
@@ -44,6 +44,7 @@ const GlobalStore = defineStore({
isIntl: false,
productProExpires: 0,
isMasterProductPro: false,
+ isOffLine: false,
currentNode: 'local',
currentNodeAddr: '',
diff --git a/frontend/src/views/app-store/apps/index.vue b/frontend/src/views/app-store/apps/index.vue
index 4f4e45c23..9595ad65f 100644
--- a/frontend/src/views/app-store/apps/index.vue
+++ b/frontend/src/views/app-store/apps/index.vue
@@ -7,7 +7,7 @@
- {{ syncCustomAppstore ? $t('app.syncCustomApp') : $t('app.syncAppList') }}
+ {{ syncCustomAppstore || isOffLine ? $t('app.syncCustomApp') : $t('app.syncAppList') }}
{{ $t('app.syncLocalApp') }}
@@ -74,11 +74,9 @@ import { searchApp, syncApp, syncCutomAppStore, syncLocalApp, getCurrentNodeCust
import Install from '../detail/install/index.vue';
import router from '@/routers';
import { MsgSuccess } from '@/utils/message';
-import { GlobalStore } from '@/store';
import { newUUID } from '@/utils/util';
import Detail from '../detail/index.vue';
import TaskLog from '@/components/log/task/index.vue';
-import { storeToRefs } from 'pinia';
import bus from '@/global/bus';
import Tags from '@/views/app-store/components/tag.vue';
import DockerStatus from '@/views/container/docker-status/index.vue';
@@ -86,9 +84,8 @@ import NoApp from '@/views/app-store/apps/no-app/index.vue';
import AppCard from '@/views/app-store/apps/app/index.vue';
import MainDiv from '@/components/main-div/index.vue';
import { jumpToInstall } from '@/utils/app';
-
-const globalStore = GlobalStore();
-const { isProductPro } = storeToRefs(globalStore);
+import { useGlobalStore } from '@/composables/useGlobalStore';
+const { globalStore, isProductPro, isOffLine } = useGlobalStore();
const mobile = computed(() => {
return globalStore.isMobile();
@@ -183,7 +180,7 @@ const sync = async () => {
};
try {
let res;
- if (isProductPro.value && syncCustomAppstore.value) {
+ if (isOffLine.value || (isProductPro.value && syncCustomAppstore.value)) {
res = await syncCutomAppStore(syncReq);
} else {
res = await syncApp(syncReq);
@@ -249,6 +246,9 @@ onMounted(async () => {
syncCustomAppstore.value = res.data.status === 'Enable';
}
}
+ if (isOffLine.value) {
+ syncCustomAppstore.value = true;
+ }
mainHeight.value = window.innerHeight - 380;
window.onresize = () => {
return (() => {
diff --git a/frontend/src/views/app-store/detail/form/index.vue b/frontend/src/views/app-store/detail/form/index.vue
index 48a7bf0ff..39a6623d9 100644
--- a/frontend/src/views/app-store/detail/form/index.vue
+++ b/frontend/src/views/app-store/detail/form/index.vue
@@ -146,6 +146,8 @@ import { Container } from '@/api/interface/container';
import CodemirrorPro from '@/components/codemirror-pro/index.vue';
import { computeSizeFromMB } from '@/utils/util';
import { loadResourceLimit } from '@/api/modules/container';
+import { useGlobalStore } from '@/composables/useGlobalStore';
+const { isOffLine } = useGlobalStore();
interface ClusterProps {
key: string;
@@ -282,6 +284,9 @@ const initForm = async (appKey: string) => {
formData.value.version = defaultVersion;
getVersionDetail(defaultVersion);
}
+ if (isOffLine.value) {
+ formData.value.pullImage = false;
+ }
};
const getMasterAppInstall = async (appInstallID: number, masterNode: string) => {
diff --git a/frontend/src/views/app-store/setting/index.vue b/frontend/src/views/app-store/setting/index.vue
index cd54e0e43..ae838cee1 100644
--- a/frontend/src/views/app-store/setting/index.vue
+++ b/frontend/src/views/app-store/setting/index.vue
@@ -39,7 +39,7 @@
@change="updateConfig('UpgradeBackup', config.upgradeBackup)"
/>
-
+
{{ $t('xpack.customApp.licenseHelper') }}
@@ -58,10 +58,11 @@
import { getCurrentNodeCustomAppConfig } from '@/api/modules/app';
import { getAppStoreConfig, updateAppStoreConfig } from '@/api/modules/setting';
import { FormRules } from 'element-plus';
-import { GlobalStore } from '@/store';
import { MsgSuccess } from '@/utils/message';
import i18n from '@/lang';
import { defineAsyncComponent } from 'vue';
+import { useGlobalStore } from '@/composables/useGlobalStore';
+const { isProductPro, isMasterProductPro } = useGlobalStore();
const CustomSetting = defineAsyncComponent(async () => {
const modules = import.meta.glob('@/xpack/views/appstore/index.vue');
@@ -72,7 +73,6 @@ const CustomSetting = defineAsyncComponent(async () => {
return { template: '' };
});
-const globalStore = GlobalStore();
const rules = ref({});
const config = ref({
uninstallDeleteImage: '',
@@ -107,7 +107,7 @@ const toUpload = () => {
};
const getNodeConfig = async () => {
- if (globalStore.isMasterProductPro) {
+ if (isMasterProductPro.value) {
return;
}
const res = await getCurrentNodeCustomAppConfig();
diff --git a/frontend/src/views/login/components/login-form.vue b/frontend/src/views/login/components/login-form.vue
index 6945386d8..75b347007 100644
--- a/frontend/src/views/login/components/login-form.vue
+++ b/frontend/src/views/login/components/login-form.vue
@@ -422,6 +422,7 @@ const getSetting = async () => {
handleCommand(loginForm.language);
isIntl.value = res.data.isIntl;
globalStore.isIntl = isIntl.value;
+ globalStore.isOffLine = res.data.isOffLine;
document.title = res.data.panelName;
i18n.locale.value = res.data.language;
diff --git a/frontend/src/views/setting/index.vue b/frontend/src/views/setting/index.vue
index 26fe1885f..855dd69ff 100644
--- a/frontend/src/views/setting/index.vue
+++ b/frontend/src/views/setting/index.vue
@@ -9,6 +9,8 @@