feat: merge from dev (#7238)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run

This commit is contained in:
zhengkunwang 2024-12-03 10:12:02 +08:00 committed by GitHub
parent 841d1a31bf
commit e5660a0d91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 1258 additions and 365 deletions

View File

@ -115,7 +115,8 @@ type Tag struct {
}
type AppForm struct {
FormFields []AppFormFields `json:"formFields"`
FormFields []AppFormFields `json:"formFields"`
SupportVersion float64 `json:"supportVersion"`
}
type AppFormFields struct {

View File

@ -93,6 +93,7 @@ type DashboardCurrent struct {
NetBytesRecv uint64 `json:"netBytesRecv"`
GPUData []GPUInfo `json:"gpuData"`
XPUData []XPUInfo `json:"xpuData"`
ShotTime time.Time `json:"shotTime"`
}
@ -141,6 +142,7 @@ type AppLauncher struct {
IsRecommend bool `json:"isRecommend"`
Detail []InstallDetail `json:"detail"`
}
type InstallDetail struct {
InstallID uint `json:"installID"`
DetailID uint `json:"detailID"`
@ -152,7 +154,18 @@ type InstallDetail struct {
HttpPort int `json:"httpPort"`
HttpsPort int `json:"httpsPort"`
}
type LauncherOption struct {
Key string `json:"key"`
IsShow bool `json:"isShow"`
}
type XPUInfo struct {
DeviceID int `json:"deviceID"`
DeviceName string `json:"deviceName"`
Memory string `json:"memory"`
Temperature string `json:"temperature"`
MemoryUsed string `json:"memoryUsed"`
Power string `json:"power"`
MemoryUtil string `json:"memoryUtil"`
}

View File

@ -199,21 +199,22 @@ type WebsiteUpdateDirPermission struct {
}
type WebsiteProxyConfig struct {
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
Enable bool `json:"enable" `
Cache bool `json:"cache" `
CacheTime int `json:"cacheTime" `
CacheUnit string `json:"cacheUnit"`
Name string `json:"name" validate:"required"`
Modifier string `json:"modifier"`
Match string `json:"match" validate:"required"`
ProxyPass string `json:"proxyPass" validate:"required"`
ProxyHost string `json:"proxyHost" validate:"required"`
Content string `json:"content"`
FilePath string `json:"filePath"`
Replaces map[string]string `json:"replaces"`
SNI bool `json:"sni"`
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
Enable bool `json:"enable" `
Cache bool `json:"cache" `
CacheTime int `json:"cacheTime" `
CacheUnit string `json:"cacheUnit"`
Name string `json:"name" validate:"required"`
Modifier string `json:"modifier"`
Match string `json:"match" validate:"required"`
ProxyPass string `json:"proxyPass" validate:"required"`
ProxyHost string `json:"proxyHost" validate:"required"`
Content string `json:"content"`
FilePath string `json:"filePath"`
Replaces map[string]string `json:"replaces"`
SNI bool `json:"sni"`
ProxySSLName string `json:"proxySSLName"`
}
type WebsiteProxyReq struct {

View File

@ -41,6 +41,7 @@ type WebsiteSSLApply struct {
ID uint `json:"ID" validate:"required"`
SkipDNSCheck bool `json:"skipDNSCheck"`
Nameservers []string `json:"nameservers"`
DisableLog bool `json:"disableLog"`
}
type WebsiteAcmeAccountCreate struct {

View File

@ -863,6 +863,10 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
settingService := NewISettingService()
_ = settingService.Update("AppStoreSyncStatus", constant.Syncing)
setting, err := settingService.GetSettingInfo()
if err != nil {
return err
}
var (
tags []*model.Tag
appTags []*model.AppTag
@ -886,10 +890,6 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
transport := xpack.LoadRequestTransport()
baseRemoteUrl := fmt.Sprintf("%s/%s/1panel", global.CONF.System.AppRepo, global.CONF.System.Mode)
setting, err := NewISettingService().GetSettingInfo()
if err != nil {
return err
}
appsMap := getApps(oldApps, list.Apps, setting.SystemVersion, t)
t.LogStart(i18n.GetMsgByKey("SyncAppDetail"))
@ -919,7 +919,13 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
version := v.Name
detail := detailsMap[version]
versionUrl := fmt.Sprintf("%s/%s/%s", baseRemoteUrl, app.Key, version)
paramByte, _ := json.Marshal(v.AppForm)
var appForm dto.AppForm
_ = json.Unmarshal(paramByte, &appForm)
if appForm.SupportVersion > 0 && common.CompareVersion(strconv.FormatFloat(appForm.SupportVersion, 'f', -1, 64), setting.SystemVersion) {
delete(detailsMap, version)
continue
}
if _, ok := InitTypes[app.Type]; ok {
dockerComposeUrl := fmt.Sprintf("%s/%s", versionUrl, "docker-compose.yml")
_, composeRes, err := httpUtil.HandleGetWithTransport(dockerComposeUrl, http.MethodGet, transport, constant.TimeOut20s)
@ -931,7 +937,6 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
detail.DockerCompose = ""
}
paramByte, _ := json.Marshal(v.AppForm)
detail.Params = string(paramByte)
detail.DownloadUrl = fmt.Sprintf("%s/%s", versionUrl, app.Key+"-"+version+".tar.gz")
detail.DownloadCallBackUrl = v.DownloadCallBackUrl

View File

@ -597,7 +597,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
_ = appDetailRepo.Update(context.Background(), detail)
}
go func() {
_, _, _ = httpUtil.HandleGet(detail.DownloadCallBackUrl, http.MethodGet, constant.TimeOut5s)
RequestDownloadCallBack(detail.DownloadCallBackUrl)
}()
}
if install.App.Resource == constant.AppResourceLocal {
@ -925,7 +925,7 @@ func copyData(task *task.Task, app model.App, appDetail model.AppDetail, appInst
return
}
go func() {
_, _, _ = httpUtil.HandleGet(appDetail.DownloadCallBackUrl, http.MethodGet, constant.TimeOut5s)
RequestDownloadCallBack(appDetail.DownloadCallBackUrl)
}()
}
appKey := app.Key
@ -1232,11 +1232,7 @@ func handleLocalAppDetail(versionDir string, appDetail *model.AppDetail) error {
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
}
additionalProperties, ok := dataMap["additionalProperties"].(map[string]interface{})
if !ok {
return buserr.WithName(constant.ErrAppParamKey, "additionalProperties")
}
additionalProperties, _ := dataMap["additionalProperties"].(map[string]interface{})
formFieldsInterface, ok := additionalProperties["formFields"]
if ok {
formFields, ok := formFieldsInterface.([]interface{})
@ -1463,6 +1459,17 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
continue
}
lastVersion := versions[0]
if app.Key == constant.AppMysql {
for _, version := range versions {
majorVersion := getMajorVersion(installed.Version)
if !strings.HasPrefix(version, majorVersion) {
continue
} else {
lastVersion = version
break
}
}
}
if common.IsCrossVersion(installed.Version, lastVersion) {
installDTO.CanUpdate = app.CrossVersionUpdate
} else {
@ -1729,3 +1736,10 @@ func ignoreUpdate(installed model.AppInstall) bool {
}
return false
}
func RequestDownloadCallBack(downloadCallBackUrl string) {
if downloadCallBackUrl == "" {
return
}
_, _, _ = httpUtil.HandleGet(downloadCallBackUrl, http.MethodGet, constant.TimeOut5s)
}

View File

@ -203,6 +203,7 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
currentInfo.DiskData = loadDiskInfo()
currentInfo.GPUData = loadGPUInfo()
currentInfo.XPUData = loadXpuInfo()
if ioOption == "all" {
diskInfo, _ := disk.IOCounters()
@ -501,3 +502,19 @@ func ArryContains(arr []string, element string) bool {
}
return false
}
func loadXpuInfo() []dto.XPUInfo {
list := xpack.LoadXpuInfo()
if len(list) == 0 {
return nil
}
var data []dto.XPUInfo
for _, gpu := range list {
var dataItem dto.XPUInfo
if err := copier.Copy(&dataItem, &gpu); err != nil {
continue
}
data = append(data, dataItem)
}
return data
}

View File

@ -75,7 +75,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
opts []repo.DBOption
)
if create.Name != "" {
opts = append(opts, commonRepo.WithByLikeName(create.Name))
opts = append(opts, commonRepo.WithByName(create.Name))
}
if create.Type != "" {
opts = append(opts, commonRepo.WithByType(create.Type))
@ -108,7 +108,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
return nil, err
}
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
if !fileOp.Stat(create.CodeDir) {
return nil, buserr.New(constant.ErrPathNotFound)
}
@ -140,7 +140,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
}
appVersionDir := filepath.Join(app.GetAppResourcePath(), appDetail.Version)
if !fileOp.Stat(appVersionDir) || appDetail.Update {
if !fileOp.Stat(appVersionDir) {
if err = downloadApp(app, appDetail, nil, nil); err != nil {
return nil, err
}
@ -162,7 +162,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
if err = handlePHP(create, runtime, fileOp, appVersionDir); err != nil {
return nil, err
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
runtime.Port = int(create.Params["port"].(float64))
if err = handleNodeAndJava(create, runtime, fileOp, appVersionDir); err != nil {
return nil, err
@ -341,7 +341,7 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) {
}
}
res.AppParams = appParams
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
res.Params = make(map[string]interface{})
envs, err := gotenv.Unmarshal(runtime.Env)
if err != nil {
@ -440,7 +440,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
if exist != nil {
return buserr.New(constant.ErrImageExist)
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
if runtime.Port != req.Port {
if err = checkPortExist(req.Port); err != nil {
return err
@ -516,7 +516,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
return err
}
go buildRuntime(runtime, imageID, oldEnv, req.Rebuild)
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
runtime.Version = req.Version
runtime.CodeDir = req.CodeDir
runtime.Port = req.Port
@ -682,7 +682,7 @@ func (r *RuntimeService) SyncRuntimeStatus() error {
return err
}
for _, runtime := range runtimes {
if runtime.Type == constant.RuntimeNode || runtime.Type == constant.RuntimeJava || runtime.Type == constant.RuntimeGo || runtime.Type == constant.RuntimePython {
if runtime.Type == constant.RuntimeNode || runtime.Type == constant.RuntimeJava || runtime.Type == constant.RuntimeGo || runtime.Type == constant.RuntimePython || runtime.Type == constant.RuntimeDotNet {
_ = SyncRuntimeContainerStatus(&runtime)
}
}

View File

@ -3,6 +3,7 @@ package service
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/agent/app/dto"
@ -17,12 +18,10 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/compose"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"github.com/1Panel-dev/1Panel/agent/utils/files"
httpUtil "github.com/1Panel-dev/1Panel/agent/utils/http"
"github.com/pkg/errors"
"github.com/subosito/gotenv"
"gopkg.in/yaml.v3"
"io"
"net/http"
"os"
"os/exec"
"path"
@ -61,10 +60,7 @@ func handleNodeAndJava(create request.RuntimeCreate, runtime *model.Runtime, fil
}
go func() {
if _, _, err := httpUtil.HandleGet(nodeDetail.DownloadCallBackUrl, http.MethodGet, constant.TimeOut5s); err != nil {
global.LOG.Errorf("http request failed(handleNode), err: %v", err)
return
}
RequestDownloadCallBack(nodeDetail.DownloadCallBackUrl)
}()
go startRuntime(runtime)
@ -221,7 +217,10 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebu
_ = logFile.Close()
}()
cmd := exec.Command("docker", "compose", "-f", composePath, "build")
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour)
defer cancel()
cmd := exec.CommandContext(ctx, "docker-compose", "-f", composePath, "build")
cmd.Stdout = logFile
var stderrBuf bytes.Buffer
multiWriterStderr := io.MultiWriter(&stderrBuf, logFile)
@ -231,8 +230,10 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebu
if err != nil {
runtime.Status = constant.RuntimeError
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String()
if stderrBuf.String() == "" {
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + err.Error()
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + buserr.New("ErrCmdTimeout").Error()
} else {
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String()
}
} else {
if err = runComposeCmdWithLog(constant.RuntimeDown, runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
@ -393,6 +394,14 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
if err != nil {
return
}
case constant.RuntimeDotNet:
create.Params["CODE_DIR"] = create.CodeDir
create.Params["DOTNET_VERSION"] = create.Version
create.Params["PANEL_APP_PORT_HTTP"] = create.Port
composeContent, err = handleCompose(env, composeContent, create, projectDir)
if err != nil {
return
}
}
newMap := make(map[string]string)
@ -438,7 +447,7 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${JAVA_APP_PORT}")
case constant.RuntimeGo:
ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${GO_APP_PORT}")
case constant.RuntimePython:
case constant.RuntimePython, constant.RuntimeDotNet:
ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${APP_PORT}")
}

View File

@ -10,6 +10,7 @@ import (
"encoding/pem"
"errors"
"fmt"
"github.com/1Panel-dev/1Panel/agent/utils/docker"
"net"
"os"
"path"
@ -1207,19 +1208,19 @@ func (w WebsiteService) OpWebsiteLog(req request.WebsiteLogReq) (*response.Websi
res.Content = strings.Join(lines, "\n")
return res, nil
case constant.DisableLog:
key := "access_log"
params := dto.NginxParam{}
switch req.LogType {
case constant.AccessLog:
params.Name = "access_log"
params.Params = []string{"off"}
website.AccessLog = false
case constant.ErrorLog:
key = "error_log"
params.Name = "error_log"
params.Params = []string{"/dev/null", "crit"}
website.ErrorLog = false
}
var nginxParams []dto.NginxParam
nginxParams = append(nginxParams, dto.NginxParam{
Name: key,
Params: []string{"off"},
})
nginxParams = append(nginxParams, params)
if err := updateNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil {
return nil, err
@ -1334,6 +1335,14 @@ func (w WebsiteService) ChangePHPVersion(req request.WebsitePHPVersionReq) error
if oldRuntime.Resource == constant.ResourceLocal {
return buserr.New("ErrPHPResource")
}
client, err := docker.NewDockerClient()
if err != nil {
return err
}
defer client.Close()
if !checkImageExist(client, oldRuntime.Image) {
return buserr.WithName("ErrImageNotExist", oldRuntime.Name)
}
}
configPath := GetSitePath(website, SiteConf)
nginxContent, err := files.NewFileOp().GetContent(configPath)
@ -1624,6 +1633,9 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
}
if req.SNI {
location.UpdateDirective("proxy_ssl_server_name", []string{"on"})
if req.ProxySSLName != "" {
location.UpdateDirective("proxy_ssl_name", []string{req.ProxySSLName})
}
} else {
location.UpdateDirective("proxy_ssl_server_name", []string{"off"})
}

View File

@ -382,6 +382,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
logger.Println(i18n.GetMsgByKey("ExecShellSuccess"))
}
}
reloadSystemSSL(websiteSSL, logger)
return websiteSSL, nil
}

View File

@ -182,6 +182,32 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
return create, nil
}
func printSSLLog(logger *log.Logger, msgKey string, params map[string]interface{}, disableLog bool) {
if disableLog {
return
}
logger.Println(i18n.GetMsgWithMap(msgKey, params))
}
func reloadSystemSSL(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
systemSSLEnable, sslID := GetSystemSSL()
if systemSSLEnable && sslID == websiteSSL.ID {
fileOp := files.NewFileOp()
certPath := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt")
keyPath := path.Join(global.CONF.System.BaseDir, "1panel/secret/server.key")
printSSLLog(logger, "StartUpdateSystemSSL", nil, logger == nil)
if err := fileOp.WriteFile(certPath, strings.NewReader(websiteSSL.Pem), 0600); err != nil {
logger.Printf("Failed to update the SSL certificate File for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return
}
if err := fileOp.WriteFile(keyPath, strings.NewReader(websiteSSL.PrivateKey), 0600); err != nil {
logger.Printf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return
}
printSSLLog(logger, "UpdateSystemSSLSuccess", nil, logger == nil)
}
}
func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
var (
err error
@ -273,11 +299,13 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
defer logFile.Close()
logger := log.New(logFile, "", log.LstdFlags)
legoLogger.Logger = logger
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
if websiteSSL.Provider == constant.DNSAccount {
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type})
if !apply.DisableLog {
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
if websiteSSL.Provider == constant.DNSAccount {
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type})
}
legoLogger.Logger.Println(startMsg)
}
legoLogger.Logger.Println(startMsg)
resource, err := client.ObtainSSL(domains, privateKey)
if err != nil {
handleError(websiteSSL, err)
@ -297,7 +325,7 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
websiteSSL.Type = cert.Issuer.CommonName
websiteSSL.Organization = cert.Issuer.Organization[0]
websiteSSL.Status = constant.SSLReady
legoLogger.Logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")}))
printSSLLog(logger, "ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")}, apply.DisableLog)
saveCertificateFile(websiteSSL, logger)
if websiteSSL.ExecShell {
@ -305,11 +333,11 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
if websiteSSL.PushDir {
workDir = websiteSSL.Dir
}
legoLogger.Logger.Println(i18n.GetMsgByKey("ExecShellStart"))
printSSLLog(logger, "ExecShellStart", nil, apply.DisableLog)
if err = cmd.ExecShellWithTimeOut(websiteSSL.Shell, workDir, logger, 30*time.Minute); err != nil {
legoLogger.Logger.Println(i18n.GetMsgWithMap("ErrExecShell", map[string]interface{}{"err": err.Error()}))
printSSLLog(logger, "ErrExecShell", map[string]interface{}{"err": err.Error()}, apply.DisableLog)
} else {
legoLogger.Logger.Println(i18n.GetMsgByKey("ExecShellSuccess"))
printSSLLog(logger, "ExecShellSuccess", nil, apply.DisableLog)
}
}
@ -321,9 +349,9 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(websiteSSL.ID))
if len(websites) > 0 {
for _, website := range websites {
legoLogger.Logger.Println(i18n.GetMsgWithMap("ApplyWebSiteSSLLog", map[string]interface{}{"name": website.PrimaryDomain}))
printSSLLog(logger, "ApplyWebSiteSSLLog", map[string]interface{}{"name": website.PrimaryDomain}, apply.DisableLog)
if err := createPemFile(website, *websiteSSL); err != nil {
legoLogger.Logger.Println(i18n.GetMsgWithMap("ErrUpdateWebsiteSSL", map[string]interface{}{"name": website.PrimaryDomain, "err": err.Error()}))
printSSLLog(logger, "ErrUpdateWebsiteSSL", map[string]interface{}{"name": website.PrimaryDomain, "err": err.Error()}, apply.DisableLog)
}
}
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
@ -331,11 +359,12 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
return
}
if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
legoLogger.Logger.Println(i18n.GetMsgByKey(constant.ErrSSLApply))
printSSLLog(logger, constant.ErrSSLApply, nil, apply.DisableLog)
return
}
legoLogger.Logger.Println(i18n.GetMsgByKey("ApplyWebSiteSSLSuccess"))
printSSLLog(logger, "ApplyWebSiteSSLSuccess", nil, apply.DisableLog)
}
reloadSystemSSL(websiteSSL, logger)
}()
return nil

