fix: fix somme issue with runtime (#8583)

#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
This commit is contained in:
CityFun 2025-05-09 16:53:48 +08:00 committed by GitHub
parent 48575f5691
commit f9a035f380
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 313 additions and 262 deletions

View file

@ -901,7 +901,7 @@ func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.App
}
}()
if err = fileOp.DownloadFile(appDetail.DownloadUrl, filePath); err != nil {
if err = files.DownloadFileWithProxy(appDetail.DownloadUrl, filePath); err != nil {
if logger == nil {
global.LOG.Errorf("download app[%s] error %v", app.Name, err)
} else {

View file

@ -213,12 +213,35 @@ func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.Runt
}
runtimeDTO := response.NewRuntimeDTO(runtime)
runtimeDTO.Params = make(map[string]interface{})
envMap, err := gotenv.Unmarshal(runtime.Env)
envs, err := gotenv.Unmarshal(runtime.Env)
if err != nil {
return 0, nil, err
}
for k, v := range envMap {
for k, v := range envs {
runtimeDTO.Params[k] = v
if strings.Contains(k, "CONTAINER_PORT") || strings.Contains(k, "HOST_PORT") {
if strings.Contains(k, "CONTAINER_PORT") {
r := regexp.MustCompile(`_(\d+)$`)
matches := r.FindStringSubmatch(k)
containerPort, err := strconv.Atoi(v)
if err != nil {
continue
}
hostPort, err := strconv.Atoi(envs[fmt.Sprintf("HOST_PORT_%s", matches[1])])
if err != nil {
continue
}
hostIP := envs[fmt.Sprintf("HOST_IP_%s", matches[1])]
if hostIP == "" {
hostIP = "0.0.0.0"
}
runtimeDTO.ExposedPorts = append(runtimeDTO.ExposedPorts, request.ExposedPort{
ContainerPort: containerPort,
HostPort: hostPort,
HostIP: hostIP,
})
}
}
}
res = append(res, runtimeDTO)
}

View file

@ -6,6 +6,7 @@ import (
"context"
"encoding/json"
"fmt"
cmd2 "github.com/1Panel-dev/1Panel/agent/utils/cmd"
"io"
"os"
"os/exec"
@ -26,7 +27,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
cmd2 "github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/files"
@ -251,6 +251,18 @@ func getRuntimeEnv(envStr, key string) string {
return ""
}
func deleteImageByID(oldImageID, imageName string, client docker.Client) {
newImageID, err := client.GetImageIDByName(imageName)
if err == nil && newImageID != oldImageID {
global.LOG.Infof("delete imageID [%s] ", oldImageID)
if err := client.DeleteImage(oldImageID); err != nil {
global.LOG.Errorf("delete imageID [%s] error %v", oldImageID, err)
} else {
global.LOG.Infof("delete old image success")
}
}
}
func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebuild bool) {
runtimePath := runtime.GetPath()
composePath := runtime.GetComposePath()
@ -283,74 +295,80 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebu
} else {
runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + stderrBuf.String()
}
} else {
if err = runComposeCmdWithLog(constant.RuntimeDown, runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
_ = runtimeRepo.Save(runtime)
return
}
if err = runComposeCmdWithLog(constant.RuntimeDown, runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
return
}
client, err := docker.NewClient()
if err != nil {
_, _ = logFile.WriteString(fmt.Sprintf("failed to connect to docker client: %v", err))
return
}
runtime.Message = ""
if rebuild && runtime.ID > 0 {
extensionsStr := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS")
extensionsArray := strings.Split(extensionsStr, ",")
oldExtensionStr := getRuntimeEnv(oldEnv, "PHP_EXTENSIONS")
oldExtensionArray := strings.Split(oldExtensionStr, ",")
var delExtensions []string
for _, oldExt := range oldExtensionArray {
exist := false
for _, ext := range extensionsArray {
if oldExt == ext {
exist = true
break
}
}
if !exist {
delExtensions = append(delExtensions, oldExt)
}
}
if err = unInstallPHPExtension(runtime, delExtensions); err != nil {
_, _ = logFile.WriteString(fmt.Sprintf("unInstallPHPExtension error %v", err))
}
}
defer func() {
_ = runtimeRepo.Save(runtime)
}()
if out, err := compose.Up(composePath); err != nil {
runtime.Status = constant.StatusStartErr
runtime.Message = out
return
}
deleteImageID := ""
extensions := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS")
if extensions != "" {
deleteImageID, _ = client.GetImageIDByName(runtime.Image)
cmdMgr := cmd2.NewCommandMgr(cmd2.WithTimeout(60*time.Minute), cmd2.WithOutputFile(logPath))
if err = cmdMgr.Run("docker", "exec", "-i", runtime.ContainerName, "install-ext", extensions); err != nil {
runtime.Status = constant.StatusError
runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + err.Error()
return
}
runtime.Message = ""
if oldImageID != "" {
client, err := docker.NewClient()
if err == nil {
defer client.Close()
newImageID, err := client.GetImageIDByName(runtime.Image)
if err == nil && newImageID != oldImageID {
global.LOG.Infof("delete imageID [%s] ", oldImageID)
if err := client.DeleteImage(oldImageID); err != nil {
global.LOG.Errorf("delete imageID [%s] error %v", oldImageID, err)
} else {
global.LOG.Infof("delete old image success")
}
}
} else {
global.LOG.Errorf("delete imageID [%s] error %v", oldImageID, err)
}
commitMgr := cmd2.NewCommandMgr(cmd2.WithTimeout(10*time.Minute), cmd2.WithOutputFile(logPath))
err = commitMgr.Run("docker", "commit", runtime.ContainerName, runtime.Image)
if err != nil {
runtime.Status = constant.StatusError
runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + err.Error()
return
}
if rebuild && runtime.ID > 0 {
extensionsStr := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS")
extensionsArray := strings.Split(extensionsStr, ",")
oldExtensionStr := getRuntimeEnv(oldEnv, "PHP_EXTENSIONS")
oldExtensionArray := strings.Split(oldExtensionStr, ",")
var delExtensions []string
for _, oldExt := range oldExtensionArray {
exist := false
for _, ext := range extensionsArray {
if oldExt == ext {
exist = true
break
}
}
if !exist {
delExtensions = append(delExtensions, oldExt)
}
}
if err = unInstallPHPExtension(runtime, delExtensions); err != nil {
global.LOG.Errorf("unInstallPHPExtension error %v", err)
}
}
if out, err := compose.Up(composePath); err != nil {
runtime.Status = constant.StatusStartErr
runtime.Message = out
_ = runtimeRepo.Save(runtime)
}
extensions := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS")
if extensions != "" {
cmdMgr := cmd2.NewCommandMgr(cmd2.WithTimeout(60*time.Minute), cmd2.WithOutputFile(logPath))
if err = cmdMgr.Run("docker", "exec", "-i", runtime.ContainerName, "install-ext", extensions); err != nil {
runtime.Status = constant.StatusError
runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + err.Error()
_ = runtimeRepo.Save(runtime)
return
}
}
if out, err := compose.DownAndUp(composePath); err != nil {
runtime.Status = constant.StatusStartErr
runtime.Message = out
_ = runtimeRepo.Save(runtime)
}
runtime.Status = constant.StatusRunning
}
if oldImageID != "" {
deleteImageByID(oldImageID, runtime.Image, client)
}
if deleteImageID != "" {
deleteImageByID(deleteImageID, runtime.Image, client)
}
if out, err := compose.DownAndUp(composePath); err != nil {
runtime.Status = constant.StatusStartErr
runtime.Message = out
return
}
runtime.Status = constant.StatusRunning
_ = runtimeRepo.Save(runtime)
}
@ -405,6 +423,10 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
create.Params["CONTAINER_PACKAGE_URL"] = create.Source
siteDir, _ := settingRepo.Get(settingRepo.WithByKey("WEBSITE_DIR"))
create.Params["PANEL_WEBSITE_DIR"] = siteDir.Value
composeContent, err = handleEnvironments(composeContent, create, projectDir)
if err != nil {
return
}
case constant.RuntimeNode:
create.Params["CODE_DIR"] = create.CodeDir
create.Params["NODE_VERSION"] = create.Version
@ -465,6 +487,42 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
return
}
func handleEnvironments(composeContent []byte, create request.RuntimeCreate, projectDir string) (composeByte []byte, err error) {
composeMap := make(map[string]interface{})
if err = yaml.Unmarshal(composeContent, &composeMap); err != nil {
return
}
services, serviceValid := composeMap["services"].(map[string]interface{})
if !serviceValid {
err = buserr.New("ErrFileParse")
return
}
serviceName := ""
serviceValue := make(map[string]interface{})
for name, service := range services {
serviceName = name
serviceValue = service.(map[string]interface{})
var environments []interface{}
for _, e := range create.Environments {
environments = append(environments, fmt.Sprintf("%s=%s", e.Key, e.Value))
}
delete(serviceValue, "environment")
if len(environments) > 0 {
serviceValue["environment"] = environments
}
break
}
services[serviceName] = serviceValue
composeMap["services"] = services
composeByte, err = yaml.Marshal(composeMap)
if err != nil {
return
}
fileOp := files.NewFileOp()
_ = fileOp.SaveFile(path.Join(projectDir, "docker-compose.yml"), string(composeByte), constant.DirPerm)
return
}
func handleCompose(env gotenv.Env, composeContent []byte, create request.RuntimeCreate, projectDir string) (composeByte []byte, err error) {
existMap := make(map[string]interface{})
composeMap := make(map[string]interface{})
@ -499,7 +557,7 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
}
var environments []interface{}
for _, e := range create.Environments {
environments = append(environments, fmt.Sprintf("%s:%s", e.Key, e.Value))
environments = append(environments, fmt.Sprintf("%s=%s", e.Key, e.Value))
}
delete(serviceValue, "environment")
if len(environments) > 0 {
@ -580,13 +638,15 @@ func unInstallPHPExtension(runtime *model.Runtime, delExtensions []string) error
if err := json.Unmarshal(nginx_conf.PHPExtensionsJson, &phpExtensions); err != nil {
return err
}
phpVersion := getRuntimeEnv(runtime.Env, "PHP_VERSION")
phpExtensionDir := path.Join(dir, "extensions", getExtensionDir(phpVersion))
delMap := make(map[string]struct{})
for _, ext := range phpExtensions {
for _, del := range delExtensions {
if ext.Name == del {
delMap[ext.Check] = struct{}{}
detail, _ := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID))
_ = fileOP.DeleteFile(path.Join(dir, "extensions", getExtensionDir(detail.Version), ext.File))
_ = fileOP.DeleteFile(path.Join(phpExtensionDir, ext.File))
_ = fileOP.DeleteFile(path.Join(dir, "conf", "conf.d", "docker-php-ext-"+ext.Check+".ini"))
_ = removePHPIniExt(path.Join(dir, "conf", "php.ini"), ext.File)
break

View file

@ -342,13 +342,6 @@
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84"],
"installed": false
},
{
"name": "igbinary",
"check": "igbinary",
"file": "igbinary.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84"],
"installed": false
},
{
"name": "zip",
"check": "zip",
@ -362,5 +355,12 @@
"file": "shmop.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84"],
"installed": false
},
{
"name": "gd",
"check": "gd",
"file": "gd.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84"],
"installed": false
}
]

