Compare commits

...
31 Commits
Author SHA1 Message Date
ssongliu 534bd98230 style: 修改按钮组边框宽度不一致的问题 (#6706)
Refs #6704
2024-10-14 14:44:58 +08:00
ssongliu 1045f2d475 feat: 修改容器列表应用商店容器默认显示 (#6705) 2024-10-14 14:44:55 +08:00
wild_pointer c5d007e4dd fix: 修复“修改文件权限时,文件所属用户和用户组不显示”问题 (#6698) 2024-10-14 10:57:38 +08:00
zzzandzengzz01 da249a2cef feat:firewalld和ufw同时存在时,不进行操作 (#6688)
Co-authored-by: zengzz01 <zengzz01@mingyuanyun.com>
2024-10-14 10:57:33 +08:00
ssongliu f738cd42be fix: 概览页磁盘显示排除 overlay 类型 (#6672)
Refs #6637
2024-10-14 10:56:44 +08:00
ssongliu d7d15479f2 feat: 容器列表缓存是否显示应用商店容器 (#6667)
Refs #5885
2024-10-14 10:56:40 +08:00
see-more d9de82dc04 chore;删除error_code组件的body标签 (#6656) 2024-10-14 10:56:26 +08:00
zhengkunwang 2d0d3704ae feat: 反向代理文本替换增加 sub_filter_types 参数 (#6661)
Refs https://github.com/1Panel-dev/1Panel/issues/6490
2024-10-14 10:56:09 +08:00
zhengkunwang a3bad3ccf9 feat: 反代回源 SNI 支持设置 proxy_ssl_name (#6659)
Refs https://github.com/1Panel-dev/1Panel/issues/5960
2024-10-14 10:54:29 +08:00
ssongliu 41471f93f0 fix: 修改上传恢复提示信息 (#6647) 2024-10-14 10:54:11 +08:00
zhengkunwang a817beb029 feat: 自签证书支持 ipv6 (#6627)
Refs https://github.com/1Panel-dev/1Panel/issues/6623
2024-10-14 10:54:08 +08:00
ssongliu e6322ad76b style: 修改许可证即将过期样式 (#6616) 2024-09-29 14:40:52 +08:00
ssongliu df003c5fb7 feat: 移除编排删除后跳转 (#6613) 2024-09-29 10:17:04 +08:00
ssongliu 10f8e8f3c4 feat: 调整编排删除逻辑 (#6610) 2024-09-28 22:29:44 +08:00
ssongliu a750e87940 feat: Compose 环境变量编辑增加是否 1Panel 创建校验 (#6605) 2024-09-28 09:43:03 +08:00
John Bro c12f690753 fix: 重构容器编排环境变量设置逻辑 (#6600) 2024-09-27 22:34:38 +08:00
zhengkunwang 0dd51d0c35 feat: 优化日志加载 (#6593) 2024-09-27 22:34:33 +08:00
Kataick 7f627c041c flx: 修复会默认出现水平滚动条的问题 (#6564) 2024-09-25 10:50:55 +08:00
wanghe-fit2cloud 472ca884fd Revert "style:优化概览界面的布局 (#6491)"
This reverts commit 7f06c7e92e.
2024-09-25 10:50:29 +08:00
John Bro 236a2dffea fix: 添加国际化及样式修改 (#6555) 2024-09-23 18:12:50 +08:00
John Bro 9be9f1974f fix: 编排模板列表日期格式化 (#6551) 2024-09-23 18:12:46 +08:00
ssongliu 7961bddf9e fix: 解决服务包含 .socket 时起停失败的问题 (#6553)
Refs #6524
2024-09-23 18:12:42 +08:00
ssongliu a05f5c71b9 fix: 解决部分浏览器无法打开文件菜单的问题 (#6537) 2024-09-20 16:02:15 +08:00
John Bro 139e77112f feat: 容器编排显示容器状态,编排模板样式优化 (#6534)
Refs #4404
2024-09-20 16:02:03 +08:00
蘭 d4e21ae032 fix: 解决图片/视频预览时,重命名文件名导致浏览器缓存文件内容问题 (#6533)
Refs #6505
2024-09-20 16:02:00 +08:00
John Bro 104af30cbf fix: 创建和编辑 compose 文件支持设置环境变量 (#6503) 2024-09-20 16:01:57 +08:00
Anye f68dd7f00b fix: 调整Logo长宽比,防止显示被拉伸 (#6510)
* fix: 调整Logo长宽比,防止显示被拉伸
2024-09-20 16:01:53 +08:00
zhengkunwang 220a6fffa0 feat: demo 环境增加首页状态接口过滤 (#6498) 2024-09-20 16:01:50 +08:00
see-more 23c4365c0e style:优化概览界面的布局 (#6491)
* fix: <body> cannot be child of <div>

* style:优化概览界面的布局
2024-09-20 16:01:46 +08:00
ssongliu 301bc4f6b8 feat: S3 备份账号支持 Path Style (#6489)
Refs #4553
2024-09-20 16:01:40 +08:00
John Bro ab03b05843 fix: 删除容器后 compose 记录保存以便后续处理 (#6447)
Refs https://github.com/1Panel-dev/1Panel/issues/5099
2024-09-20 16:01:34 +08:00
56 changed files with 465 additions and 153 deletions
+13 -10
View File
@@ -116,7 +116,7 @@ type PortHelper struct {
type ContainerOperation struct { type ContainerOperation struct {
Names []string `json:"names" validate:"required"` 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 { type ContainerRename struct {
@@ -200,6 +200,7 @@ type ComposeInfo struct {
Workdir string `json:"workdir"` Workdir string `json:"workdir"`
Path string `json:"path"` Path string `json:"path"`
Containers []ComposeContainer `json:"containers"` Containers []ComposeContainer `json:"containers"`
Env []string `json:"env"`
} }
type ComposeContainer struct { type ComposeContainer struct {
ContainerID string `json:"containerID"` ContainerID string `json:"containerID"`
@@ -208,22 +209,24 @@ type ComposeContainer struct {
State string `json:"state"` State string `json:"state"`
} }
type ComposeCreate struct { type ComposeCreate struct {
Name string `json:"name"` Name string `json:"name"`
From string `json:"from" validate:"required,oneof=edit path template"` From string `json:"from" validate:"required,oneof=edit path template"`
File string `json:"file"` File string `json:"file"`
Path string `json:"path"` Path string `json:"path"`
Template uint `json:"template"` Template uint `json:"template"`
Env []string `json:"env"`
} }
type ComposeOperation struct { type ComposeOperation struct {
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
Path string `json:"path" 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"` WithFile bool `json:"withFile"`
} }
type ComposeUpdate struct { type ComposeUpdate struct {
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"` Path string `json:"path" validate:"required"`
Content string `json:"content" validate:"required"` Content string `json:"content" validate:"required"`
Env []string `json:"env"`
} }
type ContainerLog struct { type ContainerLog struct {
+16 -15
View File
@@ -175,21 +175,22 @@ type WebsiteUpdateDirPermission struct {
} }
type WebsiteProxyConfig struct { type WebsiteProxyConfig struct {
ID uint `json:"id" validate:"required"` ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"` Operate string `json:"operate" validate:"required"`
Enable bool `json:"enable" ` Enable bool `json:"enable" `
Cache bool `json:"cache" ` Cache bool `json:"cache" `
CacheTime int `json:"cacheTime" ` CacheTime int `json:"cacheTime" `
CacheUnit string `json:"cacheUnit"` CacheUnit string `json:"cacheUnit"`
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required"`
Modifier string `json:"modifier"` Modifier string `json:"modifier"`
Match string `json:"match" validate:"required"` Match string `json:"match" validate:"required"`
ProxyPass string `json:"proxyPass" validate:"required"` ProxyPass string `json:"proxyPass" validate:"required"`
ProxyHost string `json:"proxyHost" validate:"required"` ProxyHost string `json:"proxyHost" validate:"required"`
Content string `json:"content"` Content string `json:"content"`
FilePath string `json:"filePath"` FilePath string `json:"filePath"`
Replaces map[string]string `json:"replaces"` Replaces map[string]string `json:"replaces"`
SNI bool `json:"sni"` SNI bool `json:"sni"`
ProxySSLName string `json:"proxySSLName"`
} }
type WebsiteProxyReq struct { type WebsiteProxyReq struct {
+1
View File
@@ -12,4 +12,5 @@ type Compose struct {
BaseModel BaseModel
Name string `gorm:"type:varchar(256)" json:"name"` Name string `gorm:"type:varchar(256)" json:"name"`
Path string `gorm:"type:varchar(256)" json:"path"`
} }
+5
View File
@@ -19,6 +19,7 @@ type IComposeTemplateRepo interface {
CreateRecord(compose *model.Compose) error CreateRecord(compose *model.Compose) error
DeleteRecord(opts ...DBOption) error DeleteRecord(opts ...DBOption) error
ListRecord() ([]model.Compose, error) ListRecord() ([]model.Compose, error)
UpdateRecord(name string, vars map[string]interface{}) error
} }
func NewIComposeTemplateRepo() IComposeTemplateRepo { func NewIComposeTemplateRepo() IComposeTemplateRepo {
@@ -102,3 +103,7 @@ func (u *ComposeTemplateRepo) DeleteRecord(opts ...DBOption) error {
} }
return db.Delete(&model.Compose{}).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
}
+4
View File
@@ -898,6 +898,10 @@ func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) s
break break
} }
} }
if len(containers) == 0 {
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
filePath = composeItem.Path
}
} }
if _, err := os.Stat(filePath); err != nil { if _, err := os.Stat(filePath); err != nil {
return "" return ""
+107 -13
View File
@@ -8,6 +8,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath"
"sort" "sort"
"strings" "strings"
"time" "time"
@@ -31,6 +32,12 @@ const composeConfigLabel = "com.docker.compose.project.config_files"
const composeWorkdirLabel = "com.docker.compose.project.working_dir" const composeWorkdirLabel = "com.docker.compose.project.working_dir"
const composeCreatedBy = "createdBy" 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) { func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface{}, error) {
var ( var (
records []dto.ComposeInfo records []dto.ComposeInfo
@@ -52,6 +59,20 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
} }
composeCreatedByLocal, _ := composeRepo.ListRecord() 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) composeMap := make(map[string]dto.ComposeInfo)
for _, container := range list { for _, container := range list {
if name, ok := container.Labels[composeProjectLabel]; ok { 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 { mergedMap := make(map[string]dto.ComposeInfo)
global.LOG.Error(err) 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 value.Name = key
records = append(records, value) records = append(records, value)
} }
@@ -127,7 +160,26 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
} }
BackDatas = records[start:end] 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) { 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 { if err := u.loadPath(&req); err != nil {
return false, err return false, err
} }
if err := newComposeEnv(req.Path, req.Env); err != nil {
return false, err
}
cmd := exec.Command("docker-compose", "-f", req.Path, "config") cmd := exec.Command("docker-compose", "-f", req.Path, "config")
stdout, err := cmd.CombinedOutput() stdout, err := cmd.CombinedOutput()
if err != nil { if err != nil {
@@ -173,6 +228,9 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
if err != nil { if err != nil {
return "", err return "", err
} }
if err := newComposeEnv(req.Path, req.Env); err != nil {
return "", err
}
go func() { go func() {
defer file.Close() defer file.Close()
cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d") cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d")
@@ -186,7 +244,7 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
return return
} }
global.LOG.Infof("docker-compose up %s successful!", req.Name) 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!") _, _ = 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 { if _, err := os.Stat(req.Path); 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.Path, err)
} }
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil { if req.Operation == "delete" {
return errors.New(string(stdout)) if stdout, err := compose.Operate(req.Path, "down"); 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.WithFile { if req.WithFile {
_ = os.RemoveAll(path.Dir(req.Path)) _ = 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 return nil
} }
@@ -232,6 +299,10 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
write.Flush() write.Flush()
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.Path) 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 stdout, err := compose.Up(req.Path); err != nil {
if err := recreateCompose(string(oldFile), 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) 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 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
}
+7 -6
View File
@@ -3,10 +3,6 @@ package service
import ( import (
"encoding/json" "encoding/json"
"fmt" "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" network "net"
"os" "os"
"sort" "sort"
@@ -14,6 +10,11 @@ import (
"sync" "sync"
"time" "time"
"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"
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/global" "github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd" "github.com/1Panel-dev/1Panel/backend/utils/cmd"
@@ -268,13 +269,13 @@ func loadDiskInfo() []dto.DiskInfo {
if strings.HasPrefix(fields[6], "/snap") || len(strings.Split(fields[6], "/")) > 10 { if strings.HasPrefix(fields[6], "/snap") || len(strings.Split(fields[6], "/")) > 10 {
continue continue
} }
if strings.TrimSpace(fields[1]) == "tmpfs" { if strings.TrimSpace(fields[1]) == "tmpfs" || strings.TrimSpace(fields[1]) == "overlay" {
continue continue
} }
if strings.Contains(fields[2], "K") { if strings.Contains(fields[2], "K") {
continue continue
} }
if strings.Contains(fields[6], "docker") { if strings.Contains(fields[6], "docker") || strings.Contains(fields[6], "podman") || strings.Contains(fields[6], "containerd") || strings.HasPrefix(fields[6], "/var/lib/containers") {
continue continue
} }
isExclude := false isExclude := false
+10 -1
View File
@@ -10,8 +10,10 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/constant" "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/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/docker" "github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@@ -336,8 +338,15 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
func (u *DockerService) OperateDocker(req dto.DockerOperation) error { func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
service := "docker" service := "docker"
sudo := cmd.SudoHandleCmd()
if req.Operation == "stop" { 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) stdout, err := cmd.Execf("systemctl %s %s ", req.Operation, service)
if err != nil { if err != nil {
+10
View File
@@ -124,6 +124,16 @@ func (u *SSHService) OperateSSH(operation string) error {
if operation == "enable" || operation == "disable" { if operation == "enable" || operation == "disable" {
serviceName += ".service" 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) stdout, err := cmd.Execf("%s systemctl %s %s", sudo, operation, serviceName)
if err != nil { if err != nil {
if strings.Contains(stdout, "alias name or linked unit file") { if strings.Contains(stdout, "alias name or linked unit file") {
+6
View File
@@ -1667,6 +1667,9 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
} }
if req.SNI { if req.SNI {
location.UpdateDirective("proxy_ssl_server_name", []string{"on"}) location.UpdateDirective("proxy_ssl_server_name", []string{"on"})
if req.ProxySSLName != "" {
location.UpdateDirective("proxy_ssl_name", []string{req.ProxySSLName})
}
} else { } else {
location.UpdateDirective("proxy_ssl_server_name", []string{"off"}) location.UpdateDirective("proxy_ssl_server_name", []string{"off"})
} }
@@ -1749,6 +1752,9 @@ func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, e
if directive.GetName() == "proxy_ssl_server_name" { if directive.GetName() == "proxy_ssl_server_name" {
proxyConfig.SNI = directive.GetParameters()[0] == "on" proxyConfig.SNI = directive.GetParameters()[0] == "on"
} }
if directive.GetName() == "proxy_ssl_name" {
proxyConfig.ProxySSLName = directive.GetParameters()[0]
}
} }
res = append(res, proxyConfig) res = append(res, proxyConfig)
} }
+7 -8
View File
@@ -217,15 +217,14 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
if req.Domains != "" { if req.Domains != "" {
domainArray := strings.Split(req.Domains, "\n") domainArray := strings.Split(req.Domains, "\n")
for _, domain := range domainArray { for _, domain := range domainArray {
if !common.IsValidDomain(domain) { if ipAddress := net.ParseIP(domain); ipAddress == nil {
err = buserr.WithName("ErrDomainFormat", domain) if !common.IsValidDomain(domain) {
return nil, err err = buserr.WithName("ErrDomainFormat", domain)
} else { return nil, err
if ipAddress := net.ParseIP(domain); ipAddress == nil {
domains = append(domains, domain)
} else {
ips = append(ips, ipAddress)
} }
domains = append(domains, domain)
} else {
ips = append(ips, ipAddress)
} }
} }
if len(domains) > 0 { if len(domains) > 0 {
+1
View File
@@ -94,6 +94,7 @@ func Init() {
migrations.AddClam, migrations.AddClam,
migrations.AddClamStatus, migrations.AddClamStatus,
migrations.AddAlertMenu, migrations.AddAlertMenu,
migrations.AddComposeColumn,
}) })
if err := m.Migrate(); err != nil { if err := m.Migrate(); err != nil {
global.LOG.Error(err) 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 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
},
}
+1
View File
@@ -19,6 +19,7 @@ var whiteUrlList = map[string]struct{}{
"/api/v1/logs/operation": {}, "/api/v1/logs/operation": {},
"/api/v1/logs/login": {}, "/api/v1/logs/login": {},
"/api/v1/auth/logout": {}, "/api/v1/auth/logout": {},
"/api/v1/dashboard/current": {},
"/api/v1/apps/installed/loadport": {}, "/api/v1/apps/installed/loadport": {},
"/api/v1/apps/installed/check": {}, "/api/v1/apps/installed/check": {},
+5 -1
View File
@@ -27,12 +27,16 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
if len(scType) == 0 { if len(scType) == 0 {
scType = "Standard" scType = "Standard"
} }
mode := loadParamFromVars("mode", vars)
if len(mode) == 0 {
mode = "virtual hosted"
}
sess, err := session.NewSession(&aws.Config{ sess, err := session.NewSession(&aws.Config{
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""), Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
Endpoint: aws.String(endpoint), Endpoint: aws.String(endpoint),
Region: aws.String(region), Region: aws.String(region),
DisableSSL: aws.Bool(true), DisableSSL: aws.Bool(true),
S3ForcePathStyle: aws.Bool(false), S3ForcePathStyle: aws.Bool(mode == "path"),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
+9 -2
View File
@@ -29,10 +29,17 @@ type FirewallClient interface {
} }
func NewFirewallClient() (FirewallClient, error) { func NewFirewallClient() (FirewallClient, error) {
if _, err := os.Stat("/usr/sbin/firewalld"); err == nil { _, firewalldErr := os.Stat("/usr/sbin/firewalld")
_, ufwErr := os.Stat("/usr/sbin/ufw")
if firewalldErr == nil && ufwErr == nil {
return nil, buserr.New("firewalld and ufw both found, only one firewall should be active")
}
if firewalldErr == nil {
return client.NewFirewalld() return client.NewFirewalld()
} }
if _, err := os.Stat("/usr/sbin/ufw"); err == nil { if ufwErr == nil {
return client.NewUfw() return client.NewUfw()
} }
return nil, buserr.New(constant.ErrFirewall) return nil, buserr.New(constant.ErrFirewall)
@@ -234,11 +234,13 @@ func (l *Location) AddSubFilter(subFilters map[string]string) {
} }
l.UpdateDirective("proxy_set_header", []string{"Accept-Encoding", `""`}) l.UpdateDirective("proxy_set_header", []string{"Accept-Encoding", `""`})
l.UpdateDirective("sub_filter_once", []string{"off"}) l.UpdateDirective("sub_filter_once", []string{"off"})
l.UpdateDirective("sub_filter_types", []string{"*"})
} }
func (l *Location) RemoveSubFilter() { func (l *Location) RemoveSubFilter() {
l.RemoveDirective("sub_filter", []string{}) l.RemoveDirective("sub_filter", []string{})
l.RemoveDirective("proxy_set_header", []string{"Accept-Encoding", `""`}) l.RemoveDirective("proxy_set_header", []string{"Accept-Encoding", `""`})
l.RemoveDirective("sub_filter_once", []string{"off"}) l.RemoveDirective("sub_filter_once", []string{"off"})
l.RemoveDirective("sub_filter_types", []string{"*"})
l.Replaces = nil l.Replaces = nil
} }
+1
View File
@@ -11,4 +11,5 @@ location ^~ /test {
add_header X-Cache $upstream_cache_status; add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-cache; add_header Cache-Control no-cache;
proxy_ssl_server_name off; proxy_ssl_server_name off;
proxy_ssl_name $proxy_host;
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

+1 -1
View File
@@ -41,7 +41,7 @@
"highlight.js": "^11.9.0", "highlight.js": "^11.9.0",
"js-base64": "^3.7.7", "js-base64": "^3.7.7",
"md-editor-v3": "^2.11.3", "md-editor-v3": "^2.11.3",
"monaco-editor": "^0.51.0", "monaco-editor": "^0.50.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^1.6.1", "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
View File
@@ -241,6 +241,8 @@ export namespace Container {
path: string; path: string;
containers: Array<ComposeContainer>; containers: Array<ComposeContainer>;
expand: boolean; expand: boolean;
envStr: string;
env: Array<string>;
} }
export interface ComposeContainer { export interface ComposeContainer {
name: string; name: string;
@@ -254,6 +256,8 @@ export namespace Container {
file: string; file: string;
path: string; path: string;
template: number; template: number;
env: Array<string>;
envStr: string;
} }
export interface ComposeOperation { export interface ComposeOperation {
name: string; name: string;
@@ -265,6 +269,8 @@ export namespace Container {
name: string; name: string;
path: string; path: string;
content: string; content: string;
env: Array<string>;
createdBy: string;
} }
export interface TemplateCreate { export interface TemplateCreate {
+1
View File
@@ -174,6 +174,7 @@ export namespace Setting {
} }
export interface LicenseStatus { export interface LicenseStatus {
productPro: string; productPro: string;
trial: boolean;
status: string; status: string;
} }
} }
+2
View File
@@ -373,6 +373,8 @@ export namespace Website {
content?: string; content?: string;
proxyAddress?: string; proxyAddress?: string;
proxyProtocol?: string; proxyProtocol?: string;
sni: boolean;
proxySSLName: string;
} }
export interface ProxReplace { export interface ProxReplace {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

@@ -1,16 +1,14 @@
<template> <template>
<div> <div>
<body> <div>
<div> <el-row type="flex" justify="center">
<el-row type="flex" justify="center"> <h1>{{ loadErrInfo() }}</h1>
<h1>{{ loadErrInfo() }}</h1> </el-row>
</el-row> </div>
</div> <hr />
<hr /> <div>
<div> <el-row type="flex" justify="center"><span>nginx</span></el-row>
<el-row type="flex" justify="center"><span>nginx</span></el-row> </div>
</div>
</body>
</div> </div>
</template> </template>
@@ -108,6 +108,9 @@ const showBack = computed(() => {
.el-button + .el-button { .el-button + .el-button {
margin: 0 !important; margin: 0 !important;
} }
.el-button-group .el-button {
margin: 0 !important;
}
} }
.content-container_form { .content-container_form {
+4 -4
View File
@@ -1,5 +1,5 @@
<template> <template>
<div v-loading="isLoading"> <div v-loading="firstLoading">
<div v-if="defaultButton"> <div v-if="defaultButton">
<el-checkbox border v-model="tailLog" class="float-left" @change="changeTail(false)"> <el-checkbox border v-model="tailLog" class="float-left" @change="changeTail(false)">
{{ $t('commons.button.watch') }} {{ $t('commons.button.watch') }}
@@ -78,6 +78,7 @@ const minPage = ref(1);
const maxPage = ref(1); const maxPage = ref(1);
const logs = ref([]); const logs = ref([]);
const isLoading = ref(false); const isLoading = ref(false);
const firstLoading = ref(true);
const readReq = reactive({ const readReq = reactive({
id: 0, id: 0,
@@ -129,6 +130,7 @@ const getContent = (pre: boolean) => {
} }
isLoading.value = true; isLoading.value = true;
ReadByLine(readReq).then((res) => { ReadByLine(readReq).then((res) => {
firstLoading.value = false;
if (!end.value && res.data.end) { if (!end.value && res.data.end) {
lastLogs.value = [...logs.value]; lastLogs.value = [...logs.value];
} }
@@ -193,7 +195,7 @@ const getContent = (pre: boolean) => {
maxPage.value = res.data.total; maxPage.value = res.data.total;
minPage.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); logs.value.splice(0, readReq.pageSize);
if (minPage.value > 1) { if (minPage.value > 1) {
minPage.value--; minPage.value--;
@@ -278,8 +280,6 @@ const init = () => {
} }
readReq.latest = true; readReq.latest = true;
search(); search();
nextTick(() => {});
}; };
const clearLog = (): void => { const clearLog = (): void => {
+2 -2
View File
@@ -211,8 +211,8 @@ const onRecover = async (row: File.File) => {
currentRow.value = row; currentRow.value = row;
if (type.value !== 'app' && type.value !== 'website') { if (type.value !== 'app' && type.value !== 'website') {
ElMessageBox.confirm( ElMessageBox.confirm(
i18n.global.t('commons.msg.backupHelper', [name.value + '( ' + detailName.value + ' )']), i18n.global.t('commons.msg.recoverHelper', [row.name]),
i18n.global.t('commons.button.backup'), i18n.global.t('commons.button.recover'),
{ {
confirmButtonText: i18n.global.t('commons.button.confirm'), confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'), cancelButtonText: i18n.global.t('commons.button.cancel'),
+10 -1
View File
@@ -654,6 +654,8 @@ const message = {
privileged: 'Privileged', privileged: 'Privileged',
privilegedHelper: privilegedHelper:
'Allows the container to perform certain privileged operations on the host, which may increase container risks. Use with caution!', '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', upgradeHelper: 'Repository Name/Image Name: Image Version',
upgradeWarning2: upgradeWarning2:
@@ -768,8 +770,9 @@ const message = {
composeHelper: composeHelper:
'The composition created through 1Panel editor or template will be saved in the {0}/docker/compose directory.', 'The composition created through 1Panel editor or template will be saved in the {0}/docker/compose directory.',
deleteFile: 'Delete file', deleteFile: 'Delete file',
allDelete: 'Permanently Delete',
deleteComposeHelper: 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.', deleteCompose: '" Delete this composition.',
apps: 'Apps', apps: 'Apps',
local: 'Local', local: 'Local',
@@ -781,11 +784,16 @@ const message = {
content: 'Content', content: 'Content',
contentEmpty: 'Compose content cannot be empty, please enter and try again!', contentEmpty: 'Compose content cannot be empty, please enter and try again!',
containerNumber: 'Container number', containerNumber: 'Container number',
containerStatus: 'Container Status',
exited: 'Exited',
running: 'Running',
down: 'Down', down: 'Down',
up: 'Up', up: 'Up',
composeDetailHelper: composeDetailHelper:
'The compose is created external to 1Panel. The start and stop operations are not supported.', '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?', 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', setting: 'Setting',
operatorStatusHelper: 'This action will {0} Docker service, do you want to continue?', operatorStatusHelper: 'This action will {0} Docker service, do you want to continue?',
@@ -1410,6 +1418,7 @@ const message = {
LOCAL: 'Server Disks', LOCAL: 'Server Disks',
OSS: 'Ali OSS', OSS: 'Ali OSS',
S3: 'Amazon S3', S3: 'Amazon S3',
mode: 'Mode',
MINIO: 'MINIO', MINIO: 'MINIO',
SFTP: 'SFTP', SFTP: 'SFTP',
WebDAV: 'WebDAV', WebDAV: 'WebDAV',
+9 -1
View File
@@ -629,6 +629,8 @@ const message = {
emptyUser: '為空時,將使用容器默認的用戶登錄', emptyUser: '為空時,將使用容器默認的用戶登錄',
privileged: '特權模式', privileged: '特權模式',
privilegedHelper: '允許容器在主機上執行某些特權操作,可能會增加容器風險,請謹慎開啟!', privilegedHelper: '允許容器在主機上執行某些特權操作,可能會增加容器風險,請謹慎開啟!',
editComposeHelper:
'注意:設置的環境變數會默認寫入 1panel.env 文件。\n若需在容器中使用這些參數,還需在 compose 文件中手動添加 env_file 引用。',
upgradeHelper: '倉庫名稱/鏡像名稱:鏡像版本', upgradeHelper: '倉庫名稱/鏡像名稱:鏡像版本',
upgradeWarning2: '升級操作需要重建容器,任何未持久化的數據將會丟失,是否繼續?', upgradeWarning2: '升級操作需要重建容器,任何未持久化的數據將會丟失,是否繼續?',
@@ -739,7 +741,8 @@ const message = {
composePathHelper: '配置文件保存路徑: {0}', composePathHelper: '配置文件保存路徑: {0}',
composeHelper: '通過 1Panel 編輯或者模版創建的編排,將保存在 {0}/docker/compose 路徑下', composeHelper: '通過 1Panel 編輯或者模版創建的編排,將保存在 {0}/docker/compose 路徑下',
deleteFile: '刪除文件', deleteFile: '刪除文件',
deleteComposeHelper: '刪除 {0} 目录下所有文件,包括該文件下的持久化文件等,請謹慎操作!', allDelete: '徹底刪除',
deleteComposeHelper: '刪除容器編排的所有文件,包括配置文件和持久化文件,請謹慎操作!',
deleteCompose: '" 刪除此編排', deleteCompose: '" 刪除此編排',
apps: '應用商店', apps: '應用商店',
local: '本地', local: '本地',
@@ -751,10 +754,14 @@ const message = {
content: '內容', content: '內容',
contentEmpty: '編排內容不能為空,請輸入後重試!', contentEmpty: '編排內容不能為空,請輸入後重試!',
containerNumber: '容器數量', containerNumber: '容器數量',
containerStatus: '容器狀態',
exited: '已停止',
running: '運行中',
down: '刪除', down: '刪除',
up: '啟動', up: '啟動',
composeDetailHelper: '該 compose 為 1Panel 編排外部創建。暫不支持啟停操作。', composeDetailHelper: '該 compose 為 1Panel 編排外部創建。暫不支持啟停操作。',
composeOperatorHelper: '將對 {0} 進行 {1} 操作,是否繼續?', composeOperatorHelper: '將對 {0} 進行 {1} 操作,是否繼續?',
composeDownHelper: '將停止並刪除 {0} 編排下所有容器及網絡,是否繼續?',
setting: '配置', setting: '配置',
operatorStatusHelper: '此操作將{0}Docker 服務,是否繼續?', operatorStatusHelper: '此操作將{0}Docker 服務,是否繼續?',
@@ -1323,6 +1330,7 @@ const message = {
LOCAL: '服務器磁盤', LOCAL: '服務器磁盤',
OSS: '阿裏雲 OSS', OSS: '阿裏雲 OSS',
S3: '亞馬遜 S3 雲存儲', S3: '亞馬遜 S3 雲存儲',
mode: '模式',
MINIO: 'MINIO', MINIO: 'MINIO',
SFTP: 'SFTP', SFTP: 'SFTP',
WebDAV: 'WebDAV', WebDAV: 'WebDAV',
+9 -3
View File
@@ -632,7 +632,8 @@ const message = {
emptyUser: '为空时,将使用容器默认的用户登录', emptyUser: '为空时,将使用容器默认的用户登录',
privileged: '特权模式', privileged: '特权模式',
privilegedHelper: '允许容器在主机上执行某些特权操作,可能会增加容器风险,谨慎开启!', privilegedHelper: '允许容器在主机上执行某些特权操作,可能会增加容器风险,谨慎开启!',
editComposeHelper:
'注意:设置的环境变量会默认写入 1panel.env 文件。\n如需在容器中使用这些参数,还需在 compose 文件中手动添加 env_file 引用。',
upgradeHelper: '仓库名称/镜像名称:镜像版本', upgradeHelper: '仓库名称/镜像名称:镜像版本',
upgradeWarning2: '升级操作需要重建容器,任何未持久化的数据将会丢失,是否继续?', upgradeWarning2: '升级操作需要重建容器,任何未持久化的数据将会丢失,是否继续?',
oldImage: '当前镜像', oldImage: '当前镜像',
@@ -736,13 +737,13 @@ const message = {
mirrorHelper: mirrorHelper:
'当存在多个加速器时,需要换行显示,例: \nhttp://xxxxxx.m.daocloud.io \nhttps://xxxxxx.mirror.aliyuncs.com', '当存在多个加速器时,需要换行显示,例: \nhttp://xxxxxx.m.daocloud.io \nhttps://xxxxxx.mirror.aliyuncs.com',
registrieHelper: '当存在多个私有仓库时,需要换行显示,例:\n172.16.10.111:8081 \n172.16.10.112:8081', registrieHelper: '当存在多个私有仓库时,需要换行显示,例:\n172.16.10.111:8081 \n172.16.10.112:8081',
compose: '编排', compose: '编排',
fromChangeHelper: '切换来源将清空当前已编辑内容,是否继续?', fromChangeHelper: '切换来源将清空当前已编辑内容,是否继续?',
composePathHelper: '配置文件保存路径: {0}', composePathHelper: '配置文件保存路径: {0}',
composeHelper: '通过 1Panel 编辑或者模版创建的编排,将保存在 {0}/docker/compose 路径下', composeHelper: '通过 1Panel 编辑或者模版创建的编排,将保存在 {0}/docker/compose 路径下',
deleteFile: '删除文件', deleteFile: '删除文件',
deleteComposeHelper: '删除 {0} 目录下所有文件,包括该文件下的持久化文件等,请谨慎操作!', allDelete: '彻底删除',
deleteComposeHelper: '删除容器编排的所有文件,包括配置文件和持久化文件,请谨慎操作!',
deleteCompose: '" 删除此编排', deleteCompose: '" 删除此编排',
apps: '应用商店', apps: '应用商店',
local: '本地', local: '本地',
@@ -754,10 +755,14 @@ const message = {
content: '内容', content: '内容',
contentEmpty: '编排内容不能为空,请输入后重试!', contentEmpty: '编排内容不能为空,请输入后重试!',
containerNumber: '容器数量', containerNumber: '容器数量',
containerStatus: '容器状态',
exited: '已停止',
running: '运行中',
down: '删除', down: '删除',
up: '启动', up: '启动',
composeDetailHelper: '该 compose 为 1Panel 编排外部创建。暂不支持启停操作。', composeDetailHelper: '该 compose 为 1Panel 编排外部创建。暂不支持启停操作。',
composeOperatorHelper: '将对 {0} 进行 {1} 操作,是否继续?', composeOperatorHelper: '将对 {0} 进行 {1} 操作,是否继续?',
composeDownHelper: '将停止并删除 {0} 编排下所有容器及网络,是否继续?',
setting: '配置', setting: '配置',
operatorStatusHelper: '此操作将{0}Docker 服务,是否继续?', operatorStatusHelper: '此操作将{0}Docker 服务,是否继续?',
@@ -1327,6 +1332,7 @@ const message = {
LOCAL: '服务器磁盘', LOCAL: '服务器磁盘',
OSS: '阿里云 OSS', OSS: '阿里云 OSS',
S3: '亚马逊 S3 云存储', S3: '亚马逊 S3 云存储',
mode: '模式',
MINIO: 'MINIO', MINIO: 'MINIO',
SFTP: 'SFTP', SFTP: 'SFTP',
WebDAV: 'WebDAV', WebDAV: 'WebDAV',
+1
View File
@@ -34,6 +34,7 @@ export interface GlobalState {
defaultNetwork: string; defaultNetwork: string;
isProductPro: boolean; isProductPro: boolean;
isTrial: boolean;
productProExpires: number; productProExpires: number;
errStatus: string; errStatus: string;
+1
View File
@@ -38,6 +38,7 @@ const GlobalStore = defineStore({
defaultNetwork: 'all', defaultNetwork: 'all',
isProductPro: false, isProductPro: false,
isTrial: false,
productProExpires: 0, productProExpires: 0,
errStatus: '', errStatus: '',
+2
View File
@@ -61,6 +61,7 @@ export async function loadProductProFromDB() {
res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02'; res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02';
if (globalStore.isProductPro) { if (globalStore.isProductPro) {
globalStore.productProExpires = Number(res.data.productPro); globalStore.productProExpires = Number(res.data.productPro);
globalStore.isTrial = res.data.trial;
} }
} }
switchTheme(); switchTheme();
@@ -81,6 +82,7 @@ export async function getXpackSettingForTheme() {
res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02'; res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02';
if (globalStore.isProductPro) { if (globalStore.isProductPro) {
globalStore.productProExpires = Number(res.data.productPro); globalStore.productProExpires = Number(res.data.productPro);
globalStore.isTrial = res.data.trial;
} }
if (!globalStore.isProductPro) { if (!globalStore.isProductPro) {
globalStore.isProductPro = false; globalStore.isProductPro = false;
@@ -66,7 +66,7 @@
placeholder="#Define or paste the content of your docker-compose file here" placeholder="#Define or paste the content of your docker-compose file here"
:indent-with-tab="true" :indent-with-tab="true"
:tabSize="4" :tabSize="4"
style="width: 100%; height: calc(100vh - 376px)" style="width: 100%; height: calc(100vh - 400px)"
:lineWrapping="true" :lineWrapping="true"
:matchBrackets="true" :matchBrackets="true"
theme="cobalt" theme="cobalt"
@@ -86,6 +86,26 @@
/> />
</div> </div>
</el-form-item> </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-form>
</el-col> </el-col>
</el-row> </el-row>
@@ -143,6 +163,9 @@ const form = reactive({
path: '', path: '',
file: '', file: '',
template: null as number, template: null as number,
env: [],
envStr: '',
envFileContent: `env_file:\n - 1panel.env`,
}); });
const rules = reactive({ const rules = reactive({
name: [Rules.requiredInput, Rules.imageName], name: [Rules.requiredInput, Rules.imageName],
@@ -163,6 +186,8 @@ const acceptParams = (): void => {
form.path = ''; form.path = '';
form.file = ''; form.file = '';
form.template = null; form.template = null;
form.envStr = '';
form.env = [];
loadTemplates(); loadTemplates();
loadPath(); loadPath();
isStartReading.value = false; isStartReading.value = false;
@@ -242,6 +267,9 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
MsgError(i18n.global.t('container.contentEmpty')); MsgError(i18n.global.t('container.contentEmpty'));
return; return;
} }
if (form.envStr) {
form.env = form.envStr.split('\n');
}
loading.value = true; loading.value = true;
await testCompose(form) await testCompose(form)
.then(async (res) => { .then(async (res) => {
@@ -8,8 +8,8 @@
<el-form ref="deleteForm" v-loading="loading"> <el-form ref="deleteForm" v-loading="loading">
<el-form-item> <el-form-item>
<el-checkbox v-model="deleteFile" :label="$t('container.deleteFile')" /> <el-checkbox v-model="deleteFile" :label="$t('container.deleteFile')" />
<span class="input-help"> <span class="input-help whitespace-break-spaces">
{{ $t('container.deleteComposeHelper', [loadComposeDir()]) }} {{ $t('container.deleteComposeHelper') }}
</span> </span>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@@ -64,21 +64,12 @@ const acceptParams = async (prop: DialogProps) => {
dialogVisible.value = true; 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 () => { const submit = async () => {
loading.value = true; loading.value = true;
let params = { let params = {
name: composeName.value, name: composeName.value,
path: composePath.value, path: composePath.value,
operation: 'down', operation: 'delete',
withFile: deleteFile.value, withFile: deleteFile.value,
}; };
await composeOperator(params) await composeOperator(params)
@@ -7,7 +7,7 @@
<el-tag effect="dark" type="success">{{ composeName }}</el-tag> <el-tag effect="dark" type="success">{{ composeName }}</el-tag>
</div> </div>
<div v-if="createdBy === '1Panel'" style="margin-left: 50px"> <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') }} {{ $t('container.start') }}
</el-button> </el-button>
<el-divider direction="vertical" /> <el-divider direction="vertical" />
@@ -143,7 +143,6 @@ const dialogContainerLogRef = ref();
const opRef = ref(); const opRef = ref();
const emit = defineEmits<{ (e: 'back'): void }>();
interface DialogProps { interface DialogProps {
createdBy: string; createdBy: string;
name: string; name: string;
@@ -260,15 +259,18 @@ const onOperate = async (op: string) => {
}; };
const onComposeOperate = async (operation: string) => { const onComposeOperate = async (operation: string) => {
ElMessageBox.confirm( let mes =
i18n.global.t('container.composeOperatorHelper', [composeName.value, i18n.global.t('container.' + operation)]), operation === 'down'
i18n.global.t('container.' + operation), ? i18n.global.t('container.composeDownHelper', [composeName.value])
{ : i18n.global.t('container.composeOperatorHelper', [
confirmButtonText: i18n.global.t('commons.button.confirm'), composeName.value,
cancelButtonText: i18n.global.t('commons.button.cancel'), i18n.global.t('container.' + operation),
type: 'info', ]);
}, ElMessageBox.confirm(mes, i18n.global.t('container.' + operation), {
).then(async () => { confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
let params = { let params = {
name: composeName.value, name: composeName.value,
path: composePath.value, path: composePath.value,
@@ -280,11 +282,7 @@ const onComposeOperate = async (operation: string) => {
.then(() => { .then(() => {
loading.value = false; loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
if (operation === 'down') { search();
emit('back');
} else {
search();
}
}) })
.catch(() => { .catch(() => {
loading.value = false; loading.value = false;
@@ -9,20 +9,56 @@
<template #header> <template #header>
<DrawerHeader :header="$t('commons.button.edit')" :resource="name" :back="handleClose" /> <DrawerHeader :header="$t('commons.button.edit')" :resource="name" :back="handleClose" />
</template> </template>
<div v-loading="loading"> <div v-loading="loading" style="padding-bottom: 20px">
<codemirror <el-row type="flex" justify="center">
:autofocus="true" <el-col :span="22">
placeholder="#Define or paste the content of your docker-compose file here" <el-form ref="formRef" @submit.prevent label-position="top">
:indent-with-tab="true" <el-form-item>
:tabSize="4" <codemirror
style="width: 100%; height: calc(100vh - 175px)" :autofocus="true"
:lineWrapping="true" placeholder="#Define or paste the content of your docker-compose file here"
:matchBrackets="true" :indent-with-tab="true"
theme="cobalt" :tabSize="4"
:styleActiveLine="true" :style="{ width: '100%', height: `calc(100vh - ${loadHeight()})` }"
:extensions="extensions" :lineWrapping="true"
v-model="content" :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> </div>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
@@ -45,26 +81,38 @@ import { composeUpdate } from '@/api/modules/container';
import i18n from '@/lang'; import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import DrawerHeader from '@/components/drawer-header/index.vue'; import DrawerHeader from '@/components/drawer-header/index.vue';
import { ElForm } from 'element-plus';
const emit = defineEmits<{ (e: 'search'): void }>();
const loading = ref(false); const loading = ref(false);
const composeVisible = ref(false); const composeVisible = ref(false);
const extensions = [javascript(), oneDark]; const extensions = [javascript(), oneDark];
const path = ref(); const path = ref();
const content = ref(); const content = ref();
const name = ref(); const name = ref();
const environmentStr = ref();
const environmentEnv = ref();
const createdBy = ref();
const envFileContent = `env_file:\n - 1panel.env`;
const onSubmitEdit = async () => { const onSubmitEdit = async () => {
const param = { const param = {
name: name.value, name: name.value,
path: path.value, path: path.value,
content: content.value, content: content.value,
env: environmentStr.value,
createdBy: createdBy.value,
}; };
if (environmentStr.value != undefined) {
param.env = environmentStr.value.split('\n');
}
loading.value = true; loading.value = true;
await composeUpdate(param) await composeUpdate(param)
.then(() => { .then(() => {
loading.value = false; loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
composeVisible.value = false; composeVisible.value = false;
emit('search');
}) })
.catch(() => { .catch(() => {
loading.value = false; loading.value = false;
@@ -75,13 +123,22 @@ interface DialogProps {
name: string; name: string;
path: string; path: string;
content: string; content: string;
env: Array<string>;
envStr: string;
createdBy: string;
} }
const loadHeight = () => {
return createdBy.value === '1Panel' ? '300px' : '200px';
};
const acceptParams = (props: DialogProps): void => { const acceptParams = (props: DialogProps): void => {
composeVisible.value = true; composeVisible.value = true;
path.value = props.path; path.value = props.path;
name.value = props.name; name.value = props.name;
content.value = props.content; content.value = props.content;
createdBy.value = props.createdBy;
environmentEnv.value = props.env || [];
environmentStr.value = environmentEnv.value.join('\n');
}; };
const handleClose = () => { const handleClose = () => {
composeVisible.value = false; composeVisible.value = false;
+21 -11
View File
@@ -1,7 +1,7 @@
<template> <template>
<div v-loading="loading"> <div v-loading="loading">
<div v-show="isOnDetail"> <div v-show="isOnDetail">
<ComposeDetail @back="backList" ref="composeDetailRef" /> <ComposeDetail ref="composeDetailRef" />
</div> </div>
<el-card v-if="dockerStatus != 'Running'" class="mask-prompt"> <el-card v-if="dockerStatus != 'Running'" class="mask-prompt">
<span>{{ $t('container.serviceUnavailable') }}</span> <span>{{ $t('container.serviceUnavailable') }}</span>
@@ -64,12 +64,13 @@
<span v-if="row.createdBy === '1Panel'">1Panel</span> <span v-if="row.createdBy === '1Panel'">1Panel</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column :label="$t('container.containerStatus')" min-width="80" fix>
:label="$t('container.containerNumber')" <template #default="scope">
prop="containerNumber" <div>
min-width="80" {{ getContainerStatus(scope.row.containers) }}
fix </div>
/> </template>
</el-table-column>
<el-table-column :label="$t('commons.table.createdAt')" prop="createdAt" min-width="80" fix /> <el-table-column :label="$t('commons.table.createdAt')" prop="createdAt" min-width="80" fix />
<fu-table-operations <fu-table-operations
width="200px" width="200px"
@@ -82,7 +83,7 @@
</template> </template>
</LayoutContent> </LayoutContent>
<EditDialog ref="dialogEditRef" /> <EditDialog @search="search" ref="dialogEditRef" />
<CreateDialog @search="search" ref="dialogRef" /> <CreateDialog @search="search" ref="dialogRef" />
<DeleteDialog @search="search" ref="dialogDelRef" /> <DeleteDialog @search="search" ref="dialogDelRef" />
</div> </div>
@@ -173,9 +174,16 @@ const loadDetail = async (row: Container.ComposeInfo) => {
isOnDetail.value = true; isOnDetail.value = true;
composeDetailRef.value!.acceptParams(params); composeDetailRef.value!.acceptParams(params);
}; };
const backList = async () => { const getContainerStatus = (containers) => {
isOnDetail.value = false; const safeContainers = containers || [];
search(); 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(); const dialogRef = ref();
@@ -199,6 +207,8 @@ const onEdit = async (row: Container.ComposeInfo) => {
name: row.name, name: row.name,
path: row.path, path: row.path,
content: res.data, content: res.data,
env: row.env,
createdBy: row.createdBy,
}; };
dialogEditRef.value!.acceptParams(params); dialogEditRef.value!.acceptParams(params);
}; };
@@ -369,7 +369,7 @@ const dialogUpgradeRef = ref();
const dialogCommitRef = ref(); const dialogCommitRef = ref();
const dialogPortJumpRef = ref(); const dialogPortJumpRef = ref();
const opRef = ref(); const opRef = ref();
const includeAppStore = ref(true); const includeAppStore = ref();
const columns = ref([]); const columns = ref([]);
const dockerStatus = ref('Running'); const dockerStatus = ref('Running');
@@ -423,6 +423,7 @@ const dialogRenameRef = ref();
const dialogPruneRef = ref(); const dialogPruneRef = ref();
const search = async (column?: any) => { const search = async (column?: any) => {
localStorage.setItem('includeAppStore', includeAppStore.value ? 'true' : 'false');
let filterItem = props.filters ? props.filters : ''; let filterItem = props.filters ? props.filters : '';
paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy; paginationConfig.orderBy = column?.order ? column.prop : paginationConfig.orderBy;
paginationConfig.order = column?.order ? column.order : paginationConfig.order; paginationConfig.order = column?.order ? column.order : paginationConfig.order;
@@ -767,6 +768,8 @@ const buttons = [
]; ];
onMounted(() => { onMounted(() => {
let includeItem = localStorage.getItem('includeAppStore');
includeAppStore.value = !includeItem || includeItem === 'true';
loadStatus(); loadStatus();
}); });
</script> </script>
@@ -7,9 +7,7 @@
size="50%" size="50%"
> >
<template #header> <template #header>
<div class="card-header"> <DrawerHeader :header="$t('commons.button.view')" :back="handleClose" />
<span>{{ $t('commons.button.view') }}</span>
</div>
</template> </template>
<codemirror <codemirror
:autofocus="true" :autofocus="true"
@@ -38,6 +36,7 @@ import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark'; import { oneDark } from '@codemirror/theme-one-dark';
import { ref } from 'vue'; import { ref } from 'vue';
import { Codemirror } from 'vue-codemirror'; import { Codemirror } from 'vue-codemirror';
import DrawerHeader from '@/components/drawer-header/index.vue';
const extensions = [javascript(), oneDark]; const extensions = [javascript(), oneDark];
const detailVisible = ref(false); const detailVisible = ref(false);
@@ -51,6 +50,10 @@ const acceptParams = (params: DialogProps): void => {
detailVisible.value = true; detailVisible.value = true;
}; };
const handleClose = () => {
detailVisible.value = false;
};
defineExpose({ defineExpose({
acceptParams, acceptParams,
}); });
@@ -47,7 +47,7 @@
<el-table-column :label="$t('container.description')" prop="description" min-width="200" fix /> <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> <el-table-column :label="$t('commons.table.createdAt')" min-width="80" fix>
<template #default="{ row }"> <template #default="{ row }">
{{ dateFormatSimple(row.createdAt) }} {{ dateFormat(0, 0, row.createdAt) }}
</template> </template>
</el-table-column> </el-table-column>
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" /> <fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" />
@@ -63,7 +63,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, onMounted, ref } from 'vue'; import { reactive, onMounted, ref } from 'vue';
import { dateFormatSimple } from '@/utils/util'; import { dateFormat } from '@/utils/util';
import { Container } from '@/api/interface/container'; import { Container } from '@/api/interface/container';
import DetailDialog from '@/views/container/template/detail/index.vue'; import DetailDialog from '@/views/container/template/detail/index.vue';
import OperatorDialog from '@/views/container/template/operator/index.vue'; import OperatorDialog from '@/views/container/template/operator/index.vue';
@@ -81,8 +81,8 @@ const acceptParams = (props: BatchRoleProps) => {
addForm.paths.push(file.path); addForm.paths.push(file.path);
}); });
addForm.mode = Number.parseInt(String(props.files[0].mode), 8); addForm.mode = Number.parseInt(String(props.files[0].mode), 8);
addForm.group = props.files[0].group; addForm.group = props.files[0].group || props.files[0].gid;
addForm.user = props.files[0].user; addForm.user = props.files[0].user || props.files[0].uid;
addForm.sub = true; addForm.sub = true;
mode.value = String(props.files[0].mode); mode.value = String(props.files[0].mode);
@@ -53,7 +53,9 @@
<template #prompt> <template #prompt>
<el-alert type="info" :closable="false"> <el-alert type="info" :closable="false">
<template #title> <template #title>
<span v-html="$t('file.fileHeper')"></span> <span class="input-help whitespace-break-spaces">
{{ $t('file.fileHeper') }}
</span>
</template> </template>
</el-alert> </el-alert>
</template> </template>
@@ -115,7 +115,9 @@ const acceptParams = (props: EditProps) => {
isFullscreen.value = fileType.value === 'excel'; isFullscreen.value = fileType.value === 'excel';
loading.value = true; 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,
)}&timestamp=${new Date().getTime()}`;
open.value = true; open.value = true;
loading.value = false; loading.value = false;
}; };
@@ -47,7 +47,7 @@
<el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput"> <el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput">
<el-input v-model.trim="cosData.rowData!.varsJson['endpointItem']"> <el-input v-model.trim="cosData.rowData!.varsJson['endpointItem']">
<template #prepend> <template #prepend>
<el-select v-model.trim="endpointProto" style="width: 100px"> <el-select v-model.trim="endpointProto" style="width: 120px">
<el-option label="http" value="http" /> <el-option label="http" value="http" />
<el-option label="https" value="https" /> <el-option label="https" value="https" />
</el-select> </el-select>
@@ -29,7 +29,7 @@
> >
<el-input v-model="kodoData.rowData!.varsJson['domainItem']"> <el-input v-model="kodoData.rowData!.varsJson['domainItem']">
<template #prepend> <template #prepend>
<el-select v-model.trim="domainProto" style="width: 100px"> <el-select v-model.trim="domainProto" style="width: 120px">
<el-option label="http" value="http" /> <el-option label="http" value="http" />
<el-option label="https" value="https" /> <el-option label="https" value="https" />
</el-select> </el-select>
@@ -25,7 +25,7 @@
<el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput"> <el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput">
<el-input v-model="minioData.rowData!.varsJson['endpointItem']"> <el-input v-model="minioData.rowData!.varsJson['endpointItem']">
<template #prepend> <template #prepend>
<el-select v-model.trim="endpointProto" style="width: 100px"> <el-select v-model.trim="endpointProto" style="width: 120px">
<el-option label="http" value="http" /> <el-option label="http" value="http" />
<el-option label="https" value="https" /> <el-option label="https" value="https" />
</el-select> </el-select>
@@ -25,7 +25,7 @@
<el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput"> <el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput">
<el-input v-model="ossData.rowData!.varsJson['endpointItem']"> <el-input v-model="ossData.rowData!.varsJson['endpointItem']">
<template #prepend> <template #prepend>
<el-select v-model.trim="endpointProto" style="width: 100px"> <el-select v-model.trim="endpointProto" style="width: 120px">
<el-option label="http" value="http" /> <el-option label="http" value="http" />
<el-option label="https" value="https" /> <el-option label="https" value="https" />
</el-select> </el-select>
@@ -16,6 +16,12 @@
<el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect"> <el-form-item :label="$t('commons.table.type')" prop="type" :rules="Rules.requiredSelect">
<el-tag>{{ $t('setting.' + s3Data.rowData!.type) }}</el-tag> <el-tag>{{ $t('setting.' + s3Data.rowData!.type) }}</el-tag>
</el-form-item> </el-form-item>
<el-form-item :label="$t('setting.mode')" prop="varsJson.mode" :rules="Rules.requiredSelect">
<el-radio-group v-model="s3Data.rowData!.varsJson['mode']">
<el-radio value="virtual hosted">Virtual Hosted</el-radio>
<el-radio value="path">Path</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="Access Key ID" prop="accessKey" :rules="Rules.requiredInput"> <el-form-item label="Access Key ID" prop="accessKey" :rules="Rules.requiredInput">
<el-input v-model.trim="s3Data.rowData!.accessKey" /> <el-input v-model.trim="s3Data.rowData!.accessKey" />
</el-form-item> </el-form-item>
@@ -28,7 +34,7 @@
<el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput"> <el-form-item label="Endpoint" prop="varsJson.endpointItem" :rules="Rules.requiredInput">
<el-input v-model="s3Data.rowData!.varsJson['endpointItem']"> <el-input v-model="s3Data.rowData!.varsJson['endpointItem']">
<template #prepend> <template #prepend>
<el-select v-model.trim="endpointProto" style="width: 100px"> <el-select v-model="endpointProto" style="width: 120px">
<el-option label="http" value="http" /> <el-option label="http" value="http" />
<el-option label="https" value="https" /> <el-option label="https" value="https" />
</el-select> </el-select>
@@ -117,9 +123,12 @@ const s3Data = ref<DialogProps>({
const acceptParams = (params: DialogProps): void => { const acceptParams = (params: DialogProps): void => {
buckets.value = []; buckets.value = [];
s3Data.value = params; s3Data.value = params;
if (params.title === 'create' || (params.title === 'edit' && !s3Data.value.rowData.varsJson['scType'])) { if (!s3Data.value.rowData.varsJson['scType']) {
s3Data.value.rowData.varsJson['scType'] = 'STANDARD'; s3Data.value.rowData.varsJson['scType'] = 'STANDARD';
} }
if (!s3Data.value.rowData.varsJson['mode']) {
s3Data.value.rowData.varsJson['mode'] = 'virtual hosted';
}
if (s3Data.value.title === 'edit') { if (s3Data.value.title === 'edit') {
let httpItem = splitHttp(s3Data.value.rowData!.varsJson['endpoint']); let httpItem = splitHttp(s3Data.value.rowData!.varsJson['endpoint']);
s3Data.value.rowData!.varsJson['endpointItem'] = httpItem.url; s3Data.value.rowData!.varsJson['endpointItem'] = httpItem.url;
@@ -201,6 +201,7 @@ const search = async () => {
hasLicense.value = true; hasLicense.value = true;
if (globalStore.isProductPro) { if (globalStore.isProductPro) {
globalStore.productProExpires = Number(res.data.productPro); globalStore.productProExpires = Number(res.data.productPro);
globalStore.isTrial = res.data.trial;
} }
license.licenseName = res.data.licenseName; license.licenseName = res.data.licenseName;
license.message = res.data.message; license.message = res.data.message;
@@ -5,7 +5,9 @@
<template #prompt> <template #prompt>
<el-alert type="info" :closable="false"> <el-alert type="info" :closable="false">
<template #title> <template #title>
<span v-html="$t('runtime.statusHelper')"></span> <span class="input-help whitespace-break-spaces">
{{ $t('runtime.statusHelper') }}
</span>
</template> </template>
</el-alert> </el-alert>
</template> </template>
@@ -5,7 +5,9 @@
<template #prompt> <template #prompt>
<el-alert type="info" :closable="false"> <el-alert type="info" :closable="false">
<template #title> <template #title>
<span v-html="$t('runtime.statusHelper')"></span> <span class="input-help whitespace-break-spaces">
{{ $t('runtime.statusHelper') }}
</span>
</template> </template>
</el-alert> </el-alert>
</template> </template>
@@ -5,7 +5,9 @@
<template #prompt> <template #prompt>
<el-alert type="info" :closable="false"> <el-alert type="info" :closable="false">
<template #title> <template #title>
<span v-html="$t('runtime.statusHelper')"></span> <span class="input-help whitespace-break-spaces">
{{ $t('runtime.statusHelper') }}
</span>
</template> </template>
</el-alert> </el-alert>
</template> </template>
@@ -31,6 +31,9 @@
<el-switch v-model="proxy.sni"></el-switch> <el-switch v-model="proxy.sni"></el-switch>
<span class="input-help">{{ $t('website.sniHelper') }}</span> <span class="input-help">{{ $t('website.sniHelper') }}</span>
</el-form-item> </el-form-item>
<el-form-item label="proxy_ssl_name" prop="proxySSLName" v-if="proxy.sni">
<el-input v-model.trim="proxy.proxySSLName"></el-input>
</el-form-item>
<el-form-item :label="$t('website.cacheTime')" prop="cacheTime" v-if="proxy.cache"> <el-form-item :label="$t('website.cacheTime')" prop="cacheTime" v-if="proxy.cache">
<el-input v-model.number="proxy.cacheTime" maxlength="15"> <el-input v-model.number="proxy.cacheTime" maxlength="15">
<template #append> <template #append>
@@ -47,7 +50,7 @@
</el-form-item> </el-form-item>
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="12"> <el-col :span="12">
<el-form-item :label="$t('website.proxyPass')" prop="proxyPass"> <el-form-item :label="$t('website.proxyPass')" prop="proxyAddress">
<el-input <el-input
v-model.trim="proxy.proxyAddress" v-model.trim="proxy.proxyAddress"
:placeholder="$t('website.proxyHelper')" :placeholder="$t('website.proxyHelper')"
@@ -139,6 +142,7 @@ const rules = ref({
cacheTime: [Rules.requiredInput, checkNumberRange(1, 65535)], cacheTime: [Rules.requiredInput, checkNumberRange(1, 65535)],
proxyPass: [Rules.requiredInput], proxyPass: [Rules.requiredInput],
proxyHost: [Rules.requiredInput], proxyHost: [Rules.requiredInput],
proxyAddress: [Rules.requiredInput],
}); });
const open = ref(false); const open = ref(false);
const loading = ref(false); const loading = ref(false);
@@ -159,6 +163,8 @@ const initData = (): Website.ProxyConfig => ({
replaces: {}, replaces: {},
proxyAddress: '', proxyAddress: '',
proxyProtocol: 'http://', proxyProtocol: 'http://',
sni: false,
proxySSLName: '$proxy_host',
}); });
let proxy = ref(initData()); let proxy = ref(initData());
const replaces = ref<any>([]); const replaces = ref<any>([]);