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 { if logger == nil {
global.LOG.Errorf("download app[%s] error %v", app.Name, err) global.LOG.Errorf("download app[%s] error %v", app.Name, err)
} else { } else {

View file

@ -213,12 +213,35 @@ func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.Runt
} }
runtimeDTO := response.NewRuntimeDTO(runtime) runtimeDTO := response.NewRuntimeDTO(runtime)
runtimeDTO.Params = make(map[string]interface{}) runtimeDTO.Params = make(map[string]interface{})
envMap, err := gotenv.Unmarshal(runtime.Env) envs, err := gotenv.Unmarshal(runtime.Env)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
for k, v := range envMap { for k, v := range envs {
runtimeDTO.Params[k] = v 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) res = append(res, runtimeDTO)
} }

View file

@ -6,6 +6,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
cmd2 "github.com/1Panel-dev/1Panel/agent/utils/cmd"
"io" "io"
"os" "os"
"os/exec" "os/exec"
@ -26,7 +27,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global" "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/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker" "github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/files" "github.com/1Panel-dev/1Panel/agent/utils/files"
@ -251,6 +251,18 @@ func getRuntimeEnv(envStr, key string) string {
return "" 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) { func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebuild bool) {
runtimePath := runtime.GetPath() runtimePath := runtime.GetPath()
composePath := runtime.GetComposePath() composePath := runtime.GetComposePath()
@ -283,74 +295,80 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebu
} else { } else {
runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + stderrBuf.String() runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + stderrBuf.String()
} }
} else { _ = runtimeRepo.Save(runtime)
if err = runComposeCmdWithLog(constant.RuntimeDown, runtime.GetComposePath(), runtime.GetLogPath()); err != nil { 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 return
} }
runtime.Message = "" commitMgr := cmd2.NewCommandMgr(cmd2.WithTimeout(10*time.Minute), cmd2.WithOutputFile(logPath))
if oldImageID != "" { err = commitMgr.Run("docker", "commit", runtime.ContainerName, runtime.Image)
client, err := docker.NewClient() if err != nil {
if err == nil { runtime.Status = constant.StatusError
defer client.Close() runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + err.Error()
newImageID, err := client.GetImageIDByName(runtime.Image) return
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)
}
} }
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) _ = runtimeRepo.Save(runtime)
} }
@ -405,6 +423,10 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
create.Params["CONTAINER_PACKAGE_URL"] = create.Source create.Params["CONTAINER_PACKAGE_URL"] = create.Source
siteDir, _ := settingRepo.Get(settingRepo.WithByKey("WEBSITE_DIR")) siteDir, _ := settingRepo.Get(settingRepo.WithByKey("WEBSITE_DIR"))
create.Params["PANEL_WEBSITE_DIR"] = siteDir.Value create.Params["PANEL_WEBSITE_DIR"] = siteDir.Value
composeContent, err = handleEnvironments(composeContent, create, projectDir)
if err != nil {
return
}
case constant.RuntimeNode: case constant.RuntimeNode:
create.Params["CODE_DIR"] = create.CodeDir create.Params["CODE_DIR"] = create.CodeDir
create.Params["NODE_VERSION"] = create.Version create.Params["NODE_VERSION"] = create.Version
@ -465,6 +487,42 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
return 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) { func handleCompose(env gotenv.Env, composeContent []byte, create request.RuntimeCreate, projectDir string) (composeByte []byte, err error) {
existMap := make(map[string]interface{}) existMap := make(map[string]interface{})
composeMap := 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{} var environments []interface{}
for _, e := range create.Environments { 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") delete(serviceValue, "environment")
if len(environments) > 0 { 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 { if err := json.Unmarshal(nginx_conf.PHPExtensionsJson, &phpExtensions); err != nil {
return err return err
} }
phpVersion := getRuntimeEnv(runtime.Env, "PHP_VERSION")
phpExtensionDir := path.Join(dir, "extensions", getExtensionDir(phpVersion))
delMap := make(map[string]struct{}) delMap := make(map[string]struct{})
for _, ext := range phpExtensions { for _, ext := range phpExtensions {
for _, del := range delExtensions { for _, del := range delExtensions {
if ext.Name == del { if ext.Name == del {
delMap[ext.Check] = struct{}{} delMap[ext.Check] = struct{}{}
detail, _ := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID)) _ = fileOP.DeleteFile(path.Join(phpExtensionDir, ext.File))
_ = fileOP.DeleteFile(path.Join(dir, "extensions", getExtensionDir(detail.Version), ext.File))
_ = fileOP.DeleteFile(path.Join(dir, "conf", "conf.d", "docker-php-ext-"+ext.Check+".ini")) _ = fileOP.DeleteFile(path.Join(dir, "conf", "conf.d", "docker-php-ext-"+ext.Check+".ini"))
_ = removePHPIniExt(path.Join(dir, "conf", "php.ini"), ext.File) _ = removePHPIniExt(path.Join(dir, "conf", "php.ini"), ext.File)
break break

View file

@ -342,13 +342,6 @@
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84"], "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84"],
"installed": false "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", "name": "zip",
"check": "zip", "check": "zip",
@ -362,5 +355,12 @@
"file": "shmop.so", "file": "shmop.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84"], "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84"],
"installed": false "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{ location ~ \.well-known{
allow all; allow all;
root /usr/share/nginx/html;
} }
} }

View file

@ -58,13 +58,16 @@ func initAcmeAccount() {
count, _, _ := acmeAccountService.Page(search) count, _, _ := acmeAccountService.Page(search)
if count == 0 { if count == 0 {
createAcmeAccount := request.WebsiteAcmeAccountCreate{ createAcmeAccount := request.WebsiteAcmeAccountCreate{
Email: "acme@1paneldev.com", Email: "acme@1paneldev.com",
Type: "letsencrypt", Type: "letsencrypt",
KeyType: "2048", KeyType: "2048",
UseProxy: true, }
systemProxy, _ := service.NewISettingService().GetSystemProxy()
if systemProxy.URL != "" {
createAcmeAccount.UseProxy = true
} }
if _, err := acmeAccountService.Create(createAcmeAccount); err != nil { 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', value: 'HuaweiCloud',
}, },
{ {
label: i18n.global.t('website.volcengine'), label: 'GoDaddy',
value: 'Volcengine', value: 'Godaddy',
},
{
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
value: 'DnsPod',
}, },
{ {
label: 'Cloudflare', label: 'Cloudflare',
@ -208,10 +204,6 @@ export const DNSTypes = [
label: 'Name.com', label: 'Name.com',
value: 'NameCom', value: 'NameCom',
}, },
{
label: 'GoDaddy',
value: 'Godaddy',
},
{ {
label: 'FreeMyIP', label: 'FreeMyIP',
value: 'FreeMyIP', value: 'FreeMyIP',
@ -232,6 +224,14 @@ export const DNSTypes = [
label: 'Spaceship', label: 'Spaceship',
value: 'Spaceship', value: 'Spaceship',
}, },
{
label: i18n.global.t('website.volcengine'),
value: 'Volcengine',
},
{
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
value: 'DnsPod',
},
]; ];
export const Fields = [ export const Fields = [

View file

@ -2700,8 +2700,6 @@ const message = {
open: 'Open', open: 'Open',
operatorHelper: operatorHelper:
'The {0} operation will be performed on the selected operating environment. Do you want to continue? ', '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', taobao: 'Taobao',
tencent: 'Tencent', tencent: 'Tencent',
imageSource: 'Image source', imageSource: 'Image source',

View file

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

View file

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

View file

@ -2651,8 +2651,6 @@ const message = {
open: 'Buka', open: 'Buka',
operatorHelper: operatorHelper:
'Operasi {0} akan dilakukan pada persekitaran operasi yang dipilih. Adakah anda mahu meneruskan?', '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', taobao: 'Taobao',
tencent: 'Tencent', tencent: 'Tencent',
imageSource: 'Sumber imej', 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.', 'A lista de comandos de inicialização é gerada a partir do arquivo package.json no diretório de origem.',
open: 'Abrir', open: 'Abrir',
operatorHelper: 'A operação {0} será realizada no ambiente selecionado. Deseja continuar?', 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', taobao: 'Taobao',
tencent: 'Tencent', tencent: 'Tencent',
imageSource: 'Fonte da imagem', imageSource: 'Fonte da imagem',

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,15 +2,6 @@
<div> <div>
<RouterMenu /> <RouterMenu />
<LayoutContent :title="'.NET'" v-loading="loading"> <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> <template #leftToolBar>
<el-button type="primary" @click="openCreate"> <el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }} {{ $t('runtime.create') }}

View file

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

View file

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

View file

@ -2,15 +2,6 @@
<div> <div>
<RouterMenu /> <RouterMenu />
<LayoutContent :title="'Go'" v-loading="loading"> <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> <template #leftToolBar>
<el-button type="primary" @click="openCreate"> <el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }} {{ $t('runtime.create') }}

View file

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

View file

@ -2,15 +2,6 @@
<div> <div>
<RouterMenu /> <RouterMenu />
<LayoutContent :title="'Java'" v-loading="loading"> <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> <template #leftToolBar>
<el-button type="primary" @click="openCreate"> <el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }} {{ $t('runtime.create') }}

View file

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

View file

@ -2,15 +2,6 @@
<div> <div>
<RouterMenu /> <RouterMenu />
<LayoutContent :title="'Node.js'" v-loading="loading"> <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> <template #leftToolBar>
<el-button type="primary" @click="openCreate"> <el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }} {{ $t('runtime.create') }}

View file

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

View file

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

View file

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

View file

@ -2,15 +2,6 @@
<div> <div>
<RouterMenu /> <RouterMenu />
<LayoutContent :title="'Python'" v-loading="loading"> <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> <template #leftToolBar>
<el-button type="primary" @click="openCreate"> <el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }} {{ $t('runtime.create') }}

View file

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

View file

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

View file

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

View file

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