diff --git a/agent/app/api/v2/app.go b/agent/app/api/v2/app.go index b6431ee59..3d04911ab 100644 --- a/agent/app/api/v2/app.go +++ b/agent/app/api/v2/app.go @@ -209,3 +209,39 @@ func (b *BaseApi) GetAppListUpdate(c *gin.Context) { } helper.SuccessWithData(c, res) } + +// @Tags App +// @Summary Update appstore config +// @Description 更新应用商店配置 +// @Accept json +// @Param request body request.AppstoreUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /apps/store/update [post] +func (b *BaseApi) UpdateAppstoreConfig(c *gin.Context) { + var req request.AppstoreUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := appService.UpdateAppstoreConfig(req) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithOutData(c) +} + +// @Tags App +// @Summary Get appstore config +// @Description 获取应用商店配置 +// @Success 200 {object} response.AppstoreConfig +// @Security ApiKeyAuth +// @Router /apps/store/config [get] +func (b *BaseApi) GetAppstoreConfig(c *gin.Context) { + res, err := appService.GetAppstoreConfig() + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, res) +} diff --git a/agent/app/dto/request/app.go b/agent/app/dto/request/app.go index 465d2c812..e4af74854 100644 --- a/agent/app/dto/request/app.go +++ b/agent/app/dto/request/app.go @@ -125,3 +125,7 @@ type AppUpdateVersion struct { AppInstallID uint `json:"appInstallID" validate:"required"` UpdateVersion string `json:"updateVersion"` } + +type AppstoreUpdate struct { + DefaultDomain string `json:"defaultDomain"` +} diff --git a/agent/app/dto/response/app.go b/agent/app/dto/response/app.go index a0c1e19ff..803a2be32 100644 --- a/agent/app/dto/response/app.go +++ b/agent/app/dto/response/app.go @@ -159,3 +159,7 @@ type AppConfig struct { Params []AppParam `json:"params"` request.AppContainerConfig } + +type AppstoreConfig struct { + DefaultDomain string `json:"defaultDomain"` +} diff --git a/agent/app/service/app.go b/agent/app/service/app.go index d2f3ff9c0..244e0a072 100644 --- a/agent/app/service/app.go +++ b/agent/app/service/app.go @@ -45,6 +45,9 @@ type IAppService interface { GetAppDetailByID(id uint) (*response.AppDetailDTO, error) SyncAppListFromLocal(taskID string) GetIgnoredApp() ([]response.IgnoredApp, error) + + GetAppstoreConfig() (*response.AppstoreConfig, error) + UpdateAppstoreConfig(req request.AppstoreUpdate) error } func NewIAppService() IAppService { @@ -1067,3 +1070,15 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) { return nil } + +func (a AppService) UpdateAppstoreConfig(req request.AppstoreUpdate) error { + settingService := NewISettingService() + return settingService.Update("AppDefaultDomain", req.DefaultDomain) +} + +func (a AppService) GetAppstoreConfig() (*response.AppstoreConfig, error) { + defaultDomain, _ := settingRepo.Get(settingRepo.WithByKey("AppDefaultDomain")) + res := &response.AppstoreConfig{} + res.DefaultDomain = defaultDomain.Value + return res, nil +} diff --git a/agent/app/service/setting.go b/agent/app/service/setting.go index eaa4aa583..be456075a 100644 --- a/agent/app/service/setting.go +++ b/agent/app/service/setting.go @@ -46,14 +46,16 @@ func (u *SettingService) Update(key, value string) error { case "AppStoreLastModified": exist, _ := settingRepo.Get(settingRepo.WithByKey("AppStoreLastModified")) if exist.ID == 0 { - _ = settingRepo.Create("AppStoreLastModified", value) - return nil + return settingRepo.Create("AppStoreLastModified", value) + } + case "AppDefaultDomain": + exist, _ := settingRepo.Get(settingRepo.WithByKey("AppDefaultDomain")) + if exist.ID == 0 { + return settingRepo.Create("AppDefaultDomain", value) } } - if err := settingRepo.Update(key, value); err != nil { return err } - return nil } diff --git a/agent/router/ro_app.go b/agent/router/ro_app.go index 41797bea8..21171de1d 100644 --- a/agent/router/ro_app.go +++ b/agent/router/ro_app.go @@ -39,5 +39,7 @@ func (a *AppRouter) InitRouter(Router *gin.RouterGroup) { appRouter.GET("/ignored/detail", baseApi.GetIgnoredApp) appRouter.POST("/installed/update/versions", baseApi.GetUpdateVersions) appRouter.POST("/installed/config/update", baseApi.UpdateAppConfig) + appRouter.POST("/store/update", baseApi.UpdateAppstoreConfig) + appRouter.GET("/store/config", baseApi.GetAppstoreConfig) } } diff --git a/frontend/src/api/interface/app.ts b/frontend/src/api/interface/app.ts index 3cdb31381..1328ff818 100644 --- a/frontend/src/api/interface/app.ts +++ b/frontend/src/api/interface/app.ts @@ -263,4 +263,8 @@ export namespace App { installID: number; webUI: string; } + + export interface AppStoreConfig { + defaultDomain: string; + } } diff --git a/frontend/src/api/modules/app.ts b/frontend/src/api/modules/app.ts index 7b271ecba..6d022c55a 100644 --- a/frontend/src/api/modules/app.ts +++ b/frontend/src/api/modules/app.ts @@ -110,3 +110,11 @@ export const GetIgnoredApp = () => { export const UpdateInstallConfig = (req: App.AppConfigUpdate) => { return http.post(`apps/installed/config/update`, req); }; + +export const GetAppStoreConfig = () => { + return http.get(`apps/store/config`); +}; + +export const UpdateAppStoreConfig = (req: App.AppStoreConfig) => { + return http.post(`apps/store/update`, req); +}; diff --git a/frontend/src/global/form-rules.ts b/frontend/src/global/form-rules.ts index 230d31389..cd78bd495 100644 --- a/frontend/src/global/form-rules.ts +++ b/frontend/src/global/form-rules.ts @@ -92,6 +92,23 @@ const checkIpV4V6OrDomain = (rule: any, value: any, callback: any) => { } }; +const checkDomainOrIP = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(); + } else { + const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + const ipv6Regex = + /^(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; + const domainRegex = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i; + + if (ipv4Regex.test(value) || ipv6Regex.test(value) || domainRegex.test(value)) { + callback(); + } else { + callback(new Error(i18n.global.t('commons.rule.domain'))); + } + } +}; + const checkHost = (rule: any, value: any, callback: any) => { if (value === '' || typeof value === 'undefined' || value == null) { callback(new Error(i18n.global.t('commons.rule.requiredInput'))); @@ -577,6 +594,7 @@ interface CommonRule { filePermission: FormItemRule; phpExtensions: FormItemRule; supervisorName: FormItemRule; + domainOrIP: FormItemRule; paramCommon: FormItemRule; paramComplexity: FormItemRule; @@ -806,4 +824,8 @@ export const Rules: CommonRule = { validator: checkIpv4, trigger: 'blur', }, + domainOrIP: { + validator: checkDomainOrIP, + trigger: 'blur', + }, }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 4d64924c0..0e7f070b2 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1885,6 +1885,11 @@ const message = { 'Please ensure the machine has an NVIDIA GPU and that NVIDIA drivers and the NVIDIA Docker Container Toolkit are installed', webUI: 'Web Access Address', webUIPlaceholder: 'For example: http://example.com:8080/login', + defaultWebDomain: 'Default Access Address', + defaultWebDomainHepler: + 'The default access is used for application port forwarding. For example, if the application port is 8080, the forwarding address would be http(s)://default-access-address:8080', + webUIConfig: 'Please add the access address in the application parameters or the app store settings', + toLink: 'Open', }, website: { website: 'Website', diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts index 93718d475..abd598290 100644 --- a/frontend/src/lang/modules/tw.ts +++ b/frontend/src/lang/modules/tw.ts @@ -1750,6 +1750,11 @@ const message = { gpuConfigHelper: '請確保機器有 NVIDIA GPU 並且安裝 NVIDIA 驅動 和 NVIDIA docker Container Toolkit', webUI: 'Web 訪問地址', webUIPlaceholder: '例如:http://example.com:8080/login', + defaultWebDomain: '默認訪問地址', + defaultWebDomainHepler: + '默認訪問用於應用端口跳轉,例如應用端口為 8080 則跳轉地址為 http(s)://默認訪問地址:8080', + webUIConfig: '請在應用參數或者應用商店設置處添加訪問地址', + toLink: '連結', }, website: { website: '網站', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index b1a79ebc8..af9184d47 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1751,6 +1751,10 @@ const message = { gpuConfigHelper: '请确保机器有 NVIDIA GPU 并且安装 NVIDIA 驱动 和 NVIDIA docker Container Toolkit', webUI: 'Web 访问地址', webUIPlaceholder: '例如:http://example.com:8080/login', + defaultWebDomain: '默认访问地址', + defaultWebDomainHepler: '默认访问用于应用端口跳转,例如应用端口为 8080 则跳转地址为http(s)://默认访问地址:8080', + webUIConfig: '请在应用参数或者应用商店设置处添加访问地址', + toLink: '跳转', }, website: { website: '网站', diff --git a/frontend/src/routers/modules/app-store.ts b/frontend/src/routers/modules/app-store.ts index d056def49..2bb33fc01 100644 --- a/frontend/src/routers/modules/app-store.ts +++ b/frontend/src/routers/modules/app-store.ts @@ -50,6 +50,17 @@ const appStoreRouter = { requiresAuth: false, }, }, + { + path: 'setting', + name: 'AppStoreSetting', + component: () => import('@/views/app-store/setting/index.vue'), + props: true, + hidden: true, + meta: { + activeMenu: '/apps', + requiresAuth: false, + }, + }, ], }, ], diff --git a/frontend/src/views/app-store/index.vue b/frontend/src/views/app-store/index.vue index 8d1858475..c99331145 100644 --- a/frontend/src/views/app-store/index.vue +++ b/frontend/src/views/app-store/index.vue @@ -29,6 +29,10 @@ const buttons = [ path: '/apps/upgrade', count: 0, }, + { + label: i18n.global.t('commons.button.set'), + path: '/apps/setting', + }, ]; const search = () => { diff --git a/frontend/src/views/app-store/installed/detail/index.vue b/frontend/src/views/app-store/installed/detail/index.vue index 87d6ea938..a80631730 100644 --- a/frontend/src/views/app-store/installed/detail/index.vue +++ b/frontend/src/views/app-store/installed/detail/index.vue @@ -7,27 +7,29 @@
+ + + {{ appConfigUpdate.webUI }} + + {{ $t('commons.button.edit') }} + + + + + + {{ param.showValue && param.showValue != '' ? param.showValue : param.value }} - - - - - - - {{ $t('commons.button.confirm') }} - - -
- - -
({ installID: 0, webUI: '', }); +const openConfig = ref(false); const acceptParams = async (props: ParamProps) => { submitModel.value.installId = props.id; @@ -159,6 +162,7 @@ const acceptParams = async (props: ParamProps) => { edit.value = false; await get(); open.value = true; + openConfig.value = false; }; const handleClose = () => { diff --git a/frontend/src/views/app-store/installed/index.vue b/frontend/src/views/app-store/installed/index.vue index 1cf77edcf..97073e816 100644 --- a/frontend/src/views/app-store/installed/index.vue +++ b/frontend/src/views/app-store/installed/index.vue @@ -175,23 +175,6 @@ - - - - - - - - @@ -265,13 +247,60 @@ v-if="installed.httpsPort > 0" @click="goDashboard(installed.httpsPort, 'https')" class="tagMargin" - icon="Position" plain size="small" > {{ $t('app.busPort') }}:{{ installed.httpsPort }} + + + + + + + + + + + +
+ + {{ defaultLink + ':' + installed.httpPort }} + +
+ + {{ installed.webUI }} + +
+ + {{ $t('app.webUIConfig') }} + +
+
{{ $t('app.alreadyRun') }}: @@ -339,6 +368,7 @@ import { SyncInstalledApp, AppInstalledDeleteCheck, GetAppTags, + GetAppStoreConfig, } from '@/api/modules/app'; import { onMounted, onUnmounted, reactive, ref } from 'vue'; import i18n from '@/lang'; @@ -401,6 +431,8 @@ const activeName = ref(i18n.global.t('app.installed')); const mode = ref('installed'); const moreTag = ref(''); const language = getLanguage(); +const defaultLink = ref(''); + const options = { modifiers: [ { @@ -670,7 +702,17 @@ const toLink = (link: string) => { window.open(link, '_blank'); }; +const getAppstoreConfig = async () => { + try { + const res = await GetAppStoreConfig(); + if (res.data.defaultDomain != '') { + defaultLink.value = res.data.defaultDomain; + } + } catch (error) {} +}; + onMounted(() => { + getAppstoreConfig(); const path = router.currentRoute.value.path; if (path == '/apps/upgrade') { activeName.value = i18n.global.t('app.canUpgrade'); diff --git a/frontend/src/views/app-store/setting/index.vue b/frontend/src/views/app-store/setting/index.vue new file mode 100644 index 000000000..ce7b7d8a2 --- /dev/null +++ b/frontend/src/views/app-store/setting/index.vue @@ -0,0 +1,117 @@ + + +