View File

@ -12,4 +12,5 @@ location ^~ /test {
add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-cache;
proxy_ssl_server_name off;
proxy_ssl_name $proxy_host;
}

View File

@ -20,6 +20,7 @@ const (
RuntimeJava = "java"
RuntimeGo = "go"
RuntimePython = "python"
RuntimeDotNet = "dotnet"
RuntimeProxyUnix = "unix"
RuntimeProxyTcp = "tcp"

View File

@ -1,8 +1,6 @@
package job
import (
"path"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
@ -11,7 +9,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/files"
)
type ssl struct {
@ -22,7 +19,6 @@ func NewSSLJob() *ssl {
}
func (ssl *ssl) Run() {
systemSSLEnable, sslID := service.GetSystemSSL()
sslRepo := repo.NewISSLRepo()
sslService := service.NewIWebsiteSSLService()
sslList, _ := sslRepo.List()
@ -51,25 +47,13 @@ func (ssl *ssl) Run() {
}
} else {
if err := sslService.ObtainSSL(request.WebsiteSSLApply{
ID: s.ID,
ID: s.ID,
DisableLog: true,
}); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate for the [%s] domain , err:%s", s.PrimaryDomain, err.Error())
continue
}
}
if systemSSLEnable && sslID == s.ID {
websiteSSL, _ := sslRepo.GetFirst(repo.NewCommonRepo().WithByID(s.ID))
fileOp := files.NewFileOp()
secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret")
if err := fileOp.WriteFile(path.Join(secretDir, "server.crt"), strings.NewReader(websiteSSL.Pem), 0600); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate File for 1Panel System domain [%s] , err:%s", s.PrimaryDomain, err.Error())
continue
}
if err := fileOp.WriteFile(path.Join(secretDir, "server.key"), strings.NewReader(websiteSSL.PrivateKey), 0600); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", s.PrimaryDomain, err.Error())
continue
}
}
global.LOG.Infof("The SSL certificate for the [%s] domain has been successfully updated", s.PrimaryDomain)
}
}