View file

@ -18,5 +18,6 @@ server {
location ~ \.well-known{
allow all;
root /usr/share/nginx/html;
}
}

View file

@ -58,13 +58,16 @@ func initAcmeAccount() {
count, _, _ := acmeAccountService.Page(search)
if count == 0 {
createAcmeAccount := request.WebsiteAcmeAccountCreate{
Email: "acme@1paneldev.com",
Type: "letsencrypt",
KeyType: "2048",
UseProxy: true,
Email: "acme@1paneldev.com",
Type: "letsencrypt",
KeyType: "2048",
}
systemProxy, _ := service.NewISettingService().GetSystemProxy()
if systemProxy.URL != "" {
createAcmeAccount.UseProxy = true
}
if _, err := acmeAccountService.Create(createAcmeAccount); err != nil {
global.LOG.Errorf("create acme account error: %s", err.Error())
global.LOG.Warningf("create acme account error: %s", err.Error())
}
}
}

View file

@ -177,12 +177,8 @@ export const DNSTypes = [
value: 'HuaweiCloud',
},
{
label: i18n.global.t('website.volcengine'),
value: 'Volcengine',
},
{
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
value: 'DnsPod',
label: 'GoDaddy',
value: 'Godaddy',
},
{
label: 'Cloudflare',
@ -208,10 +204,6 @@ export const DNSTypes = [
label: 'Name.com',
value: 'NameCom',
},
{
label: 'GoDaddy',
value: 'Godaddy',
},
{
label: 'FreeMyIP',
value: 'FreeMyIP',
@ -232,6 +224,14 @@ export const DNSTypes = [
label: 'Spaceship',
value: 'Spaceship',
},
{
label: i18n.global.t('website.volcengine'),
value: 'Volcengine',
},
{
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
value: 'DnsPod',
},
];
export const Fields = [

View file

@ -2700,8 +2700,6 @@ const message = {
open: 'Open',
operatorHelper:
'The {0} operation will be performed on the selected operating environment. Do you want to continue? ',
statusHelper:
'Status description: Starting - the container has been started, but the application is starting; abnormal - the container has been started, but the application status is abnormal',
taobao: 'Taobao',
tencent: 'Tencent',
imageSource: 'Image source',

View file

@ -2591,8 +2591,6 @@ const message = {
runScriptHelper: '起動コマンドリストはソースディレクトリのpackage.jsonファイルから解析されます',
open: '開ける',
operatorHelper: '{0}操作は選択した動作環境で実行されます続けたいですか',
statusHelper:
'ステータスの説明:開始 - コンテナが開始されましたがアプリケーションが開始されています異常 - 容器が開始されましたがアプリケーションステータスは異常です',
taobao: 'タオバオ',
tencent: 'テンセント',
imageSource: '画像ソース',

View file

@ -2547,8 +2547,6 @@ const message = {
runScriptHelper: '시작 명령 목록은 소스 디렉터리의 package.json 파일에서 분석됩니다.',
open: '열기',
operatorHelper: '{0} 작업이 선택된 운영 환경에서 수행됩니다. 계속하시겠습니까?',
statusHelper:
'상태 설명: 시작 - 컨테이너가 시작되었으나 애플리케이션이 시작 ; 비정상 - 컨테이너가 시작되었으나 애플리케이션 상태가 비정상.',
taobao: '타오바오',
tencent: '텐센트',
imageSource: '이미지 소스',

View file

@ -2651,8 +2651,6 @@ const message = {
open: 'Buka',
operatorHelper:
'Operasi {0} akan dilakukan pada persekitaran operasi yang dipilih. Adakah anda mahu meneruskan?',
statusHelper:
'Huraian status: Memulakan - kontena telah dimulakan, tetapi aplikasi sedang dimulakan; tidak normal - kontena telah dimulakan, tetapi status aplikasi tidak normal',
taobao: 'Taobao',
tencent: 'Tencent',
imageSource: 'Sumber imej',

View file

@ -2649,8 +2649,6 @@ const message = {
'A lista de comandos de inicialização é gerada a partir do arquivo package.json no diretório de origem.',
open: 'Abrir',
operatorHelper: 'A operação {0} será realizada no ambiente selecionado. Deseja continuar?',
statusHelper:
'Descrição do status: Iniciando - o contêiner foi iniciado, mas a aplicação está carregando; anormal - o contêiner foi iniciado, mas o status da aplicação está incorreto.',
taobao: 'Taobao',
tencent: 'Tencent',
imageSource: 'Fonte da imagem',

View file

@ -2647,8 +2647,6 @@ const message = {
runScriptHelper: 'Список команд запуска анализируется из файла package.json в исходной директории.',
open: 'Открыть',
operatorHelper: 'Операция {0} будет выполнена для выбранной среды выполнения. Хотите продолжить?',
statusHelper:
'Описание статусов: Запускается - контейнер запущен, но приложение запускается; аномальный - контейнер запущен, но статус приложения аномальный',
taobao: 'Taobao',
tencent: 'Tencent',
imageSource: 'Источник образа',

View file

@ -2499,7 +2499,6 @@ const message = {
runScriptHelper: '啟動命令是指容器啟動後運行的命令',
open: '開啟',
operatorHelper: '將對選取的執行環境進行 {0} 操作是否繼續 ',
statusHelper: '狀態說明啟動中-容器已啟動但應用正在啟動異常-容器已啟動但應用狀態異常',
taobao: '淘寶',
tencent: '騰訊',
imageSource: '鏡像源',

View file

@ -2009,7 +2009,7 @@ const message = {
applySSL: '证书申请',
SSLList: '证书列表',
createDnsAccount: 'DNS账户',
aliyun: '阿里云DNS',
aliyun: '阿里云',
manual: '手动解析',
key: '密钥',
check: '查看',
@ -2490,7 +2490,6 @@ const message = {
runScriptHelper: '启动命令列表是从源码目录下的 package.json 文件中解析而来',
open: '放开',
operatorHelper: '将对选中的运行环境进行 {0} 操作是否继续',
statusHelper: '状态说明启动中-容器已启动但应用正在启动异常-容器已启动但应用状态异常',
taobao: '淘宝',
tencent: '腾讯',
imageSource: '镜像源',
@ -3100,7 +3099,7 @@ const message = {
remote: '远程',
imagePrefix: '镜像前缀',
imagePrefixHelper:
'作用自定义镜像前缀修改 compose 文件中的镜像字段例如当镜像前缀设置为 1panel/custom MaxKB image 字段将变更为 1panel/custom/maxkb:v1.10.0',
'用于自定义镜像前缀自动修改 Compose 文件中的镜像字段\n 例如当镜像前缀设置为 1panel/custom MaxKB 镜像将变更为 1panel/custom/maxkb:v1.10.0',
closeHelper: '是否取消使用自定义仓库',
appStoreUrlHelper: '仅支持 .tar.gz 格式',
postNode: '同步至子节点',

View file

@ -82,7 +82,7 @@ html {
color: #adb0bc;
width: 100%;
display: inline-block;
white-space: normal;
white-space: pre-line;
}
.input-error {

View file

@ -1,16 +1,14 @@
<template>
<div v-if="row.port != ''">
<span v-for="(port, index) in row.port.split(',')" :key="index">
<el-button link @click="jump(port, 'http')">
{{ port }}
<el-icon class="el-icon--right"><Promotion /></el-icon>
<span v-for="(port, index) in row.exposedPorts" :key="index">
<el-button icon="Position" plain size="small" @click="jump(port, 'http')">
{{ port.hostIP }}:{{ port.hostPort }}->{{ port.containerPort }}
</el-button>
</span>
</div>
</template>
<script setup>
import { Promotion } from '@element-plus/icons-vue';
defineProps({
row: {
type: Object,

View file

@ -2,15 +2,6 @@
<div>
<RouterMenu />
<LayoutContent :title="'.NET'" v-loading="loading">
<template #prompt>
<el-alert type="info" :closable="false">
<template #title>
<span class="input-help whitespace-break-spaces">
{{ $t('runtime.statusHelper') }}
</span>
</template>
</el-alert>
</template>
<template #leftToolBar>
<el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }}

View file

@ -23,9 +23,15 @@
<el-form-item :label="$t('app.containerName')" prop="params.CONTAINER_NAME">
<el-input v-model.trim="runtime.params['CONTAINER_NAME']"></el-input>
</el-form-item>
<PortConfig v-model="runtime" :mode="mode" />
<Environment :environments="runtime.environments" />
<Volumes :volumes="runtime.volumes" />
<el-tabs type="border-card">
<el-tab-pane :label="$t('commons.table.port')">
<PortConfig v-model="runtime" :mode="mode" />
</el-tab-pane>
<el-tab-pane :label="$t('runtime.environment')">
<Environment :environments="runtime.environments" />
</el-tab-pane>
<el-tab-pane :label="$t('container.mount')"><Volumes :volumes="runtime.volumes" /></el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
<span>

View file

@ -1,32 +1,30 @@
<template>
<el-text type="info">{{ $t('php.date_timezone') }}: TZ=Asia/Shaghai</el-text>
<div class="mt-1.5">
<el-text>{{ $t('runtime.environment') }}</el-text>
<div class="mt-1.5">
<el-row :gutter="20" v-for="(env, index) in environments" :key="index">
<el-col :span="7">
<el-form-item :prop="`environments.${index}.key`" :rules="rules.value">
<el-input v-model="env.key" :placeholder="$t('runtime.envKey')" />
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item :prop="`environments.${index}.value`" :rules="rules.value">
<el-input v-model="env.value" :placeholder="$t('runtime.envValue')" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item>
<el-button type="primary" @click="removeEnv(index)" link class="mt-1">
{{ $t('commons.button.delete') }}
</el-button>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="4">
<el-button @click="addEnv">{{ $t('commons.button.add') }}</el-button>
</el-col>
</el-row>
</div>
<el-row :gutter="20" v-for="(env, index) in environments" :key="index">
<el-col :span="7">
<el-form-item :prop="`environments.${index}.key`" :rules="rules.value">
<el-input v-model="env.key" :placeholder="$t('runtime.envKey')" />
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item :prop="`environments.${index}.value`" :rules="rules.value">
<el-input v-model="env.value" :placeholder="$t('runtime.envValue')" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item>
<el-button type="primary" @click="removeEnv(index)" link class="mt-1">
{{ $t('commons.button.delete') }}
</el-button>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="4">
<el-button @click="addEnv">{{ $t('commons.button.add') }}</el-button>
</el-col>
</el-row>
</div>
</template>

View file

@ -2,15 +2,6 @@
<div>
<RouterMenu />
<LayoutContent :title="'Go'" v-loading="loading">
<template #prompt>
<el-alert type="info" :closable="false">
<template #title>
<span class="input-help whitespace-break-spaces">
{{ $t('runtime.statusHelper') }}
</span>
</template>
</el-alert>
</template>
<template #leftToolBar>
<el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }}

View file

@ -43,9 +43,15 @@
<el-form-item :label="$t('app.containerName')" prop="params.CONTAINER_NAME">
<el-input v-model.trim="runtime.params['CONTAINER_NAME']"></el-input>
</el-form-item>
<PortConfig v-model="runtime" :mode="mode" />
<Environment :environments="runtime.environments" />
<Volumes :volumes="runtime.volumes" />
<el-tabs type="border-card">
<el-tab-pane :label="$t('commons.table.port')">
<PortConfig v-model="runtime" :mode="mode" />
</el-tab-pane>
<el-tab-pane :label="$t('runtime.environment')">
<Environment :environments="runtime.environments" />
</el-tab-pane>
<el-tab-pane :label="$t('container.mount')"><Volumes :volumes="runtime.volumes" /></el-tab-pane>
</el-tabs>
</el-form>
<template #footer>

View file

@ -2,15 +2,6 @@
<div>
<RouterMenu />
<LayoutContent :title="'Java'" v-loading="loading">
<template #prompt>
<el-alert type="info" :closable="false">
<template #title>
<span class="input-help whitespace-break-spaces">
{{ $t('runtime.statusHelper') }}
</span>
</template>
</el-alert>
</template>
<template #leftToolBar>
<el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }}

View file

@ -27,9 +27,15 @@
<el-form-item :label="$t('app.containerName')" prop="params.CONTAINER_NAME">
<el-input v-model.trim="runtime.params['CONTAINER_NAME']"></el-input>
</el-form-item>
<PortConfig v-model="runtime" :mode="mode" />
<Environment :environments="runtime.environments" />
<Volumes :volumes="runtime.volumes" />
<el-tabs type="border-card">
<el-tab-pane :label="$t('commons.table.port')">
<PortConfig v-model="runtime" :mode="mode" />
</el-tab-pane>
<el-tab-pane :label="$t('runtime.environment')">
<Environment :environments="runtime.environments" />
</el-tab-pane>
<el-tab-pane :label="$t('container.mount')"><Volumes :volumes="runtime.volumes" /></el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
<span>

View file

@ -2,15 +2,6 @@
<div>
<RouterMenu />
<LayoutContent :title="'Node.js'" v-loading="loading">
<template #prompt>
<el-alert type="info" :closable="false">
<template #title>
<span class="input-help whitespace-break-spaces">
{{ $t('runtime.statusHelper') }}
</span>
</template>
</el-alert>
</template>
<template #leftToolBar>
<el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }}

View file

@ -43,9 +43,15 @@
{{ $t('runtime.phpsourceHelper') }}
</span>
</el-form-item>
<PortConfig v-model="runtime" :mode="mode" />
<Environment :environments="runtime.environments" />
<Volumes :volumes="runtime.volumes" />
<el-tabs type="border-card">
<el-tab-pane :label="$t('commons.table.port')">
<PortConfig v-model="runtime" :mode="mode" />
</el-tab-pane>
<el-tab-pane :label="$t('runtime.environment')">
<Environment :environments="runtime.environments" />
</el-tab-pane>
<el-tab-pane :label="$t('container.mount')"><Volumes :volumes="runtime.volumes" /></el-tab-pane>
</el-tabs>
</el-form>
<template #footer>

View file

@ -119,6 +119,7 @@
<el-form-item :label="$t('app.containerName')" prop="params.CONTAINER_NAME">
<el-input v-model.trim="runtime.params['CONTAINER_NAME']"></el-input>
</el-form-item>
<Environment :environments="runtime.environments" />
<el-form-item>
<el-alert type="warning" :closable="false">
<template #default>
@ -182,6 +183,7 @@
<script lang="ts" setup>
import { App } from '@/api/interface/app';
import { Runtime } from '@/api/interface/runtime';
import Environment from '@/views/website/runtime/environment/index.vue';
import { getAppByKey, getAppDetail, searchApp } from '@/api/modules/app';
import { CreateRuntime, GetRuntime, ListPHPExtensions, UpdateRuntime } from '@/api/modules/runtime';
import { Rules } from '@/global/form-rules';
@ -265,6 +267,7 @@ const initData = (type: string) => ({
resource: 'appstore',
rebuild: false,
source: phpSources[0].value,
environments: [],
});
const extensions = ref();
const formFields = ref();

View file

@ -1,36 +1,28 @@
<template>
<div class="mt-1.5">
<el-text>{{ $t('commons.table.port') }}</el-text>
<div class="mt-1.5">
<el-row :gutter="20" v-for="(port, index) in runtime.exposedPorts" :key="index">
<el-col :span="7">
<el-form-item :prop="`exposedPorts.${index}.hostPort`" :rules="rules.port">
<el-input v-model.number="port.hostPort" :placeholder="$t('runtime.externalPort')" />
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item :prop="`exposedPorts.${index}.containerPort`" :rules="rules.port">
<el-input v-model.number="port.containerPort" :placeholder="$t('runtime.appPort')" />
</el-form-item>
</el-col>
<el-col :span="7">
<el-text>{{ $t('app.allowPort') }}</el-text>
<el-switch
class="ml-1"
v-model="port.hostIP"
:active-value="'0.0.0.0'"
:inactive-value="'127.0.0.1'"
/>
</el-col>
<el-col :span="2">
<el-form-item>
<el-button type="primary" @click="removePort(index)" link class="mt-1">
{{ $t('commons.button.delete') }}
</el-button>
</el-form-item>
</el-col>
</el-row>
</div>
<el-row :gutter="20" v-for="(port, index) in runtime.exposedPorts" :key="index">
<el-col :span="7">
<el-form-item :prop="`exposedPorts.${index}.hostPort`" :rules="rules.port">
<el-input v-model.number="port.hostPort" :placeholder="$t('runtime.externalPort')" />
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item :prop="`exposedPorts.${index}.containerPort`" :rules="rules.port">
<el-input v-model.number="port.containerPort" :placeholder="$t('runtime.appPort')" />
</el-form-item>
</el-col>
<el-col :span="7">
<el-text>{{ $t('app.allowPort') }}</el-text>
<el-switch class="ml-1" v-model="port.hostIP" :active-value="'0.0.0.0'" :inactive-value="'127.0.0.1'" />
</el-col>
<el-col :span="2">
<el-form-item>
<el-button type="primary" @click="removePort(index)" link class="mt-1">
{{ $t('commons.button.delete') }}
</el-button>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="4">
<el-button @click="addPort">{{ $t('commons.button.add') }}</el-button>

View file

@ -2,15 +2,6 @@
<div>
<RouterMenu />
<LayoutContent :title="'Python'" v-loading="loading">
<template #prompt>
<el-alert type="info" :closable="false">
<template #title>
<span class="input-help whitespace-break-spaces">
{{ $t('runtime.statusHelper') }}
</span>
</template>
</el-alert>
</template>
<template #leftToolBar>
<el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }}

View file

@ -23,9 +23,15 @@
<el-form-item :label="$t('app.containerName')" prop="params.CONTAINER_NAME">
<el-input v-model.trim="runtime.params['CONTAINER_NAME']"></el-input>
</el-form-item>
<PortConfig v-model="runtime" :mode="mode" />
<Environment :environments="runtime.environments" />
<Volumes :volumes="runtime.volumes" />
<el-tabs type="border-card">
<el-tab-pane :label="$t('commons.table.port')">
<PortConfig v-model="runtime" :mode="mode" />
</el-tab-pane>
<el-tab-pane :label="$t('runtime.environment')">
<Environment :environments="runtime.environments" />
</el-tab-pane>
<el-tab-pane :label="$t('container.mount')"><Volumes :volumes="runtime.volumes" /></el-tab-pane>
</el-tabs>
</el-form>
<template #footer>

View file

@ -1,32 +1,29 @@
<template>
<div class="mt-2">
<el-text>{{ $t('container.mount') }}</el-text>
<div class="mt-2">
<el-row :gutter="20" v-for="(volume, index) in volumes" :key="index">
<el-col :span="7">
<el-form-item :prop="`volumes.${index}.source`" :rules="rules.value">
<el-input v-model="volume.source" :placeholder="$t('container.hostOption')" />
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item :prop="`volumes.${index}.target`" :rules="rules.value">
<el-input v-model="volume.target" :placeholder="$t('container.containerDir')" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item>
<el-button type="primary" @click="removeEnv(index)" link class="mt-1">
{{ $t('commons.button.delete') }}
</el-button>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="4">
<el-button @click="addEnv">{{ $t('commons.button.add') }}</el-button>
</el-col>
</el-row>
</div>
<div class="mt-1.5">
<el-row :gutter="20" v-for="(volume, index) in volumes" :key="index">
<el-col :span="7">
<el-form-item :prop="`volumes.${index}.source`" :rules="rules.value">
<el-input v-model="volume.source" :placeholder="$t('container.hostOption')" />
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item :prop="`volumes.${index}.target`" :rules="rules.value">
<el-input v-model="volume.target" :placeholder="$t('container.containerDir')" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item>
<el-button type="primary" @click="removeEnv(index)" link class="mt-1">
{{ $t('commons.button.delete') }}
</el-button>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="4">
<el-button @click="addEnv">{{ $t('commons.button.add') }}</el-button>
</el-col>
</el-row>
</div>
</template>

View file

@ -61,7 +61,7 @@
<el-option
v-for="(group, index) in groups"
:key="index"
:label="group.name"
:label="group.name == 'Default' ? $t('commons.table.default') : group.name"
:value="group.id"
></el-option>
</el-select>
@ -863,6 +863,8 @@ const listSSLs = () => {
acmeAccountID: String(website.value.acmeAccountID),
}).then((res) => {
ssls.value = res.data || [];
website.value.websiteSSLID = undefined;
websiteSSL.value = {};
if (ssls.value.length > 0) {
website.value.websiteSSLID = ssls.value[0].id;
changeSSl(website.value.websiteSSLID);

View file

@ -118,6 +118,9 @@ const gengerateDomains = () => {
const lines = create.value.domainStr.split(/\r?\n/);
lines.forEach((line) => {
const [domain, port] = line.split(':');
if (domain == '') {
return;
}
if (!checkDomain(domain)) {
MsgError(line + i18n.global.t('commons.rule.domain'));
return;