diff --git a/backend/app/api/v1/container.go b/backend/app/api/v1/container.go index 56cac8cfe..4ba8e1197 100644 --- a/backend/app/api/v1/container.go +++ b/backend/app/api/v1/container.go @@ -715,7 +715,7 @@ func (b *BaseApi) ComposeUpdate(c *gin.Context) { // @Param follow query string false "是否追踪" // @Param tail query string false "显示行号" // @Security ApiKeyAuth -// @Router /containers/compose/search/log [post] +// @Router /containers/compose/search/log [get] func (b *BaseApi) ComposeLogs(c *gin.Context) { wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) if err != nil { diff --git a/backend/app/dto/request/runtime.go b/backend/app/dto/request/runtime.go index eb26c3706..7838e58da 100644 --- a/backend/app/dto/request/runtime.go +++ b/backend/app/dto/request/runtime.go @@ -25,6 +25,7 @@ type RuntimeCreate struct { type NodeConfig struct { Install bool `json:"install"` Clean bool `json:"clean"` + Port int `json:"port"` } type RuntimeDelete struct { diff --git a/backend/app/dto/response/runtime.go b/backend/app/dto/response/runtime.go index 456661f68..09cb251b9 100644 --- a/backend/app/dto/response/runtime.go +++ b/backend/app/dto/response/runtime.go @@ -21,6 +21,8 @@ type RuntimeDTO struct { CreatedAt time.Time `json:"createdAt"` CodeDir string `json:"codeDir"` AppParams []AppParam `json:"appParams"` + Port int `json:"port"` + Path string `json:"path"` } type PackageScripts struct { @@ -41,5 +43,7 @@ func NewRuntimeDTO(runtime model.Runtime) RuntimeDTO { CreatedAt: runtime.CreatedAt, CodeDir: runtime.CodeDir, Version: runtime.Version, + Port: runtime.Port, + Path: runtime.GetPath(), } } diff --git a/backend/app/model/runtime.go b/backend/app/model/runtime.go index 937c367ae..d71051a44 100644 --- a/backend/app/model/runtime.go +++ b/backend/app/model/runtime.go @@ -18,6 +18,7 @@ type Runtime struct { Type string `gorm:"type:varchar;not null" json:"type"` Status string `gorm:"type:varchar;not null" json:"status"` Resource string `gorm:"type:varchar;not null" json:"resource"` + Port int `gorm:"type:integer;" json:"port"` Message string `gorm:"type:longtext;" json:"message"` CodeDir string `gorm:"type:varchar;" json:"codeDir"` } diff --git a/backend/app/repo/runtime.go b/backend/app/repo/runtime.go index 614f30a69..489743c62 100644 --- a/backend/app/repo/runtime.go +++ b/backend/app/repo/runtime.go @@ -17,6 +17,7 @@ type IRuntimeRepo interface { WithNotId(id uint) DBOption WithStatus(status string) DBOption WithDetailId(id uint) DBOption + WithPort(port int) DBOption Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error) Create(ctx context.Context, runtime *model.Runtime) error Save(runtime *model.Runtime) error @@ -59,6 +60,12 @@ func (r *RuntimeRepo) WithNotId(id uint) DBOption { } } +func (r *RuntimeRepo) WithPort(port int) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("port = ?", port) + } +} + func (r *RuntimeRepo) Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error) { var runtimes []model.Runtime db := getDb(opts...).Model(&model.Runtime{}) diff --git a/backend/app/service/app_utils.go b/backend/app/service/app_utils.go index 5c19f9db4..ef8b0d912 100644 --- a/backend/app/service/app_utils.go +++ b/backend/app/service/app_utils.go @@ -82,6 +82,33 @@ func checkPort(key string, params map[string]interface{}) (int, error) { return 0, nil } +func checkPortExist(port int) error { + errMap := make(map[string]interface{}) + errMap["port"] = port + appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port)) + if appInstall.ID > 0 { + errMap["type"] = i18n.GetMsgByKey("TYPE_APP") + errMap["name"] = appInstall.Name + return buserr.WithMap("ErrPortExist", errMap, nil) + } + runtime, _ := runtimeRepo.GetFirst(runtimeRepo.WithPort(port)) + if runtime != nil { + errMap["type"] = i18n.GetMsgByKey("TYPE_RUNTIME") + errMap["name"] = runtime.Name + return buserr.WithMap("ErrPortExist", errMap, nil) + } + domain, _ := websiteDomainRepo.GetFirst(websiteDomainRepo.WithPort(port)) + if domain.ID > 0 { + errMap["type"] = i18n.GetMsgByKey("TYPE_DOMAIN") + errMap["name"] = domain.Domain + return buserr.WithMap("ErrPortExist", errMap, nil) + } + if common.ScanPort(port) { + return buserr.WithDetail(constant.ErrPortInUsed, port, nil) + } + return nil +} + var DatabaseKeys = map[string]uint{ "mysql": 3306, "mariadb": 3306, @@ -559,6 +586,8 @@ func handleMap(params map[string]interface{}, envParams map[string]string) { envParams[k] = strconv.FormatFloat(t, 'f', -1, 32) case uint: envParams[k] = strconv.Itoa(int(t)) + case int: + envParams[k] = strconv.Itoa(t) default: envParams[k] = t.(string) } diff --git a/backend/app/service/runtime.go b/backend/app/service/runtime.go index 163337290..00a6b419f 100644 --- a/backend/app/service/runtime.go +++ b/backend/app/service/runtime.go @@ -39,7 +39,16 @@ func NewRuntimeService() IRuntimeService { } func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) { - exist, _ := runtimeRepo.GetFirst(runtimeRepo.WithName(create.Name), commonRepo.WithByType(create.Type)) + var ( + opts []repo.DBOption + ) + if create.Name != "" { + opts = append(opts, commonRepo.WithLikeName(create.Name)) + } + if create.Type != "" { + opts = append(opts, commonRepo.WithByType(create.Type)) + } + exist, _ := runtimeRepo.GetFirst(opts...) if exist != nil { return buserr.New(constant.ErrNameIsExist) } @@ -66,6 +75,9 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) { return buserr.New(constant.ErrPathNotFound) } create.Install = true + if err = checkPortExist(create.Port); err != nil { + return err + } } appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(create.AppDetailID)) @@ -98,6 +110,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (err error) { return } case constant.RuntimeNode: + runtime.Port = create.Port if err = handleNode(create, runtime, fileOp, appVersionDir); err != nil { return } @@ -286,6 +299,13 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error { if exist != nil { return buserr.New(constant.ErrImageExist) } + case constant.RuntimeNode: + if runtime.Port != req.Port { + if err = checkPortExist(req.Port); err != nil { + return err + } + runtime.Port = req.Port + } } projectDir := path.Join(constant.RuntimeDir, runtime.Type, runtime.Name) @@ -296,6 +316,9 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error { Params: req.Params, CodeDir: req.CodeDir, Version: req.Version, + NodeConfig: request.NodeConfig{ + Port: req.Port, + }, } composeContent, envContent, _, err := handleParams(create, projectDir) if err != nil { @@ -321,6 +344,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error { case constant.RuntimeNode: runtime.Version = req.Version runtime.CodeDir = req.CodeDir + runtime.Port = req.Port runtime.Status = constant.RuntimeReCreating _ = runtimeRepo.Save(runtime) go reCreateRuntime(runtime) diff --git a/backend/app/service/runtime_utils.go b/backend/app/service/runtime_utils.go index f1563eef6..95a2196f8 100644 --- a/backend/app/service/runtime_utils.go +++ b/backend/app/service/runtime_utils.go @@ -304,6 +304,7 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte case constant.RuntimeNode: create.Params["CODE_DIR"] = create.CodeDir create.Params["NODE_VERSION"] = create.Version + create.Params["PANEL_APP_PORT_HTTP"] = create.Port if create.NodeConfig.Install { create.Params["RUN_INSTALL"] = "1" } else { diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index 123aeb127..6f96b2841 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -14,6 +14,10 @@ ErrNameIsExist: "Name is already exist" ErrDemoEnvironment: "Demo server, prohibit this operation!" ErrCmdTimeout: "Command execution timed out!" ErrCmdIllegal: "The command contains illegal characters. Please modify and try again!" +ErrPortExist: '{{ .port }} port is already occupied by {{ .type }} [{{ .name }}]' +TYPE_APP: "Application" +TYPE_RUNTIME: "Runtime environment" +TYPE_DOMAIN: "Domain name" #app ErrPortInUsed: "{{ .detail }} port already in use" diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index e2bb4e071..2a36cd4b9 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -14,6 +14,10 @@ ErrNameIsExist: "名稱已存在" ErrDemoEnvironment: "演示伺服器,禁止此操作!" ErrCmdTimeout: "指令執行超時!" ErrCmdIllegal: "執行命令中存在不合法字符,請修改後重試!" +ErrPortExist: '{{ .port }} 埠已被 {{ .type }} [{{ .name }}] 佔用' +TYPE_APP: "應用" +TYPE_RUNTIME: "運作環境" +TYPE_DOMAIN: "網域名稱" #app ErrPortInUsed: "{{ .detail }} 端口已被佔用!" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index 95420123d..8184db9fb 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -14,6 +14,10 @@ ErrNameIsExist: "名称已存在" ErrDemoEnvironment: "演示服务器,禁止此操作!" ErrCmdTimeout: "命令执行超时!" ErrCmdIllegal: "执行命令中存在不合法字符,请修改后重试!" +ErrPortExist: '{{ .port }} 端口已被 {{ .type }} [{{ .name }}] 占用' +TYPE_APP: "应用" +TYPE_RUNTIME: "运行环境" +TYPE_DOMAIN: "域名" #app ErrPortInUsed: "{{ .detail }} 端口已被占用!" diff --git a/backend/init/migration/migrations/v_1_7.go b/backend/init/migration/migrations/v_1_7.go index 6f1816795..3d824435d 100644 --- a/backend/init/migration/migrations/v_1_7.go +++ b/backend/init/migration/migrations/v_1_7.go @@ -17,7 +17,7 @@ var AddDefaultNetwork = &gormigrate.Migration{ } var UpdateRuntime = &gormigrate.Migration{ - ID: "20230920-update-runtime", + ID: "20230927-update-runtime", Migrate: func(tx *gorm.DB) error { if err := tx.AutoMigrate(&model.Runtime{}); err != nil { return err diff --git a/frontend/src/api/interface/runtime.ts b/frontend/src/api/interface/runtime.ts index 82075a8d1..ca22905e6 100644 --- a/frontend/src/api/interface/runtime.ts +++ b/frontend/src/api/interface/runtime.ts @@ -14,6 +14,7 @@ export namespace Runtime { version: string; status: string; codeDir: string; + port: number; } export interface RuntimeReq extends ReqPage { @@ -35,6 +36,7 @@ export namespace Runtime { appParams: App.InstallParams[]; appID: number; source?: string; + path?: string; } export interface RuntimeCreate { @@ -50,6 +52,7 @@ export namespace Runtime { rebuild?: boolean; source?: string; codeDir?: string; + port?: number; } export interface RuntimeUpdate { diff --git a/frontend/src/views/app-store/installed/index.vue b/frontend/src/views/app-store/installed/index.vue index 181d72dcc..5ee6ce27e 100644 --- a/frontend/src/views/app-store/installed/index.vue +++ b/frontend/src/views/app-store/installed/index.vue @@ -13,7 +13,7 @@ > {{ $t('app.all') }} -
+
{{ $t('app.installHelper') }} - + {{ $t('firewall.quickJump') }} diff --git a/frontend/src/views/website/runtime/node/index.vue b/frontend/src/views/website/runtime/node/index.vue index 59a0acb0e..15367267e 100644 --- a/frontend/src/views/website/runtime/node/index.vue +++ b/frontend/src/views/website/runtime/node/index.vue @@ -31,10 +31,12 @@ - + + + + + + + +
@@ -88,6 +97,17 @@ import Delete from '@/views/website/runtime/delete/index.vue'; import i18n from '@/lang'; import RouterMenu from '../index.vue'; import router from '@/routers/router'; +import ComposeLogs from '@/components/compose-log/index.vue'; +import { Promotion } from '@element-plus/icons-vue'; +import PortJumpDialog from '@/components/port-jump/index.vue'; + +let timer: NodeJS.Timer | null = null; +const loading = ref(false); +const items = ref([]); +const operateRef = ref(); +const deleteRef = ref(); +const dialogPortJumpRef = ref(); +const composeLogRef = ref(); const paginationConfig = reactive({ cacheSizeKey: 'runtime-page-size', @@ -101,8 +121,6 @@ const req = reactive({ pageSize: 40, type: 'node', }); -let timer: NodeJS.Timer | null = null; - const buttons = [ { label: i18n.global.t('container.stop'), @@ -147,10 +165,6 @@ const buttons = [ }, }, ]; -const loading = ref(false); -const items = ref([]); -const operateRef = ref(); -const deleteRef = ref(); const disabledRuntime = (row: Runtime.Runtime) => { return row.status === 'starting' || row.status === 'recreating'; @@ -182,6 +196,14 @@ const openDelete = async (row: Runtime.Runtime) => { deleteRef.value.acceptParams(row.id, row.name); }; +const openLog = (row: any) => { + composeLogRef.value.acceptParams({ compose: row.path + '/docker-compose.yml', resource: row.name }); +}; + +const goDashboard = async (port: any, protocol: string) => { + dialogPortJumpRef.value.acceptParams({ port: port, protocol: protocol }); +}; + const operateRuntime = async (operate: string, ID: number) => { try { const action = await ElMessageBox.confirm( diff --git a/frontend/src/views/website/runtime/node/operate/index.vue b/frontend/src/views/website/runtime/node/operate/index.vue index 85992d443..ca6e65de8 100644 --- a/frontend/src/views/website/runtime/node/operate/index.vue +++ b/frontend/src/views/website/runtime/node/operate/index.vue @@ -89,8 +89,8 @@ - - + + {{ $t('runtime.externalPortHelper') }} @@ -170,15 +170,16 @@ const initData = (type: string) => ({ resource: 'appstore', rebuild: false, codeDir: '/', + port: 3000, }); let runtime = reactive(initData('node')); const rules = ref({ name: [Rules.appName], appID: [Rules.requiredSelect], codeDir: [Rules.requiredInput], + port: [Rules.requiredInput, Rules.port], params: { NODE_APP_PORT: [Rules.requiredInput, Rules.port], - PANEL_APP_PORT_HTTP: [Rules.requiredInput, Rules.port], PACKAGE_MANAGER: [Rules.requiredSelect], HOST_IP: [Rules.requiredSelect], EXEC_SCRIPT: [Rules.requiredSelect], @@ -192,7 +193,7 @@ watch( () => runtime.params['NODE_APP_PORT'], (newVal) => { if (newVal && mode.value == 'create') { - runtime.params['PANEL_APP_PORT_HTTP'] = newVal; + runtime.port = newVal; } }, { deep: true }, @@ -325,6 +326,7 @@ const getRuntime = async (id: number) => { source: data.source, params: data.params, codeDir: data.codeDir, + port: data.port, }); editParams.value = data.appParams; if (mode.value == 'edit') {