mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f9f4ae740 | ||
|
|
8bec6f2b28 | ||
|
|
b397363c9d | ||
|
|
acb800b505 | ||
|
|
ec37af2f18 | ||
|
|
d2b030e698 | ||
|
|
c6f496f2a5 | ||
|
|
722e1929f1 | ||
|
|
f9a20ec443 | ||
|
|
f5568d4874 | ||
|
|
87bbe4ee12 | ||
|
|
a034b03c1d | ||
|
|
470f4e4db7 | ||
|
|
9428efd8c0 | ||
|
|
bf4d91b68b | ||
|
|
781155b029 | ||
|
|
3caf0ac529 | ||
|
|
7d25228e55 | ||
|
|
4fb099912e |
@@ -393,6 +393,28 @@ func (b *BaseApi) CleanContainerLog(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Clean compose log
|
||||
// @Accept json
|
||||
// @Param request body dto.ComposeLogClean true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /containers/compose/clean/log [post]
|
||||
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清理容器编排 [name] 日志","formatEN":"clean compose [name] logs"}
|
||||
func (b *BaseApi) CleanComposeLog(c *gin.Context) {
|
||||
var req dto.ComposeLogClean
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := containerService.ComposeLogClean(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Container
|
||||
// @Summary Rename Container
|
||||
// @Accept json
|
||||
|
||||
@@ -291,6 +291,10 @@ type ComposeUpdate struct {
|
||||
Content string `json:"content" validate:"required"`
|
||||
Env []string `json:"env"`
|
||||
}
|
||||
type ComposeLogClean struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
}
|
||||
|
||||
type ContainerLog struct {
|
||||
Container string `json:"container" validate:"required"`
|
||||
|
||||
@@ -258,6 +258,7 @@ type DatabaseInfo struct {
|
||||
Version string `json:"version"`
|
||||
Address string `json:"address"`
|
||||
Port uint `json:"port"`
|
||||
InitialDB string `json:"initialDB"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
|
||||
@@ -309,13 +310,14 @@ type DatabaseCreate struct {
|
||||
}
|
||||
|
||||
type DatabaseUpdate struct {
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Version string `json:"version" validate:"required"`
|
||||
Address string `json:"address"`
|
||||
Port uint `json:"port"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password"`
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Version string `json:"version" validate:"required"`
|
||||
Address string `json:"address"`
|
||||
Port uint `json:"port"`
|
||||
InitialDB string `json:"initialDB"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password"`
|
||||
|
||||
SSL bool `json:"ssl"`
|
||||
RootCert string `json:"rootCert"`
|
||||
|
||||
@@ -39,6 +39,7 @@ type AppItem struct {
|
||||
Tags []string `json:"tags"`
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
Recommend int `json:"recommend"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type TagDTO struct {
|
||||
|
||||
@@ -125,6 +125,7 @@ func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (*response.
|
||||
GpuSupport: ap.GpuSupport,
|
||||
Recommend: ap.Recommend,
|
||||
Description: ap.GetDescription(ctx),
|
||||
Type: ap.Type,
|
||||
}
|
||||
appDTOs = append(appDTOs, appDTO)
|
||||
tags, err := getAppTags(ap.ID, lang)
|
||||
|
||||
@@ -515,6 +515,7 @@ func (a *AppInstallService) GetServices(key string) ([]response.AppService, erro
|
||||
} else {
|
||||
service.From = constant.AppResourceRemote
|
||||
service.Status = constant.StatusRunning
|
||||
service.Config = map[string]string{"PANEL_DB_ROOT_PASSWORD": db.Password, "PANEL_DB_ROOT_USER": db.Username}
|
||||
}
|
||||
res = append(res, service)
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ type IContainerService interface {
|
||||
CreateVolume(req dto.VolumeCreate) error
|
||||
TestCompose(req dto.ComposeCreate) (bool, error)
|
||||
ComposeUpdate(req dto.ComposeUpdate) error
|
||||
ComposeLogClean(req dto.ComposeLogClean) error
|
||||
Prune(req dto.ContainerPrune) error
|
||||
|
||||
LoadUsers(req dto.OperationWithName) []string
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -284,6 +285,57 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ComposeLogClean(req dto.ComposeLogClean) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
options := container.ListOptions{All: true}
|
||||
options.Filters = filters.NewArgs()
|
||||
options.Filters.Add("label", composeProjectLabel)
|
||||
|
||||
list, err := client.ContainerList(context.Background(), options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
for _, item := range list {
|
||||
if name, ok := item.Labels[composeProjectLabel]; ok {
|
||||
if name != req.Name {
|
||||
continue
|
||||
}
|
||||
containerItem, err := client.ContainerInspect(ctx, item.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := client.ContainerStop(ctx, containerItem.ID, container.StopOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.OpenFile(containerItem.LogPath, os.O_RDWR|os.O_CREATE, constant.FilePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
if err = file.Truncate(0); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = file.Seek(0, 0)
|
||||
|
||||
files, _ := filepath.Glob(fmt.Sprintf("%s.*", containerItem.LogPath))
|
||||
for _, file := range files {
|
||||
_ = os.Remove(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
return u.ComposeOperation(dto.ComposeOperation{
|
||||
Name: req.Name,
|
||||
Path: req.Path,
|
||||
Operation: "restart",
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ContainerService) loadPath(req *dto.ComposeCreate) error {
|
||||
if req.From == "template" || req.From == "edit" {
|
||||
dir := fmt.Sprintf("%s/docker/compose/%s", global.Dir.DataDir, req.Name)
|
||||
|
||||
@@ -19,6 +19,7 @@ func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
defer client.Close()
|
||||
list, err := client.VolumeList(context.TODO(), volume.ListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
|
||||
@@ -119,9 +119,10 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
|
||||
if req.Timeout == 0 {
|
||||
req.Timeout = 30
|
||||
}
|
||||
var err error
|
||||
switch req.Type {
|
||||
case constant.AppPostgresql:
|
||||
_, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{
|
||||
_, err = postgresql.NewPostgresqlClient(pgclient.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
@@ -130,17 +131,15 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
|
||||
Password: req.Password,
|
||||
Timeout: req.Timeout,
|
||||
})
|
||||
return err == nil
|
||||
case constant.AppRedis:
|
||||
_, err := redisclient.NewRedisClient(redisclient.DBInfo{
|
||||
_, err = redisclient.NewRedisClient(redisclient.DBInfo{
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Password: req.Password,
|
||||
Timeout: req.Timeout,
|
||||
})
|
||||
return err == nil
|
||||
case "mysql", "mariadb":
|
||||
_, err := mysql.NewMysqlClient(client.DBInfo{
|
||||
_, err = mysql.NewMysqlClient(client.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
@@ -154,10 +153,13 @@ func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool {
|
||||
SkipVerify: req.SkipVerify,
|
||||
Timeout: req.Timeout,
|
||||
})
|
||||
return err == nil
|
||||
}
|
||||
if err != nil {
|
||||
global.LOG.Errorf("check database connection failed, err: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
|
||||
@@ -174,12 +176,13 @@ func (u *DatabaseService) Create(req dto.DatabaseCreate) error {
|
||||
switch req.Type {
|
||||
case constant.AppPostgresql:
|
||||
if _, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Timeout: req.Timeout,
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
InitialDB: req.InitialDB,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Timeout: req.Timeout,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -275,12 +278,13 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
|
||||
switch req.Type {
|
||||
case constant.AppPostgresql:
|
||||
if _, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Timeout: req.Timeout,
|
||||
From: "remote",
|
||||
Address: req.Address,
|
||||
Port: req.Port,
|
||||
InitialDB: req.InitialDB,
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
Timeout: req.Timeout,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -326,6 +330,7 @@ func (u *DatabaseService) Update(req dto.DatabaseUpdate) error {
|
||||
upMap["port"] = req.Port
|
||||
upMap["username"] = req.Username
|
||||
upMap["password"] = pass
|
||||
upMap["initial_db"] = req.InitialDB
|
||||
upMap["timeout"] = req.Timeout
|
||||
upMap["description"] = req.Description
|
||||
upMap["ssl"] = req.SSL
|
||||
|
||||
@@ -115,7 +115,7 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
|
||||
}
|
||||
|
||||
if req.From == "local" && req.Username == "root" {
|
||||
return nil, errors.New("Cannot set root as user name")
|
||||
return nil, errors.New("cannot set root as user name")
|
||||
}
|
||||
|
||||
cli, version, err := LoadMysqlClientByFrom(req.Database)
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/postgresql/client"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type PostgresqlService struct{}
|
||||
@@ -138,10 +137,6 @@ func (u *PostgresqlService) Create(ctx context.Context, req dto.PostgresqlDBCrea
|
||||
return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil)
|
||||
}
|
||||
|
||||
if req.From == "local" && req.Username == "root" {
|
||||
return nil, errors.New("Cannot set root as user name")
|
||||
}
|
||||
|
||||
cli, err := LoadPostgresqlClientByFrom(req.Database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -185,6 +180,7 @@ func LoadPostgresqlClientByFrom(database string) (postgresql.PostgresqlClient, e
|
||||
dbInfo.Port = databaseItem.Port
|
||||
dbInfo.Username = databaseItem.Username
|
||||
dbInfo.Password = databaseItem.Password
|
||||
dbInfo.InitialDB = databaseItem.InitialDB
|
||||
} else {
|
||||
app, err := appInstallRepo.LoadBaseInfo(databaseItem.Type, database)
|
||||
if err != nil {
|
||||
|
||||
@@ -413,6 +413,7 @@ func loadContainerTree() []dto.CleanTree {
|
||||
if err != nil {
|
||||
return treeData
|
||||
}
|
||||
defer client.Close()
|
||||
diskUsage, err := client.DiskUsage(context.Background(), types.DiskUsageOptions{})
|
||||
if err != nil {
|
||||
return treeData
|
||||
@@ -552,6 +553,7 @@ func dropBuildCache() (int, int) {
|
||||
global.LOG.Errorf("do not get docker client")
|
||||
return 0, 0
|
||||
}
|
||||
defer client.Close()
|
||||
opts := build.CachePruneOptions{}
|
||||
opts.All = true
|
||||
res, err := client.BuildCachePrune(context.Background(), opts)
|
||||
@@ -568,6 +570,7 @@ func dropImages() (int, int) {
|
||||
global.LOG.Errorf("do not get docker client")
|
||||
return 0, 0
|
||||
}
|
||||
defer client.Close()
|
||||
pruneFilters := filters.NewArgs()
|
||||
pruneFilters.Add("dangling", "false")
|
||||
res, err := client.ImagesPrune(context.Background(), pruneFilters)
|
||||
@@ -584,6 +587,7 @@ func dropContainers() (int, int) {
|
||||
global.LOG.Errorf("do not get docker client")
|
||||
return 0, 0
|
||||
}
|
||||
defer client.Close()
|
||||
pruneFilters := filters.NewArgs()
|
||||
res, err := client.ContainersPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
@@ -599,6 +603,7 @@ func dropVolumes() (int, int) {
|
||||
global.LOG.Errorf("do not get docker client")
|
||||
return 0, 0
|
||||
}
|
||||
defer client.Close()
|
||||
pruneFilters := filters.NewArgs()
|
||||
versions, err := client.ServerVersion(context.Background())
|
||||
if err != nil {
|
||||
|
||||
@@ -88,31 +88,25 @@ func (t TensorRTLLMService) Page(req request.TensorRTLLMSearch) response.TensorR
|
||||
}
|
||||
|
||||
composeByte, err := files.NewFileOp().GetContent(path.Join(global.Dir.TensorRTLLMDir, item.Name, "docker-compose.yml"))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
serverDTO.Environments, err = getDockerComposeEnvironments(composeByte)
|
||||
if err != nil {
|
||||
continue
|
||||
if err == nil {
|
||||
serverDTO.Environments, _ = getDockerComposeEnvironments(composeByte)
|
||||
}
|
||||
volumes, err := getDockerComposeVolumes(composeByte)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var defaultVolumes = map[string]string{
|
||||
"${MODEL_PATH}": "${MODEL_PATH}",
|
||||
}
|
||||
for _, volume := range volumes {
|
||||
exist := false
|
||||
for key, value := range defaultVolumes {
|
||||
if key == volume.Source && value == volume.Target {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
if err == nil {
|
||||
var defaultVolumes = map[string]string{
|
||||
"${MODEL_PATH}": "${MODEL_PATH}",
|
||||
}
|
||||
if !exist {
|
||||
serverDTO.Volumes = append(serverDTO.Volumes, volume)
|
||||
for _, volume := range volumes {
|
||||
exist := false
|
||||
for key, value := range defaultVolumes {
|
||||
if key == volume.Source && value == volume.Target {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
serverDTO.Volumes = append(serverDTO.Volumes, volume)
|
||||
}
|
||||
}
|
||||
}
|
||||
items = append(items, serverDTO)
|
||||
|
||||
@@ -48,6 +48,7 @@ func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
baRouter.POST("/compose", baseApi.CreateCompose)
|
||||
baRouter.POST("/compose/test", baseApi.TestCompose)
|
||||
baRouter.POST("/compose/operate", baseApi.OperatorCompose)
|
||||
baRouter.POST("/compose/clean/log", baseApi.CleanComposeLog)
|
||||
baRouter.POST("/compose/update", baseApi.ComposeUpdate)
|
||||
|
||||
baRouter.GET("/template", baseApi.ListComposeTemplate)
|
||||
|
||||
@@ -132,7 +132,7 @@ func (r *Remote) Backup(info BackupInfo) error {
|
||||
}
|
||||
fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz")
|
||||
backupCommand := exec.Command("bash", "-c",
|
||||
fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD=\"%s\" pg_dump -h %s -p %d --no-owner -Fc -U %s %s' > %s",
|
||||
fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD='\\''%s'\\'' pg_dump -h %s -p %d --no-owner -Fc -U %s %s' > %s",
|
||||
imageTag, r.Password, r.Address, r.Port, r.User, info.Name, fileNameItem))
|
||||
_ = backupCommand.Run()
|
||||
b := make([]byte, 5)
|
||||
@@ -176,7 +176,7 @@ func (r *Remote) Recover(info RecoverInfo) error {
|
||||
}()
|
||||
}
|
||||
recoverCommand := exec.Command("bash", "-c",
|
||||
fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD=\"%s\" pg_restore -h %s -p %d --verbose --clean --no-privileges --no-owner -Fc -c --if-exists --no-owner -U %s -d %s --role=%s' < %s",
|
||||
fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD='\\''%s'\\'' pg_restore -h %s -p %d --verbose --clean --no-privileges --no-owner -Fc -c --if-exists --no-owner -U %s -d %s --role=%s' < %s",
|
||||
imageTag, r.Password, r.Address, r.Port, r.User, info.Name, info.Username, fileName))
|
||||
pipe, _ := recoverCommand.StdoutPipe()
|
||||
stderrPipe, _ := recoverCommand.StderrPipe()
|
||||
|
||||
+25
-18
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/model"
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/core/buserr"
|
||||
"github.com/1Panel-dev/1Panel/core/constant"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/i18n"
|
||||
@@ -21,27 +22,29 @@ type ActionFunc func(*Task) error
|
||||
type RollbackFunc func(*Task)
|
||||
|
||||
type Task struct {
|
||||
Name string
|
||||
TaskID string
|
||||
Logger *log.Logger
|
||||
SubTasks []*SubTask
|
||||
Rollbacks []RollbackFunc
|
||||
logFile *os.File
|
||||
taskRepo repo.ITaskRepo
|
||||
Task *model.Task
|
||||
ParentID string
|
||||
Name string
|
||||
TaskID string
|
||||
Logger *log.Logger
|
||||
SubTasks []*SubTask
|
||||
Rollbacks []RollbackFunc
|
||||
logFile *os.File
|
||||
taskRepo repo.ITaskRepo
|
||||
Task *model.Task
|
||||
ParentID string
|
||||
CancelWhenTimeout bool
|
||||
}
|
||||
|
||||
type SubTask struct {
|
||||
RootTask *Task
|
||||
Name string
|
||||
StepAlias string
|
||||
Retry int
|
||||
Timeout time.Duration
|
||||
Action ActionFunc
|
||||
Rollback RollbackFunc
|
||||
Error error
|
||||
IgnoreErr bool
|
||||
RootTask *Task
|
||||
Name string
|
||||
StepAlias string
|
||||
Retry int
|
||||
Timeout time.Duration
|
||||
Action ActionFunc
|
||||
Rollback RollbackFunc
|
||||
Error error
|
||||
IgnoreErr bool
|
||||
CancelWhenTimeout bool
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -143,6 +146,9 @@ func (s *SubTask) Execute() error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName))
|
||||
if s.CancelWhenTimeout {
|
||||
return buserr.New(i18n.GetWithName("TaskTimeout", subTaskName))
|
||||
}
|
||||
case err = <-done:
|
||||
if err != nil {
|
||||
s.RootTask.Log(i18n.GetWithNameAndErr("SubTaskFailed", subTaskName, err))
|
||||
@@ -173,6 +179,7 @@ func (t *Task) Execute() error {
|
||||
var err error
|
||||
t.Log(i18n.GetWithName("TaskStart", t.Name))
|
||||
for _, subTask := range t.SubTasks {
|
||||
subTask.CancelWhenTimeout = t.CancelWhenTimeout
|
||||
t.Task.CurrentStep = subTask.StepAlias
|
||||
t.updateTask(t.Task)
|
||||
if err = subTask.Execute(); err == nil {
|
||||
|
||||
@@ -8,15 +8,18 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
sockPath = "/etc/1panel/agent.sock"
|
||||
const SockPath = "/etc/1panel/agent.sock"
|
||||
|
||||
var (
|
||||
LocalAgentProxy *httputil.ReverseProxy
|
||||
)
|
||||
|
||||
func Init() {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
dialUnix := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return net.Dial("unix", sockPath)
|
||||
return dialer.DialContext(ctx, "unix", SockPath)
|
||||
}
|
||||
transport := &http.Transport{
|
||||
DialContext: dialUnix,
|
||||
@@ -33,7 +36,7 @@ func Init() {
|
||||
Transport: transport,
|
||||
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
rw.WriteHeader(http.StatusBadGateway)
|
||||
rw.Write([]byte("Bad Gateway"))
|
||||
_, _ = rw.Write([]byte("Bad Gateway: " + err.Error()))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package router
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -52,11 +51,6 @@ func Proxy() gin.HandlerFunc {
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(c.Request.URL.Path, "/api/v2/core") && (currentNode == "local" || len(currentNode) == 0) {
|
||||
sockPath := "/etc/1panel/agent.sock"
|
||||
if _, err := os.Stat(sockPath); err != nil {
|
||||
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrProxy", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := recover(); err != nil && err != http.ErrAbortHandler {
|
||||
global.LOG.Debug(err)
|
||||
|
||||
@@ -10,13 +10,12 @@ import (
|
||||
var store = base64Captcha.DefaultMemStore
|
||||
|
||||
func VerifyCode(codeID string, code string) string {
|
||||
if codeID == "" {
|
||||
return "ErrCaptchaCode"
|
||||
}
|
||||
vv := store.Get(codeID, true)
|
||||
vv = strings.TrimSpace(vv)
|
||||
code = strings.TrimSpace(code)
|
||||
|
||||
if codeID == "" || code == "" {
|
||||
return "ErrCaptchaCode"
|
||||
}
|
||||
if strings.EqualFold(vv, code) {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"codemirror": "^6.0.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"echarts": "^5.5.0",
|
||||
"element-plus": "^2.11.2",
|
||||
"element-plus": "2.11.9",
|
||||
"fit2cloud-ui-plus": "^1.2.3",
|
||||
"highlight.js": "^11.9.0",
|
||||
"js-base64": "^3.7.7",
|
||||
|
||||
@@ -39,9 +39,17 @@ export const commitContainer = (params: Container.ContainerCommit) => {
|
||||
export const loadContainerInfo = (name: string) => {
|
||||
return http.post<Container.ContainerHelper>(`/containers/info`, { name: name });
|
||||
};
|
||||
export const cleanComposeLog = (composeName: string, composePath: string, operateNode?: string) => {
|
||||
const params = operateNode ? `?operateNode=${operateNode}` : '';
|
||||
return http.post(
|
||||
`/containers/compose/clean/log${params}`,
|
||||
{ name: composeName, path: composePath },
|
||||
TimeoutEnum.T_60S,
|
||||
);
|
||||
};
|
||||
export const cleanContainerLog = (containerName: string, operateNode?: string) => {
|
||||
const params = operateNode ? `?operateNode=${operateNode}` : '';
|
||||
return http.post(`/containers/clean/log${params}`, { name: containerName });
|
||||
return http.post(`/containers/clean/log${params}`, { name: containerName }, TimeoutEnum.T_60S);
|
||||
};
|
||||
export const containerItemStats = (containerID: string) => {
|
||||
return http.post<Container.ContainerItemStats>(`/containers/item/stats`, { name: containerID }, TimeoutEnum.T_60S);
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { cleanContainerLog, DownloadFile } from '@/api/modules/container';
|
||||
import { cleanComposeLog, cleanContainerLog, DownloadFile } from '@/api/modules/container';
|
||||
import i18n from '@/lang';
|
||||
import { dateFormatForName } from '@/utils/util';
|
||||
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
@@ -48,6 +48,8 @@ import hightlight from '@/components/log/custom-hightlight/index.vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const em = defineEmits(['update:loading']);
|
||||
|
||||
const props = defineProps({
|
||||
container: {
|
||||
type: String,
|
||||
@@ -93,6 +95,7 @@ const logSearch = reactive({
|
||||
mode: 'all',
|
||||
tail: props.defaultFollow ? 0 : 100,
|
||||
compose: '',
|
||||
resource: '',
|
||||
});
|
||||
const logHeight = 20;
|
||||
const logCount = computed(() => logs.value.length);
|
||||
@@ -215,6 +218,19 @@ const onClean = async () => {
|
||||
if (props.node && props.node !== '') {
|
||||
currentNode = props.node;
|
||||
}
|
||||
if (logSearch.compose !== '') {
|
||||
em('update:loading', true);
|
||||
await cleanComposeLog(logSearch.resource, logSearch.compose, currentNode)
|
||||
.then(() => {
|
||||
em('update:loading', false);
|
||||
searchLogs();
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.finally(() => {
|
||||
em('update:loading', false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
await cleanContainerLog(logSearch.container, currentNode);
|
||||
searchLogs();
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
@@ -240,6 +256,7 @@ const resizeObserver = ref<ResizeObserver | null>(null);
|
||||
onMounted(() => {
|
||||
logSearch.container = props.container;
|
||||
logSearch.compose = props.compose;
|
||||
logSearch.resource = props.resource;
|
||||
|
||||
logVisible.value = true;
|
||||
logSearch.tail = 100;
|
||||
|
||||
@@ -110,6 +110,7 @@ const openEdit = (rowData: any): void => {
|
||||
if (tensorRTLLM.value.exposedPorts == null) {
|
||||
tensorRTLLM.value.exposedPorts = [];
|
||||
}
|
||||
tensorRTLLM.value.command = rowData.command.slice(1, -1).replace(/^'|'$/g, '').replace(/\\"/g, '"');
|
||||
if (tensorRTLLM.value.extraHosts == null) {
|
||||
tensorRTLLM.value.extraHosts = [];
|
||||
}
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
plain
|
||||
round
|
||||
size="small"
|
||||
:disabled="!currentCompose?.workdir"
|
||||
@click="openComposeFolder"
|
||||
:disabled="!row?.workdir"
|
||||
@click="openComposeFolder(row)"
|
||||
>
|
||||
{{ $t('home.dir') }}
|
||||
</el-button>
|
||||
@@ -97,13 +97,7 @@
|
||||
>
|
||||
{{ $t('commons.operate.restart') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
:disabled="row.createdBy !== '1Panel'"
|
||||
plain
|
||||
round
|
||||
size="small"
|
||||
@click="onDelete(row)"
|
||||
>
|
||||
<el-button plain round size="small" @click="onDelete(row)">
|
||||
{{ $t('commons.operate.delete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -174,8 +168,7 @@
|
||||
<el-descriptions-item :label="$t('container.memTotal')">
|
||||
{{ computeSizeForDocker(row.memoryLimit) }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item>
|
||||
<el-descriptions-item v-if="row.hasLoadSize">
|
||||
<template #label>
|
||||
{{ $t('container.sizeRw') }}
|
||||
<el-tooltip :content="$t('container.sizeRwHelper')">
|
||||
@@ -184,7 +177,10 @@
|
||||
</template>
|
||||
{{ computeSize2(row.sizeRw) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('container.sizeRootFs')">
|
||||
<el-descriptions-item
|
||||
:label="$t('container.sizeRootFs')"
|
||||
v-if="row.hasLoadSize"
|
||||
>
|
||||
<template #label>
|
||||
{{ $t('container.sizeRootFs') }}
|
||||
<el-tooltip :content="$t('container.sizeRootFsHelper')">
|
||||
@@ -194,6 +190,17 @@
|
||||
{{ computeSize2(row.sizeRootFs) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-button
|
||||
class="mt-2"
|
||||
v-if="!row.hasLoadSize"
|
||||
size="small"
|
||||
link
|
||||
type="primary"
|
||||
@click="loadSize(row)"
|
||||
>
|
||||
{{ $t('container.loadSize') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
@@ -245,15 +252,17 @@
|
||||
mode="yaml"
|
||||
></CodemirrorPro>
|
||||
</div>
|
||||
<el-button type="primary" class="float-right" @click="onSubmitEdit">
|
||||
<el-button type="primary" class="float-right mt-2" @click="onSubmitEdit">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form>
|
||||
|
||||
<div v-show="showType === 'log'">
|
||||
<ContainerLog
|
||||
v-model:loading="detailLoading"
|
||||
:key="currentCompose.path"
|
||||
:compose="currentCompose.path"
|
||||
:resource="currentCompose.name"
|
||||
:highlightDiff="450"
|
||||
:defaultFollow="true"
|
||||
/>
|
||||
@@ -282,7 +291,14 @@ import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
||||
import ContainerLogDialog from '@/components/log/container-drawer/index.vue';
|
||||
import CreateDialog from '@/views/container/compose/create/index.vue';
|
||||
import DeleteDialog from '@/views/container/compose/delete/index.vue';
|
||||
import { composeOperator, composeUpdate, containerListStats, inspect, searchCompose } from '@/api/modules/container';
|
||||
import {
|
||||
composeOperator,
|
||||
composeUpdate,
|
||||
containerItemStats,
|
||||
containerListStats,
|
||||
inspect,
|
||||
searchCompose,
|
||||
} from '@/api/modules/container';
|
||||
import DockerStatus from '@/views/container/docker-status/index.vue';
|
||||
import i18n from '@/lang';
|
||||
import { Container } from '@/api/interface/container';
|
||||
@@ -381,11 +397,15 @@ const loadDetail = async (row: Container.ComposeInfo, withRefresh: boolean) => {
|
||||
detailLoading.value = true;
|
||||
currentCompose.value = row;
|
||||
composeContainers.value = row.containers || [];
|
||||
await inspect({ id: currentCompose.value.name, type: 'compose' }).then((res) => {
|
||||
composeContent.value = res.data;
|
||||
detailLoading.value = false;
|
||||
});
|
||||
loadContainerStats();
|
||||
await inspect({ id: currentCompose.value.name, type: 'compose' })
|
||||
.then((res) => {
|
||||
composeContent.value = res.data;
|
||||
detailLoading.value = false;
|
||||
})
|
||||
.finally(() => {
|
||||
loadContainerStats();
|
||||
detailLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const loadContainerStats = async () => {
|
||||
@@ -421,17 +441,20 @@ const handleComposeOperate = async (operation: 'up' | 'stop' | 'restart', row: a
|
||||
loading.value = true;
|
||||
const params = {
|
||||
name: row.name,
|
||||
path: currentCompose.value.path,
|
||||
path: row.path,
|
||||
operation: operation,
|
||||
withFile: false,
|
||||
force: false,
|
||||
};
|
||||
await composeOperator(params)
|
||||
.then(() => {
|
||||
.then(async () => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
search();
|
||||
if (row.name === currentCompose.value?.name) {
|
||||
loadDetail(currentCompose.value, true);
|
||||
await search();
|
||||
if (currentCompose.value) {
|
||||
const updated = data.value.find((item) => item.name === currentCompose.value.name);
|
||||
if (updated) {
|
||||
await loadDetail(updated, true);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -440,6 +463,14 @@ const handleComposeOperate = async (operation: 'up' | 'stop' | 'restart', row: a
|
||||
});
|
||||
};
|
||||
|
||||
const loadSize = async (row: any) => {
|
||||
containerItemStats(row.containerID).then((res) => {
|
||||
row.sizeRw = res.data.sizeRw || 0;
|
||||
row.sizeRootFs = res.data.sizeRootFs || 0;
|
||||
row.hasLoadSize = true;
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmitEdit = async () => {
|
||||
const param = {
|
||||
name: currentCompose.value.name,
|
||||
@@ -452,16 +483,22 @@ const onSubmitEdit = async () => {
|
||||
await composeUpdate(param)
|
||||
.then(async () => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
await loadDetail(currentCompose.value, true);
|
||||
await search();
|
||||
if (currentCompose.value) {
|
||||
const updated = data.value.find((item) => item.name === currentCompose.value.name);
|
||||
if (updated) {
|
||||
await loadDetail(updated, true);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const openComposeFolder = () => {
|
||||
if (currentCompose.value?.workdir) {
|
||||
routerToFileWithPath(currentCompose.value.workdir);
|
||||
const openComposeFolder = (row: any) => {
|
||||
if (row?.workdir) {
|
||||
routerToFileWithPath(row.workdir);
|
||||
}
|
||||
};
|
||||
const onInspectContainer = async (item: any) => {
|
||||
|
||||
@@ -206,6 +206,9 @@ const onOpenProcess = () => {
|
||||
const onMessage = (message: any) => {
|
||||
oldData.value = JSON.parse(message.data);
|
||||
data.value = filterByStatus();
|
||||
if (data.value == null) {
|
||||
data.value = [];
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
const onerror = () => {};
|
||||
|
||||
@@ -269,6 +269,9 @@ const onMessage = (message: any) => {
|
||||
isGetData.value = false;
|
||||
oldData.value = JSON.parse(message.data);
|
||||
data.value = filterByStatus();
|
||||
if (data.value == null) {
|
||||
data.value = [];
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -424,6 +424,9 @@ const getSetting = async () => {
|
||||
globalStore.isFxplay = isFxplay.value;
|
||||
globalStore.isOffLine = res.data.isOffLine;
|
||||
globalStore.ignoreCaptcha = !res.data.needCaptcha;
|
||||
if (!globalStore.ignoreCaptcha) {
|
||||
loginVerify();
|
||||
}
|
||||
|
||||
document.title = res.data.panelName;
|
||||
i18n.warnHtmlMessage = false;
|
||||
|
||||
@@ -16,7 +16,7 @@ import Components from 'unplugin-vue-components/vite';
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
|
||||
import svgLoader from 'vite-svg-loader';
|
||||
|
||||
import monacoEditorPlugin from 'vite-plugin-monaco-editor';
|
||||
const prefix = `monaco-editor/esm/vs`;
|
||||
|
||||
const { dependencies, devDependencies, name, version } = pkg;
|
||||
const __APP_INFO__ = {
|
||||
@@ -97,9 +97,6 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||
svgLoader({
|
||||
defaultImport: 'url',
|
||||
}),
|
||||
monacoEditorPlugin({
|
||||
languageWorkers: ['editorWorkerService', 'typescript', 'json', 'html', 'css'],
|
||||
}),
|
||||
],
|
||||
esbuild: {
|
||||
pure: viteEnv.VITE_DROP_CONSOLE ? ['console.log'] : [],
|
||||
@@ -116,12 +113,15 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||
chunkFileNames: 'assets/js/[name]-[hash].js',
|
||||
entryFileNames: 'assets/js/[name]-[hash].js',
|
||||
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
|
||||
manualChunks: {
|
||||
jsonWorker: [`${prefix}/language/json/json.worker`],
|
||||
cssWorker: [`${prefix}/language/css/css.worker`],
|
||||
htmlWorker: [`${prefix}/language/html/html.worker`],
|
||||
tsWorker: [`${prefix}/language/typescript/ts.worker`],
|
||||
editorWorker: [`${prefix}/editor/editor.worker`],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['monaco-editor/esm/vs/editor/editor.api'],
|
||||
exclude: ['monaco-editor'],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user