feat: Node.js 运行环境增加端口校验 (#2407)

This commit is contained in:
zhengkunwang 2023-09-28 15:28:21 +08:00 committed by GitHub
parent ce6069da37
commit aa588205e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 125 additions and 24 deletions

View File

@ -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 {

View File

@ -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 {

View File

@ -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(),
}
}

View File

@ -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"`
}

View File

@ -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{})

View File

@ -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)
}

View File

@ -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)

View File

@ -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 {

View File

@ -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"

View File

@ -14,6 +14,10 @@ ErrNameIsExist: "名稱已存在"
ErrDemoEnvironment: "演示伺服器,禁止此操作!"
ErrCmdTimeout: "指令執行超時!"
ErrCmdIllegal: "執行命令中存在不合法字符,請修改後重試!"
ErrPortExist: '{{ .port }} 埠已被 {{ .type }} [{{ .name }}] 佔用'
TYPE_APP: "應用"
TYPE_RUNTIME: "運作環境"
TYPE_DOMAIN: "網域名稱"
#app
ErrPortInUsed: "{{ .detail }} 端口已被佔用!"

View File

@ -14,6 +14,10 @@ ErrNameIsExist: "名称已存在"
ErrDemoEnvironment: "演示服务器,禁止此操作!"
ErrCmdTimeout: "命令执行超时!"
ErrCmdIllegal: "执行命令中存在不合法字符,请修改后重试!"
ErrPortExist: '{{ .port }} 端口已被 {{ .type }} [{{ .name }}] 占用'
TYPE_APP: "应用"
TYPE_RUNTIME: "运行环境"
TYPE_DOMAIN: "域名"
#app
ErrPortInUsed: "{{ .detail }} 端口已被占用!"

View File

@ -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

View File

@ -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 {

View File

@ -13,7 +13,7 @@
>
{{ $t('app.all') }}
</el-button>
<div v-for="item in tags" :key="item.key" style="display: inline">
<div v-for="item in tags" :key="item.key" class="inline">
<el-button
class="tag-button"
:class="activeTag === item.key ? '' : 'no-active'"
@ -56,12 +56,7 @@
<template #default>
<span>
<span>{{ $t('app.installHelper') }}</span>
<el-link
style="font-size: 12px; margin-left: 5px"
icon="Position"
@click="quickJump()"
type="primary"
>
<el-link class="text-xs scroll-ml-1" icon="Position" @click="quickJump()" type="primary">
{{ $t('firewall.quickJump') }}
</el-link>
</span>

View File

@ -31,10 +31,12 @@
</template>
</el-table-column>
<el-table-column :label="$t('runtime.version')" prop="version"></el-table-column>
<el-table-column
:label="$t('runtime.externalPort')"
prop="params.PANEL_APP_PORT_HTTP"
></el-table-column>
<el-table-column :label="$t('runtime.externalPort')" prop="port">
<template #default="{ row }">
{{ row.port }}
<el-button link :icon="Promotion" @click="goDashboard(row.port, 'http')"></el-button>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.status')" prop="status">
<template #default="{ row }">
<el-popover
@ -53,6 +55,11 @@
</div>
</template>
</el-table-column>
<el-table-column :label="$t('commons.button.log')" prop="path">
<template #default="{ row }">
<el-button @click="openLog(row)" link type="primary">{{ $t('website.check') }}</el-button>
</template>
</el-table-column>
<el-table-column
prop="createdAt"
:label="$t('commons.table.date')"
@ -74,6 +81,8 @@
</LayoutContent>
<OperateNode ref="operateRef" @close="search" />
<Delete ref="deleteRef" @close="search" />
<ComposeLogs ref="composeLogRef" />
<PortJumpDialog ref="dialogPortJumpRef" />
</div>
</template>
@ -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<Runtime.RuntimeDTO[]>([]);
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<Runtime.RuntimeReq>({
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<Runtime.RuntimeDTO[]>([]);
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(

View File

@ -89,8 +89,8 @@
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item :label="$t('runtime.externalPort')" prop="params.PANEL_APP_PORT_HTTP">
<el-input v-model.number="runtime.params['PANEL_APP_PORT_HTTP']" />
<el-form-item :label="$t('runtime.externalPort')" prop="port">
<el-input v-model.number="runtime.port" />
<span class="input-help">{{ $t('runtime.externalPortHelper') }}</span>
</el-form-item>
</el-col>
@ -170,15 +170,16 @@ const initData = (type: string) => ({
resource: 'appstore',
rebuild: false,
codeDir: '/',
port: 3000,
});
let runtime = reactive<Runtime.RuntimeCreate>(initData('node'));
const rules = ref<any>({
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') {