View File

@ -131,6 +131,11 @@ ErrDefaultCA: "The default organization cannot be deleted"
ApplyWebSiteSSLLog: "Start updating {{ .name }} website certificate"
ErrUpdateWebsiteSSL: "{{ .name }} website failed to update certificate: {{ .err }}"
ApplyWebSiteSSLSuccess: "Update website certificate successfully"
StartUpdateSystemSSL: "Start updating system certificate"
UpdateSystemSSLSuccess: "Update system certificate successfully"
ErrExecShell: "Exec Shell err {{.err}}"
ExecShellStart: "Start executing script"
ExecShellSuccess: "Script executed successfully"
#mysql
ErrUserIsExist: "The current user already exists. Please enter a new user"

View File

@ -131,6 +131,11 @@ ErrDefaultCA: "默認機構不能刪除"
ApplyWebSiteSSLLog: "開始更新 {{ .name }} 網站憑證"
ErrUpdateWebsiteSSL: "{{ .name }} 網站更新憑證失敗: {{ .err }}"
ApplyWebSiteSSLSuccess: "更新網站憑證成功"
StartUpdateSystemSSL: "開始更新系統證書"
UpdateSystemSSLSuccess: "更新系統證書成功"
ErrExecShell: "執行腳本失敗 {{.err}}"
ExecShellStart: "開始執行腳本"
ExecShellSuccess: "腳本執行成功"
#mysql

View File

@ -130,6 +130,8 @@ ErrDefaultCA: "默认机构不能删除"
ApplyWebSiteSSLLog: "开始更新 {{ .name }} 网站证书"
ErrUpdateWebsiteSSL: "{{ .name }} 网站更新证书失败: {{ .err }}"
ApplyWebSiteSSLSuccess: "更新网站证书成功"
StartUpdateSystemSSL: "开始更新系统证书"
UpdateSystemSSLSuccess: "更新系统证书成功"
ErrExecShell: "执行脚本失败 {{ .err }}"
ExecShellStart: "开始执行脚本"
ExecShellSuccess: "脚本执行成功"

View File

@ -19,6 +19,7 @@ var whiteUrlList = map[string]struct{}{
"/api/v2/logs/operation": {},
"/api/v2/logs/login": {},
"/api/v2/auth/logout": {},
"/api/v1/dashboard/current": {},
"/api/v2/apps/installed/loadport": {},
"/api/v2/apps/installed/check": {},

View File

@ -3,10 +3,6 @@ package server
import (
"crypto/tls"
"fmt"
"net"
"net/http"
"os"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/cron"
"github.com/1Panel-dev/1Panel/agent/global"
@ -22,8 +18,12 @@ import (
"github.com/1Panel-dev/1Panel/agent/init/validator"
"github.com/1Panel-dev/1Panel/agent/init/viper"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
"net"
"net/http"
"os"
"github.com/gin-gonic/gin"
_ "net/http/pprof"
)
func Start() {
@ -46,6 +46,10 @@ func Start() {
Handler: rootRouter,
}
go func() {
http.ListenAndServe("0.0.0.0:6060", nil)
}()
if global.IsMaster {
_ = os.Remove("/tmp/agent.sock")
listener, err := net.Listen("unix", "/tmp/agent.sock")

View File

@ -45,6 +45,7 @@ var repeatKeys = map[string]struct {
"sub_filter": {},
"add_header": {},
"set_real_ip_from": {},
"error_page": {},
}
func IsRepeatKey(key string) bool {

View File

@ -37,6 +37,10 @@ func LoadGpuInfo() []interface{} {
return nil
}
func LoadXpuInfo() []interface{} {
return nil
}
func StartClam(startClam model.Clam, isUpdate bool) (int, error) {
return 0, buserr.New(constant.ErrXpackNotFound)
}

View File

@ -1,5 +1,7 @@
package constant
import "sync/atomic"
type DBContext string
const (
@ -35,3 +37,5 @@ const (
OneDriveRedirectURI = "http://localhost/login/authorized"
GoogleRedirectURI = "http://localhost:8080"
)
var CertStore atomic.Value

View File

@ -73,11 +73,13 @@ func Start() {
panic(err)
}
server.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return &cert, nil
},
}
global.LOG.Infof("listen at https://%s:%s [%s]", global.CONF.System.BindAddress, global.CONF.System.Port, tcpItem)
if err := server.ServeTLS(tcpKeepAliveListener{ln.(*net.TCPListener)}, certPath, keyPath); err != nil {
if err := server.ServeTLS(tcpKeepAliveListener{ln.(*net.TCPListener)}, "", ""); err != nil {
panic(err)
}
} else {

View File

@ -13,7 +13,7 @@
1Panel is an open-source, modern web-based server management control panel.
- **Efficient Management**: Users can easily manage Linux servers through a web graphical interface, achieving functions such as host monitoring, file management, database management, and container management.
- **Rapid Website Building**: Deep integration of open-source website building software WordPress and [Halo](https://github.com/halo-dev/halo/), with operations such as domain binding and SSL certificate configuration completed with one click.
- **Rapid Website Building**: Deep integration of open-source website building software WordPress, with operations such as domain binding and SSL certificate configuration completed with one click.
- **Application Store**: Curates a variety of high-quality open-source tools and application software, assisting users in easy installation and upgrades.
- **Security and Reliability**: Based on container management and application deployment, minimizing vulnerability exposure, while providing features such as firewall and log auditing.
- **One-Click Backup**: Supports one-click backup and restoration, allowing users to backup data to various cloud storage media, ensuring data is never lost.

View File

@ -90,6 +90,7 @@ export namespace Dashboard {
diskData: Array<DiskInfo>;
gpuData: Array<GPUInfo>;
xpuData: Array<XPUInfo>;
netBytesSent: number;
netBytesRecv: number;
@ -120,4 +121,14 @@ export namespace Dashboard {
memoryUsage: string;
fanSpeed: string;
}
export interface XPUInfo {
deviceID: number;
deviceName: string;
memory: string;
temperature: string;
memoryUsed: string;
power: string;
memoryUtil: string;
}
}

View File

@ -389,6 +389,7 @@ export namespace Website {
proxyAddress?: string;
proxyProtocol?: string;
sni?: boolean;
proxySSLName: string;
}
export interface ProxReplace {

View File

@ -131,7 +131,6 @@ const searchLogs = async () => {
logSocket.value.onmessage = (event) => {
logInfo.value += event.data;
nextTick(() => {
console.log(scrollerElement.value);
scrollerElement.value.scrollTop = scrollerElement.value.scrollHeight;
});
};

View File

@ -63,7 +63,6 @@ const loadTooltip = () => {
const acceptParams = (props: LogProps) => {
config.value = props;
console.log('config', config.value);
open.value = true;
if (!mobile.value) {

View File

@ -4,22 +4,23 @@
<el-checkbox border v-model="tailLog" class="float-left" @change="changeTail(false)">
{{ $t('commons.button.watch') }}
</el-checkbox>
<el-button class="ml-2.5" @click="onDownload" icon="Download" :disabled="data.content === ''">
<el-button class="ml-2.5" @click="onDownload" icon="Download" :disabled="logs.length === 0">
{{ $t('file.download') }}
</el-button>
<span v-if="$slots.button" class="ml-2.5">
<slot name="button"></slot>
</span>
</div>
<div class="mt-2.5">
<highlightjs
ref="editorRef"
class="editor-main"
language="JavaScript"
:autodetect="false"
:code="content"
:style="editorStyle"
></highlightjs>
<div class="log-container" ref="logContainer" @scroll="onScroll">
<div class="log-spacer" :style="{ height: `${totalHeight}px` }"></div>
<div
v-for="(log, index) in visibleLogs"
:key="startIndex + index"
class="log-item"
:style="{ top: `${(startIndex + index) * logHeight}px` }"
>
<span>{{ log }}</span>
</div>
</div>
</div>
</template>
@ -27,9 +28,6 @@
import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
import { downloadFile } from '@/utils/util';
import { ReadByLine } from '@/api/modules/files';
import { watch } from 'vue';
const editorRef = ref();
interface LogProps {
id?: number;
@ -38,15 +36,6 @@ interface LogProps {
tail?: boolean;
}
const editorStyle = computed(() => {
const height = 'calc(100vh - ' + props.heightDiff + 'px)';
return {
height,
width: '100%',
overflow: 'auto',
};
});
const props = defineProps({
config: {
type: Object as () => LogProps | null,
@ -74,46 +63,6 @@ const props = defineProps({
default: 500,
},
});
const data = ref({
enable: false,
content: '',
path: '',
});
let timer: NodeJS.Timer | null = null;
const tailLog = ref(false);
const content = ref('');
const end = ref(false);
const scrollerElement = ref<HTMLElement | null>(null);
const minPage = ref(1);
const maxPage = ref(1);
const logs = ref([]);
const isLoading = ref(false);
const readReq = reactive({
id: 0,
type: '',
name: '',
page: 1,
pageSize: 500,
latest: false,
});
const emit = defineEmits(['update:loading', 'update:hasContent', 'update:isReading']);
const loading = ref(props.loading);
watch(
() => props.loading,
(newLoading) => {
loading.value = newLoading;
},
);
const changeLoading = () => {
loading.value = !loading.value;
emit('update:loading', loading.value);
};
const stopSignals = [
'docker-compose up failed!',
'docker-compose up successful!',
@ -124,120 +73,63 @@ const stopSignals = [
'image push failed!',
'image push successful!',
];
const emit = defineEmits(['update:loading', 'update:hasContent', 'update:isReading']);
const tailLog = ref(false);
const loading = ref(props.loading);
const readReq = reactive({
id: 0,
type: '',
name: '',
page: 1,
pageSize: 500,
latest: false,
});
const isLoading = ref(false);
const end = ref(false);
const lastLogs = ref([]);
const maxPage = ref(0);
const minPage = ref(0);
let timer: NodeJS.Timer | null = null;
const logPath = ref('');
const getContent = (pre: boolean) => {
if (isLoading.value) {
return;
}
emit('update:isReading', true);
readReq.id = props.config.id;
readReq.type = props.config.type;
readReq.name = props.config.name;
if (readReq.page < 1) {
readReq.page = 1;
}
isLoading.value = true;
ReadByLine(readReq).then((res) => {
if (!end.value && res.data.end) {
lastLogs.value = [...logs.value];
}
const firstLoading = ref(false);
const logs = ref<string[]>([]);
const logContainer = ref<HTMLElement | null>(null);
const logHeight = 20;
const logCount = ref(0);
const totalHeight = computed(() => logHeight * logCount.value);
const containerHeight = ref(500);
const visibleCount = computed(() => Math.ceil(containerHeight.value / logHeight)); // /
const startIndex = ref(0);
data.value = res.data;
if (res.data.lines && res.data.lines.length > 0) {
res.data.lines = res.data.lines.map((line) =>
line.replace(/\\u(\w{4})/g, function (match, grp) {
return String.fromCharCode(parseInt(grp, 16));
}),
);
const newLogs = res.data.lines;
if (newLogs.length === readReq.pageSize && readReq.page < res.data.total) {
readReq.page++;
}
if (
readReq.type == 'php' &&
logs.value.length > 0 &&
newLogs.length > 0 &&
newLogs[newLogs.length - 1] === logs.value[logs.value.length - 1]
) {
isLoading.value = false;
const visibleLogs = computed(() => {
return logs.value.slice(startIndex.value, startIndex.value + visibleCount.value);
});
const onScroll = () => {
if (logContainer.value) {
const scrollTop = logContainer.value.scrollTop;
if (scrollTop == 0) {
readReq.page = minPage.value - 1;
if (readReq.page < 1) {
return;
}
if (stopSignals.some((signal) => newLogs[newLogs.length - 1].endsWith(signal))) {
onCloseLog();
}
if (end.value) {
if ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
logs.value = pre ? [...newLogs, ...lastLogs.value] : [...lastLogs.value, ...newLogs];
}
} else {
if ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
logs.value = pre ? [...newLogs, ...logs.value] : [...logs.value, ...newLogs];
}
}
minPage.value = readReq.page;
getContent(true);
}
end.value = res.data.end;
content.value = logs.value.join('\n');
emit('update:hasContent', content.value !== '');
nextTick(() => {
if (pre) {
if (scrollerElement.value.scrollHeight > 2000) {
scrollerElement.value.scrollTop = 2000;
}
} else {
scrollerElement.value.scrollTop = scrollerElement.value.scrollHeight;
}
});
if (readReq.latest) {
readReq.page = res.data.total;
readReq.latest = false;
maxPage.value = res.data.total;
minPage.value = res.data.total;
}
if (logs.value && logs.value.length > 3000) {
logs.value.splice(0, readReq.pageSize);
if (minPage.value > 1) {
minPage.value--;
}
}
isLoading.value = false;
});
startIndex.value = Math.floor(scrollTop / logHeight);
}
};
function throttle<T extends (...args: any[]) => any>(func: T, limit: number): (...args: Parameters<T>) => void {
let inThrottle: boolean;
let lastFunc: ReturnType<typeof setTimeout>;
let lastRan: number;
return function (this: any, ...args: Parameters<T>) {
if (!inThrottle) {
func.apply(this, args);
lastRan = Date.now();
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if (Date.now() - lastRan >= limit) {
func.apply(this, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
const changeLoading = () => {
loading.value = !loading.value;
emit('update:loading', loading.value);
};
const throttledGetContent = throttle(getContent, 3000);
const search = () => {
throttledGetContent(false);
const onDownload = async () => {
changeLoading();
downloadFile(logPath.value);
changeLoading();
};
const changeTail = (fromOutSide: boolean) => {
@ -246,36 +138,127 @@ const changeTail = (fromOutSide: boolean) => {
}
if (tailLog.value) {
timer = setInterval(() => {
search();
getContent(false);
}, 1000 * 3);
} else {
onCloseLog();
}
};
const onDownload = async () => {
changeLoading();
downloadFile(data.value.path);
changeLoading();
const clearLog = (): void => {
logs.value = [];
readReq.page = 1;
lastLogs.value = [];
};
const getContent = async (pre: boolean) => {
if (isLoading.value) {
return;
}
readReq.id = props.config.id;
readReq.type = props.config.type;
readReq.name = props.config.name;
if (readReq.page < 1) {
readReq.page = 1;
}
isLoading.value = true;
emit('update:isReading', true);
const res = await ReadByLine(readReq);
logPath.value = res.data.path;
firstLoading.value = false;
if (!end.value && res.data.end) {
lastLogs.value = [...logs.value];
}
if (res.data.lines && res.data.lines.length > 0) {
res.data.lines = res.data.lines.map((line) =>
line.replace(/\\u(\w{4})/g, function (match, grp) {
return String.fromCharCode(parseInt(grp, 16));
}),
);
const newLogs = res.data.lines;
if (newLogs.length === readReq.pageSize && readReq.page < res.data.total) {
readReq.page++;
}
if (
readReq.type == 'php' &&
logs.value.length > 0 &&
newLogs.length > 0 &&
newLogs[newLogs.length - 1] === logs.value[logs.value.length - 1]
) {
isLoading.value = false;
return;
}
if (stopSignals.some((signal) => newLogs[newLogs.length - 1].endsWith(signal))) {
onCloseLog();
}
if (end.value) {
if ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
logs.value = pre ? [...newLogs, ...lastLogs.value] : [...lastLogs.value, ...newLogs];
}
} else {
if ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
logs.value = pre ? [...newLogs, ...logs.value] : [...logs.value, ...newLogs];
}
}
nextTick(() => {
if (pre) {
logContainer.value.scrollTop = 2000;
} else {
logContainer.value.scrollTop = totalHeight.value;
containerHeight.value = logContainer.value.getBoundingClientRect().height;
}
});
}
logCount.value = logs.value.length;
end.value = res.data.end;
emit('update:hasContent', logs.value.length > 0);
if (readReq.latest) {
readReq.page = res.data.total;
readReq.latest = false;
maxPage.value = res.data.total;
minPage.value = res.data.total;
}
if (logs.value && logs.value.length > 3000) {
if (pre) {
logs.value.splice(logs.value.length - readReq.pageSize, readReq.pageSize);
if (maxPage.value > 1) {
maxPage.value--;
}
} else {
logs.value.splice(0, readReq.pageSize);
if (minPage.value > 1) {
minPage.value++;
}
}
}
isLoading.value = false;
};
const onCloseLog = async () => {
emit('update:isReading', false);
tailLog.value = false;
clearInterval(Number(timer));
timer = null;
isLoading.value = false;
emit('update:isReading', false);
};
function isScrolledToBottom(element: HTMLElement): boolean {
return element.scrollTop + element.clientHeight + 1 >= element.scrollHeight;
}
watch(
() => props.loading,
(newLoading) => {
loading.value = newLoading;
},
);
function isScrolledToTop(element: HTMLElement): boolean {
return element.scrollTop === 0;
}
const init = () => {
const init = async () => {
if (props.config.tail) {
tailLog.value = props.config.tail;
} else {
@ -285,59 +268,58 @@ const init = () => {
changeTail(false);
}
readReq.latest = true;
search();
nextTick(() => {});
await getContent(false);
};
const clearLog = (): void => {
content.value = '';
};
const initCodemirror = () => {
onMounted(async () => {
firstLoading.value = true;
await init();
nextTick(() => {
if (editorRef.value) {
scrollerElement.value = editorRef.value.$el as HTMLElement;
scrollerElement.value.addEventListener('scroll', function () {
if (tailLog.value) {
return;
}
if (isScrolledToBottom(scrollerElement.value)) {
if (maxPage.value > 1) {
readReq.page = maxPage.value;
}
search();
}
if (isScrolledToTop(scrollerElement.value)) {
readReq.page = minPage.value - 1;
if (readReq.page < 1) {
return;
}
minPage.value = readReq.page;
throttledGetContent(true);
}
});
let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;
hljsDom.style.minHeight = '95%';
if (logContainer.value) {
logContainer.value.scrollTop = totalHeight.value;
containerHeight.value = logContainer.value.getBoundingClientRect().height;
}
});
};
});
onUnmounted(() => {
onCloseLog();
});
onMounted(() => {
console.log(props.heightDiff);
initCodemirror();
init();
onMounted(async () => {
firstLoading.value = true;
await init();
nextTick(() => {
if (logContainer.value) {
logContainer.value.scrollTop = totalHeight.value;
containerHeight.value = logContainer.value.getBoundingClientRect().height;
}
});
});
defineExpose({ changeTail, onDownload, clearLog });
</script>
<style lang="scss" scoped>
.editor-main {
.log-container {
height: calc(100vh - 405px);
overflow-y: auto;
overflow-x: auto;
position: relative;
background-color: #1e1e1e;
margin-top: 10px;
}
.log-spacer {
position: relative;
width: 100%;
overflow: auto;
}
.log-item {
position: absolute;
width: 100%;
padding: 5px;
color: #f5f5f5;
box-sizing: border-box;
white-space: nowrap;
}
</style>

View File

@ -115,7 +115,6 @@ const getContent = (pre: boolean) => {
}
end.value = res.data.end;
nextTick(() => {
console.log('scrollerElement', scrollerElement.value);
if (pre) {
if (scrollerElement.value.scrollHeight > 2000) {
scrollerElement.value.scrollTop = 2000;
@ -123,8 +122,6 @@ const getContent = (pre: boolean) => {
} else {
scrollerElement.value.scrollTop = scrollerElement.value.scrollHeight;
}
console.log('scrollHeight', scrollerElement.value.scrollHeight);
console.log('scrollTop', scrollerElement.value.scrollTop);
});
if (readReq.latest) {
@ -182,7 +179,6 @@ const initCodemirror = () => {
nextTick(() => {
if (editorRef.value) {
scrollerElement.value = editorRef.value.$el as HTMLElement;
console.log('scrollerElement', scrollerElement.value);
scrollerElement.value.addEventListener('scroll', function () {
if (isScrolledToBottom(scrollerElement.value)) {
readReq.page = maxPage.value;

View File

@ -171,6 +171,10 @@ export const DNSTypes = [
label: i18n.global.t('website.tencentCloud'),
value: 'TencentCloud',
},
{
label: i18n.global.t('website.huaweicloud'),
value: 'HuaweiCloud',
},
{
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
value: 'DnsPod',

View File

@ -2228,6 +2228,7 @@ const message = {
sni: 'Origin SNI',
sniHelper:
"When the reverse proxy backend is HTTPS, you might need to set the origin SNI. Please refer to the CDN service provider's documentation for details.",
huaweicloud: 'Huawei Cloud',
createDb: 'Create Database',
enableSSLHelper: 'Failure to enable will not affect the creation of the website',
batchAdd: 'Batch Add Domains',
@ -2572,6 +2573,7 @@ const message = {
environment: 'Environment Variable',
pythonHelper:
'Please provide a complete start command, for example: pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000',
donetHelper: 'Please fill in the complete startup comman, for example: dotnet MyWebApp.dll',
},
process: {
pid: 'Process ID',

View File

@ -2075,6 +2075,7 @@ const message = {
website404Helper: '網站 404 錯誤頁僅支援 PHP 運行環境網站和靜態網站',
sni: '回源 SNI',
sniHelper: '反代後端為 https 的時候可能需要設置回源 SNI具體需要看 CDN 服務商文檔',
huaweicloud: '華為雲',
createDb: '建立資料庫',
enableSSLHelper: '開啟失敗不會影響網站創建',
batchAdd: '批量添加域名',
@ -2386,6 +2387,7 @@ const message = {
environment: '環境變數',
pythonHelper:
'請填寫完整啟動指令例如pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000',
donetHelper: '請填入完整啟動指令例如 dotnet MyWebApp.dll',
},
process: {
pid: '進程ID',

View File

@ -2073,6 +2073,7 @@ const message = {
website404Helper: '网站 404 错误页仅支持 PHP 运行环境网站和静态网站',
sni: '回源 SNI',
sniHelper: '反代后端为 https 的时候可能需要设置回源 SNI具体需要看 CDN 服务商文档',
huaweicloud: '华为云',
createDb: '创建数据库',
enableSSLHelper: '开启失败不会影响网站创建',
batchAdd: '批量添加域名',
@ -2385,6 +2386,7 @@ const message = {
environment: '环境变量',
pythonHelper:
'请填写完整启动命令例如pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000',
donetHelper: '请填写完整启动命令例如 dotnet MyWebApp.dll',
},
process: {
pid: '进程ID',

View File

@ -89,6 +89,16 @@ const webSiteRouter = {
requiresAuth: false,
},
},
{
path: '/websites/runtimes/dotnet',
name: 'dotNet',
hidden: true,
component: () => import('@/views/website/runtime/dotnet/index.vue'),
meta: {
activeMenu: '/websites/runtimes/php',
requiresAuth: false,
},
},
],
};

View File

@ -251,6 +251,7 @@ const openInstall = (app: App.App) => {
case 'java':
case 'go':
case 'python':
case 'dotnet':
router.push({ path: '/websites/runtimes/' + app.type });
break;
default:

View File

@ -4,14 +4,12 @@
:title="$t('app.composeDiff')"
:destroy-on-close="true"
:close-on-click-modal="false"
width="60%"
width="90%"
>
<el-row :gutter="10">
<el-col :span="22" :offset="1">
<el-text type="warning">{{ $t('app.diffHelper') }}</el-text>
<div ref="container" class="compose-diff"></div>
</el-col>
</el-row>
<div>
<el-text type="warning">{{ $t('app.diffHelper') }}</el-text>
<div ref="container" class="compose-diff"></div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>

View File

@ -212,10 +212,12 @@ const acceptParams = (): void => {
const goInstall = (key: string, type: string) => {
switch (type) {
case 'php':
router.push({ path: '/websites/runtimes/php' });
break;
case 'node':
router.push({ path: '/websites/runtimes/node' });
case 'java':
case 'go':
case 'python':
case 'donet':
router.push({ path: '/websites/runtimes/' + type });
break;
default:
router.push({ name: 'AppAll', query: { install: key } });
@ -295,7 +297,6 @@ const onOperate = async (operation: string, row: any) => {
const loadOption = async () => {
const res = await loadAppLauncherOption(filter.value || '');
options.value = res.data || [];
console.log(options.value);
};
defineExpose({

View File

@ -337,6 +337,7 @@ const currentInfo = ref<Dashboard.CurrentInfo>({
diskData: [],
gpuData: [],
xpuData: [],
netBytesSent: 0,
netBytesRecv: 0,

View File

@ -223,6 +223,51 @@
<span class="input-help" v-else>{{ item.productName }}</span>
</el-col>
</template>
<template v-for="(item, index) of currentInfo.xpuData" :key="index">
<el-col
:xs="12"
:sm="12"
:md="6"
:lg="6"
:xl="6"
align="center"
v-if="showMore || index < 4 - currentInfo.diskData.length"
>
<el-popover placement="bottom" :width="250" trigger="hover" v-if="chartsOption[`gpu${index}`]">
<el-row :gutter="5">
<el-tag style="font-weight: 500">{{ $t('home.baseInfo') }}:</el-tag>
</el-row>
<el-row :gutter="5">
<el-tag class="tagClass">{{ $t('monitor.gpuUtil') }}: {{ item.memoryUtil }}</el-tag>
</el-row>
<el-row :gutter="5">
<el-tag class="tagClass">{{ $t('monitor.temperature') }}: {{ item.temperature }}</el-tag>
</el-row>
<el-row :gutter="5">
<el-tag class="tagClass">{{ $t('monitor.powerUsage') }}: {{ item.power }}</el-tag>
</el-row>
<el-row :gutter="5">
<el-tag class="tagClass">
{{ $t('monitor.memoryUsage') }}: {{ item.memoryUsed }}/{{ item.memory }}
</el-tag>
</el-row>
<template #reference>
<v-charts
@click="goGPU()"
height="160px"
:id="`gpu${index}`"
type="pie"
:option="chartsOption[`gpu${index}`]"
v-if="chartsOption[`gpu${index}`]"
/>
</template>
</el-popover>
<el-tooltip :content="item.deviceName" v-if="item.deviceName.length > 25">
<span class="input-help">{{ item.deviceName.substring(0, 22) }}...</span>
</el-tooltip>
<span class="input-help" v-else>{{ item.deviceName }}</span>
</el-col>
</template>
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6" align="center">
<el-button v-if="!showMore" link type="primary" @click="showMore = true" class="buttonClass">
{{ $t('tabs.more') }}
@ -303,6 +348,7 @@ const currentInfo = ref<Dashboard.CurrentInfo>({
diskData: [],
gpuData: [],
xpuData: [],
netBytesSent: 0,
netBytesRecv: 0,
@ -348,6 +394,13 @@ const acceptParams = (current: Dashboard.CurrentInfo, base: Dashboard.BaseInfo,
if (currentInfo.value.diskData.length + currentInfo.value.gpuData.length > 5) {
showMore.value = isInit ? false : showMore.value || false;
}
currentInfo.value.xpuData = currentInfo.value.xpuData || [];
for (let i = 0; i < currentInfo.value.xpuData.length; i++) {
chartsOption.value['gpu' + i] = {
title: 'GPU-' + currentInfo.value.xpuData[i].deviceID,
data: formatNumber(Number(currentInfo.value.xpuData[i].memoryUtil.replaceAll('%', ''))),
};
}
});
};

View File

@ -126,6 +126,8 @@
<span class="agree" v-html="$t('commons.login.licenseHelper')"></span>
</template>
</el-checkbox>
</el-form-item>
<div class="agree-helper">
<span
v-if="!loginForm.agreeLicense && !_isMobile()"
class="input-error"
@ -133,7 +135,7 @@
>
{{ $t('commons.login.errorAgree') }}
</span>
</el-form-item>
</div>
</el-form>
<div class="demo">
<span v-if="isDemo">
@ -512,5 +514,11 @@ onMounted(() => {
:deep(.el-checkbox__input.is-checked .el-checkbox__inner::after) {
border-color: #fff !important;
}
.agree-helper {
min-height: 20px;
margin-top: -20px;
margin-left: 20px;
}
}
</style>

View File

@ -0,0 +1,306 @@
<template>
<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 #toolbar>
<div class="flex flex-wrap gap-3">
<el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }}
</el-button>
<el-button type="primary" plain @click="onOpenBuildCache()">
{{ $t('container.cleanBuildCache') }}
</el-button>
</div>
</template>
<template #main>
<ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()">
<el-table-column
:label="$t('commons.table.name')"
fix
prop="name"
min-width="120px"
show-overflow-tooltip
>
<template #default="{ row }">
<el-text type="primary" class="cursor-pointer" @click="openDetail(row)">
{{ row.name }}
</el-text>
</template>
</el-table-column>
<el-table-column :label="$t('website.runDir')" prop="codeDir">
<template #default="{ row }">
<el-button type="primary" link @click="toFolder(row.codeDir)">
<el-icon>
<FolderOpened />
</el-icon>
</el-button>
</template>
</el-table-column>
<el-table-column :label="$t('runtime.version')" prop="version"></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
v-if="row.status === 'error'"
placement="bottom"
:width="400"
trigger="hover"
:content="row.message"
>
<template #reference>
<Status :key="row.status" :status="row.status"></Status>
</template>
</el-popover>
<div v-else>
<Status :key="row.status" :status="row.status"></Status>
</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')"
:formatter="dateFormat"
show-overflow-tooltip
min-width="120"
fix
/>
<fu-table-operations
:ellipsis="mobile ? 0 : 3"
:min-width="mobile ? 'auto' : 300"
:buttons="buttons"
fixed="right"
:label="$t('commons.table.operate')"
fix
/>
</ComplexTable>
</template>
</LayoutContent>
<OperateDonet ref="operateRef" @close="search" />
<Delete ref="deleteRef" @close="search" />
<ComposeLogs ref="composeLogRef" />
<PortJumpDialog ref="dialogPortJumpRef" />
<AppResources ref="checkRef" @close="search" />
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref, computed } from 'vue';
import { Runtime } from '@/api/interface/runtime';
import { OperateRuntime, RuntimeDeleteCheck, SearchRuntimes, SyncRuntime } from '@/api/modules/runtime';
import { dateFormat } from '@/utils/util';
import OperateDonet from '@/views/website/runtime/dotnet/operate/index.vue';
import Status from '@/components/status/index.vue';
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';
import AppResources from '@/views/website/runtime/php/check/index.vue';
import { ElMessageBox } from 'element-plus';
import { containerPrune } from '@/api/modules/container';
import { MsgSuccess } from '@/utils/message';
import { GlobalStore } from '@/store';
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 checkRef = ref();
const globalStore = GlobalStore();
const mobile = computed(() => {
return globalStore.isMobile();
});
const paginationConfig = reactive({
cacheSizeKey: 'runtime-page-size',
currentPage: 1,
pageSize: 10,
total: 0,
});
const req = reactive<Runtime.RuntimeReq>({
name: '',
page: 1,
pageSize: 40,
type: 'donet',
});
const buttons = [
{
label: i18n.global.t('container.stop'),
click: function (row: Runtime.Runtime) {
operateRuntime('down', row.id);
},
disabled: function (row: Runtime.Runtime) {
return row.status === 'recreating' || row.status === 'stopped';
},
},
{
label: i18n.global.t('container.start'),
click: function (row: Runtime.Runtime) {
operateRuntime('up', row.id);
},
disabled: function (row: Runtime.Runtime) {
return row.status === 'starting' || row.status === 'recreating' || row.status === 'running';
},
},
{
label: i18n.global.t('container.restart'),
click: function (row: Runtime.Runtime) {
operateRuntime('restart', row.id);
},
disabled: function (row: Runtime.Runtime) {
return row.status === 'recreating';
},
},
{
label: i18n.global.t('commons.button.edit'),
click: function (row: Runtime.Runtime) {
openDetail(row);
},
disabled: function (row: Runtime.Runtime) {
return row.status === 'recreating';
},
},
{
label: i18n.global.t('commons.button.delete'),
click: function (row: Runtime.Runtime) {
openDelete(row);
},
},
];
const search = async () => {
req.page = paginationConfig.currentPage;
req.pageSize = paginationConfig.pageSize;
loading.value = true;
try {
const res = await SearchRuntimes(req);
items.value = res.data.items;
paginationConfig.total = res.data.total;
} catch (error) {
} finally {
loading.value = false;
}
};
const sync = () => {
SyncRuntime();
};
const openCreate = () => {
operateRef.value.acceptParams({ type: 'donet', mode: 'create' });
};
const openDetail = (row: Runtime.Runtime) => {
operateRef.value.acceptParams({ type: row.type, mode: 'edit', id: row.id });
};
const openDelete = (row: Runtime.Runtime) => {
RuntimeDeleteCheck(row.id).then(async (res) => {
const items = res.data;
if (res.data && res.data.length > 0) {
checkRef.value.acceptParams({ items: items, key: 'website', installID: row.id });
} else {
deleteRef.value.acceptParams(row.id, row.name);
}
});
};
const onOpenBuildCache = () => {
ElMessageBox.confirm(i18n.global.t('container.delBuildCacheHelper'), i18n.global.t('container.cleanBuildCache'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
loading.value = true;
let params = {
pruneType: 'buildcache',
withTagAll: false,
};
await containerPrune(params)
.then((res) => {
loading.value = false;
MsgSuccess(i18n.global.t('container.cleanSuccess', [res.data.deletedNumber]));
search();
})
.catch(() => {
loading.value = false;
});
});
};
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(
i18n.global.t('runtime.operatorHelper', [i18n.global.t('commons.operate.' + operate)]),
i18n.global.t('commons.operate.' + operate),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
},
);
if (action === 'confirm') {
loading.value = true;
await OperateRuntime({ operate: operate, ID: ID });
search();
}
} catch (error) {
} finally {
loading.value = false;
}
};
const toFolder = (folder: string) => {
router.push({ path: '/hosts/files', query: { path: folder } });
};
onMounted(() => {
sync();
search();
timer = setInterval(() => {
search();
sync();
}, 1000 * 10);
});
onUnmounted(() => {
clearInterval(Number(timer));
timer = null;
});
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,394 @@
<template>
<el-drawer
:destroy-on-close="true"
:close-on-click-modal="false"
:close-on-press-escape="false"
v-model="open"
size="50%"
>
<template #header>
<DrawerHeader
:header="$t('runtime.' + mode)"
:hideResource="mode == 'create'"
:resource="runtime.name"
:back="handleClose"
/>
</template>
<el-row v-loading="loading">
<el-col :span="22" :offset="1">
<el-form
ref="runtimeForm"
label-position="top"
:model="runtime"
label-width="125px"
:rules="rules"
:validate-on-rule-change="false"
>
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input :disabled="mode === 'edit'" v-model="runtime.name"></el-input>
</el-form-item>
<el-form-item :label="$t('runtime.app')" prop="appID">
<el-row :gutter="20">
<el-col :span="12">
<el-select
v-model="runtime.appID"
:disabled="mode === 'edit'"
@change="changeApp(runtime.appID)"
class="p-w-200"
>
<el-option
v-for="(app, index) in apps"
:key="index"
:label="app.name"
:value="app.id"
></el-option>
</el-select>
</el-col>
<el-col :span="12">
<el-select
v-model="runtime.version"
:disabled="mode === 'edit'"
@change="changeVersion()"
class="p-w-200"
>
<el-option
v-for="(version, index) in appVersions"
:key="index"
:label="version"
:value="version"
></el-option>
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item :label="$t('tool.supervisor.dir')" prop="codeDir">
<el-input v-model.trim="runtime.codeDir" :disabled="mode === 'edit'">
<template #prepend>
<FileList
:disabled="mode === 'edit'"
:path="runtime.codeDir"
@choose="getPath"
:dir="true"
></FileList>
</template>
</el-input>
</el-form-item>
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<span class="input-help">
{{ $t('runtime.donetHelper') }}
</span>
</el-form-item>
<el-row :gutter="20">
<el-col :span="7">
<el-form-item :label="$t('runtime.appPort')" prop="params.APP_PORT">
<el-input v-model.number="runtime.params['APP_PORT']" />
<span class="input-help">{{ $t('runtime.appPortHelper') }}</span>
</el-form-item>
</el-col>
<el-col :span="7">
<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>
<el-col :span="4">
<el-form-item :label="$t('commons.button.add') + $t('commons.table.port')">
<el-button @click="addPort">
<el-icon><Plus /></el-icon>
</el-button>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="$t('app.allowPort')" prop="params.HOST_IP">
<el-switch
v-model="runtime.params['HOST_IP']"
:active-value="'0.0.0.0'"
:inactive-value="'127.0.0.1'"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" v-for="(port, index) of runtime.exposedPorts" :key="index">
<el-col :span="7">
<el-form-item
:prop="'exposedPorts.' + index + '.containerPort'"
:rules="rules.params.APP_PORT"
>
<el-input v-model.number="port.containerPort" :placeholder="$t('runtime.appPort')" />
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item :prop="'exposedPorts.' + index + '.hostPort'" :rules="rules.params.APP_PORT">
<el-input v-model.number="port.hostPort" :placeholder="$t('runtime.externalPort')" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item>
<el-button type="primary" @click="removePort(index)" link>
{{ $t('commons.button.delete') }}
</el-button>
</el-form-item>
</el-col>
</el-row>
<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>
</el-form>
</el-col>
</el-row>
<template #footer>
<span>
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="submit(runtimeForm)" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import { App } from '@/api/interface/app';
import { Runtime } from '@/api/interface/runtime';
import { GetApp, GetAppDetail, SearchApp } from '@/api/modules/app';
import { CreateRuntime, GetRuntime, UpdateRuntime } from '@/api/modules/runtime';
import { Rules, checkNumberRange } from '@/global/form-rules';
import i18n from '@/lang';
import { MsgError, MsgSuccess } from '@/utils/message';
import { FormInstance } from 'element-plus';
import { reactive, ref, watch } from 'vue';
import DrawerHeader from '@/components/drawer-header/index.vue';
interface OperateRrops {
id?: number;
mode: string;
type: string;
}
const open = ref(false);
const apps = ref<App.App[]>([]);
const runtimeForm = ref<FormInstance>();
const loading = ref(false);
const mode = ref('create');
const editParams = ref<App.InstallParams[]>();
const appVersions = ref<string[]>([]);
const appReq = reactive({
type: 'donet',
page: 1,
pageSize: 20,
resource: 'remote',
});
const initData = (type: string) => ({
name: '',
appDetailID: undefined,
image: '',
params: {
HOST_IP: '0.0.0.0',
},
type: type,
resource: 'appstore',
rebuild: false,
codeDir: '/',
port: 8080,
exposedPorts: [],
});
let runtime = reactive<Runtime.RuntimeCreate>(initData('donet'));
const rules = ref<any>({
name: [Rules.requiredInput, Rules.appName],
appID: [Rules.requiredSelect],
codeDir: [Rules.requiredInput],
port: [Rules.requiredInput, Rules.paramPort, checkNumberRange(1, 65535)],
source: [Rules.requiredSelect],
params: {
APP_PORT: [Rules.requiredInput, Rules.paramPort, checkNumberRange(1, 65535)],
HOST_IP: [Rules.requiredSelect],
CONTAINER_NAME: [Rules.requiredInput, Rules.containerName],
EXEC_SCRIPT: [Rules.requiredInput],
},
});
const scripts = ref<Runtime.NodeScripts[]>([]);
const em = defineEmits(['close']);
watch(
() => runtime.params['APP_PORT'],
(newVal) => {
if (newVal && mode.value == 'create') {
runtime.port = newVal;
}
},
{ deep: true },
);
watch(
() => runtime.name,
(newVal) => {
if (newVal && mode.value == 'create') {
runtime.params['CONTAINER_NAME'] = newVal;
}
},
{ deep: true },
);
const handleClose = () => {
open.value = false;
em('close', false);
runtimeForm.value?.resetFields();
};
const getPath = (codeDir: string) => {
runtime.codeDir = codeDir;
};
const addPort = () => {
runtime.exposedPorts.push({
hostPort: undefined,
containerPort: undefined,
});
};
const removePort = (index: number) => {
runtime.exposedPorts.splice(index, 1);
};
const searchApp = (appID: number) => {
SearchApp(appReq).then((res) => {
apps.value = res.data.items || [];
if (res.data && res.data.items && res.data.items.length > 0) {
if (appID == null) {
runtime.appID = res.data.items[0].id;
getApp(res.data.items[0].key, mode.value);
} else {
res.data.items.forEach((item) => {
if (item.id === appID) {
getApp(item.key, mode.value);
}
});
}
}
});
};
const changeApp = (appID: number) => {
for (const app of apps.value) {
if (app.id === appID) {
getApp(app.key, mode.value);
break;
}
}
};
const changeVersion = () => {
loading.value = true;
GetAppDetail(runtime.appID, runtime.version, 'runtime')
.then((res) => {
runtime.appDetailID = res.data.id;
})
.finally(() => {
loading.value = false;
});
};
const getApp = (appkey: string, mode: string) => {
GetApp(appkey).then((res) => {
appVersions.value = res.data.versions || [];
if (res.data.versions.length > 0) {
if (mode === 'create') {
runtime.version = res.data.versions[0];
changeVersion();
}
}
});
};
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (!valid) {
return;
}
if (runtime.exposedPorts && runtime.exposedPorts.length > 0) {
const containerPortMap = new Map();
const hostPortMap = new Map();
containerPortMap[runtime.params['APP_PORT']] = true;
hostPortMap[runtime.port] = true;
for (const port of runtime.exposedPorts) {
if (containerPortMap[port.containerPort]) {
MsgError(i18n.global.t('runtime.portError'));
return;
}
if (hostPortMap[port.hostPort]) {
MsgError(i18n.global.t('runtime.portError'));
return;
}
hostPortMap[port.hostPort] = true;
containerPortMap[port.containerPort] = true;
}
}
if (mode.value == 'create') {
loading.value = true;
CreateRuntime(runtime)
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
handleClose();
})
.finally(() => {
loading.value = false;
});
} else {
loading.value = true;
UpdateRuntime(runtime)
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
handleClose();
})
.finally(() => {
loading.value = false;
});
}
});
};
const getRuntime = async (id: number) => {
try {
const res = await GetRuntime(id);
const data = res.data;
Object.assign(runtime, {
id: data.id,
name: data.name,
appDetailId: data.appDetailID,
image: data.image,
type: data.type,
resource: data.resource,
appID: data.appID,
version: data.version,
rebuild: true,
source: data.source,
params: data.params,
codeDir: data.codeDir,
port: data.port,
});
runtime.exposedPorts = data.exposedPorts || [];
editParams.value = data.appParams;
searchApp(data.appID);
open.value = true;
} catch (error) {}
};
const acceptParams = async (props: OperateRrops) => {
mode.value = props.mode;
scripts.value = [];
if (props.mode === 'create') {
Object.assign(runtime, initData(props.type));
searchApp(null);
open.value = true;
} else {
getRuntime(props.id);
}
};
defineExpose({
acceptParams,
});
</script>

View File

@ -67,16 +67,12 @@
{{ $t('runtime.goDirHelper') }}
</span>
</el-form-item>
<el-row :gutter="20">
<el-col :span="18">
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<span class="input-help">
{{ $t('runtime.goHelper') }}
</span>
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<span class="input-help">
{{ $t('runtime.goHelper') }}
</span>
</el-form-item>
<PortConfig :params="runtime.params" :exposedPorts="runtime.exposedPorts" :rules="rules" />
<Environment :environments="runtime.environments" />
<Volumes :volumes="runtime.volumes" />

View File

@ -29,5 +29,9 @@ const buttons = [
label: 'Python',
path: '/websites/runtimes/python',
},
{
label: '.NET',
path: '/websites/runtimes/dotnet',
},
];
</script>

View File

@ -66,16 +66,12 @@
{{ $t('runtime.javaDirHelper') }}
</span>
</el-form-item>
<el-row :gutter="20">
<el-col :span="18">
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<span class="input-help">
{{ $t('runtime.javaScriptHelper') }}
</span>
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<span class="input-help">
{{ $t('runtime.javaScriptHelper') }}
</span>
</el-form-item>
<PortConfig :params="runtime.params" :exposedPorts="runtime.exposedPorts" :rules="rules" />
<Environment :environments="runtime.environments" />
<el-form-item :label="$t('app.containerName')" prop="params.CONTAINER_NAME">

View File

@ -64,16 +64,12 @@
</template>
</el-input>
</el-form-item>
<el-row :gutter="20">
<el-col :span="18">
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<span class="input-help">
{{ $t('runtime.pythonHelper') }}
</span>
</el-form-item>
</el-col>
</el-row>
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<span class="input-help">
{{ $t('runtime.pythonHelper') }}
</span>
</el-form-item>
<PortConfig :params="runtime.params" :exposedPorts="runtime.exposedPorts" :rules="rules" />
<Environment :environments="runtime.environments" />
<Volumes :volumes="runtime.volumes" />

View File

@ -42,7 +42,7 @@
name="13"
v-if="(website.type === 'runtime' && website.runtimeType === 'php') || website.type === 'static'"
>
<PHP :website="website" v-if="tabIndex == '13'"></PHP>
<PHP :id="id" v-if="tabIndex == '13'"></PHP>
</el-tab-pane>
<el-tab-pane :label="$t('logs.resource')" name="14">
<Resource :id="id" v-if="tabIndex == '14'"></Resource>

View File

@ -33,12 +33,13 @@ import { SearchRuntimes } from '@/api/modules/runtime';
import { onMounted, reactive, ref } from 'vue';
import { Runtime } from '@/api/interface/runtime';
import { Website } from '@/api/interface/website';
import { ChangePHPVersion } from '@/api/modules/website';
import { ChangePHPVersion, GetWebsite } from '@/api/modules/website';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
const props = defineProps({
website: {
type: Object,
id: {
type: Number,
default: 0,
},
});
@ -50,6 +51,7 @@ const versionReq = reactive<Website.PHPVersionChange>({
const versions = ref([]);
const loading = ref(false);
const oldRuntimeID = ref(0);
const website = ref();
const getRuntimes = async () => {
try {
@ -76,16 +78,23 @@ const submit = async () => {
try {
await ChangePHPVersion(versionReq);
MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
getWebsiteDetail();
} catch (error) {}
loading.value = false;
});
} catch (error) {}
};
const getWebsiteDetail = async () => {
const res = await GetWebsite(props.id);
versionReq.runtimeID = res.data.runtimeID;
oldRuntimeID.value = res.data.runtimeID;
website.value = res.data;
};
onMounted(() => {
versionReq.runtimeID = props.website.runtimeID;
versionReq.websiteID = props.website.id;
oldRuntimeID.value = props.website.runtimeID;
versionReq.websiteID = props.id;
getWebsiteDetail();
getRuntimes();
});
</script>

View File

@ -20,6 +20,9 @@
<el-switch v-model="proxy.sni"></el-switch>
<span class="input-help">{{ $t('website.sniHelper') }}</span>
</el-form-item>
<el-form-item label="proxy_ssl_name" prop="proxySSLName" v-if="proxy.sni">
<el-input v-model.trim="proxy.proxySSLName"></el-input>
</el-form-item>
<el-form-item :label="$t('website.cacheTime')" prop="cacheTime" v-if="proxy.cache">
<el-input v-model.number="proxy.cacheTime" maxlength="15">
<template #append>
@ -36,7 +39,7 @@
</el-form-item>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item :label="$t('website.proxyPass')" prop="proxyPass">
<el-form-item :label="$t('website.proxyPass')" prop="proxyAddress">
<el-input
v-model.trim="proxy.proxyAddress"
:placeholder="$t('website.proxyHelper')"
@ -114,6 +117,7 @@ const rules = ref({
cacheTime: [Rules.requiredInput, checkNumberRange(1, 65535)],
proxyPass: [Rules.requiredInput],
proxyHost: [Rules.requiredInput],
proxyAddress: [Rules.requiredInput],
});
const open = ref(false);
const loading = ref(false);
@ -135,6 +139,7 @@ const initData = (): Website.ProxyConfig => ({
proxyAddress: '',
proxyProtocol: 'http://',
sni: false,
proxySSLName: '',
});
let proxy = ref(initData());
const replaces = ref<any>([]);