feat: merge from dev (#7238)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled

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 { type AppForm struct {
FormFields []AppFormFields `json:"formFields"` FormFields []AppFormFields `json:"formFields"`
SupportVersion float64 `json:"supportVersion"`
} }
type AppFormFields struct { type AppFormFields struct {

View File

@ -93,6 +93,7 @@ type DashboardCurrent struct {
NetBytesRecv uint64 `json:"netBytesRecv"` NetBytesRecv uint64 `json:"netBytesRecv"`
GPUData []GPUInfo `json:"gpuData"` GPUData []GPUInfo `json:"gpuData"`
XPUData []XPUInfo `json:"xpuData"`
ShotTime time.Time `json:"shotTime"` ShotTime time.Time `json:"shotTime"`
} }
@ -141,6 +142,7 @@ type AppLauncher struct {
IsRecommend bool `json:"isRecommend"` IsRecommend bool `json:"isRecommend"`
Detail []InstallDetail `json:"detail"` Detail []InstallDetail `json:"detail"`
} }
type InstallDetail struct { type InstallDetail struct {
InstallID uint `json:"installID"` InstallID uint `json:"installID"`
DetailID uint `json:"detailID"` DetailID uint `json:"detailID"`
@ -152,7 +154,18 @@ type InstallDetail struct {
HttpPort int `json:"httpPort"` HttpPort int `json:"httpPort"`
HttpsPort int `json:"httpsPort"` HttpsPort int `json:"httpsPort"`
} }
type LauncherOption struct { type LauncherOption struct {
Key string `json:"key"` Key string `json:"key"`
IsShow bool `json:"isShow"` 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 { type WebsiteProxyConfig struct {
ID uint `json:"id" validate:"required"` ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"` Operate string `json:"operate" validate:"required"`
Enable bool `json:"enable" ` Enable bool `json:"enable" `
Cache bool `json:"cache" ` Cache bool `json:"cache" `
CacheTime int `json:"cacheTime" ` CacheTime int `json:"cacheTime" `
CacheUnit string `json:"cacheUnit"` CacheUnit string `json:"cacheUnit"`
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
Modifier string `json:"modifier"` Modifier string `json:"modifier"`
Match string `json:"match" validate:"required"` Match string `json:"match" validate:"required"`
ProxyPass string `json:"proxyPass" validate:"required"` ProxyPass string `json:"proxyPass" validate:"required"`
ProxyHost string `json:"proxyHost" validate:"required"` ProxyHost string `json:"proxyHost" validate:"required"`
Content string `json:"content"` Content string `json:"content"`
FilePath string `json:"filePath"` FilePath string `json:"filePath"`
Replaces map[string]string `json:"replaces"` Replaces map[string]string `json:"replaces"`
SNI bool `json:"sni"` SNI bool `json:"sni"`
ProxySSLName string `json:"proxySSLName"`
} }
type WebsiteProxyReq struct { type WebsiteProxyReq struct {

View File

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

View File

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

View File

@ -597,7 +597,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
_ = appDetailRepo.Update(context.Background(), detail) _ = appDetailRepo.Update(context.Background(), detail)
} }
go func() { go func() {
_, _, _ = httpUtil.HandleGet(detail.DownloadCallBackUrl, http.MethodGet, constant.TimeOut5s) RequestDownloadCallBack(detail.DownloadCallBackUrl)
}() }()
} }
if install.App.Resource == constant.AppResourceLocal { if install.App.Resource == constant.AppResourceLocal {
@ -925,7 +925,7 @@ func copyData(task *task.Task, app model.App, appDetail model.AppDetail, appInst
return return
} }
go func() { go func() {
_, _, _ = httpUtil.HandleGet(appDetail.DownloadCallBackUrl, http.MethodGet, constant.TimeOut5s) RequestDownloadCallBack(appDetail.DownloadCallBackUrl)
}() }()
} }
appKey := app.Key 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) return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
} }
additionalProperties, ok := dataMap["additionalProperties"].(map[string]interface{}) additionalProperties, _ := dataMap["additionalProperties"].(map[string]interface{})
if !ok {
return buserr.WithName(constant.ErrAppParamKey, "additionalProperties")
}
formFieldsInterface, ok := additionalProperties["formFields"] formFieldsInterface, ok := additionalProperties["formFields"]
if ok { if ok {
formFields, ok := formFieldsInterface.([]interface{}) formFields, ok := formFieldsInterface.([]interface{})
@ -1463,6 +1459,17 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
continue continue
} }
lastVersion := versions[0] 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) { if common.IsCrossVersion(installed.Version, lastVersion) {
installDTO.CanUpdate = app.CrossVersionUpdate installDTO.CanUpdate = app.CrossVersionUpdate
} else { } else {
@ -1729,3 +1736,10 @@ func ignoreUpdate(installed model.AppInstall) bool {
} }
return false 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.DiskData = loadDiskInfo()
currentInfo.GPUData = loadGPUInfo() currentInfo.GPUData = loadGPUInfo()
currentInfo.XPUData = loadXpuInfo()
if ioOption == "all" { if ioOption == "all" {
diskInfo, _ := disk.IOCounters() diskInfo, _ := disk.IOCounters()
@ -501,3 +502,19 @@ func ArryContains(arr []string, element string) bool {
} }
return false 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 opts []repo.DBOption
) )
if create.Name != "" { if create.Name != "" {
opts = append(opts, commonRepo.WithByLikeName(create.Name)) opts = append(opts, commonRepo.WithByName(create.Name))
} }
if create.Type != "" { if create.Type != "" {
opts = append(opts, commonRepo.WithByType(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 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) { if !fileOp.Stat(create.CodeDir) {
return nil, buserr.New(constant.ErrPathNotFound) 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) 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 { if err = downloadApp(app, appDetail, nil, nil); err != nil {
return nil, err 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 { if err = handlePHP(create, runtime, fileOp, appVersionDir); err != nil {
return nil, err 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)) runtime.Port = int(create.Params["port"].(float64))
if err = handleNodeAndJava(create, runtime, fileOp, appVersionDir); err != nil { if err = handleNodeAndJava(create, runtime, fileOp, appVersionDir); err != nil {
return nil, err return nil, err
@ -341,7 +341,7 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) {
} }
} }
res.AppParams = appParams 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{}) res.Params = make(map[string]interface{})
envs, err := gotenv.Unmarshal(runtime.Env) envs, err := gotenv.Unmarshal(runtime.Env)
if err != nil { if err != nil {
@ -440,7 +440,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
if exist != nil { if exist != nil {
return buserr.New(constant.ErrImageExist) 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 runtime.Port != req.Port {
if err = checkPortExist(req.Port); err != nil { if err = checkPortExist(req.Port); err != nil {
return err return err
@ -516,7 +516,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
return err return err
} }
go buildRuntime(runtime, imageID, oldEnv, req.Rebuild) 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.Version = req.Version
runtime.CodeDir = req.CodeDir runtime.CodeDir = req.CodeDir
runtime.Port = req.Port runtime.Port = req.Port
@ -682,7 +682,7 @@ func (r *RuntimeService) SyncRuntimeStatus() error {
return err return err
} }
for _, runtime := range runtimes { 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) _ = SyncRuntimeContainerStatus(&runtime)
} }
} }

View File

@ -3,6 +3,7 @@ package service
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/agent/app/dto" "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/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"
httpUtil "github.com/1Panel-dev/1Panel/agent/utils/http"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/subosito/gotenv" "github.com/subosito/gotenv"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"io" "io"
"net/http"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@ -61,10 +60,7 @@ func handleNodeAndJava(create request.RuntimeCreate, runtime *model.Runtime, fil
} }
go func() { go func() {
if _, _, err := httpUtil.HandleGet(nodeDetail.DownloadCallBackUrl, http.MethodGet, constant.TimeOut5s); err != nil { RequestDownloadCallBack(nodeDetail.DownloadCallBackUrl)
global.LOG.Errorf("http request failed(handleNode), err: %v", err)
return
}
}() }()
go startRuntime(runtime) go startRuntime(runtime)
@ -221,7 +217,10 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebu
_ = logFile.Close() _ = 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 cmd.Stdout = logFile
var stderrBuf bytes.Buffer var stderrBuf bytes.Buffer
multiWriterStderr := io.MultiWriter(&stderrBuf, logFile) multiWriterStderr := io.MultiWriter(&stderrBuf, logFile)
@ -231,8 +230,10 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebu
if err != nil { if err != nil {
runtime.Status = constant.RuntimeError runtime.Status = constant.RuntimeError
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String() runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String()
if stderrBuf.String() == "" { if errors.Is(ctx.Err(), context.DeadlineExceeded) {
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + err.Error() runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + buserr.New("ErrCmdTimeout").Error()
} else {
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String()
} }
} else { } else {
if err = runComposeCmdWithLog(constant.RuntimeDown, runtime.GetComposePath(), runtime.GetLogPath()); err != nil { 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 { if err != nil {
return 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) 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}") ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${JAVA_APP_PORT}")
case constant.RuntimeGo: case constant.RuntimeGo:
ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${GO_APP_PORT}") 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}") ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${APP_PORT}")
} }

View File

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

View File

@ -182,6 +182,32 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
return create, nil 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 { func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
var ( var (
err error err error
@ -273,11 +299,13 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
defer logFile.Close() defer logFile.Close()
logger := log.New(logFile, "", log.LstdFlags) logger := log.New(logFile, "", log.LstdFlags)
legoLogger.Logger = logger legoLogger.Logger = logger
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)}) if !apply.DisableLog {
if websiteSSL.Provider == constant.DNSAccount { startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type}) 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) resource, err := client.ObtainSSL(domains, privateKey)
if err != nil { if err != nil {
handleError(websiteSSL, err) handleError(websiteSSL, err)
@ -297,7 +325,7 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
websiteSSL.Type = cert.Issuer.CommonName websiteSSL.Type = cert.Issuer.CommonName
websiteSSL.Organization = cert.Issuer.Organization[0] websiteSSL.Organization = cert.Issuer.Organization[0]
websiteSSL.Status = constant.SSLReady 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) saveCertificateFile(websiteSSL, logger)
if websiteSSL.ExecShell { if websiteSSL.ExecShell {
@ -305,11 +333,11 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
if websiteSSL.PushDir { if websiteSSL.PushDir {
workDir = websiteSSL.Dir 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 { 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 { } 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)) websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(websiteSSL.ID))
if len(websites) > 0 { if len(websites) > 0 {
for _, website := range websites { 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 { 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) nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
@ -331,11 +359,12 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
return return
} }
if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil { if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
legoLogger.Logger.Println(i18n.GetMsgByKey(constant.ErrSSLApply)) printSSLLog(logger, constant.ErrSSLApply, nil, apply.DisableLog)
return return
} }
legoLogger.Logger.Println(i18n.GetMsgByKey("ApplyWebSiteSSLSuccess")) printSSLLog(logger, "ApplyWebSiteSSLSuccess", nil, apply.DisableLog)
} }
reloadSystemSSL(websiteSSL, logger)
}() }()
return nil return nil

