Compare 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 {
Names []string `json:"names" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=start stop restart kill pause unpause remove"`
Operation string `json:"operation" validate:"required,oneof=up start stop restart kill pause unpause remove"`
}
type ContainerRename struct {
@@ -200,6 +200,7 @@ type ComposeInfo struct {
Workdir string `json:"workdir"`
Path string `json:"path"`
Containers []ComposeContainer `json:"containers"`
Env []string `json:"env"`
}
type ComposeContainer struct {
ContainerID string `json:"containerID"`
@@ -208,22 +209,24 @@ type ComposeContainer struct {
State string `json:"state"`
}
type ComposeCreate struct {
Name string `json:"name"`
From string `json:"from" validate:"required,oneof=edit path template"`
File string `json:"file"`
Path string `json:"path"`
Template uint `json:"template"`
Name string `json:"name"`
From string `json:"from" validate:"required,oneof=edit path template"`
File string `json:"file"`
Path string `json:"path"`
Template uint `json:"template"`
Env []string `json:"env"`
}
type ComposeOperation struct {
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=start stop down"`
Operation string `json:"operation" validate:"required,oneof=up start stop down delete"`
WithFile bool `json:"withFile"`
}
type ComposeUpdate struct {
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
Content string `json:"content" validate:"required"`
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
Content string `json:"content" validate:"required"`
Env []string `json:"env"`
}
type ContainerLog struct {
+16 -15
View File
@@ -175,21 +175,22 @@ type WebsiteUpdateDirPermission struct {
}
type WebsiteProxyConfig struct {
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
Enable bool `json:"enable" `
Cache bool `json:"cache" `
CacheTime int `json:"cacheTime" `
CacheUnit string `json:"cacheUnit"`
Name string `json:"name" validate:"required"`
Modifier string `json:"modifier"`
Match string `json:"match" validate:"required"`
ProxyPass string `json:"proxyPass" validate:"required"`
ProxyHost string `json:"proxyHost" validate:"required"`
Content string `json:"content"`
FilePath string `json:"filePath"`
Replaces map[string]string `json:"replaces"`
SNI bool `json:"sni"`
ID uint `json:"id" validate:"required"`
Operate string `json:"operate" validate:"required"`
Enable bool `json:"enable" `
Cache bool `json:"cache" `
CacheTime int `json:"cacheTime" `
CacheUnit string `json:"cacheUnit"`
Name string `json:"name" validate:"required"`
Modifier string `json:"modifier"`
Match string `json:"match" validate:"required"`
ProxyPass string `json:"proxyPass" validate:"required"`
ProxyHost string `json:"proxyHost" validate:"required"`
Content string `json:"content"`
FilePath string `json:"filePath"`
Replaces map[string]string `json:"replaces"`
SNI bool `json:"sni"`
ProxySSLName string `json:"proxySSLName"`
}
type WebsiteProxyReq struct {
+1
View File
@@ -12,4 +12,5 @@ type Compose struct {
BaseModel
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
DeleteRecord(opts ...DBOption) error
ListRecord() ([]model.Compose, error)
UpdateRecord(name string, vars map[string]interface{}) error
}
func NewIComposeTemplateRepo() IComposeTemplateRepo {
@@ -102,3 +103,7 @@ func (u *ComposeTemplateRepo) DeleteRecord(opts ...DBOption) error {
}
return db.Delete(&model.Compose{}).Error
}
func (u *ComposeTemplateRepo) UpdateRecord(name string, vars map[string]interface{}) error {
return global.DB.Model(&model.Compose{}).Where("name = ?", name).Updates(vars).Error
}
+4
View File
@@ -898,6 +898,10 @@ func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) s
break
}
}
if len(containers) == 0 {
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
filePath = composeItem.Path
}
}
if _, err := os.Stat(filePath); err != nil {
return ""
+107 -13
View File
@@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strings"
"time"
@@ -31,6 +32,12 @@ const composeConfigLabel = "com.docker.compose.project.config_files"
const composeWorkdirLabel = "com.docker.compose.project.working_dir"
const composeCreatedBy = "createdBy"
type DockerCompose struct {
Version string `yaml:"version"`
Services map[string]map[string]interface{} `yaml:"services"`
Networks map[string]interface{} `yaml:"networks"`
}
func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface{}, error) {
var (
records []dto.ComposeInfo
@@ -52,6 +59,20 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
composeCreatedByLocal, _ := composeRepo.ListRecord()
composeLocalMap := make(map[string]dto.ComposeInfo)
for _, localItem := range composeCreatedByLocal {
composeItemLocal := dto.ComposeInfo{
ContainerNumber: 0,
CreatedAt: localItem.CreatedAt.Format(constant.DateTimeLayout),
ConfigFile: localItem.Path,
Workdir: strings.TrimSuffix(localItem.Path, "/docker-compose.yml"),
}
composeItemLocal.CreatedBy = "1Panel"
composeItemLocal.Path = localItem.Path
composeLocalMap[localItem.Name] = composeItemLocal
}
composeMap := make(map[string]dto.ComposeInfo)
for _, container := range list {
if name, ok := container.Labels[composeProjectLabel]; ok {
@@ -95,12 +116,24 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
}
}
for _, item := range composeCreatedByLocal {
if err := composeRepo.DeleteRecord(commonRepo.WithByID(item.ID)); err != nil {
global.LOG.Error(err)
mergedMap := make(map[string]dto.ComposeInfo)
for key, localItem := range composeLocalMap {
mergedMap[key] = localItem
}
for key, item := range composeMap {
if existingItem, exists := mergedMap[key]; exists {
if item.ContainerNumber > 0 {
if existingItem.ContainerNumber <= 0 {
mergedMap[key] = item
}
}
} else {
mergedMap[key] = item
}
}
for key, value := range composeMap {
for key, value := range mergedMap {
value.Name = key
records = append(records, value)
}
@@ -127,7 +160,26 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
BackDatas = records[start:end]
}
return int64(total), BackDatas, nil
listItem := loadEnv(BackDatas)
return int64(total), listItem, nil
}
func loadEnv(list []dto.ComposeInfo) []dto.ComposeInfo {
for i := 0; i < len(list); i++ {
envFilePath := filepath.Join(path.Dir(list[i].Path), "1panel.env")
file, err := os.ReadFile(envFilePath)
if err != nil {
continue
}
lines := strings.Split(string(file), "\n")
for _, line := range lines {
lineItem := strings.TrimSpace(line)
if len(lineItem) != 0 && !strings.HasPrefix(lineItem, "#") {
list[i].Env = append(list[i].Env, lineItem)
}
}
}
return list
}
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
@@ -141,6 +193,9 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
if err := u.loadPath(&req); err != nil {
return false, err
}
if err := newComposeEnv(req.Path, req.Env); err != nil {
return false, err
}
cmd := exec.Command("docker-compose", "-f", req.Path, "config")
stdout, err := cmd.CombinedOutput()
if err != nil {
@@ -173,6 +228,9 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
if err != nil {
return "", err
}
if err := newComposeEnv(req.Path, req.Env); err != nil {
return "", err
}
go func() {
defer file.Close()
cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d")
@@ -186,7 +244,7 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
return
}
global.LOG.Infof("docker-compose up %s successful!", req.Name)
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name})
_ = composeRepo.CreateRecord(&model.Compose{Name: req.Name, Path: req.Path})
_, _ = file.WriteString("docker-compose up successful!")
}()
@@ -200,17 +258,26 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
return errors.New(string(stdout))
}
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
if req.Operation == "down" {
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
if req.Operation == "delete" {
if stdout, err := compose.Operate(req.Path, "down"); err != nil {
return errors.New(string(stdout))
}
if req.WithFile {
_ = os.RemoveAll(path.Dir(req.Path))
}
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
return nil
}
if req.Operation == "up" {
if stdout, err := compose.Up(req.Path); err != nil {
return errors.New(string(stdout))
}
} else {
if stdout, err := compose.Operate(req.Path, req.Operation); err != nil {
return errors.New(string(stdout))
}
}
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
return nil
}
@@ -232,6 +299,10 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
write.Flush()
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.Path)
if err := newComposeEnv(req.Path, req.Env); err != nil {
return err
}
if stdout, err := compose.Up(req.Path); err != nil {
if err := recreateCompose(string(oldFile), req.Path); err != nil {
return fmt.Errorf("update failed when handle compose up, err: %s, recreate failed: %v", string(stdout), err)
@@ -280,3 +351,26 @@ func recreateCompose(content, path string) error {
}
return nil
}
func newComposeEnv(pathItem string, env []string) error {
if len(env) == 0 {
return nil
}
envFilePath := path.Join(path.Dir(pathItem), "1panel.env")
file, err := os.OpenFile(envFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
global.LOG.Errorf("failed to create env file: %v", err)
return err
}
defer file.Close()
for _, env := range env {
envItem := strings.TrimSpace(env)
if _, err := file.WriteString(fmt.Sprintf("%s\n", envItem)); err != nil {
global.LOG.Errorf("failed to write env to file: %v", err)
return err
}
}
global.LOG.Infof("1panel.env file successfully created or updated with env variables in %s", envFilePath)
return nil
}
+7 -6
View File
@@ -3,10 +3,6 @@ package service
import (
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
network "net"
"os"
"sort"
@@ -14,6 +10,11 @@ import (
"sync"
"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/global"
"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 {
continue
}
if strings.TrimSpace(fields[1]) == "tmpfs" {
if strings.TrimSpace(fields[1]) == "tmpfs" || strings.TrimSpace(fields[1]) == "overlay" {
continue
}
if strings.Contains(fields[2], "K") {
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
}
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/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/systemctl"
"github.com/pkg/errors"
)
@@ -336,8 +338,15 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
service := "docker"
sudo := cmd.SudoHandleCmd()
if req.Operation == "stop" {
service = "docker.socket"
isSocketActive, _ := systemctl.IsActive("docker.socket")
if isSocketActive {
std, err := cmd.Execf("%s systemctl stop docker.socket", sudo)
if err != nil {
global.LOG.Errorf("handle systemctl stop docker.socket failed, err: %v", std)
}
}
}
stdout, err := cmd.Execf("systemctl %s %s ", req.Operation, service)
if err != nil {
+10
View File
@@ -124,6 +124,16 @@ func (u *SSHService) OperateSSH(operation string) error {
if operation == "enable" || operation == "disable" {
serviceName += ".service"
}
if operation == "stop" {
isSocketActive, _ := systemctl.IsActive(serviceName + ".socket")
if isSocketActive {
std, err := cmd.Execf("%s systemctl stop %s", sudo, serviceName+".socket")
if err != nil {
global.LOG.Errorf("handle systemctl stop %s.socket failed, err: %v", serviceName, std)
}
}
}
stdout, err := cmd.Execf("%s systemctl %s %s", sudo, operation, serviceName)
if err != nil {
if strings.Contains(stdout, "alias name or linked unit file") {
+6
View File
@@ -1667,6 +1667,9 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
}
if req.SNI {
location.UpdateDirective("proxy_ssl_server_name", []string{"on"})
if req.ProxySSLName != "" {
location.UpdateDirective("proxy_ssl_name", []string{req.ProxySSLName})
}
} else {
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" {
proxyConfig.SNI = directive.GetParameters()[0] == "on"
}
if directive.GetName() == "proxy_ssl_name" {
proxyConfig.ProxySSLName = directive.GetParameters()[0]
}
}
res = append(res, proxyConfig)
}
+7 -8
View File
@@ -217,15 +217,14 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
if req.Domains != "" {
domainArray := strings.Split(req.Domains, "\n")
for _, domain := range domainArray {
if !common.IsValidDomain(domain) {
err = buserr.WithName("ErrDomainFormat", domain)
return nil, err
} else {
if ipAddress := net.ParseIP(domain); ipAddress == nil {
domains = append(domains, domain)
} else {
ips = append(ips, ipAddress)
if ipAddress := net.ParseIP(domain); ipAddress == nil {
if !common.IsValidDomain(domain) {
err = buserr.WithName("ErrDomainFormat", domain)
return nil, err
}
domains = append(domains, domain)
} else {
ips = append(ips, ipAddress)
}
}
if len(domains) > 0 {
+1
View File
@@ -94,6 +94,7 @@ func Init() {
migrations.AddClam,
migrations.AddClamStatus,
migrations.AddAlertMenu,
migrations.AddComposeColumn,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
@@ -314,3 +314,13 @@ var AddAlertMenu = &gormigrate.Migration{
return tx.Model(&model.Setting{}).Where("key", "XpackHideMenu").Updates(map[string]interface{}{"value": string(data)}).Error
},
}
var AddComposeColumn = &gormigrate.Migration{
ID: "20240906-add-compose-command",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.Compose{}); err != nil {
return err
}
return nil
},
}
+1
View File
@@ -19,6 +19,7 @@ var whiteUrlList = map[string]struct{}{
"/api/v1/logs/operation": {},
"/api/v1/logs/login": {},
"/api/v1/auth/logout": {},
"/api/v1/dashboard/current": {},
"/api/v1/apps/installed/loadport": {},
"/api/v1/apps/installed/check": {},
+5 -1
View File
@@ -27,12 +27,16 @@ func NewS3Client(vars map[string]interface{}) (*s3Client, error) {
if len(scType) == 0 {
scType = "Standard"
}
mode := loadParamFromVars("mode", vars)
if len(mode) == 0 {
mode = "virtual hosted"
}
sess, err := session.NewSession(&aws.Config{
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
Endpoint: aws.String(endpoint),
Region: aws.String(region),
DisableSSL: aws.Bool(true),
S3ForcePathStyle: aws.Bool(false),
S3ForcePathStyle: aws.Bool(mode == "path"),
})
if err != nil {
return nil, err
+9 -2
View File
@@ -29,10 +29,17 @@ type FirewallClient interface {
}
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()
}
if _, err := os.Stat("/usr/sbin/ufw"); err == nil {
if ufwErr == nil {
return client.NewUfw()
}
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("sub_filter_once", []string{"off"})
l.UpdateDirective("sub_filter_types", []string{"*"})
}
func (l *Location) RemoveSubFilter() {
l.RemoveDirective("sub_filter", []string{})
l.RemoveDirective("proxy_set_header", []string{"Accept-Encoding", `""`})
l.RemoveDirective("sub_filter_once", []string{"off"})
l.RemoveDirective("sub_filter_types", []string{"*"})
l.Replaces = nil
}
+1
View File
@@ -11,4 +11,5 @@ location ^~ /test {
add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-cache;
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",
"js-base64": "^3.7.7",
"md-editor-v3": "^2.11.3",
"monaco-editor": "^0.51.0",
"monaco-editor": "^0.50.0",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^1.6.1",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

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