mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
61
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6322ad76b | ||
|
|
df003c5fb7 | ||
|
|
10f8e8f3c4 | ||
|
|
a750e87940 | ||
|
|
c12f690753 | ||
|
|
0dd51d0c35 | ||
|
|
7f627c041c | ||
|
|
472ca884fd | ||
|
|
236a2dffea | ||
|
|
9be9f1974f | ||
|
|
7961bddf9e | ||
|
|
a05f5c71b9 | ||
|
|
139e77112f | ||
|
|
d4e21ae032 | ||
|
|
104af30cbf | ||
|
|
f68dd7f00b | ||
|
|
220a6fffa0 | ||
|
|
23c4365c0e | ||
|
|
301bc4f6b8 | ||
|
|
ab03b05843 | ||
|
|
1dfbca3d5b | ||
|
|
d2ca265cc2 | ||
|
|
fc70345fcd | ||
|
|
6ae9b982a9 | ||
|
|
0122b4c6ce | ||
|
|
08c0faa20e | ||
|
|
ab7efea8c5 | ||
|
|
f4623664d5 | ||
|
|
df147435c5 | ||
|
|
71c1c5ae83 | ||
|
|
0ea142d6db | ||
|
|
f88a3059be | ||
|
|
48281ddf86 | ||
|
|
7cdcf9b757 | ||
|
|
ef0bb12fbd | ||
|
|
a0857f86b2 | ||
|
|
2219b3f47b | ||
|
|
b6c873f22d | ||
|
|
64bc92a7fd | ||
|
|
6c4c0f7595 | ||
|
|
2d9c977805 | ||
|
|
dff0375848 | ||
|
|
ab825a1a70 | ||
|
|
466617847f | ||
|
|
a53fff2416 | ||
|
|
dd1430dbbf | ||
|
|
5aad16968b | ||
|
|
f2d3acb650 | ||
|
|
666f8c813d | ||
|
|
e87fb7b50e | ||
|
|
75612879c7 | ||
|
|
94e0c80d47 | ||
|
|
feb6f4cbe0 | ||
|
|
1173bbf628 | ||
|
|
1cdc2e93ad | ||
|
|
3356bd8825 | ||
|
|
decc7a4a1e | ||
|
|
a397cab61c | ||
|
|
228e3d9ae3 | ||
|
|
051f670453 | ||
|
|
cf8aa86a67 |
@@ -2,6 +2,7 @@ package v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
@@ -55,25 +56,17 @@ func (b *BaseApi) LoadDashboardBaseInfo(c *gin.Context) {
|
||||
// @Tags Dashboard
|
||||
// @Summary Load dashboard current info
|
||||
// @Description 获取首页实时数据
|
||||
// @Accept json
|
||||
// @Param ioOption path string true "request"
|
||||
// @Param netOption path string true "request"
|
||||
// @Accept json、
|
||||
// @Param request body dto.DashboardReq true "request"
|
||||
// @Success 200 {object} dto.DashboardCurrent
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /dashboard/current/:ioOption/:netOption [get]
|
||||
// @Router /dashboard/current [post]
|
||||
func (b *BaseApi) LoadDashboardCurrentInfo(c *gin.Context) {
|
||||
ioOption, ok := c.Params.Get("ioOption")
|
||||
if !ok {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error ioOption in path"))
|
||||
var req dto.DashboardReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
netOption, ok := c.Params.Get("netOption")
|
||||
if !ok {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error netOption in path"))
|
||||
return
|
||||
}
|
||||
|
||||
data := dashboardService.LoadCurrentInfo(ioOption, netOption)
|
||||
data := dashboardService.LoadCurrentInfo(req)
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ type PortHelper struct {
|
||||
|
||||
type ContainerOperation struct {
|
||||
Names []string `json:"names" validate:"required"`
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop restart kill pause unpause remove"`
|
||||
Operation string `json:"operation" validate:"required,oneof=up start stop restart kill pause unpause remove"`
|
||||
}
|
||||
|
||||
type ContainerRename struct {
|
||||
@@ -200,6 +200,7 @@ type ComposeInfo struct {
|
||||
Workdir string `json:"workdir"`
|
||||
Path string `json:"path"`
|
||||
Containers []ComposeContainer `json:"containers"`
|
||||
Env []string `json:"env"`
|
||||
}
|
||||
type ComposeContainer struct {
|
||||
ContainerID string `json:"containerID"`
|
||||
@@ -208,22 +209,24 @@ type ComposeContainer struct {
|
||||
State string `json:"state"`
|
||||
}
|
||||
type ComposeCreate struct {
|
||||
Name string `json:"name"`
|
||||
From string `json:"from" validate:"required,oneof=edit path template"`
|
||||
File string `json:"file"`
|
||||
Path string `json:"path"`
|
||||
Template uint `json:"template"`
|
||||
Name string `json:"name"`
|
||||
From string `json:"from" validate:"required,oneof=edit path template"`
|
||||
File string `json:"file"`
|
||||
Path string `json:"path"`
|
||||
Template uint `json:"template"`
|
||||
Env []string `json:"env"`
|
||||
}
|
||||
type ComposeOperation struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Operation string `json:"operation" validate:"required,oneof=start stop down"`
|
||||
Operation string `json:"operation" validate:"required,oneof=up start stop down delete"`
|
||||
WithFile bool `json:"withFile"`
|
||||
}
|
||||
type ComposeUpdate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Env []string `json:"env"`
|
||||
}
|
||||
|
||||
type ContainerLog struct {
|
||||
|
||||
@@ -35,6 +35,12 @@ type OsInfo struct {
|
||||
DiskSize int64 `json:"diskSize"`
|
||||
}
|
||||
|
||||
type DashboardReq struct {
|
||||
Scope string `json:"scope"`
|
||||
IoOption string `json:"ioOption"`
|
||||
NetOption string `json:"netOption"`
|
||||
}
|
||||
|
||||
type DashboardCurrent struct {
|
||||
Uptime uint64 `json:"uptime"`
|
||||
TimeSinceUptime string `json:"timeSinceUptime"`
|
||||
@@ -73,6 +79,7 @@ type DashboardCurrent struct {
|
||||
NetBytesRecv uint64 `json:"netBytesRecv"`
|
||||
|
||||
GPUData []GPUInfo `json:"gpuData"`
|
||||
XPUData []XPUInfo `json:"xpuData"`
|
||||
|
||||
ShotTime time.Time `json:"shotTime"`
|
||||
}
|
||||
@@ -106,3 +113,13 @@ type GPUInfo struct {
|
||||
MemTotal string `json:"memTotal"`
|
||||
FanSpeed string `json:"fanSpeed"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -12,4 +12,5 @@ type Compose struct {
|
||||
BaseModel
|
||||
|
||||
Name string `gorm:"type:varchar(256)" json:"name"`
|
||||
Path string `gorm:"type:varchar(256)" json:"path"`
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ type IComposeTemplateRepo interface {
|
||||
CreateRecord(compose *model.Compose) error
|
||||
DeleteRecord(opts ...DBOption) error
|
||||
ListRecord() ([]model.Compose, error)
|
||||
UpdateRecord(name string, vars map[string]interface{}) error
|
||||
}
|
||||
|
||||
func NewIComposeTemplateRepo() IComposeTemplateRepo {
|
||||
@@ -102,3 +103,7 @@ func (u *ComposeTemplateRepo) DeleteRecord(opts ...DBOption) error {
|
||||
}
|
||||
return db.Delete(&model.Compose{}).Error
|
||||
}
|
||||
|
||||
func (u *ComposeTemplateRepo) UpdateRecord(name string, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.Compose{}).Where("name = ?", name).Updates(vars).Error
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
||||
IsFromApp = true
|
||||
}
|
||||
|
||||
ports := loadContainerPort(item.Ports)
|
||||
exposePorts := transPortToStr(records[i].Ports)
|
||||
info := dto.ContainerInfo{
|
||||
ContainerID: item.ID,
|
||||
CreateTime: time.Unix(item.Created, 0).Format(constant.DateTimeLayout),
|
||||
@@ -196,7 +196,7 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
||||
ImageName: item.Image,
|
||||
State: item.State,
|
||||
RunTime: item.Status,
|
||||
Ports: ports,
|
||||
Ports: exposePorts,
|
||||
IsFromApp: IsFromApp,
|
||||
IsFromCompose: IsFromCompose,
|
||||
}
|
||||
@@ -445,7 +445,8 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
|
||||
}
|
||||
}
|
||||
|
||||
data.ExposedPorts, _ = loadContainerPortForInfo(req.Name, client)
|
||||
exposePorts, _ := loadPortByInspect(oldContainer.ID, client)
|
||||
data.ExposedPorts = loadContainerPortForInfo(exposePorts)
|
||||
networkSettings := oldContainer.NetworkSettings
|
||||
bridgeNetworkSettings := networkSettings.Networks[data.Network]
|
||||
if bridgeNetworkSettings.IPAMConfig != nil {
|
||||
@@ -897,6 +898,10 @@ func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) s
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
|
||||
filePath = composeItem.Path
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
return ""
|
||||
@@ -1213,7 +1218,65 @@ func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper {
|
||||
return datas
|
||||
}
|
||||
|
||||
func loadContainerPort(ports []types.Port) []string {
|
||||
func loadPortByInspect(id string, client *client.Client) ([]types.Port, error) {
|
||||
container, err := client.ContainerInspect(context.Background(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var itemPorts []types.Port
|
||||
for key, val := range container.ContainerJSONBase.HostConfig.PortBindings {
|
||||
if !strings.Contains(string(key), "/") {
|
||||
continue
|
||||
}
|
||||
item := strings.Split(string(key), "/")
|
||||
itemPort, _ := strconv.ParseUint(item[0], 10, 16)
|
||||
|
||||
for _, itemVal := range val {
|
||||
publicPort, _ := strconv.ParseUint(itemVal.HostPort, 10, 16)
|
||||
itemPorts = append(itemPorts, types.Port{PrivatePort: uint16(itemPort), Type: item[1], PublicPort: uint16(publicPort), IP: itemVal.HostIP})
|
||||
}
|
||||
}
|
||||
return itemPorts, nil
|
||||
}
|
||||
|
||||
func loadContainerPortForInfo(itemPorts []types.Port) []dto.PortHelper {
|
||||
var exposedPorts []dto.PortHelper
|
||||
samePortMap := make(map[string]dto.PortHelper)
|
||||
ports := transPortToStr(itemPorts)
|
||||
var itemPort dto.PortHelper
|
||||
for _, item := range ports {
|
||||
itemStr := strings.Split(item, "->")
|
||||
if len(itemStr) < 2 {
|
||||
continue
|
||||
}
|
||||
lastIndex := strings.LastIndex(itemStr[0], ":")
|
||||
if lastIndex == -1 {
|
||||
itemPort.HostPort = itemStr[0]
|
||||
} else {
|
||||
itemPort.HostIP = itemStr[0][0:lastIndex]
|
||||
itemPort.HostPort = itemStr[0][lastIndex+1:]
|
||||
}
|
||||
itemContainer := strings.Split(itemStr[1], "/")
|
||||
if len(itemContainer) != 2 {
|
||||
continue
|
||||
}
|
||||
itemPort.ContainerPort = itemContainer[0]
|
||||
itemPort.Protocol = itemContainer[1]
|
||||
keyItem := fmt.Sprintf("%s->%s/%s", itemPort.HostPort, itemPort.ContainerPort, itemPort.Protocol)
|
||||
if val, ok := samePortMap[keyItem]; ok {
|
||||
val.HostIP = ""
|
||||
samePortMap[keyItem] = val
|
||||
} else {
|
||||
samePortMap[keyItem] = itemPort
|
||||
}
|
||||
}
|
||||
for _, val := range samePortMap {
|
||||
exposedPorts = append(exposedPorts, val)
|
||||
}
|
||||
return exposedPorts
|
||||
}
|
||||
|
||||
func transPortToStr(ports []types.Port) []string {
|
||||
var (
|
||||
ipv4Ports []types.Port
|
||||
ipv6Ports []types.Port
|
||||
@@ -1299,47 +1362,3 @@ func simplifyPort(ports []types.Port) []string {
|
||||
}
|
||||
return datas
|
||||
}
|
||||
|
||||
func loadContainerPortForInfo(id string, client *client.Client) ([]dto.PortHelper, error) {
|
||||
var exposedPorts []dto.PortHelper
|
||||
containers, err := client.ContainerList(context.Background(), container.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
samePortMap := make(map[string]dto.PortHelper)
|
||||
for _, container := range containers {
|
||||
if container.ID == id {
|
||||
ports := loadContainerPort(container.Ports)
|
||||
var itemPort dto.PortHelper
|
||||
for _, item := range ports {
|
||||
itemStr := strings.Split(item, "->")
|
||||
if len(itemStr) < 2 {
|
||||
continue
|
||||
}
|
||||
lastIndex := strings.LastIndex(itemStr[0], ":")
|
||||
if lastIndex == -1 {
|
||||
continue
|
||||
}
|
||||
itemContainer := strings.Split(itemStr[1], "/")
|
||||
if len(itemContainer) != 2 {
|
||||
continue
|
||||
}
|
||||
itemPort.HostIP = itemStr[0][0:lastIndex]
|
||||
itemPort.HostPort = itemStr[0][lastIndex+1:]
|
||||
itemPort.ContainerPort = itemContainer[0]
|
||||
itemPort.Protocol = itemContainer[1]
|
||||
keyItem := fmt.Sprintf("%s->%s/%s", itemPort.HostPort, itemPort.ContainerPort, itemPort.Protocol)
|
||||
if val, ok := samePortMap[keyItem]; ok {
|
||||
val.HostIP = ""
|
||||
samePortMap[keyItem] = val
|
||||
} else {
|
||||
samePortMap[keyItem] = itemPort
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, val := range samePortMap {
|
||||
exposedPorts = append(exposedPorts, val)
|
||||
}
|
||||
return exposedPorts, nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -31,6 +32,12 @@ const composeConfigLabel = "com.docker.compose.project.config_files"
|
||||
const composeWorkdirLabel = "com.docker.compose.project.working_dir"
|
||||
const composeCreatedBy = "createdBy"
|
||||
|
||||
type DockerCompose struct {
|
||||
Version string `yaml:"version"`
|
||||
Services map[string]map[string]interface{} `yaml:"services"`
|
||||
Networks map[string]interface{} `yaml:"networks"`
|
||||
}
|
||||
|
||||
func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface{}, error) {
|
||||
var (
|
||||
records []dto.ComposeInfo
|
||||
@@ -52,6 +59,20 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
}
|
||||
|
||||
composeCreatedByLocal, _ := composeRepo.ListRecord()
|
||||
|
||||
composeLocalMap := make(map[string]dto.ComposeInfo)
|
||||
for _, localItem := range composeCreatedByLocal {
|
||||
composeItemLocal := dto.ComposeInfo{
|
||||
ContainerNumber: 0,
|
||||
CreatedAt: localItem.CreatedAt.Format(constant.DateTimeLayout),
|
||||
ConfigFile: localItem.Path,
|
||||
Workdir: strings.TrimSuffix(localItem.Path, "/docker-compose.yml"),
|
||||
}
|
||||
composeItemLocal.CreatedBy = "1Panel"
|
||||
composeItemLocal.Path = localItem.Path
|
||||
composeLocalMap[localItem.Name] = composeItemLocal
|
||||
}
|
||||
|
||||
composeMap := make(map[string]dto.ComposeInfo)
|
||||
for _, container := range list {
|
||||
if name, ok := container.Labels[composeProjectLabel]; ok {
|
||||
@@ -95,12 +116,24 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, item := range composeCreatedByLocal {
|
||||
if err := composeRepo.DeleteRecord(commonRepo.WithByID(item.ID)); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
mergedMap := make(map[string]dto.ComposeInfo)
|
||||
for key, localItem := range composeLocalMap {
|
||||
mergedMap[key] = localItem
|
||||
}
|
||||
for key, item := range composeMap {
|
||||
if existingItem, exists := mergedMap[key]; exists {
|
||||
if item.ContainerNumber > 0 {
|
||||
if existingItem.ContainerNumber <= 0 {
|
||||
mergedMap[key] = item
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mergedMap[key] = item
|
||||
}
|
||||
}
|
||||
for key, value := range composeMap {
|
||||
|
||||
for key, value := range mergedMap {
|
||||
value.Name = key
|
||||
records = append(records, value)
|
||||
}
|
||||
@@ -127,7 +160,26 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
}
|
||||
BackDatas = records[start:end]
|
||||
}
|
||||
return int64(total), BackDatas, nil
|
||||
listItem := loadEnv(BackDatas)
|
||||
return int64(total), listItem, nil
|
||||
}
|
||||
|
||||
func loadEnv(list []dto.ComposeInfo) []dto.ComposeInfo {
|
||||
for i := 0; i < len(list); i++ {
|
||||
envFilePath := filepath.Join(path.Dir(list[i].Path), "1panel.env")
|
||||
file, err := os.ReadFile(envFilePath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
lines := strings.Split(string(file), "\n")
|
||||
for _, line := range lines {
|
||||
lineItem := strings.TrimSpace(line)
|
||||
if len(lineItem) != 0 && !strings.HasPrefix(lineItem, "#") {
|
||||
list[i].Env = append(list[i].Env, lineItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
|
||||
@@ -141,6 +193,9 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
|
||||
if err := u.loadPath(&req); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := newComposeEnv(req.Path, req.Env); err != nil {
|
||||
return false, err
|
||||
}
|
||||
cmd := exec.Command("docker-compose", "-f", req.Path, "config")
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
@@ -173,6 +228,9 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := newComposeEnv(req.Path, req.Env); err != nil {
|
||||
return "", err
|
||||
}
|
||||
go func() {
|
||||
defer file.Close()
|
||||
cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d")
|
||||
@@ -186,7 +244,7 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
|
||||
return
|
||||
}
|
||||
global.LOG.Infof("docker-compose up %s successful!", req.Name)
|
||||
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name})
|
||||
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name, Path: req.Path})
|
||||
_, _ = file.WriteString("docker-compose up successful!")
|
||||
}()
|
||||
|
||||
@@ -200,17 +258,26 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
||||
if _, err := os.Stat(req.Path); err != nil {
|
||||
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
|
||||
}
|
||||
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
|
||||
if req.Operation == "down" {
|
||||
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
|
||||
if req.Operation == "delete" {
|
||||
if stdout, err := compose.Operate(req.Path, "down"); err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
if req.WithFile {
|
||||
_ = os.RemoveAll(path.Dir(req.Path))
|
||||
}
|
||||
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
if req.Operation == "up" {
|
||||
if stdout, err := compose.Up(req.Path); err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
} else {
|
||||
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
}
|
||||
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -232,6 +299,10 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
|
||||
write.Flush()
|
||||
|
||||
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.Path)
|
||||
if err := newComposeEnv(req.Path, req.Env); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if stdout, err := compose.Up(req.Path); err != nil {
|
||||
if err := recreateCompose(string(oldFile), req.Path); err != nil {
|
||||
return fmt.Errorf("update failed when handle compose up, err: %s, recreate failed: %v", string(stdout), err)
|
||||
@@ -280,3 +351,26 @@ func recreateCompose(content, path string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newComposeEnv(pathItem string, env []string) error {
|
||||
if len(env) == 0 {
|
||||
return nil
|
||||
}
|
||||
envFilePath := path.Join(path.Dir(pathItem), "1panel.env")
|
||||
|
||||
file, err := os.OpenFile(envFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("failed to create env file: %v", err)
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
for _, env := range env {
|
||||
envItem := strings.TrimSpace(env)
|
||||
if _, err := file.WriteString(fmt.Sprintf("%s\n", envItem)); err != nil {
|
||||
global.LOG.Errorf("failed to write env to file: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
global.LOG.Infof("1panel.env file successfully created or updated with env variables in %s", envFilePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@ package service
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
network "net"
|
||||
"os"
|
||||
"sort"
|
||||
@@ -11,7 +15,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/copier"
|
||||
@@ -19,9 +22,6 @@ import (
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
)
|
||||
|
||||
type DashboardService struct{}
|
||||
@@ -29,7 +29,7 @@ type DashboardService struct{}
|
||||
type IDashboardService interface {
|
||||
LoadOsInfo() (*dto.OsInfo, error)
|
||||
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error)
|
||||
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
|
||||
LoadCurrentInfo(req dto.DashboardReq) *dto.DashboardCurrent
|
||||
|
||||
Restart(operation string) error
|
||||
}
|
||||
@@ -139,78 +139,88 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
|
||||
baseInfo.CPUCores, _ = cpu.Counts(false)
|
||||
baseInfo.CPULogicalCores, _ = cpu.Counts(true)
|
||||
|
||||
baseInfo.CurrentInfo = *u.LoadCurrentInfo(ioOption, netOption)
|
||||
baseInfo.CurrentInfo = *u.LoadCurrentInfo(dto.DashboardReq{
|
||||
Scope: "ioNet",
|
||||
IoOption: ioOption,
|
||||
NetOption: netOption,
|
||||
})
|
||||
return &baseInfo, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent {
|
||||
func (u *DashboardService) LoadCurrentInfo(req dto.DashboardReq) *dto.DashboardCurrent {
|
||||
var currentInfo dto.DashboardCurrent
|
||||
if req.Scope == "gpu" {
|
||||
currentInfo.GPUData = loadGPUInfo()
|
||||
currentInfo.XPUData = loadXpuInfo()
|
||||
}
|
||||
|
||||
hostInfo, _ := host.Info()
|
||||
currentInfo.Uptime = hostInfo.Uptime
|
||||
currentInfo.TimeSinceUptime = time.Now().Add(-time.Duration(hostInfo.Uptime) * time.Second).Format(constant.DateTimeLayout)
|
||||
currentInfo.Procs = hostInfo.Procs
|
||||
|
||||
currentInfo.CPUTotal, _ = cpu.Counts(true)
|
||||
totalPercent, _ := cpu.Percent(0, false)
|
||||
if len(totalPercent) == 1 {
|
||||
currentInfo.CPUUsedPercent = totalPercent[0]
|
||||
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
|
||||
}
|
||||
currentInfo.CPUPercent, _ = cpu.Percent(0, true)
|
||||
|
||||
loadInfo, _ := load.Avg()
|
||||
currentInfo.Load1 = loadInfo.Load1
|
||||
currentInfo.Load5 = loadInfo.Load5
|
||||
currentInfo.Load15 = loadInfo.Load15
|
||||
currentInfo.LoadUsagePercent = loadInfo.Load1 / (float64(currentInfo.CPUTotal*2) * 0.75) * 100
|
||||
|
||||
memoryInfo, _ := mem.VirtualMemory()
|
||||
currentInfo.MemoryTotal = memoryInfo.Total
|
||||
currentInfo.MemoryAvailable = memoryInfo.Available
|
||||
currentInfo.MemoryUsed = memoryInfo.Used
|
||||
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
|
||||
|
||||
swapInfo, _ := mem.SwapMemory()
|
||||
currentInfo.SwapMemoryTotal = swapInfo.Total
|
||||
currentInfo.SwapMemoryAvailable = swapInfo.Free
|
||||
currentInfo.SwapMemoryUsed = swapInfo.Used
|
||||
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
|
||||
|
||||
currentInfo.DiskData = loadDiskInfo()
|
||||
currentInfo.GPUData = loadGPUInfo()
|
||||
|
||||
if ioOption == "all" {
|
||||
diskInfo, _ := disk.IOCounters()
|
||||
for _, state := range diskInfo {
|
||||
currentInfo.IOReadBytes += state.ReadBytes
|
||||
currentInfo.IOWriteBytes += state.WriteBytes
|
||||
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
|
||||
currentInfo.IOReadTime += state.ReadTime
|
||||
currentInfo.IOWriteTime += state.WriteTime
|
||||
}
|
||||
} else {
|
||||
diskInfo, _ := disk.IOCounters(ioOption)
|
||||
for _, state := range diskInfo {
|
||||
currentInfo.IOReadBytes += state.ReadBytes
|
||||
currentInfo.IOWriteBytes += state.WriteBytes
|
||||
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
|
||||
currentInfo.IOReadTime += state.ReadTime
|
||||
currentInfo.IOWriteTime += state.WriteTime
|
||||
if req.Scope == "basic" {
|
||||
currentInfo.TimeSinceUptime = time.Now().Add(-time.Duration(hostInfo.Uptime) * time.Second).Format(constant.DateTimeLayout)
|
||||
currentInfo.Procs = hostInfo.Procs
|
||||
currentInfo.CPUTotal, _ = cpu.Counts(true)
|
||||
totalPercent, _ := cpu.Percent(100*time.Millisecond, false)
|
||||
if len(totalPercent) == 1 {
|
||||
currentInfo.CPUUsedPercent = totalPercent[0]
|
||||
currentInfo.CPUUsed = currentInfo.CPUUsedPercent * 0.01 * float64(currentInfo.CPUTotal)
|
||||
}
|
||||
currentInfo.CPUPercent, _ = cpu.Percent(100*time.Millisecond, true)
|
||||
|
||||
loadInfo, _ := load.Avg()
|
||||
currentInfo.Load1 = loadInfo.Load1
|
||||
currentInfo.Load5 = loadInfo.Load5
|
||||
currentInfo.Load15 = loadInfo.Load15
|
||||
currentInfo.LoadUsagePercent = loadInfo.Load1 / (float64(currentInfo.CPUTotal*2) * 0.75) * 100
|
||||
|
||||
memoryInfo, _ := mem.VirtualMemory()
|
||||
currentInfo.MemoryTotal = memoryInfo.Total
|
||||
currentInfo.MemoryAvailable = memoryInfo.Available
|
||||
currentInfo.MemoryUsed = memoryInfo.Used
|
||||
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
|
||||
|
||||
swapInfo, _ := mem.SwapMemory()
|
||||
currentInfo.SwapMemoryTotal = swapInfo.Total
|
||||
currentInfo.SwapMemoryAvailable = swapInfo.Free
|
||||
currentInfo.SwapMemoryUsed = swapInfo.Used
|
||||
currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent
|
||||
currentInfo.DiskData = loadDiskInfo()
|
||||
}
|
||||
|
||||
if netOption == "all" {
|
||||
netInfo, _ := net.IOCounters(false)
|
||||
if len(netInfo) != 0 {
|
||||
currentInfo.NetBytesSent = netInfo[0].BytesSent
|
||||
currentInfo.NetBytesRecv = netInfo[0].BytesRecv
|
||||
if req.Scope == "ioNet" {
|
||||
if req.IoOption == "all" {
|
||||
diskInfo, _ := disk.IOCounters()
|
||||
for _, state := range diskInfo {
|
||||
currentInfo.IOReadBytes += state.ReadBytes
|
||||
currentInfo.IOWriteBytes += state.WriteBytes
|
||||
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
|
||||
currentInfo.IOReadTime += state.ReadTime
|
||||
currentInfo.IOWriteTime += state.WriteTime
|
||||
}
|
||||
} else {
|
||||
diskInfo, _ := disk.IOCounters(req.IoOption)
|
||||
for _, state := range diskInfo {
|
||||
currentInfo.IOReadBytes += state.ReadBytes
|
||||
currentInfo.IOWriteBytes += state.WriteBytes
|
||||
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
|
||||
currentInfo.IOReadTime += state.ReadTime
|
||||
currentInfo.IOWriteTime += state.WriteTime
|
||||
}
|
||||
}
|
||||
} else {
|
||||
netInfo, _ := net.IOCounters(true)
|
||||
for _, state := range netInfo {
|
||||
if state.Name == netOption {
|
||||
currentInfo.NetBytesSent = state.BytesSent
|
||||
currentInfo.NetBytesRecv = state.BytesRecv
|
||||
|
||||
if req.NetOption == "all" {
|
||||
netInfo, _ := net.IOCounters(false)
|
||||
if len(netInfo) != 0 {
|
||||
currentInfo.NetBytesSent = netInfo[0].BytesSent
|
||||
currentInfo.NetBytesRecv = netInfo[0].BytesRecv
|
||||
}
|
||||
} else {
|
||||
netInfo, _ := net.IOCounters(true)
|
||||
for _, state := range netInfo {
|
||||
if state.Name == req.NetOption {
|
||||
currentInfo.NetBytesSent = state.BytesSent
|
||||
currentInfo.NetBytesRecv = state.BytesRecv
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -346,3 +356,19 @@ func loadGPUInfo() []dto.GPUInfo {
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -336,8 +338,15 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
|
||||
|
||||
func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
|
||||
service := "docker"
|
||||
sudo := cmd.SudoHandleCmd()
|
||||
if req.Operation == "stop" {
|
||||
service = "docker.socket"
|
||||
isSocketActive, _ := systemctl.IsActive("docker.socket")
|
||||
if isSocketActive {
|
||||
std, err := cmd.Execf("%s systemctl stop docker.socket", sudo)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle systemctl stop docker.socket failed, err: %v", std)
|
||||
}
|
||||
}
|
||||
}
|
||||
stdout, err := cmd.Execf("systemctl %s %s ", req.Operation, service)
|
||||
if err != nil {
|
||||
|
||||
@@ -62,9 +62,13 @@ func NewIFileService() IFileService {
|
||||
|
||||
func (f *FileService) GetFileList(op request.FileOption) (response.FileInfo, error) {
|
||||
var fileInfo response.FileInfo
|
||||
if _, err := os.Stat(op.Path); err != nil && os.IsNotExist(err) {
|
||||
data, err := os.Stat(op.Path)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return fileInfo, nil
|
||||
}
|
||||
if !data.IsDir() {
|
||||
op.FileOption.Path = filepath.Dir(op.FileOption.Path)
|
||||
}
|
||||
info, err := files.NewFileInfo(op.FileOption)
|
||||
if err != nil {
|
||||
return fileInfo, err
|
||||
|
||||
@@ -201,16 +201,19 @@ func StartMonitor(removeBefore bool, interval string) error {
|
||||
service := NewIMonitorService()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
monitorCancel = cancel
|
||||
monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %sm", interval), service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
nextMinute := now.Truncate(time.Minute).Add(time.Minute)
|
||||
time.AfterFunc(time.Until(nextMinute), func() {
|
||||
monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %sm", interval), service)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
global.MonitorCronID = monitorID
|
||||
})
|
||||
service.Run()
|
||||
|
||||
go service.saveIODataToDB(ctx, float64(intervalItem))
|
||||
go service.saveNetDataToDB(ctx, float64(intervalItem))
|
||||
|
||||
global.MonitorCronID = monitorID
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -124,6 +124,16 @@ func (u *SSHService) OperateSSH(operation string) error {
|
||||
if operation == "enable" || operation == "disable" {
|
||||
serviceName += ".service"
|
||||
}
|
||||
if operation == "stop" {
|
||||
isSocketActive, _ := systemctl.IsActive(serviceName + ".socket")
|
||||
if isSocketActive {
|
||||
std, err := cmd.Execf("%s systemctl stop %s", sudo, serviceName+".socket")
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle systemctl stop %s.socket failed, err: %v", serviceName, std)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stdout, err := cmd.Execf("%s systemctl %s %s", sudo, operation, serviceName)
|
||||
if err != nil {
|
||||
if strings.Contains(stdout, "alias name or linked unit file") {
|
||||
|
||||
@@ -641,8 +641,10 @@ func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error {
|
||||
wafSite := wafWebsite
|
||||
oldDomains := wafSite.Domains
|
||||
var newDomains []string
|
||||
removed := false
|
||||
for _, domain := range oldDomains {
|
||||
if domain == webSiteDomain.Domain {
|
||||
if domain == webSiteDomain.Domain && !removed {
|
||||
removed = true
|
||||
continue
|
||||
}
|
||||
newDomains = append(newDomains, domain)
|
||||
|
||||
@@ -432,14 +432,14 @@ func (w WebsiteCAService) DownloadFile(id uint) (*os.File, error) {
|
||||
if err = fileOp.CreateDir(dir, 0666); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = fileOp.WriteFile(path.Join(dir, "ca.csr"), strings.NewReader(ca.CSR), 0644); err != nil {
|
||||
if err = fileOp.WriteFile(path.Join(dir, "ca.crt"), strings.NewReader(ca.CSR), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = fileOp.WriteFile(path.Join(dir, "private.key"), strings.NewReader(ca.PrivateKey), 0644); err != nil {
|
||||
if err = fileOp.WriteFile(path.Join(dir, "ca.key"), strings.NewReader(ca.PrivateKey), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileName := ca.Name + ".zip"
|
||||
if err = fileOp.Compress([]string{path.Join(dir, "ca.csr"), path.Join(dir, "private.key")}, dir, fileName, files.SdkZip, ""); err != nil {
|
||||
if err = fileOp.Compress([]string{path.Join(dir, "ca.crt"), path.Join(dir, "ca.key")}, dir, fileName, files.SdkZip, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.Open(path.Join(dir, fileName))
|
||||
|
||||
Vendored
+3
-1
@@ -1,6 +1,7 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
@@ -10,7 +11,8 @@ import (
|
||||
|
||||
func Init() {
|
||||
c := global.CONF.System.Cache
|
||||
|
||||
_ = os.RemoveAll(c)
|
||||
_ = os.Mkdir(c, 0755)
|
||||
options := badger.Options{
|
||||
Dir: c,
|
||||
ValueDir: c,
|
||||
|
||||
@@ -94,6 +94,7 @@ func Init() {
|
||||
migrations.AddClam,
|
||||
migrations.AddClamStatus,
|
||||
migrations.AddAlertMenu,
|
||||
migrations.AddComposeColumn,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -314,3 +314,13 @@ var AddAlertMenu = &gormigrate.Migration{
|
||||
return tx.Model(&model.Setting{}).Where("key", "XpackHideMenu").Updates(map[string]interface{}{"value": string(data)}).Error
|
||||
},
|
||||
}
|
||||
|
||||
var AddComposeColumn = &gormigrate.Migration{
|
||||
ID: "20240906-add-compose-command",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.Compose{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ var whiteUrlList = map[string]struct{}{
|
||||
"/api/v1/logs/operation": {},
|
||||
"/api/v1/logs/login": {},
|
||||
"/api/v1/auth/logout": {},
|
||||
"/api/v1/dashboard/current": {},
|
||||
|
||||
"/api/v1/apps/installed/loadport": {},
|
||||
"/api/v1/apps/installed/check": {},
|
||||
|
||||
@@ -18,7 +18,7 @@ func (s *DashboardRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
{
|
||||
cmdRouter.GET("/base/os", baseApi.LoadDashboardOsInfo)
|
||||
cmdRouter.GET("/base/:ioOption/:netOption", baseApi.LoadDashboardBaseInfo)
|
||||
cmdRouter.GET("/current/:ioOption/:netOption", baseApi.LoadDashboardCurrentInfo)
|
||||
cmdRouter.POST("/current", baseApi.LoadDashboardCurrentInfo)
|
||||
cmdRouter.POST("/system/restart/:operation", baseApi.SystemRestart)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,16 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
|
||||
if len(scType) == 0 {
|
||||
scType = "Standard"
|
||||
}
|
||||
mode := loadParamFromVars("mode", vars)
|
||||
if len(mode) == 0 {
|
||||
mode = "virtual hosted"
|
||||
}
|
||||
sess, err := session.NewSession(&aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
|
||||
Endpoint: aws.String(endpoint),
|
||||
Region: aws.String(region),
|
||||
DisableSSL: aws.Bool(true),
|
||||
S3ForcePathStyle: aws.Bool(false),
|
||||
S3ForcePathStyle: aws.Bool(mode == "path"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -613,6 +613,9 @@ func (f FileOp) decompressWithSDK(srcFile string, dst string, cType CompressType
|
||||
func (f FileOp) Decompress(srcFile string, dst string, cType CompressType, secret string) error {
|
||||
if cType == Tar || cType == Zip || cType == TarGz {
|
||||
shellArchiver, err := NewShellArchiver(cType)
|
||||
if !f.Stat(dst) {
|
||||
_ = f.CreateDir(dst, 0755)
|
||||
}
|
||||
if err == nil {
|
||||
if err = shellArchiver.Extract(srcFile, dst, secret); err == nil {
|
||||
return nil
|
||||
|
||||
@@ -72,6 +72,9 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
|
||||
|
||||
info, err := appFs.Stat(op.Path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, buserr.New(constant.ErrLinkPathNotFound)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -101,7 +104,26 @@ func NewFileInfo(op FileOption) (*FileInfo, error) {
|
||||
}
|
||||
|
||||
if file.IsSymlink {
|
||||
file.LinkPath = GetSymlink(op.Path)
|
||||
linkPath := GetSymlink(op.Path)
|
||||
if !filepath.IsAbs(linkPath) {
|
||||
dir := filepath.Dir(op.Path)
|
||||
var err error
|
||||
linkPath, err = filepath.Abs(filepath.Join(dir, linkPath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
file.LinkPath = linkPath
|
||||
targetInfo, err := appFs.Stat(linkPath)
|
||||
if err != nil {
|
||||
file.IsDir = false
|
||||
file.Mode = "-"
|
||||
file.User = "-"
|
||||
file.Group = "-"
|
||||
} else {
|
||||
file.IsDir = targetInfo.IsDir()
|
||||
}
|
||||
file.Extension = filepath.Ext(file.LinkPath)
|
||||
}
|
||||
if op.Expand {
|
||||
if err := handleExpansion(file, op); err != nil {
|
||||
@@ -308,7 +330,26 @@ func (f *FileInfo) processFiles(files []FileSearchInfo, option FileOption) ([]*F
|
||||
file.FavoriteID = favorite.ID
|
||||
}
|
||||
if isSymlink {
|
||||
file.LinkPath = GetSymlink(fPath)
|
||||
linkPath := GetSymlink(fPath)
|
||||
if !filepath.IsAbs(linkPath) {
|
||||
dir := filepath.Dir(fPath)
|
||||
var err error
|
||||
linkPath, err = filepath.Abs(filepath.Join(dir, linkPath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
file.LinkPath = linkPath
|
||||
targetInfo, err := file.Fs.Stat(linkPath)
|
||||
if err != nil {
|
||||
file.IsDir = false
|
||||
file.Mode = "-"
|
||||
file.User = "-"
|
||||
file.Group = "-"
|
||||
} else {
|
||||
file.IsDir = targetInfo.IsDir()
|
||||
}
|
||||
file.Extension = filepath.Ext(file.LinkPath)
|
||||
}
|
||||
if df.Size() > 0 {
|
||||
file.MimeType = GetMimeType(fPath)
|
||||
|
||||
@@ -18,7 +18,7 @@ func NewTarArchiver(compressType CompressType) ShellArchiver {
|
||||
}
|
||||
|
||||
func (t TarArchiver) Extract(FilePath string, dstDir string, secret string) error {
|
||||
return cmd.ExecCmd(fmt.Sprintf("%s %s %s -C %s", t.Cmd, t.getOptionStr("extract"), FilePath, dstDir))
|
||||
return cmd.ExecCmd(fmt.Sprintf("%s %s \"%s\" -C \"%s\"", t.Cmd, t.getOptionStr("extract"), FilePath, dstDir))
|
||||
}
|
||||
|
||||
func (t TarArchiver) Compress(sourcePaths []string, dstFile string, secret string) error {
|
||||
|
||||
@@ -20,11 +20,11 @@ func (t TarGzArchiver) Extract(filePath, dstDir string, secret string) error {
|
||||
var err error
|
||||
commands := ""
|
||||
if len(secret) != 0 {
|
||||
extraCmd := "openssl enc -d -aes-256-cbc -k '" + secret + "' -in " + filePath + " | "
|
||||
commands = fmt.Sprintf("%s tar -zxvf - -C %s", extraCmd, dstDir+" > /dev/null 2>&1")
|
||||
extraCmd := fmt.Sprintf("openssl enc -d -aes-256-cbc -k '%s' -in '%s' | ", secret, filePath)
|
||||
commands = fmt.Sprintf("%s tar -zxvf - -C '%s' > /dev/null 2>&1", extraCmd, dstDir)
|
||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||
} else {
|
||||
commands = fmt.Sprintf("tar -zxvf %s %s", filePath+" -C ", dstDir+" > /dev/null 2>&1")
|
||||
commands = fmt.Sprintf("tar -zxvf '%s' -C '%s' > /dev/null 2>&1", filePath, dstDir)
|
||||
global.LOG.Debug(commands)
|
||||
}
|
||||
if err = cmd.ExecCmd(commands); err != nil {
|
||||
@@ -34,27 +34,25 @@ func (t TarGzArchiver) Extract(filePath, dstDir string, secret string) error {
|
||||
}
|
||||
|
||||
func (t TarGzArchiver) Compress(sourcePaths []string, dstFile string, secret string) error {
|
||||
var err error
|
||||
path := ""
|
||||
itemDir := ""
|
||||
var itemDirs []string
|
||||
for _, item := range sourcePaths {
|
||||
itemDir += filepath.Base(item) + " "
|
||||
itemDirs = append(itemDirs, fmt.Sprintf("\"%s\"", filepath.Base(item)))
|
||||
}
|
||||
aheadDir := dstFile[:strings.LastIndex(dstFile, "/")]
|
||||
itemDir := strings.Join(itemDirs, " ")
|
||||
aheadDir := filepath.Dir(sourcePaths[0])
|
||||
if len(aheadDir) == 0 {
|
||||
aheadDir = "/"
|
||||
}
|
||||
path += fmt.Sprintf("- -C %s %s", aheadDir, itemDir)
|
||||
commands := ""
|
||||
if len(secret) != 0 {
|
||||
extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out"
|
||||
commands = fmt.Sprintf("tar -zcf %s %s %s", path, extraCmd, dstFile)
|
||||
extraCmd := fmt.Sprintf("| openssl enc -aes-256-cbc -salt -k '%s' -out '%s'", secret, dstFile)
|
||||
commands = fmt.Sprintf("tar -zcf - -C \"%s\" %s %s", aheadDir, itemDir, extraCmd)
|
||||
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
|
||||
} else {
|
||||
commands = fmt.Sprintf("tar -zcf %s -C %s %s", dstFile, aheadDir, itemDir)
|
||||
commands = fmt.Sprintf("tar -zcf \"%s\" -C \"%s\" %s", dstFile, aheadDir, itemDir)
|
||||
global.LOG.Debug(commands)
|
||||
}
|
||||
if err = cmd.ExecCmd(commands); err != nil {
|
||||
if err := cmd.ExecCmd(commands); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -32,6 +32,10 @@ func LoadGpuInfo() []interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadXpuInfo() []interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartClam(startClam model.Clam, isUpdate bool) (int, error) {
|
||||
return 0, buserr.New(constant.ErrXpackNotFound)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ location ^~ /test {
|
||||
proxy_set_header Connection $http_connection;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
add_header Cache-Control no-cache;
|
||||
proxy_ssl_server_name off;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 6.1 KiB |
@@ -41,7 +41,7 @@
|
||||
"highlight.js": "^11.9.0",
|
||||
"js-base64": "^3.7.7",
|
||||
"md-editor-v3": "^2.11.3",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^1.6.1",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 6.1 KiB |
@@ -6,6 +6,7 @@ export namespace Backup {
|
||||
type: string;
|
||||
accessKey: string;
|
||||
bucket: string;
|
||||
bucketInput: boolean;
|
||||
credential: string;
|
||||
backupPath: string;
|
||||
vars: string;
|
||||
|
||||
@@ -241,6 +241,8 @@ export namespace Container {
|
||||
path: string;
|
||||
containers: Array<ComposeContainer>;
|
||||
expand: boolean;
|
||||
envStr: string;
|
||||
env: Array<string>;
|
||||
}
|
||||
export interface ComposeContainer {
|
||||
name: string;
|
||||
@@ -254,6 +256,8 @@ export namespace Container {
|
||||
file: string;
|
||||
path: string;
|
||||
template: number;
|
||||
env: Array<string>;
|
||||
envStr: string;
|
||||
}
|
||||
export interface ComposeOperation {
|
||||
name: string;
|
||||
@@ -265,6 +269,8 @@ export namespace Container {
|
||||
name: string;
|
||||
path: string;
|
||||
content: string;
|
||||
env: Array<string>;
|
||||
createdBy: string;
|
||||
}
|
||||
|
||||
export interface TemplateCreate {
|
||||
|
||||
@@ -64,6 +64,7 @@ export namespace Dashboard {
|
||||
diskData: Array<DiskInfo>;
|
||||
|
||||
gpuData: Array<GPUInfo>;
|
||||
xpuData: Array<XPUInfo>;
|
||||
|
||||
netBytesSent: number;
|
||||
netBytesRecv: number;
|
||||
@@ -94,4 +95,20 @@ export namespace Dashboard {
|
||||
memoryUsage: string;
|
||||
fanSpeed: string;
|
||||
}
|
||||
|
||||
export interface XPUInfo {
|
||||
deviceID: number;
|
||||
deviceName: string;
|
||||
memory: string;
|
||||
temperature: string;
|
||||
memoryUsed: string;
|
||||
power: string;
|
||||
memoryUtil: string;
|
||||
}
|
||||
|
||||
export interface DashboardReq {
|
||||
scope: string;
|
||||
ioOption: string;
|
||||
netOption: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export namespace File {
|
||||
size: number;
|
||||
isDir: boolean;
|
||||
isSymlink: boolean;
|
||||
linkPath: boolean;
|
||||
linkPath: string;
|
||||
type: string;
|
||||
updateTime: string;
|
||||
modTime: string;
|
||||
|
||||
@@ -174,6 +174,7 @@ export namespace Setting {
|
||||
}
|
||||
export interface LicenseStatus {
|
||||
productPro: string;
|
||||
trial: boolean;
|
||||
status: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ export const loadBaseInfo = (ioOption: string, netOption: string) => {
|
||||
return http.get<Dashboard.BaseInfo>(`/dashboard/base/${ioOption}/${netOption}`);
|
||||
};
|
||||
|
||||
export const loadCurrentInfo = (ioOption: string, netOption: string) => {
|
||||
return http.get<Dashboard.CurrentInfo>(`/dashboard/current/${ioOption}/${netOption}`);
|
||||
export const loadCurrentInfo = (req: Dashboard.DashboardReq) => {
|
||||
return http.post<Dashboard.CurrentInfo>(`/dashboard/current`, req);
|
||||
};
|
||||
|
||||
export const systemRestart = (operation: string) => {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 6.1 KiB |
@@ -2,75 +2,68 @@
|
||||
<div>
|
||||
<div class="app-status" v-if="data.isExist">
|
||||
<el-card>
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<div class="flex w-full flex-col gap-4 md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<el-tag effect="dark" type="success">{{ data.app }}</el-tag>
|
||||
<Status :key="refresh" :status="data.status"></Status>
|
||||
<el-tag>{{ $t('app.version') }}:{{ data.version }}</el-tag>
|
||||
</div>
|
||||
<div>
|
||||
<Status class="status-content" :key="refresh" :status="data.status"></Status>
|
||||
</div>
|
||||
<div>
|
||||
<el-tag class="status-content">{{ $t('app.version') }}:{{ data.version }}</el-tag>
|
||||
</div>
|
||||
<div>
|
||||
<span class="buttons">
|
||||
<el-button
|
||||
type="primary"
|
||||
v-if="data.status != 'Running'"
|
||||
link
|
||||
@click="onOperate('start')"
|
||||
:disabled="data.status === 'Installing'"
|
||||
>
|
||||
{{ $t('app.start') }}
|
||||
</el-button>
|
||||
<el-button type="primary" v-if="data.status === 'Running'" link @click="onOperate('stop')">
|
||||
{{ $t('app.stop') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
:disabled="data.status === 'Installing'"
|
||||
@click="onOperate('restart')"
|
||||
>
|
||||
{{ $t('app.restart') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
v-if="data.app === 'OpenResty'"
|
||||
@click="onOperate('reload')"
|
||||
:disabled="data.status !== 'Running'"
|
||||
>
|
||||
{{ $t('app.reload') }}
|
||||
</el-button>
|
||||
<el-divider v-if="data.app === 'OpenResty'" direction="vertical" />
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="setting"
|
||||
link
|
||||
:disabled="
|
||||
data.status === 'Installing' ||
|
||||
(data.status !== 'Running' && data.app === 'OpenResty')
|
||||
"
|
||||
>
|
||||
{{ $t('commons.button.set') }}
|
||||
</el-button>
|
||||
<el-divider v-if="data.app === 'OpenResty'" direction="vertical" />
|
||||
<el-button
|
||||
v-if="data.app === 'OpenResty'"
|
||||
type="primary"
|
||||
@click="clear"
|
||||
link
|
||||
:disabled="
|
||||
data.status === 'Installing' ||
|
||||
(data.status !== 'Running' && data.app === 'OpenResty')
|
||||
"
|
||||
>
|
||||
{{ $t('nginx.clearProxyCache') }}
|
||||
</el-button>
|
||||
</span>
|
||||
|
||||
<div class="mt-0.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
v-if="data.status != 'Running'"
|
||||
link
|
||||
@click="onOperate('start')"
|
||||
:disabled="data.status === 'Installing'"
|
||||
>
|
||||
{{ $t('app.start') }}
|
||||
</el-button>
|
||||
<el-button type="primary" v-if="data.status === 'Running'" link @click="onOperate('stop')">
|
||||
{{ $t('app.stop') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
:disabled="data.status === 'Installing'"
|
||||
@click="onOperate('restart')"
|
||||
>
|
||||
{{ $t('app.restart') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
v-if="data.app === 'OpenResty'"
|
||||
@click="onOperate('reload')"
|
||||
:disabled="data.status !== 'Running'"
|
||||
>
|
||||
{{ $t('app.reload') }}
|
||||
</el-button>
|
||||
<el-divider v-if="data.app === 'OpenResty'" direction="vertical" />
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="setting"
|
||||
link
|
||||
:disabled="
|
||||
data.status === 'Installing' || (data.status !== 'Running' && data.app === 'OpenResty')
|
||||
"
|
||||
>
|
||||
{{ $t('commons.button.set') }}
|
||||
</el-button>
|
||||
<el-divider v-if="data.app === 'OpenResty'" direction="vertical" />
|
||||
<el-button
|
||||
v-if="data.app === 'OpenResty'"
|
||||
type="primary"
|
||||
@click="clear"
|
||||
link
|
||||
:disabled="
|
||||
data.status === 'Installing' || (data.status !== 'Running' && data.app === 'OpenResty')
|
||||
"
|
||||
>
|
||||
{{ $t('nginx.clearProxyCache') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="ml-5" v-if="key === 'openresty' && (httpPort != 80 || httpsPort != 443)">
|
||||
@@ -95,12 +88,12 @@
|
||||
<LayoutContent :title="getTitle(key)" :divider="true">
|
||||
<template #main>
|
||||
<div class="app-warn">
|
||||
<div class="flx-center">
|
||||
<span>{{ $t('app.checkInstalledWarn', [data.app]) }}</span>
|
||||
<span @click="goRouter(key)" class="flx-align-center">
|
||||
<el-icon class="ml-2"><Position /></el-icon>
|
||||
<div class="flex flex-col gap-4 items-cetner justify-center w-full sm:flex-row">
|
||||
<div>{{ $t('app.checkInstalledWarn', [data.app]) }}</div>
|
||||
<div @click="goRouter(key)" class="flex items-cetner justify-center text-[#74a4f3]">
|
||||
<el-icon class="flex items-center justify-center"><Position /></el-icon>
|
||||
{{ $t('database.goInstall') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<img src="@/assets/images/no_app.svg" />
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
:title="isBackup ? $t('commons.button.backup') : $t('commons.button.recover') + ' - ' + name"
|
||||
width="40%"
|
||||
:close-on-click-modal="false"
|
||||
:before-close="handleClose"
|
||||
:before-close="handleBackupClose"
|
||||
>
|
||||
<el-form ref="backupForm" label-position="left" v-loading="loading">
|
||||
<el-form-item
|
||||
@@ -90,7 +90,7 @@
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose" :disabled="loading">
|
||||
<el-button @click="handleBackupClose" :disabled="loading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="onSubmit" :disabled="loading">
|
||||
|
||||
@@ -105,6 +105,9 @@ const showBack = computed(() => {
|
||||
|
||||
.content-container__toolbar {
|
||||
margin-top: 20px;
|
||||
.el-button + .el-button {
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.content-container_form {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-loading="firstLoading">
|
||||
<div v-if="defaultButton">
|
||||
<el-checkbox border v-model="tailLog" class="float-left" @change="changeTail(false)">
|
||||
{{ $t('commons.button.watch') }}
|
||||
@@ -78,6 +78,7 @@ const minPage = ref(1);
|
||||
const maxPage = ref(1);
|
||||
const logs = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const firstLoading = ref(true);
|
||||
|
||||
const readReq = reactive({
|
||||
id: 0,
|
||||
@@ -129,6 +130,7 @@ const getContent = (pre: boolean) => {
|
||||
}
|
||||
isLoading.value = true;
|
||||
ReadByLine(readReq).then((res) => {
|
||||
firstLoading.value = false;
|
||||
if (!end.value && res.data.end) {
|
||||
lastLogs.value = [...logs.value];
|
||||
}
|
||||
@@ -193,7 +195,7 @@ const getContent = (pre: boolean) => {
|
||||
maxPage.value = res.data.total;
|
||||
minPage.value = res.data.total;
|
||||
}
|
||||
if (logs.value && logs.value.length > 3000) {
|
||||
if (logs.value && logs.value.length > 1000) {
|
||||
logs.value.splice(0, readReq.pageSize);
|
||||
if (minPage.value > 1) {
|
||||
minPage.value--;
|
||||
@@ -278,8 +280,6 @@ const init = () => {
|
||||
}
|
||||
readReq.latest = true;
|
||||
search();
|
||||
|
||||
nextTick(() => {});
|
||||
};
|
||||
|
||||
const clearLog = (): void => {
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
<template>
|
||||
<el-card class="router_card">
|
||||
<el-radio-group v-model="activeName" @change="handleChange">
|
||||
<el-radio-button
|
||||
class="router_card_button"
|
||||
:label="button.label"
|
||||
:value="button.label"
|
||||
v-for="(button, index) in buttonArray"
|
||||
size="large"
|
||||
:key="index"
|
||||
>
|
||||
<el-badge :value="button.count" v-if="button.count" is-dot>
|
||||
<span>{{ button.label }}</span>
|
||||
</el-badge>
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
<slot name="route-button"></slot>
|
||||
<div class="flex w-full flex-col md:justify-between md:flex-row">
|
||||
<el-radio-group v-model="activeName" @change="handleChange">
|
||||
<el-radio-button
|
||||
class="router_card_button"
|
||||
:label="button.label"
|
||||
:value="button.label"
|
||||
v-for="(button, index) in buttonArray"
|
||||
size="large"
|
||||
:key="index"
|
||||
>
|
||||
<el-badge :value="button.count" v-if="button.count" is-dot>
|
||||
<span>{{ button.label }}</span>
|
||||
</el-badge>
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
<div class="flex flex-row gap-2 md:flex-col lg:flex-row">
|
||||
<slot name="route-button"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,38 +1,47 @@
|
||||
<template>
|
||||
<div class="flx-center">
|
||||
<span v-if="props.footer">
|
||||
<el-button type="primary" link @click="toForum">
|
||||
<span class="font-normal">{{ $t('setting.forum') }}</span>
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" link @click="toDoc">
|
||||
<span class="font-normal">{{ $t('setting.doc2') }}</span>
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" link @click="toGithub">
|
||||
<span class="font-normal">{{ $t('setting.project') }}</span>
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
</span>
|
||||
<el-button type="primary" link @click="toHalo">
|
||||
<span class="font-normal">{{ isProductPro ? $t('license.pro') : $t('license.community') }}</span>
|
||||
</el-button>
|
||||
<span class="version" @click="copyText(version)">{{ version }}</span>
|
||||
<el-badge is-dot style="margin-top: -3px" v-if="version !== 'Waiting' && globalStore.hasNewVersion">
|
||||
<el-button type="primary" link @click="onLoadUpgradeInfo">
|
||||
<span class="font-normal">({{ $t('setting.hasNewVersion') }})</span>
|
||||
</el-button>
|
||||
</el-badge>
|
||||
<el-button
|
||||
v-if="version !== 'Waiting' && !globalStore.hasNewVersion"
|
||||
type="primary"
|
||||
link
|
||||
@click="onLoadUpgradeInfo"
|
||||
>
|
||||
<span>({{ $t('setting.upgradeCheck') }})</span>
|
||||
</el-button>
|
||||
<el-tag v-if="version === 'Waiting'" round style="margin-left: 10px">{{ $t('setting.upgrading') }}</el-tag>
|
||||
<div>
|
||||
<div class="flex w-full flex-col gap-2 md:flex-row">
|
||||
<div class="flex flex-wrap" v-if="props.footer">
|
||||
<el-button type="primary" link @click="toForum">
|
||||
<span class="font-normal">{{ $t('setting.forum') }}</span>
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" link @click="toDoc">
|
||||
<span class="font-normal">{{ $t('setting.doc2') }}</span>
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" link @click="toGithub">
|
||||
<span class="font-normal">{{ $t('setting.project') }}</span>
|
||||
</el-button>
|
||||
<el-divider v-if="!mobile" direction="vertical" />
|
||||
</div>
|
||||
<div class="flex flex-wrap">
|
||||
<el-button type="primary" link @click="toHalo">
|
||||
<span class="font-normal">
|
||||
{{ isProductPro ? $t('license.pro') : $t('license.community') }}
|
||||
</span>
|
||||
</el-button>
|
||||
<span class="version" @click="copyText(version)">{{ version }}</span>
|
||||
<el-badge is-dot style="margin-top: -3px" v-if="version !== 'Waiting' && globalStore.hasNewVersion">
|
||||
<el-button type="primary" link @click="onLoadUpgradeInfo">
|
||||
<span class="font-normal">({{ $t('setting.hasNewVersion') }})</span>
|
||||
</el-button>
|
||||
</el-badge>
|
||||
<el-button
|
||||
v-if="version !== 'Waiting' && !globalStore.hasNewVersion"
|
||||
type="primary"
|
||||
link
|
||||
@click="onLoadUpgradeInfo"
|
||||
>
|
||||
<span>({{ $t('setting.upgradeCheck') }})</span>
|
||||
</el-button>
|
||||
<el-tag v-if="version === 'Waiting'" round style="margin-left: 10px">
|
||||
{{ $t('setting.upgrading') }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-drawer
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
@@ -82,7 +91,7 @@ import i18n from '@/lang';
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { copyText } from '@/utils/util';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@@ -90,6 +99,10 @@ import { storeToRefs } from 'pinia';
|
||||
const globalStore = GlobalStore();
|
||||
const { isDarkTheme } = storeToRefs(globalStore);
|
||||
|
||||
const mobile = computed(() => {
|
||||
return globalStore.isMobile();
|
||||
});
|
||||
|
||||
const version = ref<string>('');
|
||||
const isProductPro = ref();
|
||||
const loading = ref(false);
|
||||
@@ -192,6 +205,7 @@ onMounted(() => {
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.5px;
|
||||
cursor: pointer;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.line-height {
|
||||
line-height: 25px;
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
:title="$t('commons.button.recover') + ' - ' + name"
|
||||
width="40%"
|
||||
:close-on-click-modal="false"
|
||||
:before-close="handleClose"
|
||||
:before-close="handleBackupClose"
|
||||
>
|
||||
<el-form ref="backupForm" label-position="left" v-loading="loading">
|
||||
<el-form-item
|
||||
@@ -110,7 +110,7 @@
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose" :disabled="loading">
|
||||
<el-button @click="handleBackupClose" :disabled="loading">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="onHandleRecover" :disabled="loading">
|
||||
|
||||
@@ -654,6 +654,8 @@ const message = {
|
||||
privileged: 'Privileged',
|
||||
privilegedHelper:
|
||||
'Allows the container to perform certain privileged operations on the host, which may increase container risks. Use with caution!',
|
||||
editComposeHelper:
|
||||
'Note: The environment variables set will be written to the 1panel.env file by default.\nIf you want to use these parameters in the container, you also need to manually add an env_file reference in the compose file.',
|
||||
|
||||
upgradeHelper: 'Repository Name/Image Name: Image Version',
|
||||
upgradeWarning2:
|
||||
@@ -768,8 +770,9 @@ const message = {
|
||||
composeHelper:
|
||||
'The composition created through 1Panel editor or template will be saved in the {0}/docker/compose directory.',
|
||||
deleteFile: 'Delete file',
|
||||
allDelete: 'Permanently Delete',
|
||||
deleteComposeHelper:
|
||||
'Delete all files in the {0} directory, including persistent files in this directory. Please proceed with caution!',
|
||||
'Delete all files related to container compose, including configuration files and persistent files. Please proceed with caution!',
|
||||
deleteCompose: '" Delete this composition.',
|
||||
apps: 'Apps',
|
||||
local: 'Local',
|
||||
@@ -781,11 +784,16 @@ const message = {
|
||||
content: 'Content',
|
||||
contentEmpty: 'Compose content cannot be empty, please enter and try again!',
|
||||
containerNumber: 'Container number',
|
||||
containerStatus: 'Container Status',
|
||||
exited: 'Exited',
|
||||
running: 'Running',
|
||||
down: 'Down',
|
||||
up: 'Up',
|
||||
composeDetailHelper:
|
||||
'The compose is created external to 1Panel. The start and stop operations are not supported.',
|
||||
composeOperatorHelper: '{1} operation will be performed on {0}. Do you want to continue?',
|
||||
composeDownHelper:
|
||||
'This will stop and remove all containers and networks under the {0} compose. Do you want to continue?',
|
||||
|
||||
setting: 'Setting',
|
||||
operatorStatusHelper: 'This action will {0} Docker service, do you want to continue?',
|
||||
@@ -1176,6 +1184,7 @@ const message = {
|
||||
info: 'Attribute',
|
||||
linkFile: 'Soft link',
|
||||
terminal: 'Terminal',
|
||||
batchoperation: 'batch operation',
|
||||
shareList: 'Share list',
|
||||
zip: 'Compressed',
|
||||
group: 'Group',
|
||||
@@ -1276,6 +1285,7 @@ const message = {
|
||||
noNameFolder: 'Untitled Folder',
|
||||
noNameFile: 'Untitled File',
|
||||
minimap: 'Code Mini Map',
|
||||
fileCanNotRead: 'File can not read',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: 'Auto Start',
|
||||
@@ -1408,6 +1418,7 @@ const message = {
|
||||
LOCAL: 'Server Disks',
|
||||
OSS: 'Ali OSS',
|
||||
S3: 'Amazon S3',
|
||||
mode: 'Mode',
|
||||
MINIO: 'MINIO',
|
||||
SFTP: 'SFTP',
|
||||
WebDAV: 'WebDAV',
|
||||
|
||||
@@ -629,6 +629,8 @@ const message = {
|
||||
emptyUser: '為空時,將使用容器默認的用戶登錄',
|
||||
privileged: '特權模式',
|
||||
privilegedHelper: '允許容器在主機上執行某些特權操作,可能會增加容器風險,請謹慎開啟!',
|
||||
editComposeHelper:
|
||||
'注意:設置的環境變數會默認寫入 1panel.env 文件。\n若需在容器中使用這些參數,還需在 compose 文件中手動添加 env_file 引用。',
|
||||
|
||||
upgradeHelper: '倉庫名稱/鏡像名稱:鏡像版本',
|
||||
upgradeWarning2: '升級操作需要重建容器,任何未持久化的數據將會丟失,是否繼續?',
|
||||
@@ -739,7 +741,8 @@ const message = {
|
||||
composePathHelper: '配置文件保存路徑: {0}',
|
||||
composeHelper: '通過 1Panel 編輯或者模版創建的編排,將保存在 {0}/docker/compose 路徑下',
|
||||
deleteFile: '刪除文件',
|
||||
deleteComposeHelper: '刪除 {0} 目录下所有文件,包括該文件下的持久化文件等,請謹慎操作!',
|
||||
allDelete: '徹底刪除',
|
||||
deleteComposeHelper: '刪除容器編排的所有文件,包括配置文件和持久化文件,請謹慎操作!',
|
||||
deleteCompose: '" 刪除此編排',
|
||||
apps: '應用商店',
|
||||
local: '本地',
|
||||
@@ -751,10 +754,14 @@ const message = {
|
||||
content: '內容',
|
||||
contentEmpty: '編排內容不能為空,請輸入後重試!',
|
||||
containerNumber: '容器數量',
|
||||
containerStatus: '容器狀態',
|
||||
exited: '已停止',
|
||||
running: '運行中',
|
||||
down: '刪除',
|
||||
up: '啟動',
|
||||
composeDetailHelper: '該 compose 為 1Panel 編排外部創建。暫不支持啟停操作。',
|
||||
composeOperatorHelper: '將對 {0} 進行 {1} 操作,是否繼續?',
|
||||
composeDownHelper: '將停止並刪除 {0} 編排下所有容器及網絡,是否繼續?',
|
||||
|
||||
setting: '配置',
|
||||
operatorStatusHelper: '此操作將{0}Docker 服務,是否繼續?',
|
||||
@@ -1114,6 +1121,7 @@ const message = {
|
||||
info: '屬性',
|
||||
linkFile: '軟連接文件',
|
||||
terminal: '終端',
|
||||
batchoperation: '批量操作',
|
||||
shareList: '分享列表',
|
||||
zip: '壓縮',
|
||||
group: '用戶組',
|
||||
@@ -1210,6 +1218,7 @@ const message = {
|
||||
noNameFolder: '未命名資料夾',
|
||||
noNameFile: '未命名檔案',
|
||||
minimap: '縮略圖',
|
||||
fileCanNotRead: '此文件不支持預覽',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '開機自啟',
|
||||
@@ -1321,6 +1330,7 @@ const message = {
|
||||
LOCAL: '服務器磁盤',
|
||||
OSS: '阿裏雲 OSS',
|
||||
S3: '亞馬遜 S3 雲存儲',
|
||||
mode: '模式',
|
||||
MINIO: 'MINIO',
|
||||
SFTP: 'SFTP',
|
||||
WebDAV: 'WebDAV',
|
||||
|
||||
@@ -632,7 +632,8 @@ const message = {
|
||||
emptyUser: '为空时,将使用容器默认的用户登录',
|
||||
privileged: '特权模式',
|
||||
privilegedHelper: '允许容器在主机上执行某些特权操作,可能会增加容器风险,谨慎开启!',
|
||||
|
||||
editComposeHelper:
|
||||
'注意:设置的环境变量会默认写入 1panel.env 文件。\n如需在容器中使用这些参数,还需在 compose 文件中手动添加 env_file 引用。',
|
||||
upgradeHelper: '仓库名称/镜像名称:镜像版本',
|
||||
upgradeWarning2: '升级操作需要重建容器,任何未持久化的数据将会丢失,是否继续?',
|
||||
oldImage: '当前镜像',
|
||||
@@ -736,13 +737,13 @@ const message = {
|
||||
mirrorHelper:
|
||||
'当存在多个加速器时,需要换行显示,例: \nhttp://xxxxxx.m.daocloud.io \nhttps://xxxxxx.mirror.aliyuncs.com',
|
||||
registrieHelper: '当存在多个私有仓库时,需要换行显示,例:\n172.16.10.111:8081 \n172.16.10.112:8081',
|
||||
|
||||
compose: '编排',
|
||||
fromChangeHelper: '切换来源将清空当前已编辑内容,是否继续?',
|
||||
composePathHelper: '配置文件保存路径: {0}',
|
||||
composeHelper: '通过 1Panel 编辑或者模版创建的编排,将保存在 {0}/docker/compose 路径下',
|
||||
deleteFile: '删除文件',
|
||||
deleteComposeHelper: '删除 {0} 目录下所有文件,包括该文件下的持久化文件等,请谨慎操作!',
|
||||
allDelete: '彻底删除',
|
||||
deleteComposeHelper: '删除容器编排的所有文件,包括配置文件和持久化文件,请谨慎操作!',
|
||||
deleteCompose: '" 删除此编排',
|
||||
apps: '应用商店',
|
||||
local: '本地',
|
||||
@@ -754,10 +755,14 @@ const message = {
|
||||
content: '内容',
|
||||
contentEmpty: '编排内容不能为空,请输入后重试!',
|
||||
containerNumber: '容器数量',
|
||||
containerStatus: '容器状态',
|
||||
exited: '已停止',
|
||||
running: '运行中',
|
||||
down: '删除',
|
||||
up: '启动',
|
||||
composeDetailHelper: '该 compose 为 1Panel 编排外部创建。暂不支持启停操作。',
|
||||
composeOperatorHelper: '将对 {0} 进行 {1} 操作,是否继续?',
|
||||
composeDownHelper: '将停止并删除 {0} 编排下所有容器及网络,是否继续?',
|
||||
|
||||
setting: '配置',
|
||||
operatorStatusHelper: '此操作将{0}Docker 服务,是否继续?',
|
||||
@@ -1118,6 +1123,7 @@ const message = {
|
||||
info: '属性',
|
||||
linkFile: '软连接文件',
|
||||
terminal: '终端',
|
||||
batchoperation: '批量操作',
|
||||
shareList: '分享列表',
|
||||
zip: '压缩',
|
||||
group: '用户组',
|
||||
@@ -1214,6 +1220,7 @@ const message = {
|
||||
noNameFolder: '未命名文件夹',
|
||||
noNameFile: '未命名文件',
|
||||
minimap: '缩略图',
|
||||
fileCanNotRead: '此文件不支持预览',
|
||||
},
|
||||
ssh: {
|
||||
autoStart: '开机自启',
|
||||
@@ -1325,6 +1332,7 @@ const message = {
|
||||
LOCAL: '服务器磁盘',
|
||||
OSS: '阿里云 OSS',
|
||||
S3: '亚马逊 S3 云存储',
|
||||
mode: '模式',
|
||||
MINIO: 'MINIO',
|
||||
SFTP: 'SFTP',
|
||||
WebDAV: 'WebDAV',
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
<template>
|
||||
<div class="footer">
|
||||
<a href="https://fit2cloud.com/" target="_blank">Copyright © 2014-2024 FIT2CLOUD 飞致云</a>
|
||||
<SystemUpgrade :footer="true" />
|
||||
<div class="footer" :style="{ height: mobile ? '108px' : '48px' }">
|
||||
<div class="flex w-full flex-col gap-4 md:justify-between md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<a href="https://fit2cloud.com/" target="_blank">Copyright © 2014-2024 FIT2CLOUD 飞致云</a>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 md:flex-col lg:flex-row">
|
||||
<SystemUpgrade :footer="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import SystemUpgrade from '@/components/system-upgrade/index.vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const mobile = computed(() => {
|
||||
return globalStore.isMobile();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface GlobalState {
|
||||
defaultNetwork: string;
|
||||
|
||||
isProductPro: boolean;
|
||||
isTrial: boolean;
|
||||
productProExpires: number;
|
||||
|
||||
errStatus: string;
|
||||
|
||||
@@ -38,6 +38,7 @@ const GlobalStore = defineStore({
|
||||
defaultNetwork: 'all',
|
||||
|
||||
isProductPro: false,
|
||||
isTrial: false,
|
||||
productProExpires: 0,
|
||||
|
||||
errStatus: '',
|
||||
|
||||
@@ -181,8 +181,8 @@ html {
|
||||
|
||||
.mask-prompt {
|
||||
position: absolute;
|
||||
z-index: 9998;
|
||||
top: 220px;
|
||||
z-index: 1;
|
||||
top: 320px;
|
||||
left: 45%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 400px;
|
||||
@@ -374,7 +374,6 @@ html {
|
||||
}
|
||||
|
||||
.tag-button {
|
||||
margin-right: 10px;
|
||||
&.no-active {
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -387,7 +386,8 @@ html {
|
||||
}
|
||||
|
||||
.router-button {
|
||||
margin-right: 20px;
|
||||
margin-right: 30px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.text-parent {
|
||||
@@ -444,6 +444,12 @@ html {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dialog-footer{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.monaco-editor-tree-light .el-tree-node__content:hover {
|
||||
background-color: #e5eefd;
|
||||
}
|
||||
|
||||
@@ -194,6 +194,8 @@ let icons = new Map([
|
||||
['.zip', 'p-file-zip'],
|
||||
['.gz', 'p-file-zip'],
|
||||
['.tar.bz2', 'p-file-zip'],
|
||||
['.bz2', 'p-file-zip'],
|
||||
['.xz', 'p-file-zip'],
|
||||
['.tar', 'p-file-zip'],
|
||||
['.tar.gz', 'p-file-zip'],
|
||||
['.war', 'p-file-zip'],
|
||||
@@ -347,6 +349,19 @@ export function checkCidr(value: string): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export function checkCidrV6(value: string): boolean {
|
||||
if (value === '') {
|
||||
return true;
|
||||
}
|
||||
if (checkIpV6(value.split('/')[0])) {
|
||||
return true;
|
||||
}
|
||||
const reg = /^(?:[1-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$/;
|
||||
if (!reg.test(value.split('/')[1])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function checkPort(value: string): boolean {
|
||||
if (Number(value) <= 0) {
|
||||
@@ -556,7 +571,7 @@ export function emptyLineFilter(str: string, spilt: string) {
|
||||
// 文件类型映射
|
||||
let fileTypes = {
|
||||
image: ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.ico', '.svg', '.webp'],
|
||||
compress: ['.zip', '.rar', '.gz', '.war', '.tgz', '.7z', '.tar.gz', '.tar'],
|
||||
compress: ['.zip', '.rar', '.gz', '.war', '.tgz', '.7z', '.tar.gz', '.tar', '.bz2', '.xz', '.tar.bz2', '.tar.xz'],
|
||||
video: ['.mp4', '.webm', '.mov', '.wmv', '.mkv', '.avi', '.wma', '.flv'],
|
||||
audio: ['.mp3', '.wav', '.wma', '.ape', '.acc', '.ogg', '.flac'],
|
||||
pdf: ['.pdf'],
|
||||
|
||||
@@ -61,6 +61,7 @@ export async function loadProductProFromDB() {
|
||||
res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02';
|
||||
if (globalStore.isProductPro) {
|
||||
globalStore.productProExpires = Number(res.data.productPro);
|
||||
globalStore.isTrial = res.data.trial;
|
||||
}
|
||||
}
|
||||
switchTheme();
|
||||
@@ -81,6 +82,7 @@ export async function getXpackSettingForTheme() {
|
||||
res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02';
|
||||
if (globalStore.isProductPro) {
|
||||
globalStore.productProExpires = Number(res.data.productPro);
|
||||
globalStore.isTrial = res.data.trial;
|
||||
}
|
||||
if (!globalStore.isProductPro) {
|
||||
globalStore.isProductPro = false;
|
||||
|
||||
@@ -85,7 +85,6 @@
|
||||
}
|
||||
|
||||
.tag-button {
|
||||
margin-right: 10px;
|
||||
&.no-active {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
<div v-if="showButton">
|
||||
<RouterButton :buttons="buttons">
|
||||
<template #route-button>
|
||||
<el-badge is-dot :hidden="!canUpdate" class="pr-5">
|
||||
<el-button @click="sync" type="primary" plain :disabled="syncing">
|
||||
{{ $t('app.syncAppList') }}
|
||||
</el-button>
|
||||
</el-badge>
|
||||
<div class="pr-5 mt-1">
|
||||
<el-badge is-dot :hidden="!canUpdate">
|
||||
<el-button @click="sync" type="primary" plain :disabled="syncing">
|
||||
{{ $t('app.syncAppList') }}
|
||||
</el-button>
|
||||
</el-badge>
|
||||
</div>
|
||||
</template>
|
||||
</RouterButton>
|
||||
</div>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
placeholder="#Define or paste the content of your docker-compose file here"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="width: 100%; height: calc(100vh - 376px)"
|
||||
style="width: 100%; height: calc(100vh - 400px)"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
@@ -86,6 +86,26 @@
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.env')" prop="envStr">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:placeholder="$t('container.tagHelper')"
|
||||
:rows="3"
|
||||
v-model="form.envStr"
|
||||
/>
|
||||
</el-form-item>
|
||||
<span class="input-help whitespace-break-spaces">{{ $t('container.editComposeHelper') }}</span>
|
||||
<codemirror
|
||||
v-model="form.envFileContent"
|
||||
:autofocus="true"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
></codemirror>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -143,6 +163,9 @@ const form = reactive({
|
||||
path: '',
|
||||
file: '',
|
||||
template: null as number,
|
||||
env: [],
|
||||
envStr: '',
|
||||
envFileContent: `env_file:\n - 1panel.env`,
|
||||
});
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput, Rules.imageName],
|
||||
@@ -163,6 +186,8 @@ const acceptParams = (): void => {
|
||||
form.path = '';
|
||||
form.file = '';
|
||||
form.template = null;
|
||||
form.envStr = '';
|
||||
form.env = [];
|
||||
loadTemplates();
|
||||
loadPath();
|
||||
isStartReading.value = false;
|
||||
@@ -242,6 +267,9 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
|
||||
MsgError(i18n.global.t('container.contentEmpty'));
|
||||
return;
|
||||
}
|
||||
if (form.envStr) {
|
||||
form.env = form.envStr.split('\n');
|
||||
}
|
||||
loading.value = true;
|
||||
await testCompose(form)
|
||||
.then(async (res) => {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<el-form ref="deleteForm" v-loading="loading">
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="deleteFile" :label="$t('container.deleteFile')" />
|
||||
<span class="input-help">
|
||||
{{ $t('container.deleteComposeHelper', [loadComposeDir()]) }}
|
||||
<span class="input-help whitespace-break-spaces">
|
||||
{{ $t('container.deleteComposeHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -64,21 +64,12 @@ const acceptParams = async (prop: DialogProps) => {
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
|
||||
const loadComposeDir = () => {
|
||||
const parts = composePath.value.split('/');
|
||||
if (parts.length <= 2) {
|
||||
return '/';
|
||||
}
|
||||
const parentDirectory = parts.slice(0, -1).join('/');
|
||||
return parentDirectory;
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
name: composeName.value,
|
||||
path: composePath.value,
|
||||
operation: 'down',
|
||||
operation: 'delete',
|
||||
withFile: deleteFile.value,
|
||||
};
|
||||
await composeOperator(params)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<el-tag effect="dark" type="success">{{ composeName }}</el-tag>
|
||||
</div>
|
||||
<div v-if="createdBy === '1Panel'" style="margin-left: 50px">
|
||||
<el-button link type="primary" @click="onComposeOperate('start')">
|
||||
<el-button link type="primary" @click="onComposeOperate('up')">
|
||||
{{ $t('container.start') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
@@ -143,7 +143,6 @@ const dialogContainerLogRef = ref();
|
||||
|
||||
const opRef = ref();
|
||||
|
||||
const emit = defineEmits<{ (e: 'back'): void }>();
|
||||
interface DialogProps {
|
||||
createdBy: string;
|
||||
name: string;
|
||||
@@ -260,15 +259,18 @@ const onOperate = async (op: string) => {
|
||||
};
|
||||
|
||||
const onComposeOperate = async (operation: string) => {
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('container.composeOperatorHelper', [composeName.value, i18n.global.t('container.' + operation)]),
|
||||
i18n.global.t('container.' + operation),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
},
|
||||
).then(async () => {
|
||||
let mes =
|
||||
operation === 'down'
|
||||
? i18n.global.t('container.composeDownHelper', [composeName.value])
|
||||
: i18n.global.t('container.composeOperatorHelper', [
|
||||
composeName.value,
|
||||
i18n.global.t('container.' + operation),
|
||||
]);
|
||||
ElMessageBox.confirm(mes, i18n.global.t('container.' + operation), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
let params = {
|
||||
name: composeName.value,
|
||||
path: composePath.value,
|
||||
@@ -280,11 +282,7 @@ const onComposeOperate = async (operation: string) => {
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
if (operation === 'down') {
|
||||
emit('back');
|
||||
} else {
|
||||
search();
|
||||
}
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
|
||||
@@ -9,20 +9,56 @@
|
||||
<template #header>
|
||||
<DrawerHeader :header="$t('commons.button.edit')" :resource="name" :back="handleClose" />
|
||||
</template>
|
||||
<div v-loading="loading">
|
||||
<codemirror
|
||||
:autofocus="true"
|
||||
placeholder="#Define or paste the content of your docker-compose file here"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="width: 100%; height: calc(100vh - 175px)"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="content"
|
||||
/>
|
||||
<div v-loading="loading" style="padding-bottom: 20px">
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="22">
|
||||
<el-form ref="formRef" @submit.prevent label-position="top">
|
||||
<el-form-item>
|
||||
<codemirror
|
||||
:autofocus="true"
|
||||
placeholder="#Define or paste the content of your docker-compose file here"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
:style="{ width: '100%', height: `calc(100vh - ${loadHeight()})` }"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
v-model="content"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div v-if="createdBy === '1Panel'">
|
||||
<el-form-item
|
||||
:label="$t('container.env')"
|
||||
prop="environmentStr"
|
||||
v-if="createdBy === '1Panel'"
|
||||
>
|
||||
<el-input
|
||||
type="textarea"
|
||||
:placeholder="$t('container.tagHelper')"
|
||||
:rows="3"
|
||||
v-model="environmentStr"
|
||||
/>
|
||||
</el-form-item>
|
||||
<span class="input-help whitespace-break-spaces">
|
||||
{{ $t('container.editComposeHelper') }}
|
||||
</span>
|
||||
<codemirror
|
||||
v-model="envFileContent"
|
||||
:autofocus="true"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
:lineWrapping="true"
|
||||
:matchBrackets="true"
|
||||
theme="cobalt"
|
||||
:styleActiveLine="true"
|
||||
:extensions="extensions"
|
||||
></codemirror>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -45,26 +81,38 @@ import { composeUpdate } from '@/api/modules/container';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { ElForm } from 'element-plus';
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
const loading = ref(false);
|
||||
const composeVisible = ref(false);
|
||||
const extensions = [javascript(), oneDark];
|
||||
const path = ref();
|
||||
const content = ref();
|
||||
const name = ref();
|
||||
const environmentStr = ref();
|
||||
const environmentEnv = ref();
|
||||
const createdBy = ref();
|
||||
const envFileContent = `env_file:\n - 1panel.env`;
|
||||
|
||||
const onSubmitEdit = async () => {
|
||||
const param = {
|
||||
name: name.value,
|
||||
path: path.value,
|
||||
content: content.value,
|
||||
env: environmentStr.value,
|
||||
createdBy: createdBy.value,
|
||||
};
|
||||
if (environmentStr.value != undefined) {
|
||||
param.env = environmentStr.value.split('\n');
|
||||
}
|
||||
loading.value = true;
|
||||
await composeUpdate(param)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
composeVisible.value = false;
|
||||
emit('search');
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
@@ -75,13 +123,22 @@ interface DialogProps {
|
||||
name: string;
|
||||
path: string;
|
||||
content: string;
|
||||
env: Array<string>;
|
||||
envStr: string;
|
||||
createdBy: string;
|
||||
}
|
||||
|
||||
const loadHeight = () => {
|
||||
return createdBy.value === '1Panel' ? '300px' : '200px';
|
||||
};
|
||||
const acceptParams = (props: DialogProps): void => {
|
||||
composeVisible.value = true;
|
||||
path.value = props.path;
|
||||
name.value = props.name;
|
||||
content.value = props.content;
|
||||
createdBy.value = props.createdBy;
|
||||
environmentEnv.value = props.env || [];
|
||||
environmentStr.value = environmentEnv.value.join('\n');
|
||||
};
|
||||
const handleClose = () => {
|
||||
composeVisible.value = false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div v-loading="loading">
|
||||
<div v-show="isOnDetail">
|
||||
<ComposeDetail @back="backList" ref="composeDetailRef" />
|
||||
<ComposeDetail ref="composeDetailRef" />
|
||||
</div>
|
||||
<el-card v-if="dockerStatus != 'Running'" class="mask-prompt">
|
||||
<span>{{ $t('container.serviceUnavailable') }}</span>
|
||||
@@ -25,17 +25,17 @@
|
||||
</el-alert>
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="onOpenDialog()">
|
||||
{{ $t('container.createCompose') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
@@ -64,12 +64,13 @@
|
||||
<span v-if="row.createdBy === '1Panel'">1Panel</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('container.containerNumber')"
|
||||
prop="containerNumber"
|
||||
min-width="80"
|
||||
fix
|
||||
/>
|
||||
<el-table-column :label="$t('container.containerStatus')" min-width="80" fix>
|
||||
<template #default="scope">
|
||||
<div>
|
||||
{{ getContainerStatus(scope.row.containers) }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.table.createdAt')" prop="createdAt" min-width="80" fix />
|
||||
<fu-table-operations
|
||||
width="200px"
|
||||
@@ -82,7 +83,7 @@
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
<EditDialog ref="dialogEditRef" />
|
||||
<EditDialog @search="search" ref="dialogEditRef" />
|
||||
<CreateDialog @search="search" ref="dialogRef" />
|
||||
<DeleteDialog @search="search" ref="dialogDelRef" />
|
||||
</div>
|
||||
@@ -173,9 +174,16 @@ const loadDetail = async (row: Container.ComposeInfo) => {
|
||||
isOnDetail.value = true;
|
||||
composeDetailRef.value!.acceptParams(params);
|
||||
};
|
||||
const backList = async () => {
|
||||
isOnDetail.value = false;
|
||||
search();
|
||||
const getContainerStatus = (containers) => {
|
||||
const safeContainers = containers || [];
|
||||
const runningCount = safeContainers.filter((container) => container.state.toLowerCase() === 'running').length;
|
||||
const totalCount = safeContainers.length;
|
||||
const statusText = runningCount > 0 ? 'Running' : 'Exited';
|
||||
if (statusText === 'Exited') {
|
||||
return i18n.global.t('container.exited');
|
||||
} else {
|
||||
return i18n.global.t('container.running') + ` (${runningCount}/${totalCount})`;
|
||||
}
|
||||
};
|
||||
|
||||
const dialogRef = ref();
|
||||
@@ -199,6 +207,8 @@ const onEdit = async (row: Container.ComposeInfo) => {
|
||||
name: row.name,
|
||||
path: row.path,
|
||||
content: res.data,
|
||||
env: row.env,
|
||||
createdBy: row.createdBy,
|
||||
};
|
||||
dialogEditRef.value!.acceptParams(params);
|
||||
};
|
||||
|
||||
@@ -22,15 +22,16 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16">
|
||||
<div class="flex w-full flex-col gap-4 md:justify-between md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('container.create') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="onClean()">
|
||||
{{ $t('container.containerPrune') }}
|
||||
</el-button>
|
||||
<el-button-group class="ml-4">
|
||||
|
||||
<el-button-group>
|
||||
<el-button :disabled="checkStatus('start', null)" @click="onOperate('start', null)">
|
||||
{{ $t('container.start') }}
|
||||
</el-button>
|
||||
@@ -53,12 +54,12 @@
|
||||
{{ $t('container.remove') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
</div>
|
||||
<div class="flex flex-row gap-2 md:flex-col lg:flex-row">
|
||||
<TableSetting title="container-refresh" @search="refresh()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #search>
|
||||
<el-select v-model="searchState" @change="search()" clearable class="p-w-200">
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
</template>
|
||||
</DrawerHeader>
|
||||
</template>
|
||||
<div>
|
||||
<el-select @change="searchLogs" class="fetchClass" v-model="logSearch.mode">
|
||||
<div class="flex w-full flex-col gap-2 md:flex-row">
|
||||
<el-select @change="searchLogs" v-model="logSearch.mode">
|
||||
<template #prefix>{{ $t('container.fetch') }}</template>
|
||||
<el-option v-for="item in timeOptions" :key="item.label" :value="item.value" :label="item.label" />
|
||||
</el-select>
|
||||
<el-select @change="searchLogs" class="tailClass" v-model.number="logSearch.tail">
|
||||
<el-select @change="searchLogs" v-model.number="logSearch.tail">
|
||||
<template #prefix>{{ $t('container.lines') }}</template>
|
||||
<el-option :value="0" :label="$t('commons.table.all')" />
|
||||
<el-option :value="100" :label="100" />
|
||||
@@ -30,15 +30,13 @@
|
||||
<el-option :value="500" :label="500" />
|
||||
<el-option :value="1000" :label="1000" />
|
||||
</el-select>
|
||||
<div class="margin-button" style="float: left">
|
||||
<el-checkbox border @change="searchLogs" v-model="logSearch.isWatch">
|
||||
{{ $t('commons.button.watch') }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<el-button class="margin-button" @click="onDownload" icon="Download">
|
||||
<el-checkbox border @change="searchLogs" v-model="logSearch.isWatch">
|
||||
{{ $t('commons.button.watch') }}
|
||||
</el-checkbox>
|
||||
<el-button @click="onDownload" icon="Download">
|
||||
{{ $t('file.download') }}
|
||||
</el-button>
|
||||
<el-button class="margin-button" @click="onClean" icon="Delete">
|
||||
<el-button @click="onClean" icon="Delete">
|
||||
{{ $t('commons.button.clean') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -236,19 +234,14 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.margin-button {
|
||||
margin-left: 20px;
|
||||
}
|
||||
.fullScreen {
|
||||
border: none;
|
||||
}
|
||||
.tailClass {
|
||||
width: 20%;
|
||||
.select-width {
|
||||
width: 50%;
|
||||
float: left;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.fetchClass {
|
||||
width: 30%;
|
||||
float: left;
|
||||
.el-button + .el-button {
|
||||
margin: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
<LayoutContent :title="$t('container.image')" :class="{ mask: dockerStatus != 'Running' }">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" plain @click="onOpenPull">
|
||||
{{ $t('container.imagePull') }}
|
||||
</el-button>
|
||||
@@ -25,12 +25,12 @@
|
||||
<el-button type="primary" plain @click="onOpenPrune()">
|
||||
{{ $t('container.imagePrune') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
<LayoutContent :title="$t('container.network')" :class="{ mask: dockerStatus != 'Running' }">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="onCreate()">
|
||||
{{ $t('container.createNetwork') }}
|
||||
</el-button>
|
||||
@@ -19,12 +19,12 @@
|
||||
<el-button :disabled="selects.length === 0" @click="batchDelete(null)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
|
||||
@@ -8,17 +8,17 @@
|
||||
|
||||
<LayoutContent :title="$t('container.repo')" :class="{ mask: dockerStatus != 'Running' }">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="onOpenDialog('add')">
|
||||
{{ $t('container.createRepo') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
|
||||
@@ -2,35 +2,29 @@
|
||||
<div v-loading="loading">
|
||||
<div class="app-status" style="margin-top: 20px">
|
||||
<el-card>
|
||||
<div>
|
||||
<el-tag style="float: left" effect="dark" type="success">Docker</el-tag>
|
||||
<el-tag round class="status-content" v-if="form.status === 'Running'" type="success">
|
||||
{{ $t('commons.status.running') }}
|
||||
</el-tag>
|
||||
<el-tag round class="status-content" v-if="form.status === 'Stopped'" type="info">
|
||||
{{ $t('commons.status.stopped') }}
|
||||
</el-tag>
|
||||
<el-tag class="status-content">{{ $t('app.version') }}: {{ form.version }}</el-tag>
|
||||
|
||||
<span v-if="form.status === 'Running'" class="buttons">
|
||||
<el-button type="primary" @click="onOperator('stop')" link>
|
||||
<div class="flex w-full flex-col gap-4 md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<el-tag style="float: left" effect="dark" type="success">Docker</el-tag>
|
||||
<el-tag round v-if="form.status === 'Running'" type="success">
|
||||
{{ $t('commons.status.running') }}
|
||||
</el-tag>
|
||||
<el-tag round v-if="form.status === 'Stopped'" type="info">
|
||||
{{ $t('commons.status.stopped') }}
|
||||
</el-tag>
|
||||
<el-tag>{{ $t('app.version') }}: {{ form.version }}</el-tag>
|
||||
</div>
|
||||
<div class="mt-0.5">
|
||||
<el-button type="primary" v-if="form.status === 'Running'" @click="onOperator('stop')" link>
|
||||
{{ $t('container.stop') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" @click="onOperator('restart')" link>
|
||||
{{ $t('container.restart') }}
|
||||
</el-button>
|
||||
</span>
|
||||
|
||||
<span v-if="form.status === 'Stopped'" class="buttons">
|
||||
<el-button type="primary" @click="onOperator('start')" link>
|
||||
<el-button type="primary" v-if="form.status === 'Stopped'" @click="onOperator('start')" link>
|
||||
{{ $t('container.start') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" @click="onOperator('restart')" link>
|
||||
{{ $t('container.restart') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -46,13 +40,16 @@
|
||||
<el-col :xs="24" :sm="24" :md="15" :lg="12" :xl="10">
|
||||
<el-form :model="form" label-position="left" :rules="rules" ref="formRef" label-width="120px">
|
||||
<el-form-item :label="$t('container.mirrors')" prop="mirrors">
|
||||
<div style="width: 100%" v-if="form.mirrors">
|
||||
<div
|
||||
class="flex w-full justify-start flex-col sm:flex-row sm:items-end"
|
||||
v-if="form.mirrors"
|
||||
>
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
disabled
|
||||
v-model="form.mirrors"
|
||||
style="width: calc(100% - 80px)"
|
||||
class="sm:calc(100% - 80px)"
|
||||
/>
|
||||
<el-button @click="onChangeMirrors" icon="Setting">
|
||||
{{ $t('commons.button.set') }}
|
||||
@@ -65,18 +62,19 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="input-help">{{ $t('container.mirrorsHelper') }}</span>
|
||||
<span class="input-help flx-align-center" style="display: flex">
|
||||
{{ $t('container.mirrorsHelper2') }}
|
||||
<el-link
|
||||
style="font-size: 12px; margin-left: 5px"
|
||||
icon="Position"
|
||||
@click="toDoc()"
|
||||
type="primary"
|
||||
>
|
||||
{{ $t('firewall.quickJump') }}
|
||||
</el-link>
|
||||
</span>
|
||||
<div class="flex">
|
||||
<span>
|
||||
{{ $t('container.mirrorsHelper') }} {{ $t('container.mirrorsHelper2') }}
|
||||
<el-link
|
||||
style="font-size: 12px; margin-left: 5px"
|
||||
icon="Position"
|
||||
@click="toDoc()"
|
||||
type="primary"
|
||||
>
|
||||
{{ $t('firewall.quickJump') }}
|
||||
</el-link>
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.registries')" prop="registries">
|
||||
<div style="width: 100%" v-if="form.registries">
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
size="50%"
|
||||
>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('commons.button.view') }}</span>
|
||||
</div>
|
||||
<DrawerHeader :header="$t('commons.button.view')" :back="handleClose" />
|
||||
</template>
|
||||
<codemirror
|
||||
:autofocus="true"
|
||||
@@ -38,6 +36,7 @@ import { javascript } from '@codemirror/lang-javascript';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { ref } from 'vue';
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
const extensions = [javascript(), oneDark];
|
||||
|
||||
const detailVisible = ref(false);
|
||||
@@ -51,6 +50,10 @@ const acceptParams = (params: DialogProps): void => {
|
||||
detailVisible.value = true;
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
detailVisible.value = false;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
|
||||
@@ -8,20 +8,20 @@
|
||||
|
||||
<LayoutContent :title="$t('container.composeTemplate')" :class="{ mask: dockerStatus != 'Running' }">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('container.createComposeTemplate') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain :disabled="selects.length === 0" @click="onBatchDelete(null)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
@@ -47,7 +47,7 @@
|
||||
<el-table-column :label="$t('container.description')" prop="description" min-width="200" fix />
|
||||
<el-table-column :label="$t('commons.table.createdAt')" min-width="80" fix>
|
||||
<template #default="{ row }">
|
||||
{{ dateFormatSimple(row.createdAt) }}
|
||||
{{ dateFormat(0, 0, row.createdAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" />
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
import { dateFormatSimple } from '@/utils/util';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { Container } from '@/api/interface/container';
|
||||
import DetailDialog from '@/views/container/template/detail/index.vue';
|
||||
import OperatorDialog from '@/views/container/template/operator/index.vue';
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
<LayoutContent :title="$t('container.volume')" :class="{ mask: dockerStatus != 'Running' }">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="onCreate()">
|
||||
{{ $t('container.createVolume') }}
|
||||
</el-button>
|
||||
@@ -19,12 +19,12 @@
|
||||
<el-button :disabled="selects.length === 0" @click="batchDelete(null)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
/>
|
||||
<LayoutContent v-loading="loading" v-if="!isRecordShow" :title="$t('cronjob.cronTask')">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('commons.button.create') }}{{ $t('cronjob.cronTask') }}
|
||||
</el-button>
|
||||
@@ -26,12 +26,12 @@
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
|
||||
@@ -2,32 +2,39 @@
|
||||
<div v-if="recordShow" v-loading="loading">
|
||||
<div class="app-status p-mt-20">
|
||||
<el-card>
|
||||
<div>
|
||||
<el-popover
|
||||
v-if="dialogData.rowData.name.length >= 15"
|
||||
placement="top-start"
|
||||
trigger="hover"
|
||||
width="250"
|
||||
:content="$t('cronjob.' + dialogData.rowData.type) + ' - ' + dialogData.rowData.name"
|
||||
>
|
||||
<template #reference>
|
||||
<el-tag style="float: left" effect="dark" type="success">
|
||||
{{ $t('cronjob.' + dialogData.rowData.type) }} -
|
||||
{{ dialogData.rowData.name.substring(0, 12) }}...
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-tag v-if="dialogData.rowData.name.length < 15" class="float-left" effect="dark" type="success">
|
||||
{{ $t('cronjob.' + dialogData.rowData.type) }} - {{ dialogData.rowData.name }}
|
||||
</el-tag>
|
||||
<div class="flex w-full flex-col gap-4 md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<el-popover
|
||||
v-if="dialogData.rowData.name.length >= 15"
|
||||
placement="top-start"
|
||||
trigger="hover"
|
||||
width="250"
|
||||
:content="$t('cronjob.' + dialogData.rowData.type) + ' - ' + dialogData.rowData.name"
|
||||
>
|
||||
<template #reference>
|
||||
<el-tag style="float: left" effect="dark" type="success">
|
||||
{{ $t('cronjob.' + dialogData.rowData.type) }} -
|
||||
{{ dialogData.rowData.name.substring(0, 12) }}...
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-tag
|
||||
v-if="dialogData.rowData.name.length < 15"
|
||||
class="float-left"
|
||||
effect="dark"
|
||||
type="success"
|
||||
>
|
||||
{{ $t('cronjob.' + dialogData.rowData.type) }} - {{ dialogData.rowData.name }}
|
||||
</el-tag>
|
||||
|
||||
<el-tag v-if="dialogData.rowData.status === 'Enable'" round class="status-content" type="success">
|
||||
{{ $t('commons.status.running') }}
|
||||
</el-tag>
|
||||
<el-tag v-if="dialogData.rowData.status === 'Disable'" round class="status-content" type="info">
|
||||
{{ $t('commons.status.stopped') }}
|
||||
</el-tag>
|
||||
<span class="buttons">
|
||||
<el-tag v-if="dialogData.rowData.status === 'Enable'" round type="success">
|
||||
{{ $t('commons.status.running') }}
|
||||
</el-tag>
|
||||
<el-tag v-if="dialogData.rowData.status === 'Disable'" round type="info">
|
||||
{{ $t('commons.status.stopped') }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="mt-0.5">
|
||||
<el-button type="primary" @click="onHandle(dialogData.rowData)" link>
|
||||
{{ $t('commons.button.handle') }}
|
||||
</el-button>
|
||||
@@ -52,7 +59,7 @@
|
||||
<el-button :disabled="!hasRecords" type="primary" @click="onClean" link>
|
||||
{{ $t('commons.button.clean') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
<div v-loading="loading">
|
||||
<div class="app-status" style="margin-top: 20px" v-if="currentDB?.from === 'remote'">
|
||||
<el-card>
|
||||
<div>
|
||||
<el-tag style="float: left" effect="dark" type="success">
|
||||
{{ currentDB?.type === 'mysql' ? 'Mysql' : 'MariaDB' }}
|
||||
</el-tag>
|
||||
<el-tag class="status-content">{{ $t('app.version') }}: {{ currentDB?.version }}</el-tag>
|
||||
<div class="flex w-full flex-col gap-4 md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<el-tag style="float: left" effect="dark" type="success">
|
||||
{{ currentDB?.type === 'mysql' ? 'Mysql' : 'MariaDB' }}
|
||||
</el-tag>
|
||||
<el-tag>{{ $t('app.version') }}: {{ currentDB?.version }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -61,8 +63,8 @@
|
||||
</template>
|
||||
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button
|
||||
v-if="currentDB && (currentDB.from !== 'local' || mysqlStatus === 'Running')"
|
||||
type="primary"
|
||||
@@ -84,27 +86,29 @@
|
||||
<el-button @click="goRemoteDB" type="primary" plain>
|
||||
{{ $t('database.remoteDB') }}
|
||||
</el-button>
|
||||
<el-dropdown class="ml-3">
|
||||
<el-button type="primary" plain>
|
||||
{{ $t('database.manage') }}
|
||||
<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item icon="Position" @click="goDashboard('phpMyAdmin')">
|
||||
phpMyAdmin
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item icon="Position" @click="goDashboard('Adminer')" divided>
|
||||
Adminer
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
|
||||
<div>
|
||||
<el-dropdown>
|
||||
<el-button type="primary" plain>
|
||||
{{ $t('database.manage') }}
|
||||
<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item icon="Position" @click="goDashboard('phpMyAdmin')">
|
||||
phpMyAdmin
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item icon="Position" @click="goDashboard('Adminer')" divided>
|
||||
Adminer
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main v-if="currentDB">
|
||||
<ComplexTable
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
<back-button name="MySQL" :header="$t('database.remoteDB')" />
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('database.createRemoteDB') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
<div v-loading="loading">
|
||||
<div class="app-status" style="margin-top: 20px" v-if="currentDB?.from === 'remote'">
|
||||
<el-card>
|
||||
<div>
|
||||
<el-tag style="float: left" effect="dark" type="success">PostgreSQL</el-tag>
|
||||
<el-tag class="status-content">{{ $t('app.version') }}: {{ currentDB?.version }}</el-tag>
|
||||
<div class="flex w-full flex-col gap-4 md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<el-tag style="float: left" effect="dark" type="success">PostgreSQL</el-tag>
|
||||
<el-tag>{{ $t('app.version') }}: {{ currentDB?.version }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -53,8 +55,8 @@
|
||||
</template>
|
||||
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button
|
||||
v-if="currentDB && (currentDB.from !== 'local' || postgresqlStatus === 'Running')"
|
||||
type="primary"
|
||||
@@ -77,11 +79,9 @@
|
||||
{{ $t('database.remoteDB') }}
|
||||
</el-button>
|
||||
<el-button @click="goDashboard()" type="primary" plain>PGAdmin4</el-button>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div><TableSearch @search="search()" v-model:searchName="searchName" /></div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main v-if="currentDB">
|
||||
<ComplexTable
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
<back-button name="PostgreSQL" :header="$t('database.remoteDB')" />
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('database.createRemoteDB') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
<div v-loading="loading">
|
||||
<div class="app-status" style="margin-top: 20px" v-if="currentDB && currentDB.from === 'remote'">
|
||||
<el-card>
|
||||
<div>
|
||||
<el-tag style="float: left" effect="dark" type="success">Redis</el-tag>
|
||||
<el-tag class="status-content">{{ $t('app.version') }}: {{ currentDB?.version }}</el-tag>
|
||||
<div class="flex w-full flex-col gap-4 md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<el-tag style="float: left" effect="dark" type="success">Redis</el-tag>
|
||||
<el-tag>{{ $t('app.version') }}: {{ currentDB?.version }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -51,12 +53,16 @@
|
||||
</el-select>
|
||||
</template>
|
||||
<template #toolbar v-if="!isOnSetting">
|
||||
<el-button v-if="currentDB" type="primary" plain @click="onLoadConn">
|
||||
{{ $t('database.databaseConnInfo') }}
|
||||
</el-button>
|
||||
<el-button @click="goRemoteDB" type="primary" plain>
|
||||
{{ $t('database.remoteDB') }}
|
||||
</el-button>
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button v-if="currentDB" type="primary" plain @click="onLoadConn">
|
||||
{{ $t('database.databaseConnInfo') }}
|
||||
</el-button>
|
||||
<el-button @click="goRemoteDB" type="primary" plain>
|
||||
{{ $t('database.remoteDB') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutContent>
|
||||
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
<back-button name="Redis" :header="$t('database.remoteDB')" />
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="20">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('database.createRemoteDB') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
|
||||
|
||||
@@ -309,6 +309,7 @@ const isProductPro = ref();
|
||||
const searchInfo = reactive({
|
||||
ioOption: 'all',
|
||||
netOption: 'all',
|
||||
scope: 'all',
|
||||
});
|
||||
|
||||
const baseInfo = ref<Dashboard.BaseInfo>({
|
||||
@@ -364,6 +365,7 @@ const currentInfo = ref<Dashboard.CurrentInfo>({
|
||||
|
||||
diskData: [],
|
||||
gpuData: [],
|
||||
xpuData: [],
|
||||
|
||||
netBytesSent: 0,
|
||||
netBytesRecv: 0,
|
||||
@@ -415,79 +417,148 @@ const onLoadBaseInfo = async (isInit: boolean, range: string) => {
|
||||
}
|
||||
const res = await loadBaseInfo(searchInfo.ioOption, searchInfo.netOption);
|
||||
baseInfo.value = res.data;
|
||||
currentInfo.value = baseInfo.value.currentInfo;
|
||||
await onLoadCurrentInfo();
|
||||
|
||||
const resData = res.data.currentInfo;
|
||||
currentInfo.value.ioReadBytes = resData.ioReadBytes;
|
||||
currentInfo.value.ioWriteBytes = resData.ioWriteBytes;
|
||||
currentInfo.value.ioCount = resData.ioCount;
|
||||
currentInfo.value.ioReadTime = resData.ioReadTime;
|
||||
currentInfo.value.ioWriteTime = resData.ioWriteTime;
|
||||
currentInfo.value.netBytesSent = resData.netBytesSent;
|
||||
currentInfo.value.netBytesRecv = resData.netBytesRecv;
|
||||
currentInfo.value.uptime = resData.uptime;
|
||||
|
||||
loadAppCurrentInfo();
|
||||
isStatusInit.value = false;
|
||||
statusRef.value.acceptParams(currentInfo.value, baseInfo.value, isStatusInit.value);
|
||||
appRef.value.acceptParams();
|
||||
if (isInit) {
|
||||
timer = setInterval(async () => {
|
||||
if (isActive.value && !globalStore.isOnRestart) {
|
||||
await onLoadCurrentInfo();
|
||||
loadAppCurrentInfo();
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
const onLoadCurrentInfo = async () => {
|
||||
const res = await loadCurrentInfo(searchInfo.ioOption, searchInfo.netOption);
|
||||
currentInfo.value.timeSinceUptime = res.data.timeSinceUptime;
|
||||
|
||||
let timeInterval = Number(res.data.uptime - currentInfo.value.uptime) || 3;
|
||||
currentChartInfo.netBytesSent =
|
||||
res.data.netBytesSent - currentInfo.value.netBytesSent > 0
|
||||
? Number(((res.data.netBytesSent - currentInfo.value.netBytesSent) / 1024 / timeInterval).toFixed(2))
|
||||
: 0;
|
||||
netBytesSents.value.push(currentChartInfo.netBytesSent);
|
||||
if (netBytesSents.value.length > 20) {
|
||||
netBytesSents.value.splice(0, 1);
|
||||
}
|
||||
|
||||
currentChartInfo.netBytesRecv =
|
||||
res.data.netBytesRecv - currentInfo.value.netBytesRecv > 0
|
||||
? Number(((res.data.netBytesRecv - currentInfo.value.netBytesRecv) / 1024 / timeInterval).toFixed(2))
|
||||
: 0;
|
||||
netBytesRecvs.value.push(currentChartInfo.netBytesRecv);
|
||||
if (netBytesRecvs.value.length > 20) {
|
||||
netBytesRecvs.value.splice(0, 1);
|
||||
}
|
||||
|
||||
currentChartInfo.ioReadBytes =
|
||||
res.data.ioReadBytes - currentInfo.value.ioReadBytes > 0
|
||||
? Number(((res.data.ioReadBytes - currentInfo.value.ioReadBytes) / 1024 / 1024 / timeInterval).toFixed(2))
|
||||
: 0;
|
||||
ioReadBytes.value.push(currentChartInfo.ioReadBytes);
|
||||
if (ioReadBytes.value.length > 20) {
|
||||
ioReadBytes.value.splice(0, 1);
|
||||
}
|
||||
|
||||
currentChartInfo.ioWriteBytes =
|
||||
res.data.ioWriteBytes - currentInfo.value.ioWriteBytes > 0
|
||||
? Number(((res.data.ioWriteBytes - currentInfo.value.ioWriteBytes) / 1024 / 1024 / timeInterval).toFixed(2))
|
||||
: 0;
|
||||
ioWriteBytes.value.push(currentChartInfo.ioWriteBytes);
|
||||
if (ioWriteBytes.value.length > 20) {
|
||||
ioWriteBytes.value.splice(0, 1);
|
||||
}
|
||||
currentChartInfo.ioCount = Math.round(Number((res.data.ioCount - currentInfo.value.ioCount) / timeInterval));
|
||||
let ioReadTime = res.data.ioReadTime - currentInfo.value.ioReadTime;
|
||||
let ioWriteTime = res.data.ioWriteTime - currentInfo.value.ioWriteTime;
|
||||
let ioChoose = ioReadTime > ioWriteTime ? ioReadTime : ioWriteTime;
|
||||
currentChartInfo.ioTime = Math.round(Number(ioChoose / timeInterval));
|
||||
|
||||
timeIODatas.value.push(dateFormatForSecond(res.data.shotTime));
|
||||
if (timeIODatas.value.length > 20) {
|
||||
timeIODatas.value.splice(0, 1);
|
||||
}
|
||||
timeNetDatas.value.push(dateFormatForSecond(res.data.shotTime));
|
||||
if (timeNetDatas.value.length > 20) {
|
||||
timeNetDatas.value.splice(0, 1);
|
||||
}
|
||||
loadData();
|
||||
currentInfo.value = res.data;
|
||||
const loadAppCurrentInfo = async () => {
|
||||
await Promise.all([onLoadCurrentInfo('gpu'), onLoadCurrentInfo('basic'), onLoadCurrentInfo('ioNet')]);
|
||||
statusRef.value.acceptParams(currentInfo.value, baseInfo.value, isStatusInit.value);
|
||||
};
|
||||
|
||||
const onLoadCurrentInfo = async (scope: string) => {
|
||||
const req = {
|
||||
scope: scope,
|
||||
ioOption: searchInfo.ioOption,
|
||||
netOption: searchInfo.netOption,
|
||||
};
|
||||
const res = await loadCurrentInfo(req);
|
||||
const resData = res.data;
|
||||
|
||||
if (scope === 'ioNet') {
|
||||
let timeInterval = Number(res.data.uptime - currentInfo.value.uptime) || 3;
|
||||
currentChartInfo.netBytesSent =
|
||||
res.data.netBytesSent - currentInfo.value.netBytesSent > 0
|
||||
? Number(((res.data.netBytesSent - currentInfo.value.netBytesSent) / 1024 / timeInterval).toFixed(2))
|
||||
: 0;
|
||||
netBytesSents.value.push(currentChartInfo.netBytesSent);
|
||||
|
||||
if (netBytesSents.value.length > 20) {
|
||||
netBytesSents.value.splice(0, 1);
|
||||
}
|
||||
|
||||
currentChartInfo.netBytesRecv =
|
||||
res.data.netBytesRecv - currentInfo.value.netBytesRecv > 0
|
||||
? Number(((res.data.netBytesRecv - currentInfo.value.netBytesRecv) / 1024 / timeInterval).toFixed(2))
|
||||
: 0;
|
||||
netBytesRecvs.value.push(currentChartInfo.netBytesRecv);
|
||||
if (netBytesRecvs.value.length > 20) {
|
||||
netBytesRecvs.value.splice(0, 1);
|
||||
}
|
||||
|
||||
currentChartInfo.ioReadBytes =
|
||||
res.data.ioReadBytes - currentInfo.value.ioReadBytes > 0
|
||||
? Number(
|
||||
((res.data.ioReadBytes - currentInfo.value.ioReadBytes) / 1024 / 1024 / timeInterval).toFixed(2),
|
||||
)
|
||||
: 0;
|
||||
ioReadBytes.value.push(currentChartInfo.ioReadBytes);
|
||||
if (ioReadBytes.value.length > 20) {
|
||||
ioReadBytes.value.splice(0, 1);
|
||||
}
|
||||
|
||||
currentChartInfo.ioWriteBytes =
|
||||
res.data.ioWriteBytes - currentInfo.value.ioWriteBytes > 0
|
||||
? Number(
|
||||
((res.data.ioWriteBytes - currentInfo.value.ioWriteBytes) / 1024 / 1024 / timeInterval).toFixed(
|
||||
2,
|
||||
),
|
||||
)
|
||||
: 0;
|
||||
ioWriteBytes.value.push(currentChartInfo.ioWriteBytes);
|
||||
if (ioWriteBytes.value.length > 20) {
|
||||
ioWriteBytes.value.splice(0, 1);
|
||||
}
|
||||
currentChartInfo.ioCount = Math.round(Number((res.data.ioCount - currentInfo.value.ioCount) / timeInterval));
|
||||
let ioReadTime = res.data.ioReadTime - currentInfo.value.ioReadTime;
|
||||
let ioWriteTime = res.data.ioWriteTime - currentInfo.value.ioWriteTime;
|
||||
let ioChoose = ioReadTime > ioWriteTime ? ioReadTime : ioWriteTime;
|
||||
currentChartInfo.ioTime = Math.round(Number(ioChoose / timeInterval));
|
||||
|
||||
timeIODatas.value.push(dateFormatForSecond(res.data.shotTime));
|
||||
if (timeIODatas.value.length > 20) {
|
||||
timeIODatas.value.splice(0, 1);
|
||||
}
|
||||
timeNetDatas.value.push(dateFormatForSecond(res.data.shotTime));
|
||||
if (timeNetDatas.value.length > 20) {
|
||||
timeNetDatas.value.splice(0, 1);
|
||||
}
|
||||
loadData();
|
||||
|
||||
currentInfo.value.ioReadBytes = resData.ioReadBytes;
|
||||
currentInfo.value.ioWriteBytes = resData.ioWriteBytes;
|
||||
currentInfo.value.ioCount = resData.ioCount;
|
||||
currentInfo.value.ioReadTime = resData.ioReadTime;
|
||||
currentInfo.value.ioWriteTime = resData.ioWriteTime;
|
||||
|
||||
currentInfo.value.netBytesSent = resData.netBytesSent;
|
||||
currentInfo.value.netBytesRecv = resData.netBytesRecv;
|
||||
}
|
||||
if (scope === 'gpu') {
|
||||
currentInfo.value.gpuData = resData.gpuData;
|
||||
currentInfo.value.xpuData = resData.xpuData;
|
||||
}
|
||||
if (scope === 'basic') {
|
||||
currentInfo.value.uptime = resData.uptime;
|
||||
currentInfo.value.timeSinceUptime = resData.timeSinceUptime;
|
||||
currentInfo.value.procs = resData.procs;
|
||||
|
||||
currentInfo.value.load1 = resData.load1;
|
||||
currentInfo.value.load5 = resData.load5;
|
||||
currentInfo.value.load15 = resData.load15;
|
||||
currentInfo.value.loadUsagePercent = resData.loadUsagePercent;
|
||||
|
||||
currentInfo.value.cpuPercent = resData.cpuPercent;
|
||||
currentInfo.value.cpuUsedPercent = resData.cpuUsedPercent;
|
||||
currentInfo.value.cpuUsed = resData.cpuUsed;
|
||||
currentInfo.value.cpuTotal = resData.cpuTotal;
|
||||
|
||||
currentInfo.value.memoryTotal = resData.memoryTotal;
|
||||
currentInfo.value.memoryAvailable = resData.memoryAvailable;
|
||||
currentInfo.value.memoryUsed = resData.memoryUsed;
|
||||
currentInfo.value.memoryUsedPercent = resData.memoryUsedPercent;
|
||||
|
||||
currentInfo.value.swapMemoryTotal = resData.swapMemoryTotal;
|
||||
currentInfo.value.swapMemoryAvailable = resData.swapMemoryAvailable;
|
||||
currentInfo.value.swapMemoryUsed = resData.swapMemoryUsed;
|
||||
currentInfo.value.swapMemoryUsedPercent = resData.swapMemoryUsedPercent;
|
||||
|
||||
currentInfo.value.timeSinceUptime = res.data.timeSinceUptime;
|
||||
currentInfo.value.shotTime = resData.shotTime;
|
||||
currentInfo.value.diskData = resData.diskData;
|
||||
}
|
||||
};
|
||||
|
||||
function loadUpTime(uptime: number) {
|
||||
if (uptime <= 0) {
|
||||
return '-';
|
||||
@@ -619,6 +690,12 @@ onBeforeUnmount(() => {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
@media only screen and (max-width: 1300px) {
|
||||
span:first-child {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
}
|
||||
|
||||
.count {
|
||||
margin-top: 10px;
|
||||
|
||||
@@ -53,28 +53,22 @@
|
||||
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6" align="center">
|
||||
<el-popover placement="bottom" :width="160" trigger="hover" v-if="chartsOption['memory']">
|
||||
<el-tag style="font-weight: 500">{{ $t('home.mem') }}:</el-tag>
|
||||
<el-tag class="tagClass">
|
||||
{{ $t('home.total') }}: {{ formatNumber(currentInfo.memoryTotal / 1024 / 1024) }} MB
|
||||
</el-tag>
|
||||
<el-tag class="tagClass">
|
||||
{{ $t('home.used') }}: {{ formatNumber(currentInfo.memoryUsed / 1024 / 1024) }} MB
|
||||
</el-tag>
|
||||
<el-tag class="tagClass">
|
||||
{{ $t('home.free') }}: {{ formatNumber(currentInfo.memoryAvailable / 1024 / 1024) }} MB
|
||||
</el-tag>
|
||||
<el-tag class="tagClass">{{ $t('home.total') }}: {{ computeSize(currentInfo.memoryTotal) }}</el-tag>
|
||||
<el-tag class="tagClass">{{ $t('home.used') }}: {{ computeSize(currentInfo.memoryUsed) }}</el-tag>
|
||||
<el-tag class="tagClass">{{ $t('home.free') }}: {{ computeSize(currentInfo.memoryAvailable) }}</el-tag>
|
||||
<el-tag class="tagClass">
|
||||
{{ $t('home.percent') }}: {{ formatNumber(currentInfo.memoryUsedPercent) }}%
|
||||
</el-tag>
|
||||
<div v-if="currentInfo.swapMemoryTotal" class="mt-2">
|
||||
<el-tag style="font-weight: 500">{{ $t('home.swapMem') }}:</el-tag>
|
||||
<el-tag class="tagClass">
|
||||
{{ $t('home.total') }}: {{ formatNumber(currentInfo.swapMemoryTotal / 1024 / 1024) }} MB
|
||||
{{ $t('home.total') }}: {{ computeSize(currentInfo.swapMemoryTotal) }}
|
||||
</el-tag>
|
||||
<el-tag class="tagClass">
|
||||
{{ $t('home.used') }}: {{ formatNumber(currentInfo.swapMemoryUsed / 1024 / 1024) }} MB
|
||||
{{ $t('home.used') }}: {{ computeSize(currentInfo.swapMemoryUsed) }}
|
||||
</el-tag>
|
||||
<el-tag class="tagClass">
|
||||
{{ $t('home.free') }}: {{ formatNumber(currentInfo.swapMemoryAvailable / 1024 / 1024) }} MB
|
||||
{{ $t('home.free') }}: {{ computeSize(currentInfo.swapMemoryAvailable) }}
|
||||
</el-tag>
|
||||
<el-tag class="tagClass">
|
||||
{{ $t('home.percent') }}: {{ formatNumber(currentInfo.swapMemoryUsedPercent) }}%
|
||||
@@ -91,8 +85,7 @@
|
||||
</template>
|
||||
</el-popover>
|
||||
<span class="input-help">
|
||||
( {{ formatNumber(currentInfo.memoryUsed / 1024 / 1024) }} /
|
||||
{{ formatNumber(currentInfo.memoryTotal / 1024 / 1024) }} ) MB
|
||||
{{ computeSize(currentInfo.memoryUsed) }} / {{ computeSize(currentInfo.memoryTotal) }}
|
||||
</span>
|
||||
</el-col>
|
||||
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6" align="center">
|
||||
@@ -166,7 +159,9 @@
|
||||
/>
|
||||
</template>
|
||||
</el-popover>
|
||||
<span class="input-help">{{ computeSize(item.used) }} / {{ computeSize(item.total) }}</span>
|
||||
<span class="input-help" v-if="chartsOption[`disk${index}`]">
|
||||
{{ computeSize(item.used) }} / {{ computeSize(item.total) }}
|
||||
</span>
|
||||
</el-col>
|
||||
</template>
|
||||
<template v-for="(item, index) of currentInfo.gpuData" :key="index">
|
||||
@@ -222,6 +217,51 @@
|
||||
<span class="input-help" v-else>{{ item.productName }}</span>
|
||||
</el-col>
|
||||
</template>
|
||||
<template v-for="(item, index) of currentInfo.xpuData" :key="index">
|
||||
<el-col
|
||||
:xs="12"
|
||||
:sm="12"
|
||||
:md="6"
|
||||
:lg="6"
|
||||
:xl="6"
|
||||
align="center"
|
||||
v-if="showMore || index < 4 - currentInfo.diskData.length"
|
||||
>
|
||||
<el-popover placement="bottom" :width="250" trigger="hover" v-if="chartsOption[`gpu${index}`]">
|
||||
<el-row :gutter="5">
|
||||
<el-tag style="font-weight: 500">{{ $t('home.baseInfo') }}:</el-tag>
|
||||
</el-row>
|
||||
<el-row :gutter="5">
|
||||
<el-tag class="tagClass">{{ $t('monitor.gpuUtil') }}: {{ item.memoryUtil }}</el-tag>
|
||||
</el-row>
|
||||
<el-row :gutter="5">
|
||||
<el-tag class="tagClass">{{ $t('monitor.temperature') }}: {{ item.temperature }}</el-tag>
|
||||
</el-row>
|
||||
<el-row :gutter="5">
|
||||
<el-tag class="tagClass">{{ $t('monitor.powerUsage') }}: {{ item.power }}</el-tag>
|
||||
</el-row>
|
||||
<el-row :gutter="5">
|
||||
<el-tag class="tagClass">
|
||||
{{ $t('monitor.memoryUsage') }}: {{ item.memoryUsed }}/{{ item.memory }}
|
||||
</el-tag>
|
||||
</el-row>
|
||||
<template #reference>
|
||||
<v-charts
|
||||
@click="goGPU()"
|
||||
height="160px"
|
||||
:id="`gpu${index}`"
|
||||
type="pie"
|
||||
:option="chartsOption[`gpu${index}`]"
|
||||
v-if="chartsOption[`gpu${index}`]"
|
||||
/>
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-tooltip :content="item.deviceName" v-if="item.deviceName.length > 25">
|
||||
<span class="input-help">{{ item.deviceName.substring(0, 22) }}...</span>
|
||||
</el-tooltip>
|
||||
<span class="input-help" v-else>{{ item.deviceName }}</span>
|
||||
</el-col>
|
||||
</template>
|
||||
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6" align="center">
|
||||
<el-button v-if="!showMore" link type="primary" @click="showMore = true" class="buttonClass">
|
||||
{{ $t('tabs.more') }}
|
||||
@@ -268,6 +308,9 @@ const baseInfo = ref<Dashboard.BaseInfo>({
|
||||
cpuLogicalCores: 0,
|
||||
cpuModelName: '',
|
||||
currentInfo: null,
|
||||
|
||||
ipv4Addr: '',
|
||||
systemProxy: '',
|
||||
});
|
||||
const currentInfo = ref<Dashboard.CurrentInfo>({
|
||||
uptime: 0,
|
||||
@@ -301,6 +344,7 @@ const currentInfo = ref<Dashboard.CurrentInfo>({
|
||||
|
||||
diskData: [],
|
||||
gpuData: [],
|
||||
xpuData: [],
|
||||
|
||||
netBytesSent: 0,
|
||||
netBytesRecv: 0,
|
||||
@@ -343,6 +387,13 @@ const acceptParams = (current: Dashboard.CurrentInfo, base: Dashboard.BaseInfo,
|
||||
data: formatNumber(Number(currentInfo.value.gpuData[i].gpuUtil.replaceAll(' %', ''))),
|
||||
};
|
||||
}
|
||||
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('%', ''))),
|
||||
};
|
||||
}
|
||||
if (currentInfo.value.diskData.length + currentInfo.value.gpuData.length > 5) {
|
||||
showMore.value = isInit ? false : showMore.value || false;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
</div>
|
||||
<div v-loading="loading">
|
||||
<div class="flex">
|
||||
<div class="sm:w-48 w-1/3 monaco-editor-background tree-container" v-if="isShow">
|
||||
<div class="monaco-editor sm:w-48 w-1/3 monaco-editor-background border-0 tree-container" v-if="isShow">
|
||||
<div class="flex items-center justify-between pl-1 sm:pr-4 pr-1 pt-1">
|
||||
<el-tooltip :content="$t('file.top')" placement="top">
|
||||
<el-text size="small" @click="getUpData()" class="cursor-pointer">
|
||||
@@ -711,6 +711,11 @@ defineExpose({ acceptParams });
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
.monaco-editor-background {
|
||||
outline-style: none;
|
||||
background-color: var(--vscode-editor-background) !important;
|
||||
}
|
||||
|
||||
.tree-widget {
|
||||
background-color: var(--el-button--primary);
|
||||
}
|
||||
|
||||
@@ -53,77 +53,227 @@
|
||||
<template #prompt>
|
||||
<el-alert type="info" :closable="false">
|
||||
<template #title>
|
||||
<span v-html="$t('file.fileHeper')"></span>
|
||||
<span class="input-help whitespace-break-spaces">
|
||||
{{ $t('file.fileHeper') }}
|
||||
</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<div class="btn-container">
|
||||
<div class="left-section">
|
||||
<el-dropdown @command="handleCreate">
|
||||
<el-button type="primary">
|
||||
{{ $t('commons.button.create') }}
|
||||
<el-icon><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="dir">
|
||||
<svg-icon iconName="p-file-folder"></svg-icon>
|
||||
{{ $t('file.dir') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="file">
|
||||
<svg-icon iconName="p-file-normal"></svg-icon>
|
||||
{{ $t('file.file') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button-group>
|
||||
<el-button plain @click="openUpload">{{ $t('file.upload') }}</el-button>
|
||||
<el-button plain @click="openWget">{{ $t('file.remoteFile') }}</el-button>
|
||||
<el-button plain @click="openMove('copy')" :disabled="selects.length === 0">
|
||||
{{ $t('file.copy') }}
|
||||
</el-button>
|
||||
<el-button plain @click="openMove('cut')" :disabled="selects.length === 0">
|
||||
{{ $t('file.move') }}
|
||||
</el-button>
|
||||
<el-button plain @click="openCompress(selects)" :disabled="selects.length === 0">
|
||||
{{ $t('file.compress') }}
|
||||
</el-button>
|
||||
<el-button plain @click="openBatchRole(selects)" :disabled="selects.length === 0">
|
||||
{{ $t('file.role') }}
|
||||
</el-button>
|
||||
<el-button plain @click="batchDelFiles" :disabled="selects.length === 0">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
<div class="hidden sm:block">
|
||||
<div class="btn-container">
|
||||
<div class="left-section">
|
||||
<el-dropdown @command="handleCreate">
|
||||
<el-button type="primary">
|
||||
{{ $t('commons.button.create') }}
|
||||
<el-icon><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="dir">
|
||||
<svg-icon iconName="p-file-folder"></svg-icon>
|
||||
{{ $t('file.dir') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="file">
|
||||
<svg-icon iconName="p-file-normal"></svg-icon>
|
||||
{{ $t('file.file') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button-group>
|
||||
<el-button plain @click="openUpload">{{ $t('file.upload') }}</el-button>
|
||||
<el-button plain @click="openWget">{{ $t('file.remoteFile') }}</el-button>
|
||||
<el-button plain @click="openMove('copy')" :disabled="selects.length === 0">
|
||||
{{ $t('file.copy') }}
|
||||
</el-button>
|
||||
<el-button plain @click="openMove('cut')" :disabled="selects.length === 0">
|
||||
{{ $t('file.move') }}
|
||||
</el-button>
|
||||
<el-button plain @click="openCompress(selects)" :disabled="selects.length === 0">
|
||||
{{ $t('file.compress') }}
|
||||
</el-button>
|
||||
<el-button plain @click="openBatchRole(selects)" :disabled="selects.length === 0">
|
||||
{{ $t('file.role') }}
|
||||
</el-button>
|
||||
<el-button plain @click="batchDelFiles" :disabled="selects.length === 0">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
|
||||
<el-button class="btn" @click="toTerminal">
|
||||
<el-button class="btn" @click="toTerminal">
|
||||
{{ $t('menu.terminal') }}
|
||||
</el-button>
|
||||
|
||||
<el-button-group class="copy-button" v-if="moveOpen">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
:content="$t('file.paste')"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-button plain @click="openPaste">
|
||||
{{ $t('file.paste') }}({{ fileMove.count }})
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
:content="$t('file.cancel')"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-button plain class="close" @click="closeMove">
|
||||
<el-icon class="close-icon"><Close /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
</div>
|
||||
|
||||
<div class="right-section">
|
||||
<el-popover placement="bottom" :width="200" trigger="hover" @before-enter="getFavoriates">
|
||||
<template #reference>
|
||||
<el-button @click="openFavorite">
|
||||
{{ $t('file.favorite') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="favorite-item">
|
||||
<el-table :data="favorites">
|
||||
<el-table-column prop="name">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
:content="row.path"
|
||||
placement="top"
|
||||
>
|
||||
<span
|
||||
class="table-link text-ellipsis"
|
||||
@click="toFavorite(row)"
|
||||
type="primary"
|
||||
>
|
||||
<svg-icon
|
||||
v-if="row.isDir"
|
||||
className="table-icon"
|
||||
iconName="p-file-folder"
|
||||
></svg-icon>
|
||||
<svg-icon
|
||||
v-else
|
||||
className="table-icon"
|
||||
iconName="p-file-normal"
|
||||
></svg-icon>
|
||||
{{ row.name }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-popover>
|
||||
|
||||
<el-button class="btn" @click="openRecycleBin">
|
||||
{{ $t('file.recycleBin') }}
|
||||
</el-button>
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="req.search"
|
||||
clearable
|
||||
@clear="search()"
|
||||
@keydown.enter="search()"
|
||||
:placeholder="$t('file.search')"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-checkbox v-model="req.containSub">
|
||||
{{ $t('file.sub') }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button icon="Search" @click="search" round />
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block flex flex-wrap gap-4 sm:hidden">
|
||||
<el-dropdown @command="handleCreate">
|
||||
<el-button type="primary">
|
||||
{{ $t('commons.button.create') }}
|
||||
<el-icon><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="dir">
|
||||
<svg-icon iconName="p-file-folder"></svg-icon>
|
||||
{{ $t('file.dir') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="file">
|
||||
<svg-icon iconName="p-file-normal"></svg-icon>
|
||||
{{ $t('file.file') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-dropdown @command="handleFilePatch">
|
||||
<el-button type="primary">
|
||||
{{ $t('file.batchoperation') }}
|
||||
<el-icon><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="openUpload">
|
||||
{{ $t('file.upload') }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item command="openWget">
|
||||
{{ $t('file.remoteFile') }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item command="openMove:copy" :disabled="selects.length === 0">
|
||||
{{ $t('file.copy') }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item command="openMove:cut" :disabled="selects.length === 0">
|
||||
{{ $t('file.move') }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item command="openCompress" :disabled="selects.length === 0">
|
||||
{{ $t('file.compress') }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item command="openBatchRole" :disabled="selects.length === 0">
|
||||
{{ $t('file.role') }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item command="batchDelFiles" :disabled="selects.length === 0">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
||||
<el-button-group v-if="moveOpen">
|
||||
<el-tooltip class="box-item" effect="dark" :content="$t('file.paste')" placement="bottom">
|
||||
<el-button plain @click="openPaste">{{ $t('file.paste') }}({{ fileMove.count }})</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="box-item" effect="dark" :content="$t('file.cancel')" placement="bottom">
|
||||
<el-button plain class="close" @click="closeMove">
|
||||
<el-icon class="close-icon"><Close /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
|
||||
<div class="flex flex-row gap-4">
|
||||
<el-button @click="toTerminal">
|
||||
{{ $t('menu.terminal') }}
|
||||
</el-button>
|
||||
|
||||
<el-button-group class="copy-button" v-if="moveOpen">
|
||||
<el-tooltip class="box-item" effect="dark" :content="$t('file.paste')" placement="bottom">
|
||||
<el-button plain @click="openPaste">
|
||||
{{ $t('file.paste') }}({{ fileMove.count }})
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="box-item" effect="dark" :content="$t('file.cancel')" placement="bottom">
|
||||
<el-button plain class="close" @click="closeMove">
|
||||
<el-icon class="close-icon"><Close /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
</div>
|
||||
|
||||
<div class="right-section">
|
||||
<el-popover placement="bottom" :width="200" trigger="hover" @before-enter="getFavoriates">
|
||||
<template #reference>
|
||||
<el-button @click="openFavorite">
|
||||
{{ $t('file.favorite') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="favorite-item">
|
||||
<div>
|
||||
<el-popover placement="bottom" trigger="hover" @before-enter="getFavoriates">
|
||||
<template #reference>
|
||||
<el-button @click="openFavorite">
|
||||
{{ $t('file.favorite') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<el-table :data="favorites">
|
||||
<el-table-column prop="name">
|
||||
<template #default="{ row }">
|
||||
@@ -154,31 +304,29 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-popover>
|
||||
</el-popover>
|
||||
</div>
|
||||
|
||||
<el-button class="btn" @click="openRecycleBin">
|
||||
<el-button @click="openRecycleBin">
|
||||
{{ $t('file.recycleBin') }}
|
||||
</el-button>
|
||||
<div class="search-button">
|
||||
<el-input
|
||||
v-model="req.search"
|
||||
clearable
|
||||
@clear="search()"
|
||||
@keydown.enter="search()"
|
||||
:placeholder="$t('file.search')"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-checkbox v-model="req.containSub">
|
||||
{{ $t('file.sub') }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button icon="Search" @click="search" round />
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="req.search"
|
||||
clearable
|
||||
@clear="search()"
|
||||
@keydown.enter="search()"
|
||||
:placeholder="$t('file.search')"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-checkbox v-model="req.containSub">
|
||||
{{ $t('file.sub') }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button icon="Search" @click="search" round />
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
@@ -443,6 +591,34 @@ const mobile = computed(() => {
|
||||
return globalStore.isMobile();
|
||||
});
|
||||
|
||||
const handleFilePatch = (command: string) => {
|
||||
switch (command) {
|
||||
case 'openUpload':
|
||||
openUpload();
|
||||
break;
|
||||
case 'openWget':
|
||||
openWget();
|
||||
break;
|
||||
case 'openMove:copy':
|
||||
openMove('copy');
|
||||
break;
|
||||
case 'openMove:cut':
|
||||
openMove('cut');
|
||||
break;
|
||||
case 'openCompress':
|
||||
openCompress(selects.value);
|
||||
break;
|
||||
case 'openBatchRole':
|
||||
openBatchRole(selects.value);
|
||||
break;
|
||||
case 'batchDelFiles':
|
||||
openDetail(selects.value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const search = async () => {
|
||||
loading.value = true;
|
||||
if (req.search != '') {
|
||||
@@ -554,23 +730,18 @@ const jump = async (url: string) => {
|
||||
history.push(url);
|
||||
pointer = history.length - 1;
|
||||
|
||||
const oldUrl = req.path;
|
||||
const oldPageSize = req.pageSize;
|
||||
// reset search params before exec jump
|
||||
Object.assign(req, initData());
|
||||
req.path = url;
|
||||
req.containSub = false;
|
||||
req.search = '';
|
||||
req.pageSize = oldPageSize;
|
||||
const { path: oldUrl, pageSize: oldPageSize } = req;
|
||||
Object.assign(req, initData(), { path: url, containSub: false, search: '', pageSize: oldPageSize });
|
||||
let searchResult = await searchFile();
|
||||
|
||||
globalStore.setLastFilePath(req.path);
|
||||
// check search result,the file is exists?
|
||||
if (!searchResult.data.path) {
|
||||
req.path = oldUrl;
|
||||
globalStore.setLastFilePath(req.path);
|
||||
MsgWarning(i18n.global.t('commons.res.notFound'));
|
||||
return;
|
||||
}
|
||||
req.path = searchResult.data.path;
|
||||
globalStore.setLastFilePath(req.path);
|
||||
handleSearchResult(searchResult);
|
||||
getPaths(req.path);
|
||||
nextTick(function () {
|
||||
@@ -704,12 +875,16 @@ const openView = (item: File.File) => {
|
||||
compress: openDeCompress,
|
||||
text: () => openCodeEditor(item.path, item.extension),
|
||||
};
|
||||
|
||||
return actionMap[fileType] ? actionMap[fileType](item) : openCodeEditor(item.path, item.extension);
|
||||
const path = item.isSymlink ? item.linkPath : item.path;
|
||||
return actionMap[fileType] ? actionMap[fileType](item) : openCodeEditor(path, item.extension);
|
||||
};
|
||||
|
||||
const openPreview = (item: File.File, fileType: string) => {
|
||||
filePreview.path = item.path;
|
||||
if (item.mode.toString() == '-' && item.user == '-' && item.group == '-') {
|
||||
MsgWarning(i18n.global.t('file.fileCanNotRead'));
|
||||
return;
|
||||
}
|
||||
filePreview.path = item.isSymlink ? item.linkPath : item.path;
|
||||
filePreview.name = item.name;
|
||||
filePreview.extension = item.extension;
|
||||
filePreview.fileType = fileType;
|
||||
@@ -910,7 +1085,7 @@ const buttons = [
|
||||
label: i18n.global.t('file.deCompress'),
|
||||
click: openDeCompress,
|
||||
disabled: (row: File.File) => {
|
||||
return row.isDir;
|
||||
return !isDecompressFile(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -950,6 +1125,20 @@ const buttons = [
|
||||
},
|
||||
];
|
||||
|
||||
const isDecompressFile = (row: File.File) => {
|
||||
if (row.isDir) {
|
||||
return false;
|
||||
}
|
||||
if (getFileType(row.extension) === 'compress') {
|
||||
return true;
|
||||
}
|
||||
if (row.mimeType == 'application/octet-stream') {
|
||||
return false;
|
||||
} else {
|
||||
return Mimetypes.get(row.mimeType) != undefined;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (router.currentRoute.value.query.path) {
|
||||
req.path = String(router.currentRoute.value.query.path);
|
||||
@@ -1018,6 +1207,7 @@ onMounted(() => {
|
||||
.right-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.left-section > *:not(:first-child) {
|
||||
|
||||
@@ -115,7 +115,9 @@ const acceptParams = (props: EditProps) => {
|
||||
isFullscreen.value = fileType.value === 'excel';
|
||||
|
||||
loading.value = true;
|
||||
fileUrl.value = `${import.meta.env.VITE_API_URL as string}/files/download?path=${encodeURIComponent(props.path)}`;
|
||||
fileUrl.value = `${import.meta.env.VITE_API_URL as string}/files/download?path=${encodeURIComponent(
|
||||
props.path,
|
||||
)}×tamp=${new Date().getTime()}`;
|
||||
open.value = true;
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
@@ -19,20 +19,20 @@
|
||||
|
||||
<LayoutContent :title="$t('firewall.forwardRule')" :class="{ mask: fireStatus != 'running' }">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('commons.button.create') }}{{ $t('firewall.forwardRule') }}
|
||||
</el-button>
|
||||
<el-button @click="onDelete(null)" plain :disabled="selects.length === 0">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
|
||||
@@ -59,7 +59,7 @@ import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { Host } from '@/api/interface/host';
|
||||
import { operateForwardRule } from '@/api/modules/host';
|
||||
import { checkCidr, checkIp, checkPort, deepCopy } from '@/utils/util';
|
||||
import { checkCidr, checkCidrV6, checkIp, checkPort, deepCopy } from '@/utils/util';
|
||||
|
||||
const loading = ref();
|
||||
const oldRule = ref<Host.RuleForward>();
|
||||
@@ -111,8 +111,14 @@ function checkAddress(rule: any, value: string, callback: any) {
|
||||
let addrs = value.split(',');
|
||||
for (const item of addrs) {
|
||||
if (item.indexOf('/') !== -1) {
|
||||
if (checkCidr(item)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
if (item.indexOf(':') !== -1) {
|
||||
if (checkCidrV6(item)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
} else {
|
||||
if (checkCidr(item)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (checkIp(item)) {
|
||||
|
||||
@@ -20,20 +20,20 @@
|
||||
|
||||
<LayoutContent :title="$t('firewall.ipRule')" :class="{ mask: fireStatus != 'running' }">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('commons.button.create') }} {{ $t('firewall.ipRule') }}
|
||||
</el-button>
|
||||
<el-button @click="onDelete(null)" plain :disabled="selects.length === 0">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #search>
|
||||
<div class="flx-align-center">
|
||||
|
||||
@@ -56,7 +56,7 @@ import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { Host } from '@/api/interface/host';
|
||||
import { operateIPRule, updateAddrRule } from '@/api/modules/host';
|
||||
import { checkCidr, checkIpV4V6, deepCopy } from '@/utils/util';
|
||||
import { checkCidr, checkCidrV6, checkIpV4V6, deepCopy } from '@/utils/util';
|
||||
|
||||
const loading = ref();
|
||||
const oldRule = ref<Host.RuleIP>();
|
||||
@@ -95,8 +95,14 @@ function checkAddress(rule: any, value: any, callback: any) {
|
||||
let addrs = dialogData.value.rowData.address.split(',');
|
||||
for (const item of addrs) {
|
||||
if (item.indexOf('/') !== -1) {
|
||||
if (checkCidr(item)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
if (item.indexOf(':') !== -1) {
|
||||
if (checkCidrV6(item)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
} else {
|
||||
if (checkCidr(item)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (checkIpV4V6(item)) {
|
||||
|
||||
@@ -52,20 +52,20 @@
|
||||
</div>
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('commons.button.create') }}{{ $t('firewall.portRule') }}
|
||||
</el-button>
|
||||
<el-button @click="onDelete(null)" plain :disabled="selects.length === 0">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
|
||||
@@ -81,7 +81,7 @@ import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
import { Host } from '@/api/interface/host';
|
||||
import { operatePortRule, updatePortRule } from '@/api/modules/host';
|
||||
import { checkCidr, checkIpV4V6, checkPort, deepCopy } from '@/utils/util';
|
||||
import { checkCidr, checkCidrV6, checkIpV4V6, checkPort, deepCopy } from '@/utils/util';
|
||||
|
||||
const loading = ref();
|
||||
const oldRule = ref<Host.RulePort>();
|
||||
@@ -128,8 +128,14 @@ function checkAddress(rule: any, value: any, callback: any) {
|
||||
let addrs = dialogData.value.rowData.address.split(',');
|
||||
for (const item of addrs) {
|
||||
if (item.indexOf('/') !== -1) {
|
||||
if (checkCidr(item)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
if (item.indexOf(':') !== -1) {
|
||||
if (checkCidrV6(item)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
} else {
|
||||
if (checkCidr(item)) {
|
||||
return callback(new Error(i18n.global.t('firewall.addressFormatError')));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (checkIpV4V6(item)) {
|
||||
|
||||
@@ -2,42 +2,46 @@
|
||||
<div>
|
||||
<div class="app-status" style="margin-top: 20px">
|
||||
<el-card>
|
||||
<div>
|
||||
<el-tag effect="dark" type="success">{{ baseInfo.name }}</el-tag>
|
||||
<el-tag round class="status-content" v-if="baseInfo.status === 'running'" type="success">
|
||||
{{ $t('commons.status.running') }}
|
||||
</el-tag>
|
||||
<el-tag round class="status-content" v-if="baseInfo.status === 'not running'" type="info">
|
||||
{{ $t('commons.status.stopped') }}
|
||||
</el-tag>
|
||||
<el-tag class="status-content">{{ $t('app.version') }}: {{ baseInfo.version }}</el-tag>
|
||||
|
||||
<span v-if="baseInfo.status === 'running'" class="buttons">
|
||||
<el-button type="primary" @click="onOperate('stop')" link>
|
||||
<div class="flex w-full flex-col gap-4 md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<el-tag effect="dark" type="success">{{ baseInfo.name }}</el-tag>
|
||||
<el-tag round v-if="baseInfo.status === 'running'" type="success">
|
||||
{{ $t('commons.status.running') }}
|
||||
</el-tag>
|
||||
<el-tag round v-if="baseInfo.status === 'not running'" type="info">
|
||||
{{ $t('commons.status.stopped') }}
|
||||
</el-tag>
|
||||
<el-tag>{{ $t('app.version') }}: {{ baseInfo.version }}</el-tag>
|
||||
</div>
|
||||
<div class="mt-0.5">
|
||||
<el-button type="primary" v-if="baseInfo.status === 'running'" @click="onOperate('stop')" link>
|
||||
{{ $t('commons.button.stop') }}
|
||||
</el-button>
|
||||
</span>
|
||||
<span v-if="baseInfo.status === 'not running'" class="buttons">
|
||||
<el-button type="primary" @click="onOperate('start')" link>
|
||||
<el-button
|
||||
type="primary"
|
||||
v-if="baseInfo.status === 'not running'"
|
||||
@click="onOperate('start')"
|
||||
link
|
||||
>
|
||||
{{ $t('commons.button.start') }}
|
||||
</el-button>
|
||||
</span>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" @click="onOperate('restart')" link>
|
||||
{{ $t('container.restart') }}
|
||||
</el-button>
|
||||
<span v-if="onPing !== 'None'">
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" link>{{ $t('firewall.noPing') }}</el-button>
|
||||
<el-switch
|
||||
size="small"
|
||||
class="ml-2"
|
||||
inactive-value="Disable"
|
||||
active-value="Enable"
|
||||
@change="onPingOperate"
|
||||
v-model="onPing"
|
||||
/>
|
||||
</span>
|
||||
<el-button type="primary" @click="onOperate('restart')" link>
|
||||
{{ $t('container.restart') }}
|
||||
</el-button>
|
||||
<span v-if="onPing !== 'None'">
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="primary" link>{{ $t('firewall.noPing') }}</el-button>
|
||||
<el-switch
|
||||
size="small"
|
||||
class="ml-2"
|
||||
inactive-value="Disable"
|
||||
active-value="Enable"
|
||||
@change="onPingOperate"
|
||||
v-model="onPing"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
@@ -4,18 +4,19 @@
|
||||
|
||||
<div class="content-container__search">
|
||||
<el-card>
|
||||
<span style="font-size: 14px">{{ $t('monitor.globalFilter') }}</span>
|
||||
<el-date-picker
|
||||
@change="searchGlobal()"
|
||||
v-model="timeRangeGlobal"
|
||||
type="datetimerange"
|
||||
:range-separator="$t('commons.search.timeRange')"
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
:shortcuts="shortcuts"
|
||||
style="max-width: 360px; width: 100%; margin-left: 10px"
|
||||
:size="mobile ? 'small' : 'default'"
|
||||
></el-date-picker>
|
||||
<div :class="mobile ? 'flx-wrap' : 'flx-justify-between'">
|
||||
<el-date-picker
|
||||
@change="searchGlobal()"
|
||||
v-model="timeRangeGlobal"
|
||||
type="datetimerange"
|
||||
:range-separator="$t('commons.search.timeRange')"
|
||||
:start-placeholder="$t('commons.search.timeStart')"
|
||||
:end-placeholder="$t('commons.search.timeEnd')"
|
||||
:shortcuts="shortcuts"
|
||||
style="max-width: 360px; width: 100%"
|
||||
:size="mobile ? 'small' : 'default'"
|
||||
></el-date-picker>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-row :gutter="20" style="margin-top: 20px">
|
||||
|
||||
@@ -3,37 +3,28 @@
|
||||
<FireRouter />
|
||||
<LayoutContent :title="$t('menu.network')" v-loading="loading">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div style="width: 100%">
|
||||
<el-form-item style="float: right">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<TableSearch
|
||||
@search="search()"
|
||||
:placeholder="$t('process.pid')"
|
||||
v-model:searchName="netSearch.processID"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<TableSearch
|
||||
@search="search()"
|
||||
:placeholder="$t('process.processName')"
|
||||
v-model:searchName="netSearch.processName"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<TableSearch
|
||||
@search="search()"
|
||||
:placeholder="$t('commons.table.port')"
|
||||
v-model:searchName="netSearch.port"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div><!-- 占位 --></div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSearch
|
||||
@search="search()"
|
||||
:placeholder="$t('process.pid')"
|
||||
v-model:searchName="netSearch.processID"
|
||||
/>
|
||||
|
||||
<TableSearch
|
||||
@search="search()"
|
||||
:placeholder="$t('process.processName')"
|
||||
v-model:searchName="netSearch.processName"
|
||||
/>
|
||||
|
||||
<TableSearch
|
||||
@search="search()"
|
||||
:placeholder="$t('commons.table.port')"
|
||||
v-model:searchName="netSearch.port"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :data="data" @sort-change="changeSort" @filter-change="changeFilter" ref="tableRef">
|
||||
|
||||
@@ -3,37 +3,28 @@
|
||||
<FireRouter />
|
||||
<LayoutContent :title="$t('menu.process')" v-loading="loading">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div style="width: 100%">
|
||||
<el-form-item class="float-right">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<TableSearch
|
||||
@search="search()"
|
||||
:placeholder="$t('process.pid')"
|
||||
v-model:searchName="processSearch.pid"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<TableSearch
|
||||
@search="search()"
|
||||
:placeholder="$t('commons.table.name')"
|
||||
v-model:searchName="processSearch.name"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<TableSearch
|
||||
@search="search()"
|
||||
:placeholder="$t('commons.table.user')"
|
||||
v-model:searchName="processSearch.username"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div><!-- 占位 --></div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSearch
|
||||
@search="search()"
|
||||
:placeholder="$t('process.pid')"
|
||||
v-model:searchName="processSearch.pid"
|
||||
/>
|
||||
|
||||
<TableSearch
|
||||
@search="search()"
|
||||
:placeholder="$t('commons.table.name')"
|
||||
v-model:searchName="processSearch.name"
|
||||
/>
|
||||
|
||||
<TableSearch
|
||||
@search="search()"
|
||||
:placeholder="$t('commons.table.user')"
|
||||
v-model:searchName="processSearch.username"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :data="data" @sort-change="changeSort" @filter-change="changeFilter" ref="tableRef">
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
</el-select>
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16"></el-col>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div><!-- 占位 --></div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchInfo" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #main>
|
||||
|
||||
@@ -3,14 +3,11 @@
|
||||
<FireRouter />
|
||||
<LayoutContent :title="$t('ssh.session')">
|
||||
<template #toolbar>
|
||||
<div style="width: 100%">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8"></el-col>
|
||||
<el-col :span="8"></el-col>
|
||||
<el-col :span="8">
|
||||
<TableSearch @search="search()" v-model:searchName="sshSearch.loginUser" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div><!-- 占位 --></div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSearch @search="search()" v-model:searchName="sshSearch.loginUser" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
|
||||
@@ -4,25 +4,27 @@
|
||||
|
||||
<div class="app-status" style="margin-top: 20px">
|
||||
<el-card>
|
||||
<div>
|
||||
<el-tag style="float: left" effect="dark" type="success">SSH</el-tag>
|
||||
<el-tag round class="status-content" v-if="form.status === 'Enable'" type="success">
|
||||
{{ $t('commons.status.running') }}
|
||||
</el-tag>
|
||||
<el-popover
|
||||
v-if="form.status === 'Disable'"
|
||||
placement="top-start"
|
||||
trigger="hover"
|
||||
width="450"
|
||||
:content="form.message"
|
||||
>
|
||||
<template #reference>
|
||||
<el-tag round class="status-content" v-if="form.status === 'Disable'" type="info">
|
||||
{{ $t('commons.status.stopped') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
<span class="buttons">
|
||||
<div class="flex w-full flex-col gap-4 md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<el-tag style="float: left" effect="dark" type="success">SSH</el-tag>
|
||||
<el-tag round v-if="form.status === 'Enable'" type="success">
|
||||
{{ $t('commons.status.running') }}
|
||||
</el-tag>
|
||||
<el-popover
|
||||
v-if="form.status === 'Disable'"
|
||||
placement="top-start"
|
||||
trigger="hover"
|
||||
width="450"
|
||||
:content="form.message"
|
||||
>
|
||||
<template #reference>
|
||||
<el-tag round v-if="form.status === 'Disable'" type="info">
|
||||
{{ $t('commons.status.stopped') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="mt-0.5">
|
||||
<el-button v-if="form.status === 'Enable'" type="primary" @click="onOperate('stop')" link>
|
||||
{{ $t('commons.button.stop') }}
|
||||
</el-button>
|
||||
@@ -45,7 +47,7 @@
|
||||
@change="onOperate(autoStart)"
|
||||
v-model="autoStart"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
@@ -5,15 +5,19 @@
|
||||
<el-alert type="info" :title="$t('terminal.quickCommandHelper')" :closable="false" />
|
||||
</template>
|
||||
<template #toolbar>
|
||||
<el-button type="primary" @click="onCreate()">
|
||||
{{ $t('commons.button.create') }} {{ $t('terminal.quickCommand') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="onOpenGroupDialog()">
|
||||
{{ $t('terminal.group') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain :disabled="selects.length === 0" @click="batchDelete(null)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
<div class="flex w-full flex-col gap-4 md:justify-between md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<el-button type="primary" @click="onCreate()">
|
||||
{{ $t('commons.button.create') }} {{ $t('terminal.quickCommand') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="onOpenGroupDialog()">
|
||||
{{ $t('terminal.group') }}
|
||||
</el-button>
|
||||
<el-button type="primary" plain :disabled="selects.length === 0" @click="batchDelete(null)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #search>
|
||||
<el-row :gutter="5">
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<div>
|
||||
<LayoutContent v-loading="loading" :title="$t('terminal.host')">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :span="20">
|
||||
<div class="flex w-full flex-col gap-4 md:justify-between md:flex-row">
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<el-button type="primary" @click="onOpenDialog('create')">
|
||||
{{ $t('terminal.addHost') }}
|
||||
</el-button>
|
||||
@@ -13,11 +13,11 @@
|
||||
<el-button type="primary" plain :disabled="selects.length === 0" @click="onBatchDelete(null)">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSearch @search="search()" v-model:searchName="info" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #search>
|
||||
<el-select v-model="group" @change="search()" clearable class="p-w-200">
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
:ref="'t-' + item.index"
|
||||
:key="item.Refresh"
|
||||
></Terminal>
|
||||
<div>
|
||||
<el-select v-model="quickCmd" clearable filterable @change="quickInput" style="width: 25%">
|
||||
<div class="flex w-full flex-col md:flex-row">
|
||||
<el-select v-model="quickCmd" clearable filterable @change="quickInput">
|
||||
<template #prefix>{{ $t('terminal.quickCommand') }}</template>
|
||||
<el-option-group v-for="group in commandTree" :key="group.label" :label="group.label">
|
||||
<el-option
|
||||
@@ -60,7 +60,7 @@
|
||||
/>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
<el-input v-model="batchVal" @keyup.enter="batchInput" style="width: 75%">
|
||||
<el-input v-model="batchVal" @keyup.enter="batchInput">
|
||||
<template #prepend>
|
||||
<el-checkbox :label="$t('terminal.batchInput')" v-model="isBatch" />
|
||||
</template>
|
||||
@@ -407,7 +407,7 @@ defineExpose({
|
||||
onMounted(() => {
|
||||
if (router.currentRoute.value.query.path) {
|
||||
const path = String(router.currentRoute.value.query.path);
|
||||
initCmd.value = `cd ${path} \n`;
|
||||
initCmd.value = `cd "${path}" \n`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<div>
|
||||
<LayoutContent v-loading="loading" :title="$t('logs.login')">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button class="tag-button no-active" @click="onChangeRoute('OperationLog')">
|
||||
{{ $t('logs.operation') }}
|
||||
</el-button>
|
||||
@@ -13,14 +13,12 @@
|
||||
<el-button class="tag-button no-active" @click="onChangeRoute('SystemLog')">
|
||||
{{ $t('logs.system') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
<div class="flex justify-end">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchIP" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchIP" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #search>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<div>
|
||||
<LayoutContent v-loading="loading" :title="$t('logs.operation')">
|
||||
<template #toolbar>
|
||||
<el-row>
|
||||
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16">
|
||||
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<el-button type="primary" class="tag-button" @click="onChangeRoute('OperationLog')">
|
||||
{{ $t('logs.operation') }}
|
||||
</el-button>
|
||||
@@ -13,44 +13,40 @@
|
||||
<el-button class="tag-button no-active" @click="onChangeRoute('SystemLog')">
|
||||
{{ $t('logs.system') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<TableSetting @search="search()" />
|
||||
<TableSearch @search="search()" v-model:searchName="searchName" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #search>
|
||||
<el-select v-model="searchGroup" @change="search()" clearable class="p-w-200">
|
||||
<template #prefix>{{ $t('logs.resource') }}</template>
|
||||
<el-option :label="$t('commons.table.all')" value=""></el-option>
|
||||
<el-option :label="$t('logs.detail.apps')" value="apps"></el-option>
|
||||
<el-option :label="$t('logs.detail.websites')" value="websites"></el-option>
|
||||
<el-option :label="$t('logs.detail.runtimes')" value="runtimes"></el-option>
|
||||
<el-option :label="$t('logs.detail.databases')" value="databases"></el-option>
|
||||
<el-option :label="$t('logs.detail.containers')" value="containers"></el-option>
|
||||
<el-option :label="$t('logs.detail.cronjobs')" value="cronjobs"></el-option>
|
||||
<el-option :label="$t('logs.detail.files')" value="files"></el-option>
|
||||
<el-option :label="$t('logs.detail.hosts')" value="hosts"></el-option>
|
||||
<el-option :label="$t('logs.detail.process')" value="process"></el-option>
|
||||
<el-option :label="$t('logs.detail.logs')" value="logs"></el-option>
|
||||
<el-option :label="$t('logs.detail.settings')" value="settings"></el-option>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="searchStatus"
|
||||
@change="search()"
|
||||
clearable
|
||||
style="margin-left: 10px"
|
||||
class="p-w-200"
|
||||
>
|
||||
<template #prefix>{{ $t('commons.table.status') }}</template>
|
||||
<el-option :label="$t('commons.table.all')" value=""></el-option>
|
||||
<el-option :label="$t('commons.status.success')" value="Success"></el-option>
|
||||
<el-option :label="$t('commons.status.failed')" value="Failed"></el-option>
|
||||
</el-select>
|
||||
<el-button type="primary" style="margin-left: 10px" plain @click="onClean()">
|
||||
{{ $t('logs.deleteLogs') }}
|
||||
</el-button>
|
||||
<div class="flex flex-wrap gap-2 sm:gap-4 items-center">
|
||||
<el-select v-model="searchGroup" @change="search()" clearable class="p-w-200">
|
||||
<template #prefix>{{ $t('logs.resource') }}</template>
|
||||
<el-option :label="$t('commons.table.all')" value=""></el-option>
|
||||
<el-option :label="$t('logs.detail.apps')" value="apps"></el-option>
|
||||
<el-option :label="$t('logs.detail.websites')" value="websites"></el-option>
|
||||
<el-option :label="$t('logs.detail.runtimes')" value="runtimes"></el-option>
|
||||
<el-option :label="$t('logs.detail.databases')" value="databases"></el-option>
|
||||
<el-option :label="$t('logs.detail.containers')" value="containers"></el-option>
|
||||
<el-option :label="$t('logs.detail.cronjobs')" value="cronjobs"></el-option>
|
||||
<el-option :label="$t('logs.detail.files')" value="files"></el-option>
|
||||
<el-option :label="$t('logs.detail.hosts')" value="hosts"></el-option>
|
||||
<el-option :label="$t('logs.detail.process')" value="process"></el-option>
|
||||
<el-option :label="$t('logs.detail.logs')" value="logs"></el-option>
|
||||
<el-option :label="$t('logs.detail.settings')" value="settings"></el-option>
|
||||
</el-select>
|
||||
<el-select v-model="searchStatus" @change="search()" clearable class="p-w-200">
|
||||
<template #prefix>{{ $t('commons.table.status') }}</template>
|
||||
<el-option :label="$t('commons.table.all')" value=""></el-option>
|
||||
<el-option :label="$t('commons.status.success')" value="Success"></el-option>
|
||||
<el-option :label="$t('commons.status.failed')" value="Failed"></el-option>
|
||||
</el-select>
|
||||
<el-button type="primary" plain @click="onClean()">
|
||||
{{ $t('logs.deleteLogs') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
</el-row>
|
||||
</template>
|
||||
<template #search>
|
||||
<el-select class="float-left p-w-200" v-model="logConfig.name" @change="search()">
|
||||
<template #prefix>{{ $t('commons.button.log') }}</template>
|
||||
<el-option v-for="(item, index) in fileList" :key="index" :label="item" :value="item" />
|
||||
</el-select>
|
||||
<div class="watchCheckbox">
|
||||
<div class="flex flex-wrap gap-2 sm:gap-4">
|
||||
<el-select class="p-w-200" v-model="logConfig.name" @change="search()">
|
||||
<template #prefix>{{ $t('commons.button.log') }}</template>
|
||||
<el-option v-for="(item, index) in fileList" :key="index" :label="item" :value="item" />
|
||||
</el-select>
|
||||
<el-checkbox border @change="changeTail" v-model="isWatch">
|
||||
{{ $t('commons.button.watch') }}
|
||||
</el-checkbox>
|
||||
@@ -31,6 +31,7 @@
|
||||
<LogFile
|
||||
ref="logRef"
|
||||
:config="logConfig"
|
||||
v-if="showLog"
|
||||
:default-button="false"
|
||||
v-model:loading="loading"
|
||||
v-model:hasContent="hasContent"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user