mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-01-13 18:44:14 +08:00
Merge branch 'dev-v2' into feat/puny
This commit is contained in:
commit
68a7fbc22a
23 changed files with 185 additions and 142 deletions
|
|
@ -15,8 +15,9 @@ type PageContainer struct {
|
|||
}
|
||||
|
||||
type InspectReq struct {
|
||||
ID string `json:"id" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
ID string `json:"id" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Detail string `json:"detail"`
|
||||
}
|
||||
|
||||
type ContainerInfo struct {
|
||||
|
|
@ -284,17 +285,19 @@ type ComposeOperation struct {
|
|||
Path string `json:"path"`
|
||||
Operation string `json:"operation" validate:"required,oneof=up start restart stop down delete"`
|
||||
WithFile bool `json:"withFile"`
|
||||
Force bool `josn:"force"`
|
||||
Force bool `json:"force"`
|
||||
}
|
||||
type ComposeUpdate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
Env string `json:"env"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
DetailPath string `json:"detailPath"`
|
||||
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"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Path string `json:"path" validate:"required"`
|
||||
DetailPath string `json:"detailPath"`
|
||||
}
|
||||
|
||||
type ContainerLog struct {
|
||||
|
|
|
|||
|
|
@ -19,12 +19,8 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
|
|
@ -45,7 +41,9 @@ import (
|
|||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/gin-gonic/gin"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
)
|
||||
|
|
@ -362,6 +360,9 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
|||
}
|
||||
for _, container := range containers {
|
||||
config := container.Labels[composeConfigLabel]
|
||||
if len(req.Detail) != 0 && strings.Contains(config, req.Detail) {
|
||||
config = req.Detail
|
||||
}
|
||||
workdir := container.Labels[composeWorkdirLabel]
|
||||
if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) {
|
||||
filePath = config
|
||||
|
|
@ -970,13 +971,20 @@ func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<-
|
|||
var dockerCmd *exec.Cmd
|
||||
if params.Type == "compose" {
|
||||
dockerComposCmd := common.GetDockerComposeCommand()
|
||||
var yamlFiles []string
|
||||
for _, item := range strings.Split(params.Compose, ",") {
|
||||
if len(item) != 0 {
|
||||
yamlFiles = append(yamlFiles, "-f", item)
|
||||
}
|
||||
}
|
||||
if dockerComposCmd == "docker-compose" {
|
||||
newCmdArgs := append([]string{"-f", params.Compose}, cmdArgs...)
|
||||
newCmdArgs := append(yamlFiles, cmdArgs...)
|
||||
dockerCmd = exec.Command(dockerComposCmd, newCmdArgs...)
|
||||
} else {
|
||||
newCmdArgs := append([]string{"compose", "-f", params.Compose}, cmdArgs...)
|
||||
newCmdArgs := append(append([]string{"compose"}, yamlFiles...), cmdArgs...)
|
||||
dockerCmd = exec.Command("docker", newCmdArgs...)
|
||||
}
|
||||
global.LOG.Debug("Docker command:", dockerCmd.Args)
|
||||
} else {
|
||||
dockerCmd = exec.Command("docker", cmdArgs...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package service
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
|
@ -24,7 +25,6 @@ import (
|
|||
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const composeProjectLabel = "com.docker.compose.project"
|
||||
|
|
@ -233,14 +233,15 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
|||
return err
|
||||
}
|
||||
if req.WithFile {
|
||||
_ = os.RemoveAll(path.Dir(req.Path))
|
||||
for _, item := range strings.Split(req.Path, ",") {
|
||||
if len(item) != 0 {
|
||||
_ = os.RemoveAll(path.Dir(item))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = composeRepo.DeleteRecord(repo.WithByName(req.Name))
|
||||
return nil
|
||||
}
|
||||
if _, err := os.Stat(req.Path); err != nil {
|
||||
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
|
||||
}
|
||||
if req.Operation == "up" {
|
||||
if stdout, err := compose.Up(req.Path); err != nil {
|
||||
return fmt.Errorf("docker-compose up failed, std: %s, err: %v", stdout, err)
|
||||
|
|
@ -257,11 +258,11 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
|
|||
if cmd.CheckIllegal(req.Name, req.Path) {
|
||||
return buserr.New("ErrCmdIllegal")
|
||||
}
|
||||
oldFile, err := os.ReadFile(req.Path)
|
||||
oldFile, err := os.ReadFile(req.DetailPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
|
||||
return fmt.Errorf("load file with path %s failed, %v", req.DetailPath, err)
|
||||
}
|
||||
file, err := os.OpenFile(req.Path, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
file, err := os.OpenFile(req.DetailPath, os.O_WRONLY|os.O_TRUNC, 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -270,8 +271,8 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
|
|||
_, _ = write.WriteString(req.Content)
|
||||
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 {
|
||||
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.DetailPath)
|
||||
if err := newComposeEnv(req.DetailPath, req.Env); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -361,11 +362,8 @@ func (u *ContainerService) loadPath(req *dto.ComposeCreate) error {
|
|||
}
|
||||
|
||||
func removeContainerForCompose(composeName, composePath string) error {
|
||||
if _, err := os.Stat(composePath); err == nil {
|
||||
if stdout, err := compose.Operate(composePath, "down"); err != nil {
|
||||
return errors.New(stdout)
|
||||
}
|
||||
return nil
|
||||
if stdout, err := compose.Operate(composePath, "down"); err != nil {
|
||||
return errors.New(stdout)
|
||||
}
|
||||
var options container.ListOptions
|
||||
options.All = true
|
||||
|
|
@ -405,7 +403,11 @@ func recreateCompose(content, path string) error {
|
|||
|
||||
func loadEnv(list []dto.ComposeInfo) []dto.ComposeInfo {
|
||||
for i := 0; i < len(list); i++ {
|
||||
envFilePath := path.Join(path.Dir(list[i].Path), ".env")
|
||||
tmpPath := list[i].Path
|
||||
if strings.Contains(list[i].Path, ",") {
|
||||
tmpPath = strings.Split(list[i].Path, ",")[0]
|
||||
}
|
||||
envFilePath := path.Join(path.Dir(tmpPath), ".env")
|
||||
file, err := os.ReadFile(envFilePath)
|
||||
if err != nil {
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -1745,8 +1745,7 @@ func (w WebsiteService) OperateRedirect(req request.NginxRedirectReq) (err error
|
|||
|
||||
func (w WebsiteService) GetRedirect(id uint) (res []response.NginxRedirectConfig, err error) {
|
||||
var (
|
||||
website model.Website
|
||||
fileList response.FileInfo
|
||||
website model.Website
|
||||
)
|
||||
website, err = websiteRepo.GetFirst(repo.WithByID(id))
|
||||
if err != nil {
|
||||
|
|
@ -1757,27 +1756,39 @@ func (w WebsiteService) GetRedirect(id uint) (res []response.NginxRedirectConfig
|
|||
if !fileOp.Stat(includeDir) {
|
||||
return
|
||||
}
|
||||
fileList, err = NewIFileService().GetFileList(request.FileOption{FileOption: files.FileOption{Path: includeDir, Expand: true, Page: 1, PageSize: 100}})
|
||||
if len(fileList.Items) == 0 {
|
||||
entries, err := os.ReadDir(includeDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
content []byte
|
||||
config *components.Config
|
||||
)
|
||||
for _, configFile := range fileList.Items {
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
fileName := entry.Name()
|
||||
if !strings.HasSuffix(fileName, ".conf") && !strings.HasSuffix(fileName, ".conf.bak") {
|
||||
continue
|
||||
}
|
||||
redirectConfig := response.NginxRedirectConfig{
|
||||
WebsiteID: website.ID,
|
||||
}
|
||||
parts := strings.Split(configFile.Name, ".")
|
||||
parts := strings.Split(fileName, ".")
|
||||
redirectConfig.Name = parts[0]
|
||||
if parts[1] == "conf" {
|
||||
redirectConfig.Enable = true
|
||||
} else {
|
||||
redirectConfig.Enable = false
|
||||
}
|
||||
redirectConfig.FilePath = configFile.Path
|
||||
content, err = fileOp.GetContent(configFile.Path)
|
||||
filePath := path.Join(includeDir, fileName)
|
||||
redirectConfig.FilePath = filePath
|
||||
content, err = fileOp.GetContent(filePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@ func Up(filePath string) (string, error) {
|
|||
if err := checkCmd(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut(global.CONF.DockerConfig.Command+" -f %s up -d", 20*time.Minute, filePath)
|
||||
return stdout, err
|
||||
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s up -d", global.CONF.DockerConfig.Command, loadFiles(filePath))
|
||||
}
|
||||
|
||||
func UpWithTask(filePath string, task *task.Task) error {
|
||||
|
|
@ -81,54 +80,56 @@ func UpWithTask(filePath string, task *task.Task) error {
|
|||
}
|
||||
}
|
||||
|
||||
dockerCommand := global.CONF.DockerConfig.Command
|
||||
if dockerCommand == "docker-compose" {
|
||||
return cmd.NewCommandMgr(cmd.WithTask(*task)).Run("docker-compose", "-f", filePath, "up", "-d")
|
||||
} else {
|
||||
return cmd.NewCommandMgr(cmd.WithTask(*task)).Run("docker", "compose", "-f", filePath, "up", "-d")
|
||||
}
|
||||
return cmd.NewCommandMgr(cmd.WithTask(*task)).Run("%s %s up -d", global.CONF.DockerConfig.Command, loadFiles(filePath))
|
||||
}
|
||||
|
||||
func Down(filePath string) (string, error) {
|
||||
if err := checkCmd(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut(global.CONF.DockerConfig.Command+" -f %s down --remove-orphans", 20*time.Minute, filePath)
|
||||
return stdout, err
|
||||
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s down --remove-orphans", global.CONF.DockerConfig.Command, loadFiles(filePath))
|
||||
}
|
||||
|
||||
func Stop(filePath string) (string, error) {
|
||||
if err := checkCmd(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf(global.CONF.DockerConfig.Command+" -f %s stop", filePath)
|
||||
return stdout, err
|
||||
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s stop", global.CONF.DockerConfig.Command, loadFiles(filePath))
|
||||
}
|
||||
|
||||
func Restart(filePath string) (string, error) {
|
||||
if err := checkCmd(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf(global.CONF.DockerConfig.Command+" -f %s restart", filePath)
|
||||
return stdout, err
|
||||
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s restart", global.CONF.DockerConfig.Command, loadFiles(filePath))
|
||||
}
|
||||
|
||||
func Operate(filePath, operation string) (string, error) {
|
||||
if err := checkCmd(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf(global.CONF.DockerConfig.Command+" -f %s %s", filePath, operation)
|
||||
return stdout, err
|
||||
return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s %s", global.CONF.DockerConfig.Command, loadFiles(filePath), operation)
|
||||
}
|
||||
|
||||
func DownAndUp(filePath string) (string, error) {
|
||||
if err := checkCmd(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
stdout, err := cmd.RunDefaultWithStdoutBashCf(global.CONF.DockerConfig.Command+" -f %s down", filePath)
|
||||
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Minute))
|
||||
stdout, err := cmdMgr.RunWithStdoutBashCf("%s %s down", global.CONF.DockerConfig.Command, loadFiles(filePath))
|
||||
if err != nil {
|
||||
return stdout, err
|
||||
}
|
||||
stdout, err = cmd.RunDefaultWithStdoutBashCf(global.CONF.DockerConfig.Command+" -f %s up -d", filePath)
|
||||
stdout, err = cmdMgr.RunWithStdoutBashCf("%s %s up -d", global.CONF.DockerConfig.Command, loadFiles(filePath))
|
||||
return stdout, err
|
||||
}
|
||||
|
||||
func loadFiles(filePath string) string {
|
||||
var fileItem []string
|
||||
for _, item := range strings.Split(filePath, ",") {
|
||||
if len(item) != 0 {
|
||||
fileItem = append(fileItem, fmt.Sprintf("-f %s", item))
|
||||
}
|
||||
}
|
||||
return strings.Join(fileItem, " ")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ export namespace Container {
|
|||
export interface ContainerInspect {
|
||||
id: string;
|
||||
type: string;
|
||||
detail: string;
|
||||
}
|
||||
export interface ContainerPrune {
|
||||
pruneType: string;
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ export const upCompose = (params: Container.ComposeCreate) => {
|
|||
export const testCompose = (params: Container.ComposeCreate) => {
|
||||
return http.post<boolean>(`/containers/compose/test`, params);
|
||||
};
|
||||
export const composeOperator = (params: Container.ComposeOperation) => {
|
||||
export const composeOperate = (params: Container.ComposeOperation) => {
|
||||
return http.post(`/containers/compose/operate`, params);
|
||||
};
|
||||
export const composeUpdate = (params: Container.ComposeUpdate) => {
|
||||
|
|
|
|||
|
|
@ -1017,6 +1017,7 @@ const message = {
|
|||
'If multiple private repositories exist, newlines must be displayed, for example:\n172.16.10.111:8081 \n172.16.10.112:8081',
|
||||
|
||||
compose: 'Compose | Composes',
|
||||
composeFile: 'Compose File',
|
||||
fromChangeHelper: 'Switching the source will clean the current edited content. Do you want to continue?',
|
||||
composePathHelper: 'Configuration file save path: {0}',
|
||||
composeHelper:
|
||||
|
|
|
|||
|
|
@ -1021,7 +1021,9 @@ const message = {
|
|||
'Si hay varios mirrors, deben estar en líneas separadas. Ejemplo:\nhttp://xxxxxx.m.daocloud.io\nhttps://xxxxxx.mirror.aliyuncs.com',
|
||||
registrieHelper:
|
||||
'Si existen varios repositorios privados, deben estar en líneas separadas. Ejemplo:\n172.16.10.111:8081\n172.16.10.112:8081',
|
||||
|
||||
compose: 'Compose | Composes',
|
||||
composeFile: 'Archivo de Orquestación',
|
||||
fromChangeHelper: 'Cambiar la fuente limpiará el contenido actualmente editado. ¿Desea continuar?',
|
||||
composePathHelper: 'Ruta de guardado del archivo de configuración: {0}',
|
||||
composeHelper:
|
||||
|
|
|
|||
|
|
@ -995,6 +995,7 @@ const message = {
|
|||
registrieHelper: '複数のプライベートリポジトリが存在する場合、たとえばnewlinesを表示する必要があります。',
|
||||
|
||||
compose: '構成|作曲',
|
||||
composeFile: 'オーケストレーションファイル',
|
||||
fromChangeHelper: 'ソースを切り替えると、現在の編集されたコンテンツがきれいになります。続けたいですか?',
|
||||
composePathHelper: '構成ファイル保存パス:{0}',
|
||||
composeHelper:
|
||||
|
|
|
|||
|
|
@ -983,6 +983,7 @@ const message = {
|
|||
'개인 레지스트리가 여러 개 있을 경우 각 줄에 하나씩 표시해야 합니다. 예시:\n172.16.10.111:8081 \n172.16.10.112:8081',
|
||||
|
||||
compose: '컴포즈 | 컴포즈들',
|
||||
composeFile: '컴포즈 파일',
|
||||
fromChangeHelper: '소스를 변경하면 현재 편집한 내용이 삭제됩니다. 계속 하시겠습니까?',
|
||||
composePathHelper: '구성 파일 저장 경로: {0}',
|
||||
composeHelper: '1Panel 에디터나 템플릿을 통해 생성된 컴포지션은 {0}/docker/compose 디렉토리에 저장됩니다.',
|
||||
|
|
|
|||
|
|
@ -1012,6 +1012,7 @@ const message = {
|
|||
'Jika terdapat banyak repositori persendirian, baris baru mesti dipaparkan, contohnya:\n172.16.10.111:8081 \n172.16.10.112:8081',
|
||||
|
||||
compose: 'Compose | Compose-compose',
|
||||
composeFile: 'Fail Susunan',
|
||||
fromChangeHelper: 'Menukar sumber akan membersihkan kandungan yang sedang diedit. Adakah anda mahu meneruskan?',
|
||||
composePathHelper: 'Laluan simpan fail konfigurasi: {0}',
|
||||
composeHelper:
|
||||
|
|
|
|||
|
|
@ -1008,6 +1008,7 @@ const message = {
|
|||
'Se houver múltiplos repositórios privados, eles devem ser exibidos em novas linhas, por exemplo:\n172.16.10.111:8081 \n172.16.10.112:8081',
|
||||
|
||||
compose: 'Compose | Composições',
|
||||
composeFile: 'Arquivo de Orquestração',
|
||||
fromChangeHelper: 'Trocar a origem limpará o conteúdo editado atual. Deseja continuar?',
|
||||
composePathHelper: 'Caminho de salvamento do arquivo de configuração: {0}',
|
||||
composeHelper:
|
||||
|
|
|
|||
|
|
@ -1007,6 +1007,7 @@ const message = {
|
|||
'Если существует несколько частных репозиториев, они должны быть разделены новой строкой, например:\n172.16.10.111:8081 \n172.16.10.112:8081',
|
||||
|
||||
compose: 'Compose | Composes',
|
||||
composeFile: 'Файл Оркестрации',
|
||||
fromChangeHelper: 'Переключение источника очистит текущее отредактированное содержимое. Хотите продолжить?',
|
||||
composePathHelper: 'Путь сохранения файла конфигурации: {0}',
|
||||
composeHelper:
|
||||
|
|
|
|||
|
|
@ -1029,6 +1029,7 @@ const message = {
|
|||
'Birden fazla özel depo varsa, yeni satırlar gösterilmelidir, örneğin:\n172.16.10.111:8081 \n172.16.10.112:8081',
|
||||
|
||||
compose: 'Compose | Composelar',
|
||||
composeFile: 'Düzenleme Dosyası',
|
||||
fromChangeHelper:
|
||||
'Kaynağın değiştirilmesi mevcut düzenlenen içeriği temizleyecektir. Devam etmek istiyor musunuz?',
|
||||
composePathHelper: 'Yapılandırma dosyası kaydetme yolu: {0}',
|
||||
|
|
|
|||
|
|
@ -970,6 +970,7 @@ const message = {
|
|||
registrieHelper: '當存在多個私有倉庫時,需要換行顯示,例:\n172.16.10.111:8081 \n172.16.10.112:8081',
|
||||
|
||||
compose: '編排',
|
||||
composeFile: '編排檔案',
|
||||
fromChangeHelper: '切換來源將清空檔前已編輯內容,是否繼續?',
|
||||
composePathHelper: '設定檔儲存路徑: {0}',
|
||||
composeHelper: '通過 1Panel 編輯或者模版建立的編排,將儲存在 {0}/docker/compose 路徑下',
|
||||
|
|
|
|||
|
|
@ -972,6 +972,7 @@ const message = {
|
|||
registrieHelper: '当存在多个私有仓库时,需要换行显示,例:\n172.16.10.111:8081 \n172.16.10.112:8081',
|
||||
|
||||
compose: '编排',
|
||||
composeFile: '编排文件',
|
||||
fromChangeHelper: '切换来源将清空当前已编辑内容,是否继续?',
|
||||
composePathHelper: '配置文件保存路径: {0}',
|
||||
composeHelper: '通过 1Panel 编辑或者模版创建的编排,将保存在 {0}/docker/compose 路径下',
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import { FormInstance } from 'element-plus';
|
|||
import { ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { composeOperator } from '@/api/modules/container';
|
||||
import { composeOperate } from '@/api/modules/container';
|
||||
|
||||
let open = ref(false);
|
||||
let loading = ref(false);
|
||||
|
|
@ -76,7 +76,7 @@ const submit = async () => {
|
|||
withFile: deleteFile.value,
|
||||
force: force.value,
|
||||
};
|
||||
await composeOperator(params)
|
||||
await composeOperate(params)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
emit('search');
|
||||
|
|
|
|||
|
|
@ -221,10 +221,24 @@
|
|||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-radio-group size="small" class="mt-1 mb-1" v-model="showType">
|
||||
<el-radio-group class="mt-1 mb-1" v-model="showType">
|
||||
<el-radio-button value="compose">{{ $t('container.compose') }}</el-radio-button>
|
||||
<el-radio-button value="log">{{ $t('commons.button.log') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-select
|
||||
class="p-w-300 mt-2 ml-2"
|
||||
v-model="currentYamlPath"
|
||||
@change="inspectCompose(currentCompose.name, currentYamlPath)"
|
||||
v-if="currentCompose.path.indexOf(',') !== -1"
|
||||
>
|
||||
<template #prefix>{{ $t('container.composeFile') }}</template>
|
||||
<el-option
|
||||
v-for="item in currentCompose.path.split(',')"
|
||||
:key="item"
|
||||
:value="item"
|
||||
:label="item.split('/').pop()"
|
||||
/>
|
||||
</el-select>
|
||||
<div v-show="showType === 'compose'">
|
||||
<CodemirrorPro
|
||||
v-model="composeContent"
|
||||
|
|
@ -357,7 +371,7 @@ import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
|||
import ContainerLogDialog from '@/components/log/container-drawer/index.vue';
|
||||
import DeleteDialog from '@/views/container/compose/delete/index.vue';
|
||||
import {
|
||||
composeOperator,
|
||||
composeOperate,
|
||||
composeUpdate,
|
||||
containerItemStats,
|
||||
containerListStats,
|
||||
|
|
@ -381,6 +395,7 @@ const data = ref<any[]>([]);
|
|||
const loading = ref(false);
|
||||
const detailLoading = ref(false);
|
||||
const currentCompose = ref<Container.ComposeInfo | null>(null);
|
||||
const currentYamlPath = ref('');
|
||||
const composeContainers = ref([]);
|
||||
const composeContent = ref('');
|
||||
|
||||
|
|
@ -410,7 +425,7 @@ const form = reactive({
|
|||
path: '',
|
||||
file: '',
|
||||
template: null as number,
|
||||
env: [],
|
||||
env: '',
|
||||
});
|
||||
const rules = reactive({
|
||||
name: [Rules.requiredInput, Rules.composeName],
|
||||
|
|
@ -491,9 +506,14 @@ const loadDetail = async (row: Container.ComposeInfo, withRefresh: boolean) => {
|
|||
isOnCreate.value = false;
|
||||
detailLoading.value = true;
|
||||
currentCompose.value = row;
|
||||
currentYamlPath.value = row.path.indexOf(',') !== -1 ? row.path.split(',')[0] : row.path;
|
||||
env.value = row.env || '';
|
||||
composeContainers.value = row.containers || [];
|
||||
await inspect({ id: currentCompose.value.name, type: 'compose' })
|
||||
inspectCompose(row.name, currentYamlPath.value);
|
||||
};
|
||||
|
||||
const inspectCompose = async (name: string, detailPath: string) => {
|
||||
await inspect({ id: name, type: 'compose', detail: detailPath })
|
||||
.then((res) => {
|
||||
composeContent.value = res.data;
|
||||
detailLoading.value = false;
|
||||
|
|
@ -521,7 +541,7 @@ const onOpenDialog = async () => {
|
|||
form.path = '';
|
||||
form.file = '';
|
||||
form.template = null;
|
||||
form.env = [];
|
||||
form.env = '';
|
||||
loadPath();
|
||||
loadTemplates();
|
||||
};
|
||||
|
|
@ -613,7 +633,7 @@ const handleComposeOperate = async (operation: 'up' | 'stop' | 'restart', row: a
|
|||
withFile: false,
|
||||
force: false,
|
||||
};
|
||||
await composeOperator(params)
|
||||
await composeOperate(params)
|
||||
.then(async () => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
await search();
|
||||
|
|
@ -642,6 +662,7 @@ const onSubmitEdit = async () => {
|
|||
const param = {
|
||||
name: currentCompose.value.name,
|
||||
path: currentCompose.value.path,
|
||||
detailPath: currentYamlPath.value,
|
||||
content: composeContent.value,
|
||||
createdBy: currentCompose.value.createdBy,
|
||||
env: env.value || '',
|
||||
|
|
@ -700,7 +721,7 @@ const onInspectContainer = async (item: any) => {
|
|||
if (!item.containerID) {
|
||||
return;
|
||||
}
|
||||
const res = await inspect({ id: item.containerID, type: 'container' });
|
||||
const res = await inspect({ id: item.containerID, type: 'container', detail: '' });
|
||||
containerInspectRef.value!.acceptParams({ data: res.data, ports: item.ports || [] });
|
||||
};
|
||||
const onOpenTerminal = (row: any) => {
|
||||
|
|
|
|||
|
|
@ -206,8 +206,7 @@
|
|||
:props="defaultProps"
|
||||
:filter-node-method="filterHost"
|
||||
:empty-text="$t('terminal.noHost')"
|
||||
class="host-tree compact"
|
||||
:style="{ 'max-height': '200px' }"
|
||||
class="host-tree"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span class="custom-tree-node w-full">
|
||||
|
|
@ -613,59 +612,8 @@ onMounted(() => {
|
|||
}
|
||||
|
||||
.host-tree {
|
||||
:deep(.el-tree-node) {
|
||||
.el-tree-node__content {
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
}
|
||||
|
||||
.el-tree-node__label {
|
||||
flex: 1;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.el-tree-node__expand-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-tree__empty-block) {
|
||||
padding: 24px 0;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
:deep(.el-tree__empty-text) {
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&.compact {
|
||||
:deep(.el-tree-node) {
|
||||
.el-tree-node__content {
|
||||
height: 28px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.el-tree-node__label {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.el-tree-node__expand-icon {
|
||||
margin-right: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-tree-node__children) {
|
||||
.el-tree-node__content {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
|
|
|
|||
|
|
@ -135,6 +135,11 @@ const handleScroll = (event: WheelEvent) => {
|
|||
const tabContainer = tabsRef.value.$el.querySelector('.el-tabs__nav-scroll');
|
||||
if (!tabContainer) return;
|
||||
|
||||
const currentScrollTop = tabContainer.scrollTop;
|
||||
if (currentScrollTop == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = event.target as HTMLElement;
|
||||
if (!target.classList.contains('el-tabs__item')) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<el-form-item :label="$t('commons.table.name')" prop="primaryDomain">
|
||||
<el-input v-model="form.primaryDomain"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('website.alias')" prop="primaryDomain">
|
||||
<el-form-item :label="$t('website.alias')" prop="alias">
|
||||
<el-input v-model="form.alias" disabled></el-input>
|
||||
</el-form-item>
|
||||
<GroupSelect
|
||||
|
|
@ -60,7 +60,7 @@ const form = reactive({
|
|||
favorite: false,
|
||||
});
|
||||
const rules = ref({
|
||||
primaryDomain: [Rules.requiredInput],
|
||||
primaryDomain: [Rules.requiredInput, Rules.linuxName],
|
||||
webSiteGroupId: [Rules.requiredSelect],
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
<template>
|
||||
<div class="name-row">
|
||||
<div>
|
||||
<el-input
|
||||
v-if="isEditing"
|
||||
v-model="editValue"
|
||||
@keyup.enter="saveEdit"
|
||||
@blur="saveEdit"
|
||||
@keyup.esc="cancelEdit"
|
||||
class="domain-input"
|
||||
ref="inputRef"
|
||||
/>
|
||||
<el-form :model="formData" :rules="rules" ref="formRef" v-if="isEditing" @submit.prevent>
|
||||
<el-form-item prop="domainName" class="inline-form-item">
|
||||
<el-input
|
||||
v-model="formData.domainName"
|
||||
@keyup.enter.prevent="saveEdit"
|
||||
@blur="saveEdit"
|
||||
@keyup.esc="cancelEdit"
|
||||
class="domain-input"
|
||||
ref="inputRef"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-text v-else type="primary" class="cursor-pointer" @click="openConfig(row.id)">
|
||||
{{ row.primaryDomain }}
|
||||
<span class="text-gray-400" v-if="isPunycoded(row.primaryDomain)">
|
||||
|
|
@ -65,6 +68,7 @@ import { ref, nextTick } from 'vue';
|
|||
import { listDomains } from '@/api/modules/website';
|
||||
import { Website } from '@/api/interface/website';
|
||||
import { routerToNameWithParams } from '@/utils/router';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import { GetPunyCodeDomain, isPunycoded } from '@/utils/util';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -75,11 +79,17 @@ const props = defineProps<Props>();
|
|||
const emit = defineEmits(['favoriteChange', 'domainEdit']);
|
||||
const inputRef = ref();
|
||||
const isEditing = ref(false);
|
||||
const editValue = ref('');
|
||||
const domains = ref<Website.Domain[]>([]);
|
||||
const formData = reactive({
|
||||
domainName: '',
|
||||
});
|
||||
const rules = ref({
|
||||
domainName: [Rules.requiredInput, Rules.linuxName],
|
||||
});
|
||||
const formRef = ref();
|
||||
|
||||
const startEdit = () => {
|
||||
editValue.value = props.row.primaryDomain;
|
||||
formData.domainName = props.row.primaryDomain;
|
||||
isEditing.value = true;
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus();
|
||||
|
|
@ -87,15 +97,20 @@ const startEdit = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const saveEdit = () => {
|
||||
if (editValue.value.trim() && editValue.value !== props.row.primaryDomain) {
|
||||
emit('domainEdit', props.row, editValue.value.trim());
|
||||
}
|
||||
isEditing.value = false;
|
||||
const saveEdit = async () => {
|
||||
await formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
const editValue = formData.domainName.trim();
|
||||
if (editValue && editValue !== props.row.primaryDomain) {
|
||||
emit('domainEdit', props.row, editValue);
|
||||
}
|
||||
isEditing.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const cancelEdit = () => {
|
||||
editValue.value = props.row.primaryDomain;
|
||||
formData.domainName = props.row.primaryDomain;
|
||||
isEditing.value = false;
|
||||
};
|
||||
|
||||
|
|
@ -137,4 +152,21 @@ const favoriteWebsite = (row: Website.Website) => {
|
|||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.el-form) {
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
:deep(.el-form-item__error) {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.domain-input {
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue