mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-12-12 15:39:09 +08:00
feat: merge from dev (#7238)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
This commit is contained in:
parent
841d1a31bf
commit
e5660a0d91
@ -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 {
|
||||||
|
@ -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"`
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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}")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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"})
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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: "脚本执行成功"
|
||||||
|
@ -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": {},
|
||||||
|
@ -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")
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -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) {
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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',
|
||||||
|
@ -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',
|
||||||
|
@ -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',
|
||||||
|
@ -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',
|
||||||
|
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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>
|
||||||
|
@ -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({
|
||||||
|
@ -337,6 +337,7 @@ const currentInfo = ref<Dashboard.CurrentInfo>({
|
|||||||
|
|
||||||
diskData: [],
|
diskData: [],
|
||||||
gpuData: [],
|
gpuData: [],
|
||||||
|
xpuData: [],
|
||||||
|
|
||||||
netBytesSent: 0,
|
netBytesSent: 0,
|
||||||
netBytesRecv: 0,
|
netBytesRecv: 0,
|
||||||
|
@ -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('%', ''))),
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
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') }}
|
{{ $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" />
|
||||||
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
@ -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" />
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>([]);
|
||||||
|
Loading…
Reference in New Issue
Block a user