1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-14 01:34:47 +08:00

feat: 应用增加 Web 访问地址 (#6248)

This commit is contained in:
zhengkunwang 2024-08-26 15:37:13 +08:00 committed by GitHub
parent 4ac5ff7cf0
commit 53cfb2e755
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 137 additions and 10 deletions

View File

@ -325,3 +325,24 @@ func (b *BaseApi) IgnoreUpgrade(c *gin.Context) {
} }
helper.SuccessWithOutData(c) helper.SuccessWithOutData(c)
} }
// @Tags App
// @Summary Update app config
// @Description 更新应用配置
// @Accept json
// @Param request body request.AppConfigUpdate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /apps/installed/config/update [post]
// @x-panel-log {"bodyKeys":["installID","webUI"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"应用配置更新 [installID]","formatEN":"Application config update [installID]"}
func (b *BaseApi) UpdateAppConfig(c *gin.Context) {
var req request.AppConfigUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := appInstallService.UpdateAppConfig(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithOutData(c)
}

View File

@ -37,6 +37,8 @@ type AppContainerConfig struct {
HostMode bool `json:"hostMode"` HostMode bool `json:"hostMode"`
PullImage bool `json:"pullImage"` PullImage bool `json:"pullImage"`
GpuConfig bool `json:"gpuConfig"` GpuConfig bool `json:"gpuConfig"`
WebUI string `json:"webUI"`
Type string `json:"type"`
} }
type AppInstalledSearch struct { type AppInstalledSearch struct {
@ -103,6 +105,11 @@ type AppInstalledUpdate struct {
AppContainerConfig AppContainerConfig
} }
type AppConfigUpdate struct {
InstallID uint `json:"installID" validate:"required"`
WebUI string `json:"webUI"`
}
type AppInstalledIgnoreUpgrade struct { type AppInstalledIgnoreUpgrade struct {
DetailID uint `json:"detailID" validate:"required"` DetailID uint `json:"detailID" validate:"required"`
Operate string `json:"operate" validate:"required,oneof=cancel ignore"` Operate string `json:"operate" validate:"required,oneof=cancel ignore"`

View File

@ -120,6 +120,7 @@ type AppInstallDTO struct {
AppType string `json:"appType"` AppType string `json:"appType"`
AppStatus string `json:"appStatus"` AppStatus string `json:"appStatus"`
DockerCompose string `json:"dockerCompose"` DockerCompose string `json:"dockerCompose"`
WebUI string `json:"webUI"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
App AppDetail `json:"app"` App AppDetail `json:"app"`
} }

View File

@ -21,8 +21,9 @@ type AppInstall struct {
Message string `json:"message"` Message string `json:"message"`
ContainerName string `json:"containerName" gorm:"not null"` ContainerName string `json:"containerName" gorm:"not null"`
ServiceName string `json:"serviceName" gorm:"not null"` ServiceName string `json:"serviceName" gorm:"not null"`
HttpPort int `json:"httpPort" gorm:"not null"` HttpPort int `json:"httpPort"`
HttpsPort int `json:"httpsPort" gorm:"not null"` HttpsPort int `json:"httpsPort"`
WebUI string `json:"webUI"`
App App `json:"app" gorm:"-:migration"` App App `json:"app" gorm:"-:migration"`
} }

View File

@ -59,6 +59,7 @@ type IAppInstallService interface {
GetDefaultConfigByKey(key, name string) (string, error) GetDefaultConfigByKey(key, name string) (string, error)
DeleteCheck(installId uint) ([]dto.AppResource, error) DeleteCheck(installId uint) ([]dto.AppResource, error)
UpdateAppConfig(req request.AppConfigUpdate) error
GetInstallList() ([]dto.AppInstallInfo, error) GetInstallList() ([]dto.AppInstallInfo, error)
} }
@ -304,6 +305,18 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
} }
} }
func (a *AppInstallService) UpdateAppConfig(req request.AppConfigUpdate) error {
installed, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallID))
if err != nil {
return err
}
installed.WebUI = ""
if req.WebUI != "" {
installed.WebUI = req.WebUI
}
return appInstallRepo.Save(context.Background(), &installed)
}
func (a *AppInstallService) Update(req request.AppInstalledUpdate) error { func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
installed, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId)) installed, err := appInstallRepo.GetFirst(commonRepo.WithByID(req.InstallId))
if err != nil { if err != nil {
@ -767,6 +780,8 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
} }
res.AppContainerConfig = config res.AppContainerConfig = config
res.HostMode = isHostModel(install.DockerCompose) res.HostMode = isHostModel(install.DockerCompose)
res.WebUI = install.WebUI
res.Type = install.App.Type
return &res, nil return &res, nil
} }

View File

@ -1363,6 +1363,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
AppType: installed.App.Type, AppType: installed.App.Type,
Path: installed.GetPath(), Path: installed.GetPath(),
CreatedAt: installed.CreatedAt, CreatedAt: installed.CreatedAt,
WebUI: installed.WebUI,
App: response.AppDetail{ App: response.AppDetail{
Github: installed.App.Github, Github: installed.App.Github,
Website: installed.App.Website, Website: installed.App.Website,

View File

@ -20,6 +20,7 @@ func Init() {
migrations.UpdateWebsiteDomain, migrations.UpdateWebsiteDomain,
migrations.UpdateApp, migrations.UpdateApp,
migrations.AddTaskDB, migrations.AddTaskDB,
migrations.UpdateAppInstall,
}) })
if err := m.Migrate(); err != nil { if err := m.Migrate(); err != nil {
global.LOG.Error(err) global.LOG.Error(err)

View File

@ -251,3 +251,11 @@ var UpdateApp = &gormigrate.Migration{
&model.App{}) &model.App{})
}, },
} }
var UpdateAppInstall = &gormigrate.Migration{
ID: "20240828-update-app-install",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.AppInstall{})
},
}

View File

@ -38,5 +38,6 @@ func (a *AppRouter) InitRouter(Router *gin.RouterGroup) {
appRouter.POST("/installed/ignore", baseApi.IgnoreUpgrade) appRouter.POST("/installed/ignore", baseApi.IgnoreUpgrade)
appRouter.GET("/ignored/detail", baseApi.GetIgnoredApp) appRouter.GET("/ignored/detail", baseApi.GetIgnoredApp)
appRouter.POST("/installed/update/versions", baseApi.GetUpdateVersions) appRouter.POST("/installed/update/versions", baseApi.GetUpdateVersions)
appRouter.POST("/installed/config/update", baseApi.UpdateAppConfig)
} }
} }

View File

@ -239,6 +239,8 @@ export namespace App {
allowPort: boolean; allowPort: boolean;
dockerCompose: string; dockerCompose: string;
hostMode?: boolean; hostMode?: boolean;
type: string;
webUI: string;
} }
export interface IgnoredApp { export interface IgnoredApp {
@ -256,4 +258,9 @@ export namespace App {
export interface AppStoreSync { export interface AppStoreSync {
taskID: string; taskID: string;
} }
export interface AppConfigUpdate {
installID: number;
webUI: string;
}
} }

View File

@ -106,3 +106,7 @@ export const IgnoreUpgrade = (req: any) => {
export const GetIgnoredApp = () => { export const GetIgnoredApp = () => {
return http.get<App.IgnoredApp>(`apps/ignored/detail`); return http.get<App.IgnoredApp>(`apps/ignored/detail`);
}; };
export const UpdateInstallConfig = (req: App.AppConfigUpdate) => {
return http.post(`apps/installed/config/update`, req);
};

View File

@ -1883,6 +1883,8 @@ const message = {
gpuConfig: 'Enable GPU Support', gpuConfig: 'Enable GPU Support',
gpuConfigHelper: gpuConfigHelper:
'Please ensure the machine has an NVIDIA GPU and that NVIDIA drivers and the NVIDIA Docker Container Toolkit are installed', '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',
}, },
website: { website: {
website: 'Website', website: 'Website',

View File

@ -1748,6 +1748,8 @@ const message = {
memoryRequiredHelper: '目前應用記憶體需求 {0}', memoryRequiredHelper: '目前應用記憶體需求 {0}',
gpuConfig: '開啟 GPU 支援', gpuConfig: '開啟 GPU 支援',
gpuConfigHelper: '請確保機器有 NVIDIA GPU 並且安裝 NVIDIA 驅動 NVIDIA docker Container Toolkit', gpuConfigHelper: '請確保機器有 NVIDIA GPU 並且安裝 NVIDIA 驅動 NVIDIA docker Container Toolkit',
webUI: 'Web 訪問地址',
webUIPlaceholder: '例如http://example.com:8080/login',
}, },
website: { website: {
website: '網站', website: '網站',

View File

@ -1749,6 +1749,8 @@ const message = {
memoryRequiredHelper: '当前应用内存需求 {0}', memoryRequiredHelper: '当前应用内存需求 {0}',
gpuConfig: '开启 GPU 支持', gpuConfig: '开启 GPU 支持',
gpuConfigHelper: '请确保机器有 NVIDIA GPU 并且安装 NVIDIA 驱动 NVIDIA docker Container Toolkit', gpuConfigHelper: '请确保机器有 NVIDIA GPU 并且安装 NVIDIA 驱动 NVIDIA docker Container Toolkit',
webUI: 'Web 访问地址',
webUIPlaceholder: '例如http://example.com:8080/login',
}, },
website: { website: {
website: '网站', website: '网站',

View File

@ -5,15 +5,29 @@
{{ edit ? $t('app.detail') : $t('commons.button.edit') }} {{ edit ? $t('app.detail') : $t('commons.button.edit') }}
</el-button> </el-button>
</template> </template>
<el-descriptions border :column="1" v-if="!edit"> <div v-if="!edit">
<el-descriptions-item v-for="(param, key) in params" :label="getLabel(param)" :key="key"> <el-descriptions border :column="1">
<span>{{ param.showValue && param.showValue != '' ? param.showValue : param.value }}</span> <el-descriptions-item v-for="(param, key) in params" :label="getLabel(param)" :key="key">
</el-descriptions-item> <span>{{ param.showValue && param.showValue != '' ? param.showValue : param.value }}</span>
</el-descriptions> </el-descriptions-item>
</el-descriptions>
<el-form label-position="top" class="mt-2">
<el-form-item v-if="appType == 'website'" :label="$t('app.webUI')">
<el-input v-model="appConfigUpdate.webUI" :placeholder="$t('app.webUIPlaceholder')"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :disabled="loading" @click="updateAppConfig">
{{ $t('commons.button.confirm') }}
</el-button>
</el-form-item>
</el-form>
</div>
<div v-else v-loading="loading"> <div v-else v-loading="loading">
<el-alert :title="$t('app.updateHelper')" type="warning" :closable="false" class="common-prompt" /> <el-alert :title="$t('app.updateHelper')" type="warning" :closable="false" class="common-prompt" />
<el-form @submit.prevent ref="paramForm" :model="paramModel" label-position="top" :rules="rules"> <el-form @submit.prevent ref="paramForm" :model="paramModel" label-position="top" :rules="rules">
<el-form-item v-if="appType == 'website'" :label="$t('app.webUI')">
<el-input v-model="appConfigUpdate.webUI" :placeholder="$t('app.webUIPlaceholder')"></el-input>
</el-form-item>
<div v-for="(p, index) in params" :key="index"> <div v-for="(p, index) in params" :key="index">
<el-form-item :prop="p.key" :label="getLabel(p)"> <el-form-item :prop="p.key" :label="getLabel(p)">
<el-input <el-input
@ -93,7 +107,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { App } from '@/api/interface/app'; import { App } from '@/api/interface/app';
import { GetAppInstallParams, UpdateAppInstallParams } from '@/api/modules/app'; import { GetAppInstallParams, UpdateAppInstallParams, UpdateInstallConfig } from '@/api/modules/app';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { FormInstance } from 'element-plus'; import { FormInstance } from 'element-plus';
import { Rules, checkNumberRange } from '@/global/form-rules'; import { Rules, checkNumberRange } from '@/global/form-rules';
@ -128,7 +142,14 @@ const rules = reactive({
memoryLimit: [Rules.requiredInput, checkNumberRange(0, 9999999999)], memoryLimit: [Rules.requiredInput, checkNumberRange(0, 9999999999)],
containerName: [Rules.containerName], containerName: [Rules.containerName],
}); });
const submitModel = ref<any>({}); const submitModel = ref<any>({
webUI: '',
});
const appType = ref('');
const appConfigUpdate = ref<App.AppConfigUpdate>({
installID: 0,
webUI: '',
});
const acceptParams = async (props: ParamProps) => { const acceptParams = async (props: ParamProps) => {
submitModel.value.installId = props.id; submitModel.value.installId = props.id;
@ -188,6 +209,8 @@ const get = async () => {
paramModel.value.advanced = false; paramModel.value.advanced = false;
paramModel.value.dockerCompose = res.data.dockerCompose; paramModel.value.dockerCompose = res.data.dockerCompose;
paramModel.value.isHostMode = res.data.hostMode; paramModel.value.isHostMode = res.data.hostMode;
appConfigUpdate.value.webUI = res.data.webUI;
appType.value = res.data.type;
} catch (error) { } catch (error) {
} finally { } finally {
loading.value = false; loading.value = false;
@ -240,6 +263,17 @@ const submit = async (formEl: FormInstance) => {
}); });
}; };
const updateAppConfig = async () => {
try {
await UpdateInstallConfig({
installID: Number(paramData.value.id),
webUI: appConfigUpdate.value.webUI,
});
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
handleClose();
} catch (error) {}
};
defineExpose({ acceptParams }); defineExpose({ acceptParams });
</script> </script>

View File

@ -175,6 +175,22 @@
</el-button> </el-button>
</el-tooltip> </el-tooltip>
</span> </span>
<span class="ml-1">
<el-tooltip
v-if="installed.webUI !== ''"
effect="dark"
:content="installed.webUI"
placement="top"
>
<el-button
type="primary"
link
@click="toLink(installed.webUI)"
>
<el-icon><Promotion /></el-icon>
</el-button>
</el-tooltip>
</span>
<el-button <el-button
class="h-button" class="h-button"
@ -650,6 +666,10 @@ const openLog = (row: any) => {
} }
}; };
const toLink = (link: string) => {
window.open(link, '_blank');
};
onMounted(() => { onMounted(() => {
const path = router.currentRoute.value.path; const path = router.currentRoute.value.path;
if (path == '/apps/upgrade') { if (path == '/apps/upgrade') {