View File

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

View File

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

View File

@ -1,8 +1,6 @@
package job package job
import ( import (
"path"
"strings"
"time" "time"
"github.com/1Panel-dev/1Panel/agent/app/dto/request" "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/constant"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/common" "github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/files"
) )
type ssl struct { type ssl struct {
@ -22,7 +19,6 @@ func NewSSLJob() *ssl {
} }
func (ssl *ssl) Run() { func (ssl *ssl) Run() {
systemSSLEnable, sslID := service.GetSystemSSL()
sslRepo := repo.NewISSLRepo() sslRepo := repo.NewISSLRepo()
sslService := service.NewIWebsiteSSLService() sslService := service.NewIWebsiteSSLService()
sslList, _ := sslRepo.List() sslList, _ := sslRepo.List()
@ -51,25 +47,13 @@ func (ssl *ssl) Run() {
} }
} else { } else {
if err := sslService.ObtainSSL(request.WebsiteSSLApply{ if err := sslService.ObtainSSL(request.WebsiteSSLApply{
ID: s.ID, ID: s.ID,
DisableLog: true,
}); err != nil { }); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate for the [%s] domain , err:%s", s.PrimaryDomain, err.Error()) global.LOG.Errorf("Failed to update the SSL certificate for the [%s] domain , err:%s", s.PrimaryDomain, err.Error())
continue 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) 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" ApplyWebSiteSSLLog: "Start updating {{ .name }} website certificate"
ErrUpdateWebsiteSSL: "{{ .name }} website failed to update certificate: {{ .err }}" ErrUpdateWebsiteSSL: "{{ .name }} website failed to update certificate: {{ .err }}"
ApplyWebSiteSSLSuccess: "Update website certificate successfully" 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 #mysql
ErrUserIsExist: "The current user already exists. Please enter a new user" ErrUserIsExist: "The current user already exists. Please enter a new user"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -73,11 +73,13 @@ func Start() {
panic(err) panic(err)
} }
server.TLSConfig = &tls.Config{ 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) 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) panic(err)
} }
} else { } else {

View File

@ -13,7 +13,7 @@
1Panel is an open-source, modern web-based server management control panel. 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. - **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. - **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. - **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. - **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>; diskData: Array<DiskInfo>;
gpuData: Array<GPUInfo>; gpuData: Array<GPUInfo>;
xpuData: Array<XPUInfo>;
netBytesSent: number; netBytesSent: number;
netBytesRecv: number; netBytesRecv: number;
@ -120,4 +121,14 @@ export namespace Dashboard {
memoryUsage: string; memoryUsage: string;
fanSpeed: 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; proxyAddress?: string;
proxyProtocol?: string; proxyProtocol?: string;
sni?: boolean; sni?: boolean;
proxySSLName: string;
} }
export interface ProxReplace { export interface ProxReplace {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2228,6 +2228,7 @@ const message = {
sni: 'Origin SNI', sni: 'Origin SNI',
sniHelper: 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.", "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', createDb: 'Create Database',
enableSSLHelper: 'Failure to enable will not affect the creation of the website', enableSSLHelper: 'Failure to enable will not affect the creation of the website',
batchAdd: 'Batch Add Domains', batchAdd: 'Batch Add Domains',
@ -2572,6 +2573,7 @@ const message = {
environment: 'Environment Variable', environment: 'Environment Variable',
pythonHelper: pythonHelper:
'Please provide a complete start command, for example: pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000', '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: { process: {
pid: 'Process ID', pid: 'Process ID',

View File

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

View File

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

View File

@ -89,6 +89,16 @@ const webSiteRouter = {
requiresAuth: false, 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 'java':
case 'go': case 'go':
case 'python': case 'python':
case 'dotnet':
router.push({ path: '/websites/runtimes/' + app.type }); router.push({ path: '/websites/runtimes/' + app.type });
break; break;
default: default:

View File

@ -4,14 +4,12 @@
:title="$t('app.composeDiff')" :title="$t('app.composeDiff')"
:destroy-on-close="true" :destroy-on-close="true"
:close-on-click-modal="false" :close-on-click-modal="false"
width="60%" width="90%"
> >
<el-row :gutter="10"> <div>
<el-col :span="22" :offset="1"> <el-text type="warning">{{ $t('app.diffHelper') }}</el-text>
<el-text type="warning">{{ $t('app.diffHelper') }}</el-text> <div ref="container" class="compose-diff"></div>
<div ref="container" class="compose-diff"></div> </div>
</el-col>
</el-row>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button> <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) => { const goInstall = (key: string, type: string) => {
switch (type) { switch (type) {
case 'php': case 'php':
router.push({ path: '/websites/runtimes/php' });
break;
case 'node': case 'node':
router.push({ path: '/websites/runtimes/node' }); case 'java':
case 'go':
case 'python':
case 'donet':
router.push({ path: '/websites/runtimes/' + type });
break; break;
default: default:
router.push({ name: 'AppAll', query: { install: key } }); router.push({ name: 'AppAll', query: { install: key } });
@ -295,7 +297,6 @@ const onOperate = async (operation: string, row: any) => {
const loadOption = async () => { const loadOption = async () => {
const res = await loadAppLauncherOption(filter.value || ''); const res = await loadAppLauncherOption(filter.value || '');
options.value = res.data || []; options.value = res.data || [];
console.log(options.value);
}; };
defineExpose({ defineExpose({

View File

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

View File

@ -223,6 +223,51 @@
<span class="input-help" v-else>{{ item.productName }}</span> <span class="input-help" v-else>{{ item.productName }}</span>
</el-col> </el-col>
</template> </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-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"> <el-button v-if="!showMore" link type="primary" @click="showMore = true" class="buttonClass">
{{ $t('tabs.more') }} {{ $t('tabs.more') }}
@ -303,6 +348,7 @@ const currentInfo = ref<Dashboard.CurrentInfo>({
diskData: [], diskData: [],
gpuData: [], gpuData: [],
xpuData: [],
netBytesSent: 0, netBytesSent: 0,
netBytesRecv: 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) { if (currentInfo.value.diskData.length + currentInfo.value.gpuData.length > 5) {
showMore.value = isInit ? false : showMore.value || false; 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> <span class="agree" v-html="$t('commons.login.licenseHelper')"></span>
</template> </template>
</el-checkbox> </el-checkbox>
</el-form-item>
<div class="agree-helper">
<span <span
v-if="!loginForm.agreeLicense && !_isMobile()" v-if="!loginForm.agreeLicense && !_isMobile()"
class="input-error" class="input-error"
@ -133,7 +135,7 @@
> >
{{ $t('commons.login.errorAgree') }} {{ $t('commons.login.errorAgree') }}
</span> </span>
</el-form-item> </div>
</el-form> </el-form>
<div class="demo"> <div class="demo">
<span v-if="isDemo"> <span v-if="isDemo">
@ -512,5 +514,11 @@ onMounted(() => {
:deep(.el-checkbox__input.is-checked .el-checkbox__inner::after) { :deep(.el-checkbox__input.is-checked .el-checkbox__inner::after) {
border-color: #fff !important; border-color: #fff !important;
} }
.agree-helper {
min-height: 20px;
margin-top: -20px;
margin-left: 20px;
}
} }
</style> </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') }} {{ $t('runtime.goDirHelper') }}
</span> </span>
</el-form-item> </el-form-item>
<el-row :gutter="20"> <el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-col :span="18"> <el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT"> <span class="input-help">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input> {{ $t('runtime.goHelper') }}
<span class="input-help"> </span>
{{ $t('runtime.goHelper') }} </el-form-item>
</span>
</el-form-item>
</el-col>
</el-row>
<PortConfig :params="runtime.params" :exposedPorts="runtime.exposedPorts" :rules="rules" /> <PortConfig :params="runtime.params" :exposedPorts="runtime.exposedPorts" :rules="rules" />
<Environment :environments="runtime.environments" /> <Environment :environments="runtime.environments" />
<Volumes :volumes="runtime.volumes" /> <Volumes :volumes="runtime.volumes" />

View File

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

View File

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

View File

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

View File

@ -42,7 +42,7 @@
name="13" name="13"
v-if="(website.type === 'runtime' && website.runtimeType === 'php') || website.type === 'static'" 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>
<el-tab-pane :label="$t('logs.resource')" name="14"> <el-tab-pane :label="$t('logs.resource')" name="14">
<Resource :id="id" v-if="tabIndex == '14'"></Resource> <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 { onMounted, reactive, ref } from 'vue';
import { Runtime } from '@/api/interface/runtime'; import { Runtime } from '@/api/interface/runtime';
import { Website } from '@/api/interface/website'; import { Website } from '@/api/interface/website';
import { ChangePHPVersion } from '@/api/modules/website'; import { ChangePHPVersion, GetWebsite } from '@/api/modules/website';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
const props = defineProps({ const props = defineProps({
website: { id: {
type: Object, type: Number,
default: 0,
}, },
}); });
@ -50,6 +51,7 @@ const versionReq = reactive<Website.PHPVersionChange>({
const versions = ref([]); const versions = ref([]);
const loading = ref(false); const loading = ref(false);
const oldRuntimeID = ref(0); const oldRuntimeID = ref(0);
const website = ref();
const getRuntimes = async () => { const getRuntimes = async () => {
try { try {
@ -76,16 +78,23 @@ const submit = async () => {
try { try {
await ChangePHPVersion(versionReq); await ChangePHPVersion(versionReq);
MsgSuccess(i18n.global.t('commons.msg.updateSuccess')); MsgSuccess(i18n.global.t('commons.msg.updateSuccess'));
getWebsiteDetail();
} catch (error) {} } catch (error) {}
loading.value = false; loading.value = false;
}); });
} catch (error) {} } 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(() => { onMounted(() => {
versionReq.runtimeID = props.website.runtimeID; versionReq.websiteID = props.id;
versionReq.websiteID = props.website.id; getWebsiteDetail();
oldRuntimeID.value = props.website.runtimeID;
getRuntimes(); getRuntimes();
}); });
</script> </script>

View File

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