mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-12-04 09:49:20 +08:00
feat: merge from dev (#7238)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
This commit is contained in:
parent
841d1a31bf
commit
e5660a0d91
@ -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 {
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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}")
|
||||
|
||||
}
|
||||
|
@ -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"})
|
||||
}
|
||||
|
@ -382,6 +382,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
|
||||
logger.Println(i18n.GetMsgByKey("ExecShellSuccess"))
|
||||
}
|
||||
}
|
||||
reloadSystemSSL(websiteSSL, logger)
|
||||
return websiteSSL, nil
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ const (
|
||||
RuntimeJava = "java"
|
||||
RuntimeGo = "go"
|
||||
RuntimePython = "python"
|
||||
RuntimeDotNet = "dotnet"
|
||||
|
||||
RuntimeProxyUnix = "unix"
|
||||
RuntimeProxyTcp = "tcp"
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -131,6 +131,11 @@ ErrDefaultCA: "默認機構不能刪除"
|
||||
ApplyWebSiteSSLLog: "開始更新 {{ .name }} 網站憑證"
|
||||
ErrUpdateWebsiteSSL: "{{ .name }} 網站更新憑證失敗: {{ .err }}"
|
||||
ApplyWebSiteSSLSuccess: "更新網站憑證成功"
|
||||
StartUpdateSystemSSL: "開始更新系統證書"
|
||||
UpdateSystemSSLSuccess: "更新系統證書成功"
|
||||
ErrExecShell: "執行腳本失敗 {{.err}}"
|
||||
ExecShellStart: "開始執行腳本"
|
||||
ExecShellSuccess: "腳本執行成功"
|
||||
|
||||
|
||||
#mysql
|
||||
|
@ -130,6 +130,8 @@ ErrDefaultCA: "默认机构不能删除"
|
||||
ApplyWebSiteSSLLog: "开始更新 {{ .name }} 网站证书"
|
||||
ErrUpdateWebsiteSSL: "{{ .name }} 网站更新证书失败: {{ .err }}"
|
||||
ApplyWebSiteSSLSuccess: "更新网站证书成功"
|
||||
StartUpdateSystemSSL: "开始更新系统证书"
|
||||
UpdateSystemSSLSuccess: "更新系统证书成功"
|
||||
ErrExecShell: "执行脚本失败 {{ .err }}"
|
||||
ExecShellStart: "开始执行脚本"
|
||||
ExecShellSuccess: "脚本执行成功"
|
||||
|
@ -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": {},
|
||||
|
@ -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")
|
||||
|
@ -45,6 +45,7 @@ var repeatKeys = map[string]struct {
|
||||
"sub_filter": {},
|
||||
"add_header": {},
|
||||
"set_real_ip_from": {},
|
||||
"error_page": {},
|
||||
}
|
||||
|
||||
func IsRepeatKey(key string) bool {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -389,6 +389,7 @@ export namespace Website {
|
||||
proxyAddress?: string;
|
||||
proxyProtocol?: string;
|
||||
sni?: boolean;
|
||||
proxySSLName: string;
|
||||
}
|
||||
|
||||
export interface ProxReplace {
|
||||
|
@ -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;
|
||||
});
|
||||
};
|
||||
|
@ -63,7 +63,6 @@ const loadTooltip = () => {
|
||||
|
||||
const acceptParams = (props: LogProps) => {
|
||||
config.value = props;
|
||||
console.log('config', config.value);
|
||||
open.value = true;
|
||||
|
||||
if (!mobile.value) {
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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>
|
||||
|
@ -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({
|
||||
|
@ -337,6 +337,7 @@ const currentInfo = ref<Dashboard.CurrentInfo>({
|
||||
|
||||
diskData: [],
|
||||
gpuData: [],
|
||||
xpuData: [],
|
||||
|
||||
netBytesSent: 0,
|
||||
netBytesRecv: 0,
|
||||
|
@ -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('%', ''))),
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
|
306
frontend/src/views/website/runtime/dotnet/index.vue
Normal file
306
frontend/src/views/website/runtime/dotnet/index.vue
Normal 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>
|
394
frontend/src/views/website/runtime/dotnet/operate/index.vue
Normal file
394
frontend/src/views/website/runtime/dotnet/operate/index.vue
Normal 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>
|
@ -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" />
|
||||
|
@ -29,5 +29,9 @@ const buttons = [
|
||||
label: 'Python',
|
||||
path: '/websites/runtimes/python',
|
||||
},
|
||||
{
|
||||
label: '.NET',
|
||||
path: '/websites/runtimes/dotnet',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
@ -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">
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>([]);
|
||||
|
Loading…
Reference in New Issue
Block a user