Compare commits

..
12 Commits
Author SHA1 Message Date
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
112 changed files with 1684 additions and 2687 deletions
+1 -2
View File
@@ -2,7 +2,6 @@ package v1
import (
"errors"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
@@ -57,7 +56,7 @@ func (b *BaseApi) LoadDashboardBaseInfo(c *gin.Context) {
// @Tags Dashboard
// @Summary Load dashboard current info
// @Description 获取首页实时数据
// @Accept json
// @Accept json、
// @Param request body dto.DashboardReq true "request"
// @Success 200 {object} dto.DashboardCurrent
// @Security ApiKeyAuth
+1 -1
View File
@@ -62,7 +62,7 @@ func (b *BaseApi) CreateRuntime(c *gin.Context) {
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes/del [post]
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除运行环境 [name]","formatEN":"Delete runtime [name]"}
// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除网站 [name]","formatEN":"Delete website [name]"}
func (b *BaseApi) DeleteRuntime(c *gin.Context) {
var req request.RuntimeDelete
if err := helper.CheckBindAndValidate(&req, c); err != nil {
-22
View File
@@ -120,28 +120,6 @@ func (b *BaseApi) SearchSnapshot(c *gin.Context) {
})
}
// @Tags System Setting
// @Summary Load system snapshot size
// @Description 获取系统快照大小
// @Accept json
// @Param request body dto.PageSnapshot true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /settings/snapshot/size [post]
func (b *BaseApi) LoadSnapshotSize(c *gin.Context) {
var req dto.PageSnapshot
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
data, err := snapshotService.LoadSize(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags System Setting
// @Summary Recover system backup
// @Description 从系统快照恢复
-2
View File
@@ -82,8 +82,6 @@ type ClamUpdate struct {
InfectedDir string `json:"infectedDir"`
Spec string `json:"spec"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
}
type ClamUpdateStatus struct {
+2 -3
View File
@@ -200,7 +200,6 @@ 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"`
@@ -218,8 +217,8 @@ type ComposeCreate struct {
}
type ComposeOperation struct {
Name string `json:"name" validate:"required"`
Path string `json:"path"`
Operation string `json:"operation" validate:"required,oneof=up start stop down delete"`
Path string `json:"path" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=up start stop down"`
WithFile bool `json:"withFile"`
}
type ComposeUpdate struct {
+15 -16
View File
@@ -175,22 +175,21 @@ 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"`
ProxySSLName string `json:"proxySSLName"`
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"`
}
type WebsiteProxyReq struct {
-1
View File
@@ -41,7 +41,6 @@ type WebsiteSSLApply struct {
ID uint `json:"ID" validate:"required"`
SkipDNSCheck bool `json:"skipDNSCheck"`
Nameservers []string `json:"nameservers"`
DisableLog bool `json:"disableLog"`
}
type WebsiteAcmeAccountCreate struct {
+8 -15
View File
@@ -30,7 +30,6 @@ type SettingInfo struct {
ServerPort string `json:"serverPort"`
SSL string `json:"ssl"`
SSLType string `json:"sslType"`
AutoRestart string `json:"autoRestart"`
BindDomain string `json:"bindDomain"`
AllowIPs string `json:"allowIPs"`
SecurityEntrance string `json:"securityEntrance"`
@@ -74,13 +73,12 @@ type SettingUpdate struct {
}
type SSLUpdate struct {
SSLType string `json:"sslType" validate:"required,oneof=self select import import-paste import-local"`
Domain string `json:"domain"`
SSL string `json:"ssl" validate:"required,oneof=enable disable"`
Cert string `json:"cert"`
Key string `json:"key"`
SSLID uint `json:"sslID"`
AutoRestart string `json:"autoRestart"`
SSLType string `json:"sslType" validate:"required,oneof=self select import import-paste import-local"`
Domain string `json:"domain"`
SSL string `json:"ssl" validate:"required,oneof=enable disable"`
Cert string `json:"cert"`
Key string `json:"key"`
SSLID uint `json:"sslID"`
}
type SSLInfo struct {
Domain string `json:"domain"`
@@ -149,8 +147,9 @@ type SnapshotInfo struct {
DefaultDownload string `json:"defaultDownload"`
Status string `json:"status"`
Message string `json:"message"`
CreatedAt time.Time `json:"created_at"`
CreatedAt time.Time `json:"createdAt"`
Version string `json:"version"`
Size int64 `json:"size"`
InterruptStep string `json:"interruptStep"`
RecoverStatus string `json:"recoverStatus"`
@@ -161,12 +160,6 @@ type SnapshotInfo struct {
LastRollbackedAt string `json:"lastRollbackedAt"`
}
type SnapshotFile struct {
ID uint `json:"id"`
Name string `json:"name"`
Size int64 `json:"size"`
}
type UpgradeInfo struct {
TestVersion string `json:"testVersion"`
NewVersion string `json:"newVersion"`
-12
View File
@@ -1342,18 +1342,6 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
continue
}
lastVersion := versions[0]
if app.Key == constant.AppMysql {
for _, version := range versions {
majorVersion := getMajorVersion(installed.Version)
if !strings.HasPrefix(version, majorVersion) {
continue
} else {
lastVersion = version
break
}
}
}
if common.IsCrossVersion(installed.Version, lastVersion) {
installDTO.CanUpdate = app.CrossVersionUpdate
} else {
+143 -76
View File
@@ -4,11 +4,12 @@ import (
"bufio"
"errors"
"fmt"
"gopkg.in/yaml.v3"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strings"
"time"
@@ -160,26 +161,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
}
BackDatas = records[start:end]
}
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
return int64(total), BackDatas, nil
}
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
@@ -193,9 +175,6 @@ 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 {
@@ -204,6 +183,59 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
return true, nil
}
func formatYAML(data []byte) []byte {
return []byte(strings.ReplaceAll(string(data), "\t", " "))
}
func updateDockerComposeWithEnv(req dto.ComposeCreate) error {
data, err := ioutil.ReadFile(req.Path)
if err != nil {
return fmt.Errorf("failed to read docker-compose.yml: %v", err)
}
var composeItem DockerCompose
if err := yaml.Unmarshal(data, &composeItem); err != nil {
return fmt.Errorf("failed to parse docker-compose.yml: %v", err)
}
for serviceName, service := range composeItem.Services {
envMap := make(map[string]string)
if existingEnv, exists := service["environment"].([]interface{}); exists {
for _, env := range existingEnv {
envStr := env.(string)
parts := strings.SplitN(envStr, "=", 2)
if len(parts) == 2 {
envMap[parts[0]] = parts[1]
}
}
}
for _, env := range req.Env {
parts := strings.SplitN(env, "=", 2)
if len(parts) == 2 {
envMap[parts[0]] = parts[1]
}
}
envVars := []string{}
for key, value := range envMap {
envVars = append(envVars, key+"="+value)
}
service["environment"] = envVars
composeItem.Services[serviceName] = service
}
if composeItem.Networks != nil {
for key := range composeItem.Networks {
composeItem.Networks[key] = map[string]interface{}{}
}
}
newData, err := yaml.Marshal(&composeItem)
if err != nil {
return fmt.Errorf("failed to marshal docker-compose.yml: %v", err)
}
formattedData := formatYAML(newData)
if err := ioutil.WriteFile(req.Path, formattedData, 0644); err != nil {
return fmt.Errorf("failed to write docker-compose.yml: %v", err)
}
return nil
}
func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error) {
if cmd.CheckIllegal(req.Name, req.Path) {
return "", buserr.New(constant.ErrCmdIllegal)
@@ -228,8 +260,11 @@ 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
if len(req.Env) > 0 {
if err := updateDockerComposeWithEnv(req); err != nil {
fmt.Printf("failed to update docker-compose.yml with env: %v\n", err)
return "", err
}
}
go func() {
defer file.Close()
@@ -252,26 +287,12 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) (string, error)
}
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
if len(req.Path) == 0 && req.Operation == "delete" {
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
return nil
}
if cmd.CheckIllegal(req.Path, req.Operation) {
return buserr.New(constant.ErrCmdIllegal)
}
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
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))
@@ -282,6 +303,74 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
}
}
global.LOG.Infof("docker-compose %s %s successful", req.Operation, req.Name)
if req.Operation == "down" {
if req.WithFile {
_ = composeRepo.DeleteRecord(commonRepo.WithByName(req.Name))
_ = os.RemoveAll(path.Dir(req.Path))
} else {
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name))
if composeItem.Path == "" {
upMap := make(map[string]interface{})
upMap["path"] = req.Path
_ = composeRepo.UpdateRecord(req.Name, upMap)
}
}
}
return nil
}
func updateComposeWithEnv(req dto.ComposeUpdate) error {
var composeItem DockerCompose
if err := yaml.Unmarshal([]byte(req.Content), &composeItem); err != nil {
return fmt.Errorf("failed to parse docker-compose content: %v", err)
}
for serviceName, service := range composeItem.Services {
envMap := make(map[string]string)
for _, env := range req.Env {
parts := strings.SplitN(env, "=", 2)
if len(parts) == 2 {
envMap[parts[0]] = parts[1]
}
}
newEnvVars := []string{}
if existingEnv, exists := service["environment"].([]interface{}); exists {
for _, env := range existingEnv {
envStr := env.(string)
parts := strings.SplitN(envStr, "=", 2)
if len(parts) == 2 {
key := parts[0]
if value, found := envMap[key]; found {
newEnvVars = append(newEnvVars, key+"="+value)
delete(envMap, key)
} else {
newEnvVars = append(newEnvVars, envStr)
}
}
}
}
for key, value := range envMap {
newEnvVars = append(newEnvVars, key+"="+value)
}
if len(newEnvVars) > 0 {
service["environment"] = newEnvVars
} else {
delete(service, "environment")
}
composeItem.Services[serviceName] = service
}
if composeItem.Networks != nil {
for key := range composeItem.Networks {
composeItem.Networks[key] = map[string]interface{}{}
}
}
newData, err := yaml.Marshal(&composeItem)
if err != nil {
return fmt.Errorf("failed to marshal docker-compose.yml: %v", err)
}
if err := ioutil.WriteFile(req.Path, newData, 0644); err != nil {
return fmt.Errorf("failed to write docker-compose.yml to path: %v", err)
}
return nil
}
@@ -293,20 +382,21 @@ func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error {
if err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
file, err := os.OpenFile(req.Path, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
if len(req.Env) > 0 {
if err := updateComposeWithEnv(req); err != nil {
return fmt.Errorf("failed to update docker-compose with env: %v", err)
}
} else {
file, err := os.OpenFile(req.Path, os.O_WRONLY|os.O_TRUNC, 0640)
if err != nil {
return err
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(req.Content)
write.Flush()
global.LOG.Infof("docker-compose.yml %s has been replaced", req.Path)
}
defer file.Close()
write := bufio.NewWriter(file)
_, _ = write.WriteString(req.Content)
write.Flush()
global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.Path)
if err := newComposeEnv(req.Path, req.Env); err != nil {
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)
@@ -355,26 +445,3 @@ 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
}
-10
View File
@@ -1,7 +1,6 @@
package service
import (
"errors"
"fmt"
"os"
"path"
@@ -31,9 +30,6 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) e
if err != nil {
return err
}
if len(appItems) == 0 {
return errors.New("no such app in database!")
}
apps = appItems
}
accountMap, err := loadClientMap(cronjob.BackupAccounts)
@@ -69,9 +65,6 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time) e
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Time) error {
webs := loadWebsForJob(cronjob)
if len(webs) == 0 {
return errors.New("no such website in database!")
}
accountMap, err := loadClientMap(cronjob.BackupAccounts)
if err != nil {
return err
@@ -105,9 +98,6 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Time) error {
dbs := loadDbsForJob(cronjob)
if len(dbs) == 0 {
return errors.New("no such db in database!")
}
accountMap, err := loadClientMap(cronjob.BackupAccounts)
if err != nil {
return err
+1
View File
@@ -85,6 +85,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
_ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records})
err = u.handleSnapshot(*cronjob, record.StartTime, record.Records)
}
if err != nil {
if len(message) != 0 {
record.Records, _ = mkdirAndWriteFile(cronjob, record.StartTime, message)
+6 -7
View File
@@ -3,6 +3,10 @@ package service
import (
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
network "net"
"os"
"sort"
@@ -10,11 +14,6 @@ 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"
@@ -269,13 +268,13 @@ func loadDiskInfo() []dto.DiskInfo {
if strings.HasPrefix(fields[6], "/snap") || len(strings.Split(fields[6], "/")) > 10 {
continue
}
if strings.TrimSpace(fields[1]) == "tmpfs" || strings.TrimSpace(fields[1]) == "overlay" {
if strings.TrimSpace(fields[1]) == "tmpfs" {
continue
}
if strings.Contains(fields[2], "K") {
continue
}
if strings.Contains(fields[6], "docker") || strings.Contains(fields[6], "podman") || strings.Contains(fields[6], "containerd") || strings.HasPrefix(fields[6], "/var/lib/containers") {
if strings.Contains(fields[6], "docker") {
continue
}
isExclude := false
+20 -79
View File
@@ -4,7 +4,6 @@ import (
"bufio"
"context"
"encoding/json"
"fmt"
"os"
"path"
"strings"
@@ -201,8 +200,9 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
}
if len(daemonMap) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
if err := restartDocker(); err != nil {
return err
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
return nil
}
@@ -213,12 +213,10 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
return err
}
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
return nil
}
@@ -262,12 +260,9 @@ func (u *DockerService) UpdateLogOption(req dto.LogOption) error {
return err
}
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
return nil
}
@@ -305,12 +300,9 @@ func (u *DockerService) UpdateIpv6Option(req dto.Ipv6Option) error {
return err
}
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
return nil
}
@@ -318,8 +310,9 @@ func (u *DockerService) UpdateIpv6Option(req dto.Ipv6Option) error {
func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
if len(req.File) == 0 {
_ = os.Remove(constant.DaemonJsonPath)
if err := restartDocker(); err != nil {
return err
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
return nil
}
@@ -336,12 +329,9 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
_, _ = write.WriteString(req.File)
write.Flush()
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
return nil
}
@@ -349,10 +339,6 @@ func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error {
func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
service := "docker"
sudo := cmd.SudoHandleCmd()
dockerCmd, err := getDockerRestartCommand()
if err != nil {
return err
}
if req.Operation == "stop" {
isSocketActive, _ := systemctl.IsActive("docker.socket")
if isSocketActive {
@@ -362,16 +348,9 @@ func (u *DockerService) OperateDocker(req dto.DockerOperation) error {
}
}
}
if req.Operation == "restart" {
if err := validateDockerConfig(); err != nil {
return err
}
}
stdout, err := cmd.Execf("%s %s %s", dockerCmd, req.Operation, service)
stdout, err := cmd.Execf("systemctl %s %s ", req.Operation, service)
if err != nil {
return errors.New(stdout)
return errors.New(string(stdout))
}
return nil
}
@@ -424,41 +403,3 @@ func changeLogOption(daemonMap map[string]interface{}, logMaxFile, logMaxSize st
}
}
}
func validateDockerConfig() error {
if !cmd.Which("dockerd") {
return nil
}
stdout, err := cmd.Exec("dockerd --validate")
if strings.Contains(stdout, "unknown flag: --validate") {
return nil
}
if err != nil || (stdout != "" && strings.TrimSpace(stdout) != "configuration OK") {
return fmt.Errorf("Docker configuration validation failed, err: %v", stdout)
}
return nil
}
func getDockerRestartCommand() (string, error) {
stdout, err := cmd.Exec("which docker")
if err != nil {
return "", fmt.Errorf("failed to find docker: %v", err)
}
dockerPath := stdout
if strings.Contains(dockerPath, "snap") {
return "snap", nil
}
return "systemctl", nil
}
func restartDocker() error {
restartCmd, err := getDockerRestartCommand()
if err != nil {
return err
}
stdout, err := cmd.Execf("%s restart docker", restartCmd)
if err != nil {
return fmt.Errorf("failed to restart Docker: %s", stdout)
}
return nil
}
+7 -12
View File
@@ -179,7 +179,6 @@ func (u *FirewallService) OperateFirewall(operation string) error {
if err != nil {
return err
}
needRestartDocker := false
switch operation {
case "start":
if err := client.Start(); err != nil {
@@ -189,30 +188,26 @@ func (u *FirewallService) OperateFirewall(operation string) error {
_ = client.Stop()
return err
}
needRestartDocker = true
_, _ = cmd.Exec("systemctl restart docker")
return nil
case "stop":
if err := client.Stop(); err != nil {
return err
}
needRestartDocker = true
_, _ = cmd.Exec("systemctl restart docker")
return nil
case "restart":
if err := client.Restart(); err != nil {
return err
}
needRestartDocker = true
_, _ = cmd.Exec("systemctl restart docker")
return nil
case "disablePing":
return u.updatePingStatus("0")
case "enablePing":
return u.updatePingStatus("1")
default:
return fmt.Errorf("not supported operation: %s", operation)
}
if needRestartDocker {
if err := restartDocker(); err != nil {
return err
}
}
return nil
return fmt.Errorf("not support such operation: %s", operation)
}
func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool) error {
+27 -42
View File
@@ -84,16 +84,11 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
if imageRepo.ID != 0 {
return constant.ErrRecordExist
}
if req.Protocol == "http" {
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
}
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
_ = u.handleRegistries(req.DownloadUrl, "", "create")
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
@@ -117,18 +112,23 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
return err
}
}
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
return err
}
}
if err := copier.Copy(&imageRepo, &req); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
imageRepo.Status = constant.StatusSuccess
return imageRepoRepo.Create(&imageRepo)
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
imageRepo.Status = constant.StatusFailed
imageRepo.Message = err.Error()
}
}
if err := imageRepoRepo.Create(&imageRepo); err != nil {
return err
}
return nil
}
func (u *ImageRepoService) BatchDelete(req dto.ImageRepoDelete) error {
@@ -154,47 +154,32 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if err != nil {
return err
}
if repo.Protocol == "http" && req.Protocol == "https" {
if err := u.handleRegistries("", repo.DownloadUrl, "delete"); err != nil {
return fmt.Errorf("delete registry %s failed, err: %v", repo.DownloadUrl, err)
}
}
if repo.Protocol == "http" && req.Protocol == "http" {
if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil {
return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err)
}
}
if repo.Protocol == "https" && req.Protocol == "http" {
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
}
}
if repo.Auth != req.Auth || repo.DownloadUrl != req.DownloadUrl {
if repo.DownloadUrl != req.DownloadUrl || (!repo.Auth && req.Auth) {
_ = u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update")
if repo.Auth {
_, _ = cmd.ExecWithCheck("docker", "logout", repo.DownloadUrl)
}
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
return err
}
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(string(stdout))
}
}
if err := validateDockerConfig(); err != nil {
return err
}
if err := restartDocker(); err != nil {
return err
}
upMap := make(map[string]interface{})
upMap["download_url"] = req.DownloadUrl
upMap["protocol"] = req.Protocol
upMap["username"] = req.Username
upMap["password"] = req.Password
upMap["auth"] = req.Auth
upMap["status"] = constant.StatusSuccess
upMap["message"] = ""
if req.Auth {
if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil {
upMap["status"] = constant.StatusFailed
upMap["message"] = err.Error()
}
}
return imageRepoRepo.Update(req.ID, upMap)
}
+8 -11
View File
@@ -56,7 +56,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
opts []repo.DBOption
)
if create.Name != "" {
opts = append(opts, commonRepo.WithByName(create.Name))
opts = append(opts, commonRepo.WithLikeName(create.Name))
}
if create.Type != "" {
opts = append(opts, commonRepo.WithByType(create.Type))
@@ -83,7 +83,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
if exist != nil {
return nil, buserr.New(constant.ErrImageExist)
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
if !fileOp.Stat(create.CodeDir) {
return nil, buserr.New(constant.ErrPathNotFound)
}
@@ -133,7 +133,7 @@ func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, e
if err = handlePHP(create, runtime, fileOp, appVersionDir); err != nil {
return nil, err
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
runtime.Port = create.Port
if err = handleNodeAndJava(create, runtime, fileOp, appVersionDir); err != nil {
return nil, err
@@ -217,7 +217,7 @@ func (r *RuntimeService) Delete(runtimeDelete request.RuntimeDelete) error {
global.LOG.Errorf("delete image id [%s] error %v", imageID, err)
}
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
if out, err := compose.Down(runtime.GetComposePath()); err != nil && !runtimeDelete.ForceDelete {
if out != "" {
return errors.New(out)
@@ -300,7 +300,7 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) {
}
}
res.AppParams = appParams
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
res.Params = make(map[string]interface{})
envs, err := gotenv.Unmarshal(runtime.Env)
if err != nil {
@@ -308,7 +308,7 @@ func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) {
}
for k, v := range envs {
switch k {
case "NODE_APP_PORT", "PANEL_APP_PORT_HTTP", "JAVA_APP_PORT", "GO_APP_PORT", "APP_PORT":
case "NODE_APP_PORT", "PANEL_APP_PORT_HTTP", "JAVA_APP_PORT", "GO_APP_PORT":
port, err := strconv.Atoi(v)
if err != nil {
return nil, err
@@ -361,7 +361,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
if exist != nil {
return buserr.New(constant.ErrImageExist)
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
if runtime.Port != req.Port {
if err = checkPortExist(req.Port); err != nil {
return err
@@ -441,7 +441,7 @@ func (r *RuntimeService) Update(req request.RuntimeUpdate) error {
return err
}
go buildRuntime(runtime, imageID, req.Rebuild)
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
runtime.Version = req.Version
runtime.CodeDir = req.CodeDir
runtime.Port = req.Port
@@ -495,9 +495,6 @@ func (r *RuntimeService) OperateRuntime(req request.RuntimeOperate) error {
_ = runtimeRepo.Save(runtime)
}
}()
go func() {
_ = docker.CreateDefaultDockerNetwork()
}()
switch req.Operate {
case constant.RuntimeUp:
if err = runComposeCmdWithLog(req.Operate, runtime.GetComposePath(), runtime.GetLogPath()); err != nil {
+3 -20
View File
@@ -2,7 +2,6 @@ package service
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
@@ -219,10 +218,7 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
_ = logFile.Close()
}()
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour)
defer cancel()
cmd := exec.CommandContext(ctx, "docker-compose", "-f", composePath, "build")
cmd := exec.Command("docker-compose", "-f", composePath, "build")
multiWriterStdout := io.MultiWriter(os.Stdout, logFile)
cmd.Stdout = multiWriterStdout
var stderrBuf bytes.Buffer
@@ -232,11 +228,7 @@ func buildRuntime(runtime *model.Runtime, oldImageID string, rebuild bool) {
err = cmd.Run()
if err != nil {
runtime.Status = constant.RuntimeError
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + buserr.New("ErrCmdTimeout").Error()
} else {
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String()
}
runtime.Message = buserr.New(constant.ErrImageBuildErr).Error() + ":" + stderrBuf.String()
} else {
runtime.Status = constant.RuntimeNormal
runtime.Message = ""
@@ -346,14 +338,6 @@ func handleParams(create request.RuntimeCreate, projectDir string) (composeConte
if err != nil {
return
}
case constant.RuntimePython:
create.Params["CODE_DIR"] = create.CodeDir
create.Params["PYTHON_VERSION"] = create.Version
create.Params["PANEL_APP_PORT_HTTP"] = create.Port
composeContent, err = handleCompose(env, composeContent, create, projectDir)
if err != nil {
return
}
}
newMap := make(map[string]string)
@@ -400,8 +384,7 @@ func handleCompose(env gotenv.Env, composeContent []byte, create request.Runtime
ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${JAVA_APP_PORT}")
case constant.RuntimeGo:
ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${GO_APP_PORT}")
case constant.RuntimePython:
ports = append(ports, "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${APP_PORT}")
}
for i, port := range create.ExposedPorts {
-3
View File
@@ -322,9 +322,6 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
if err := settingRepo.Update("SSL", req.SSL); err != nil {
return err
}
if err := settingRepo.Update("AutoRestart", req.AutoRestart); err != nil {
return err
}
sID, _ := c.Cookie(constant.SessionName)
c.SetCookie(constant.SessionName, sID, 0, "", "", true, true)
+10 -24
View File
@@ -28,7 +28,6 @@ type SnapshotService struct {
type ISnapshotService interface {
SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error)
LoadSize(req dto.PageSnapshot) ([]dto.SnapshotFile, error)
SnapshotCreate(req dto.SnapshotCreate) error
SnapshotRecover(req dto.SnapshotRecover) error
SnapshotRollback(req dto.SnapshotRecover) error
@@ -48,31 +47,15 @@ func NewISnapshotService() ISnapshotService {
}
func (u *SnapshotService) SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error) {
total, records, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info), commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
total, systemBackups, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info), commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
if err != nil {
return 0, nil, err
}
var data []dto.SnapshotInfo
for _, record := range records {
var item dto.SnapshotInfo
if err := copier.Copy(&item, &record); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
data = append(data, item)
}
return total, data, err
}
func (u *SnapshotService) LoadSize(req dto.PageSnapshot) ([]dto.SnapshotFile, error) {
_, records, err := snapshotRepo.Page(req.Page, req.PageSize, commonRepo.WithLikeName(req.Info), commonRepo.WithOrderRuleBy(req.OrderBy, req.Order))
dtoSnap, err := loadSnapSize(systemBackups)
if err != nil {
return nil, err
return 0, nil, err
}
data, err := loadSnapSize(records)
if err != nil {
return nil, err
}
return data, err
return total, dtoSnap, err
}
func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
@@ -527,12 +510,15 @@ func loadOs() string {
}
}
func loadSnapSize(records []model.Snapshot) ([]dto.SnapshotFile, error) {
var datas []dto.SnapshotFile
func loadSnapSize(records []model.Snapshot) ([]dto.SnapshotInfo, error) {
var datas []dto.SnapshotInfo
clientMap := make(map[string]loadSizeHelper)
var wg sync.WaitGroup
for i := 0; i < len(records); i++ {
item := dto.SnapshotFile{Name: records[i].Name, ID: records[i].ID}
var item dto.SnapshotInfo
if err := copier.Copy(&item, &records[i]); err != nil {
return nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
itemPath := fmt.Sprintf("system_snapshot/%s.tar.gz", item.Name)
if _, ok := clientMap[records[i].DefaultDownload]; !ok {
backup, err := backupRepo.Get(commonRepo.WithByType(records[i].DefaultDownload))
+1 -3
View File
@@ -231,9 +231,7 @@ func recoverDaemonJson(src string, fileOp files.FileOp) error {
}
}
if err := restartDocker(); err != nil {
return err
}
_, _ = cmd.Exec("systemctl restart docker")
return nil
}
+1 -11
View File
@@ -309,10 +309,6 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
if err != nil {
return err
}
if err = docker.CreateDefaultDockerNetwork(); err != nil {
err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil)
return
}
website.RuntimeID = runtime.ID
switch runtime.Type {
case constant.RuntimePHP:
@@ -356,7 +352,7 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
}
website.Proxy = proxy
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port)
}
}
@@ -1671,9 +1667,6 @@ 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"})
}
@@ -1756,9 +1749,6 @@ 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)
}
+8 -7
View File
@@ -217,14 +217,15 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website
if req.Domains != "" {
domainArray := strings.Split(req.Domains, "\n")
for _, domain := range domainArray {
if ipAddress := net.ParseIP(domain); ipAddress == nil {
if !common.IsValidDomain(domain) {
err = buserr.WithName("ErrDomainFormat", domain)
return nil, err
}
domains = append(domains, domain)
if !common.IsValidDomain(domain) {
err = buserr.WithName("ErrDomainFormat", domain)
return nil, err
} else {
ips = append(ips, ipAddress)
if ipAddress := net.ParseIP(domain); ipAddress == nil {
domains = append(domains, domain)
} else {
ips = append(ips, ipAddress)
}
}
}
if len(domains) > 0 {
+12 -21
View File
@@ -181,13 +181,6 @@ func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.Webs
return create, nil
}
func printSSLLog(logger *log.Logger, msgKey string, params map[string]interface{}, disableLog bool) {
if disableLog {
return
}
logger.Println(i18n.GetMsgWithMap(msgKey, params))
}
func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
var (
err error
@@ -279,13 +272,11 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
defer logFile.Close()
logger := log.New(logFile, "", log.LstdFlags)
legoLogger.Logger = logger
if !apply.DisableLog {
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
if websiteSSL.Provider == constant.DNSAccount {
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type})
}
legoLogger.Logger.Println(startMsg)
startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)})
if websiteSSL.Provider == constant.DNSAccount {
startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type})
}
legoLogger.Logger.Println(startMsg)
resource, err := client.ObtainSSL(domains, privateKey)
if err != nil {
handleError(websiteSSL, err)
@@ -305,7 +296,7 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
websiteSSL.Type = cert.Issuer.CommonName
websiteSSL.Organization = cert.Issuer.Organization[0]
websiteSSL.Status = constant.SSLReady
printSSLLog(logger, "ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")}, apply.DisableLog)
legoLogger.Logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")}))
saveCertificateFile(websiteSSL, logger)
if websiteSSL.ExecShell {
@@ -313,11 +304,11 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
if websiteSSL.PushDir {
workDir = websiteSSL.Dir
}
printSSLLog(logger, "ExecShellStart", nil, apply.DisableLog)
legoLogger.Logger.Println(i18n.GetMsgByKey("ExecShellStart"))
if err = cmd.ExecShellWithTimeOut(websiteSSL.Shell, workDir, logger, 30*time.Minute); err != nil {
printSSLLog(logger, "ErrExecShell", map[string]interface{}{"err": err.Error()}, apply.DisableLog)
legoLogger.Logger.Println(i18n.GetMsgWithMap("ErrExecShell", map[string]interface{}{"err": err.Error()}))
} else {
printSSLLog(logger, "ExecShellSuccess", nil, apply.DisableLog)
legoLogger.Logger.Println(i18n.GetMsgByKey("ExecShellSuccess"))
}
}
@@ -329,9 +320,9 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(websiteSSL.ID))
if len(websites) > 0 {
for _, website := range websites {
printSSLLog(logger, "ApplyWebSiteSSLLog", map[string]interface{}{"name": website.PrimaryDomain}, apply.DisableLog)
legoLogger.Logger.Println(i18n.GetMsgWithMap("ApplyWebSiteSSLLog", map[string]interface{}{"name": website.PrimaryDomain}))
if err := createPemFile(website, *websiteSSL); err != nil {
printSSLLog(logger, "ErrUpdateWebsiteSSL", map[string]interface{}{"name": website.PrimaryDomain, "err": err.Error()}, apply.DisableLog)
legoLogger.Logger.Println(i18n.GetMsgWithMap("ErrUpdateWebsiteSSL", map[string]interface{}{"name": website.PrimaryDomain, "err": err.Error()}))
}
}
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
@@ -339,10 +330,10 @@ func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error {
return
}
if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
printSSLLog(logger, constant.ErrSSLApply, nil, apply.DisableLog)
legoLogger.Logger.Println(i18n.GetMsgByKey(constant.ErrSSLApply))
return
}
printSSLLog(logger, "ApplyWebSiteSSLSuccess", nil, apply.DisableLog)
legoLogger.Logger.Println(i18n.GetMsgByKey("ApplyWebSiteSSLSuccess"))
}
}()
+8 -15
View File
@@ -3,6 +3,7 @@ package service
import (
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"log"
"os"
"path"
@@ -12,8 +13,6 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
@@ -277,7 +276,7 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
server.UpdateRoot(rootIndex)
server.UpdatePHPProxy([]string{website.Proxy}, "")
}
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython:
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
proxy := fmt.Sprintf("http://127.0.0.1:%d", runtime.Port)
server.UpdateRootProxy([]string{proxy})
}
@@ -1002,23 +1001,20 @@ func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) {
}
}
func GetSystemSSL() (bool, bool, uint) {
func GetSystemSSL() (bool, uint) {
sslSetting, err := settingRepo.Get(settingRepo.WithByKey("SSL"))
if err != nil {
global.LOG.Errorf("load service ssl from setting failed, err: %v", err)
return false, false, 0
return false, 0
}
if sslSetting.Value == "enable" {
sslID, _ := settingRepo.Get(settingRepo.WithByKey("SSLID"))
idValue, _ := strconv.Atoi(sslID.Value)
if idValue <= 0 {
return false, false, 0
if idValue > 0 {
return true, uint(idValue)
}
auto, _ := settingRepo.Get(settingRepo.WithByKey("AutoRestart"))
return true, auto.Value == "enable", uint(idValue)
}
return false, false, 0
return false, 0
}
func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
@@ -1037,7 +1033,7 @@ func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
return buserr.WithErr(constant.ErrSSLApply, err)
}
}
enable, auto, sslID := GetSystemSSL()
enable, sslID := GetSystemSSL()
if enable && sslID == websiteSSL.ID {
fileOp := files.NewFileOp()
secretDir := path.Join(global.CONF.System.BaseDir, "1panel/secret")
@@ -1049,9 +1045,6 @@ func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error {
global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error())
return err
}
if auto {
_, _ = cmd.Exec("systemctl restart 1panel.service")
}
}
return nil
}
+4 -5
View File
@@ -14,11 +14,10 @@ const (
RuntimeUnhealthy = "unhealthy"
RuntimeCreating = "creating"
RuntimePHP = "php"
RuntimeNode = "node"
RuntimeJava = "java"
RuntimeGo = "go"
RuntimePython = "python"
RuntimePHP = "php"
RuntimeNode = "node"
RuntimeJava = "java"
RuntimeGo = "go"
RuntimeProxyUnix = "unix"
RuntimeProxyTcp = "tcp"
+2 -7
View File
@@ -10,7 +10,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/service"
"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/common"
"github.com/1Panel-dev/1Panel/backend/utils/files"
)
@@ -23,7 +22,7 @@ func NewSSLJob() *ssl {
}
func (ssl *ssl) Run() {
systemSSLEnable, auto, sslID := service.GetSystemSSL()
systemSSLEnable, sslID := service.GetSystemSSL()
sslRepo := repo.NewISSLRepo()
sslService := service.NewIWebsiteSSLService()
sslList, _ := sslRepo.List()
@@ -52,8 +51,7 @@ func (ssl *ssl) Run() {
}
} else {
if err := sslService.ObtainSSL(request.WebsiteSSLApply{
ID: s.ID,
DisableLog: true,
ID: s.ID,
}); err != nil {
global.LOG.Errorf("Failed to update the SSL certificate for the [%s] domain , err:%s", s.PrimaryDomain, err.Error())
continue
@@ -71,9 +69,6 @@ func (ssl *ssl) Run() {
global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", s.PrimaryDomain, err.Error())
continue
}
if auto {
_, _ = cmd.Exec("systemctl restart 1panel.service")
}
}
global.LOG.Infof("The SSL certificate for the [%s] domain has been successfully updated", s.PrimaryDomain)
}
-2
View File
@@ -178,8 +178,6 @@ func handleUserInfo(tags string, settingRepo repo.ISettingRepo) {
if err := settingRepo.Update("SecurityEntrance", common.RandStrAndNum(10)); err != nil {
global.LOG.Fatalf("init entrance before start failed, err: %v", err)
}
sudo := cmd.SudoHandleCmd()
_, _ = cmd.Execf("%s sed -i '/CHANGE_USER_INFO=%v/d' /usr/local/bin/1pctl", sudo, global.CONF.System.ChangeUserInfo)
return
}
if strings.Contains(global.CONF.System.ChangeUserInfo, "username") {
-2
View File
@@ -95,8 +95,6 @@ func Init() {
migrations.AddClamStatus,
migrations.AddAlertMenu,
migrations.AddComposeColumn,
migrations.AddAutoRestart,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
@@ -324,13 +324,3 @@ var AddComposeColumn = &gormigrate.Migration{
return nil
},
}
var AddAutoRestart = &gormigrate.Migration{
ID: "20241021-add-auto-restart",
Migrate: func(tx *gorm.DB) error {
if err := tx.Create(&model.Setting{Key: "AutoRestart", Value: "enable"}).Error; err != nil {
return err
}
return nil
},
}
@@ -127,9 +127,6 @@ var AddTablePHPExtensions = &gormigrate.Migration{
if err := tx.Create(&model.PHPExtensions{Name: "WordPress", Extensions: "exif,igbinary,imagick,intl,zip,apcu,memcached,opcache,redis,bc,image,shmop,mysqli,pdo_mysql,gd"}).Error; err != nil {
return err
}
if err := tx.Create(&model.PHPExtensions{Name: "BookStack", Extensions: "gd,dom,iconv,mbstring,mysqlnd,openssl,pdo,pdo_mysql,tokenizer,xml"}).Error; err != nil {
return err
}
if err := tx.Create(&model.PHPExtensions{Name: "Flarum", Extensions: "curl,gd,pdo_mysql,mysqli,bz2,exif,yaf,imap"}).Error; err != nil {
return err
}
-1
View File
@@ -37,7 +37,6 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
settingRouter.POST("/snapshot", baseApi.CreateSnapshot)
settingRouter.POST("/snapshot/status", baseApi.LoadSnapShotStatus)
settingRouter.POST("/snapshot/search", baseApi.SearchSnapshot)
settingRouter.POST("/snapshot/size", baseApi.LoadSnapshotSize)
settingRouter.POST("/snapshot/import", baseApi.ImportSnapshot)
settingRouter.POST("/snapshot/del", baseApi.DeleteSnapshot)
settingRouter.POST("/snapshot/recover", baseApi.RecoverSnapshot)
+1 -1
View File
@@ -204,7 +204,7 @@ func SudoHandleCmd() string {
func Which(name string) bool {
stdout, err := Execf("which %s", name)
if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0) {
if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0 && strings.HasPrefix(stdout, "/")) {
return false
}
return true
+2 -9
View File
@@ -29,17 +29,10 @@ type FirewallClient interface {
}
func NewFirewallClient() (FirewallClient, error) {
_, 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 {
if _, err := os.Stat("/usr/sbin/firewalld"); err == nil {
return client.NewFirewalld()
}
if ufwErr == nil {
if _, err := os.Stat("/usr/sbin/ufw"); err == nil {
return client.NewUfw()
}
return nil, buserr.New(constant.ErrFirewall)
-1
View File
@@ -35,7 +35,6 @@ var repeatKeys = map[string]struct {
"include": {},
"sub_filter": {},
"add_header": {},
"error_page": {},
}
func IsRepeatKey(key string) bool {
@@ -234,13 +234,11 @@ 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 -13
View File
@@ -18,7 +18,6 @@ import (
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
"github.com/go-acme/lego/v4/providers/dns/dnspod"
"github.com/go-acme/lego/v4/providers/dns/godaddy"
"github.com/go-acme/lego/v4/providers/dns/huaweicloud"
"github.com/go-acme/lego/v4/providers/dns/namecheap"
"github.com/go-acme/lego/v4/providers/dns/namedotcom"
"github.com/go-acme/lego/v4/providers/dns/namesilo"
@@ -73,7 +72,6 @@ const (
NameCom DnsType = "NameCom"
Godaddy DnsType = "Godaddy"
TencentCloud DnsType = "TencentCloud"
HuaweiCloud DnsType = "HuaweiCloud"
)
type DNSParam struct {
@@ -86,7 +84,6 @@ type DNSParam struct {
APIUser string `json:"apiUser"`
APISecret string `json:"apiSecret"`
SecretID string `json:"secretID"`
Region string `json:"region"`
}
var (
@@ -169,15 +166,6 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
tencentCloudConfig.PollingInterval = pollingInterval
tencentCloudConfig.TTL = ttl
p, err = tencentcloud.NewDNSProviderConfig(tencentCloudConfig)
case HuaweiCloud:
huaweiCloudConfig := huaweicloud.NewDefaultConfig()
huaweiCloudConfig.AccessKeyID = param.AccessKey
huaweiCloudConfig.SecretAccessKey = param.SecretKey
huaweiCloudConfig.Region = param.Region
huaweiCloudConfig.PropagationTimeout = propagationTimeout
huaweiCloudConfig.PollingInterval = pollingInterval
huaweiCloudConfig.TTL = int32(ttl)
p, err = huaweicloud.NewDNSProviderConfig(huaweiCloudConfig)
}
if err != nil {
return err
@@ -199,7 +187,7 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.Web
dns01.CondOption(len(nameservers) > 0,
dns01.AddRecursiveNameservers(nameservers)),
dns01.CondOption(websiteSSL.SkipDNS,
dns01.DisableAuthoritativeNssPropagationRequirement()),
dns01.DisableCompletePropagationRequirement()),
dns01.AddDNSTimeout(10*time.Minute),
)
}
+1 -1
View File
@@ -2,7 +2,7 @@ system:
db_file: 1Panel.db
base_dir: /opt
mode: dev
repo_url: https://resource.fit2cloud.com/1panel/package/aliyun
repo_url: https://resource.fit2cloud.com/1panel/package
app_repo: https://apps-assets.fit2cloud.com
is_demo: false
port: 9999
+20 -192
View File
@@ -3872,8 +3872,8 @@ const docTemplate = `{
}
}
},
"/dashboard/current": {
"post": {
"/dashboard/current/:ioOption/:netOption": {
"get": {
"security": [
{
"ApiKeyAuth": []
@@ -3889,13 +3889,18 @@ const docTemplate = `{
"summary": "Load dashboard current info",
"parameters": [
{
"type": "string",
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.DashboardReq"
}
"name": "ioOption",
"in": "path",
"required": true
},
{
"type": "string",
"description": "request",
"name": "netOption",
"in": "path",
"required": true
}
],
"responses": {
@@ -9125,8 +9130,8 @@ const docTemplate = `{
"bodyKeys": [
"id"
],
"formatEN": "Delete runtime [name]",
"formatZH": "删除运行环境 [name]",
"formatEN": "Delete website [name]",
"formatZH": "删除网站 [name]",
"paramKeys": []
}
}
@@ -10803,7 +10808,7 @@ const docTemplate = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PageSnapshot"
"$ref": "#/definitions/dto.SearchWithPage"
}
}
],
@@ -10817,39 +10822,6 @@ const docTemplate = `{
}
}
},
"/settings/snapshot/size": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取系统快照大小",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Load system snapshot size",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PageSnapshot"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/settings/snapshot/status": {
"post": {
"security": [
@@ -15619,12 +15591,6 @@ const docTemplate = `{
"dto.ClamCreate": {
"type": "object",
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"description": {
"type": "string"
},
@@ -15723,12 +15689,6 @@ const docTemplate = `{
"dto.ClamUpdate": {
"type": "object",
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"description": {
"type": "string"
},
@@ -15845,9 +15805,6 @@ const docTemplate = `{
"detailName": {
"type": "string"
},
"fileName": {
"type": "string"
},
"name": {
"type": "string"
},
@@ -15919,12 +15876,6 @@ const docTemplate = `{
"from"
],
"properties": {
"env": {
"type": "array",
"items": {
"type": "string"
}
},
"file": {
"type": "string"
},
@@ -15951,7 +15902,8 @@ const docTemplate = `{
"type": "object",
"required": [
"name",
"operation"
"operation",
"path"
],
"properties": {
"name": {
@@ -15960,11 +15912,9 @@ const docTemplate = `{
"operation": {
"type": "string",
"enum": [
"up",
"start",
"stop",
"down",
"delete"
"down"
]
},
"path": {
@@ -16037,12 +15987,6 @@ const docTemplate = `{
"content": {
"type": "string"
},
"env": {
"type": "array",
"items": {
"type": "string"
}
},
"name": {
"type": "string"
},
@@ -16218,7 +16162,6 @@ const docTemplate = `{
"operation": {
"type": "string",
"enum": [
"up",
"start",
"stop",
"restart",
@@ -16366,12 +16309,6 @@ const docTemplate = `{
"type"
],
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"appID": {
"type": "string"
},
@@ -16446,16 +16383,9 @@ const docTemplate = `{
"required": [
"id",
"name",
"spec",
"type"
"spec"
],
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"appID": {
"type": "string"
},
@@ -16502,9 +16432,6 @@ const docTemplate = `{
"spec": {
"type": "string"
},
"type": {
"type": "string"
},
"url": {
"type": "string"
},
@@ -16630,9 +16557,6 @@ const docTemplate = `{
"dto.DashboardBase": {
"type": "object",
"properties": {
"SystemProxy": {
"type": "string"
},
"appInstalledNumber": {
"type": "integer"
},
@@ -16657,9 +16581,6 @@ const docTemplate = `{
"hostname": {
"type": "string"
},
"ipv4Addr": {
"type": "string"
},
"kernelArch": {
"type": "string"
},
@@ -16784,26 +16705,6 @@ const docTemplate = `{
},
"uptime": {
"type": "integer"
},
"xpuData": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.XPUInfo"
}
}
}
},
"dto.DashboardReq": {
"type": "object",
"properties": {
"ioOption": {
"type": "string"
},
"netOption": {
"type": "string"
},
"scope": {
"type": "string"
}
}
},
@@ -18707,41 +18608,6 @@ const docTemplate = `{
}
}
},
"dto.PageSnapshot": {
"type": "object",
"required": [
"order",
"orderBy",
"page",
"pageSize"
],
"properties": {
"info": {
"type": "string"
},
"order": {
"type": "string",
"enum": [
"null",
"ascending",
"descending"
]
},
"orderBy": {
"type": "string",
"enum": [
"name",
"created_at"
]
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
}
}
},
"dto.PasswordUpdate": {
"type": "object",
"required": [
@@ -19407,9 +19273,6 @@ const docTemplate = `{
"sslType"
],
"properties": {
"autoRestart": {
"type": "string"
},
"cert": {
"type": "string"
},
@@ -19638,9 +19501,6 @@ const docTemplate = `{
"appStoreVersion": {
"type": "string"
},
"autoRestart": {
"type": "string"
},
"bindAddress": {
"type": "string"
},
@@ -20065,32 +19925,6 @@ const docTemplate = `{
}
}
},
"dto.XPUInfo": {
"type": "object",
"properties": {
"deviceID": {
"type": "integer"
},
"deviceName": {
"type": "string"
},
"memory": {
"type": "string"
},
"memoryUsed": {
"type": "string"
},
"memoryUtil": {
"type": "string"
},
"power": {
"type": "string"
},
"temperature": {
"type": "string"
}
}
},
"files.FileInfo": {
"type": "object",
"properties": {
@@ -22600,9 +22434,6 @@ const docTemplate = `{
"proxyPass": {
"type": "string"
},
"proxySSLName": {
"type": "string"
},
"replaces": {
"type": "object",
"additionalProperties": {
@@ -22645,9 +22476,6 @@ const docTemplate = `{
"ID": {
"type": "integer"
},
"disableLog": {
"type": "boolean"
},
"nameservers": {
"type": "array",
"items": {
+20 -192
View File
@@ -3866,8 +3866,8 @@
}
}
},
"/dashboard/current": {
"post": {
"/dashboard/current/:ioOption/:netOption": {
"get": {
"security": [
{
"ApiKeyAuth": []
@@ -3883,13 +3883,18 @@
"summary": "Load dashboard current info",
"parameters": [
{
"type": "string",
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.DashboardReq"
}
"name": "ioOption",
"in": "path",
"required": true
},
{
"type": "string",
"description": "request",
"name": "netOption",
"in": "path",
"required": true
}
],
"responses": {
@@ -9119,8 +9124,8 @@
"bodyKeys": [
"id"
],
"formatEN": "Delete runtime [name]",
"formatZH": "删除运行环境 [name]",
"formatEN": "Delete website [name]",
"formatZH": "删除网站 [name]",
"paramKeys": []
}
}
@@ -10797,7 +10802,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PageSnapshot"
"$ref": "#/definitions/dto.SearchWithPage"
}
}
],
@@ -10811,39 +10816,6 @@
}
}
},
"/settings/snapshot/size": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取系统快照大小",
"consumes": [
"application/json"
],
"tags": [
"System Setting"
],
"summary": "Load system snapshot size",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.PageSnapshot"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/settings/snapshot/status": {
"post": {
"security": [
@@ -15613,12 +15585,6 @@
"dto.ClamCreate": {
"type": "object",
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"description": {
"type": "string"
},
@@ -15717,12 +15683,6 @@
"dto.ClamUpdate": {
"type": "object",
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"description": {
"type": "string"
},
@@ -15839,9 +15799,6 @@
"detailName": {
"type": "string"
},
"fileName": {
"type": "string"
},
"name": {
"type": "string"
},
@@ -15913,12 +15870,6 @@
"from"
],
"properties": {
"env": {
"type": "array",
"items": {
"type": "string"
}
},
"file": {
"type": "string"
},
@@ -15945,7 +15896,8 @@
"type": "object",
"required": [
"name",
"operation"
"operation",
"path"
],
"properties": {
"name": {
@@ -15954,11 +15906,9 @@
"operation": {
"type": "string",
"enum": [
"up",
"start",
"stop",
"down",
"delete"
"down"
]
},
"path": {
@@ -16031,12 +15981,6 @@
"content": {
"type": "string"
},
"env": {
"type": "array",
"items": {
"type": "string"
}
},
"name": {
"type": "string"
},
@@ -16212,7 +16156,6 @@
"operation": {
"type": "string",
"enum": [
"up",
"start",
"stop",
"restart",
@@ -16360,12 +16303,6 @@
"type"
],
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"appID": {
"type": "string"
},
@@ -16440,16 +16377,9 @@
"required": [
"id",
"name",
"spec",
"type"
"spec"
],
"properties": {
"alertCount": {
"type": "integer"
},
"alertTitle": {
"type": "string"
},
"appID": {
"type": "string"
},
@@ -16496,9 +16426,6 @@
"spec": {
"type": "string"
},
"type": {
"type": "string"
},
"url": {
"type": "string"
},
@@ -16624,9 +16551,6 @@
"dto.DashboardBase": {
"type": "object",
"properties": {
"SystemProxy": {
"type": "string"
},
"appInstalledNumber": {
"type": "integer"
},
@@ -16651,9 +16575,6 @@
"hostname": {
"type": "string"
},
"ipv4Addr": {
"type": "string"
},
"kernelArch": {
"type": "string"
},
@@ -16778,26 +16699,6 @@
},
"uptime": {
"type": "integer"
},
"xpuData": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.XPUInfo"
}
}
}
},
"dto.DashboardReq": {
"type": "object",
"properties": {
"ioOption": {
"type": "string"
},
"netOption": {
"type": "string"
},
"scope": {
"type": "string"
}
}
},
@@ -18701,41 +18602,6 @@
}
}
},
"dto.PageSnapshot": {
"type": "object",
"required": [
"order",
"orderBy",
"page",
"pageSize"
],
"properties": {
"info": {
"type": "string"
},
"order": {
"type": "string",
"enum": [
"null",
"ascending",
"descending"
]
},
"orderBy": {
"type": "string",
"enum": [
"name",
"created_at"
]
},
"page": {
"type": "integer"
},
"pageSize": {
"type": "integer"
}
}
},
"dto.PasswordUpdate": {
"type": "object",
"required": [
@@ -19401,9 +19267,6 @@
"sslType"
],
"properties": {
"autoRestart": {
"type": "string"
},
"cert": {
"type": "string"
},
@@ -19632,9 +19495,6 @@
"appStoreVersion": {
"type": "string"
},
"autoRestart": {
"type": "string"
},
"bindAddress": {
"type": "string"
},
@@ -20059,32 +19919,6 @@
}
}
},
"dto.XPUInfo": {
"type": "object",
"properties": {
"deviceID": {
"type": "integer"
},
"deviceName": {
"type": "string"
},
"memory": {
"type": "string"
},
"memoryUsed": {
"type": "string"
},
"memoryUtil": {
"type": "string"
},
"power": {
"type": "string"
},
"temperature": {
"type": "string"
}
}
},
"files.FileInfo": {
"type": "object",
"properties": {
@@ -22594,9 +22428,6 @@
"proxyPass": {
"type": "string"
},
"proxySSLName": {
"type": "string"
},
"replaces": {
"type": "object",
"additionalProperties": {
@@ -22639,9 +22470,6 @@
"ID": {
"type": "integer"
},
"disableLog": {
"type": "boolean"
},
"nameservers": {
"type": "array",
"items": {
+14 -128
View File
@@ -233,10 +233,6 @@ definitions:
type: object
dto.ClamCreate:
properties:
alertCount:
type: integer
alertTitle:
type: string
description:
type: string
infectedDir:
@@ -301,10 +297,6 @@ definitions:
type: object
dto.ClamUpdate:
properties:
alertCount:
type: integer
alertTitle:
type: string
description:
type: string
id:
@@ -379,8 +371,6 @@ definitions:
properties:
detailName:
type: string
fileName:
type: string
name:
type: string
secret:
@@ -434,10 +424,6 @@ definitions:
type: object
dto.ComposeCreate:
properties:
env:
items:
type: string
type: array
file:
type: string
from:
@@ -461,11 +447,9 @@ definitions:
type: string
operation:
enum:
- up
- start
- stop
- down
- delete
type: string
path:
type: string
@@ -474,6 +458,7 @@ definitions:
required:
- name
- operation
- path
type: object
dto.ComposeTemplateCreate:
properties:
@@ -512,10 +497,6 @@ definitions:
properties:
content:
type: string
env:
items:
type: string
type: array
name:
type: string
path:
@@ -633,7 +614,6 @@ definitions:
type: array
operation:
enum:
- up
- start
- stop
- restart
@@ -733,10 +713,6 @@ definitions:
type: object
dto.CronjobCreate:
properties:
alertCount:
type: integer
alertTitle:
type: string
appID:
type: string
backupAccounts:
@@ -789,10 +765,6 @@ definitions:
type: object
dto.CronjobUpdate:
properties:
alertCount:
type: integer
alertTitle:
type: string
appID:
type: string
backupAccounts:
@@ -824,8 +796,6 @@ definitions:
type: string
spec:
type: string
type:
type: string
url:
type: string
website:
@@ -834,7 +804,6 @@ definitions:
- id
- name
- spec
- type
type: object
dto.CronjobUpdateStatus:
properties:
@@ -914,8 +883,6 @@ definitions:
type: object
dto.DashboardBase:
properties:
SystemProxy:
type: string
appInstalledNumber:
type: integer
cpuCores:
@@ -932,8 +899,6 @@ definitions:
type: integer
hostname:
type: string
ipv4Addr:
type: string
kernelArch:
type: string
kernelVersion:
@@ -1017,19 +982,6 @@ definitions:
type: string
uptime:
type: integer
xpuData:
items:
$ref: '#/definitions/dto.XPUInfo'
type: array
type: object
dto.DashboardReq:
properties:
ioOption:
type: string
netOption:
type: string
scope:
type: string
type: object
dto.DatabaseCreate:
properties:
@@ -2326,31 +2278,6 @@ definitions:
total:
type: integer
type: object
dto.PageSnapshot:
properties:
info:
type: string
order:
enum:
- "null"
- ascending
- descending
type: string
orderBy:
enum:
- name
- created_at
type: string
page:
type: integer
pageSize:
type: integer
required:
- order
- orderBy
- page
- pageSize
type: object
dto.PasswordUpdate:
properties:
newPassword:
@@ -2796,8 +2723,6 @@ definitions:
type: object
dto.SSLUpdate:
properties:
autoRestart:
type: string
cert:
type: string
domain:
@@ -2957,8 +2882,6 @@ definitions:
type: string
appStoreVersion:
type: string
autoRestart:
type: string
bindAddress:
type: string
bindDomain:
@@ -3238,23 +3161,6 @@ definitions:
type:
type: string
type: object
dto.XPUInfo:
properties:
deviceID:
type: integer
deviceName:
type: string
memory:
type: string
memoryUsed:
type: string
memoryUtil:
type: string
power:
type: string
temperature:
type: string
type: object
files.FileInfo:
properties:
content:
@@ -4930,8 +4836,6 @@ definitions:
type: string
proxyPass:
type: string
proxySSLName:
type: string
replaces:
additionalProperties:
type: string
@@ -4964,8 +4868,6 @@ definitions:
properties:
ID:
type: integer
disableLog:
type: boolean
nameservers:
items:
type: string
@@ -8079,18 +7981,22 @@ paths:
summary: Load os info
tags:
- Dashboard
/dashboard/current:
post:
/dashboard/current/:ioOption/:netOption:
get:
consumes:
- application/json
description: 获取首页实时数据
parameters:
- description: request
in: body
name: request
in: path
name: ioOption
required: true
schema:
$ref: '#/definitions/dto.DashboardReq'
type: string
- description: request
in: path
name: netOption
required: true
type: string
responses:
"200":
description: OK
@@ -11404,8 +11310,8 @@ paths:
BeforeFunctions: []
bodyKeys:
- id
formatEN: Delete runtime [name]
formatZH: 删除运行环境 [name]
formatEN: Delete website [name]
formatZH: 删除网站 [name]
paramKeys: []
/runtimes/node/modules:
post:
@@ -12462,7 +12368,7 @@ paths:
name: request
required: true
schema:
$ref: '#/definitions/dto.PageSnapshot'
$ref: '#/definitions/dto.SearchWithPage'
responses:
"200":
description: OK
@@ -12473,26 +12379,6 @@ paths:
summary: Page system snapshot
tags:
- System Setting
/settings/snapshot/size:
post:
consumes:
- application/json
description: 获取系统快照大小
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.PageSnapshot'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Load system snapshot size
tags:
- System Setting
/settings/snapshot/status:
post:
consumes:
-1
View File
@@ -11,5 +11,4 @@ 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;
}
-3
View File
@@ -241,8 +241,6 @@ export namespace Container {
path: string;
containers: Array<ComposeContainer>;
expand: boolean;
envStr: string;
env: Array<string>;
}
export interface ComposeContainer {
name: string;
@@ -270,7 +268,6 @@ export namespace Container {
path: string;
content: string;
env: Array<string>;
createdBy: string;
}
export interface TemplateCreate {
+1 -9
View File
@@ -29,7 +29,6 @@ export namespace Setting {
bindAddress: string;
ssl: string;
sslType: string;
autoRestart: string;
allowIPs: string;
bindDomain: string;
securityEntrance: string;
@@ -135,7 +134,7 @@ export namespace Setting {
description: string;
status: string;
message: string;
created_at: DateTimeFormats;
createdAt: DateTimeFormats;
version: string;
interruptStep: string;
recoverStatus: string;
@@ -146,11 +145,6 @@ export namespace Setting {
lastRollbackedAt: string;
secret: string;
}
export interface SnapshotFile {
id: number;
name: string;
size: number;
}
export interface SnapshotStatus {
panel: string;
panelInfo: string;
@@ -175,13 +169,11 @@ export namespace Setting {
assigneeName: string;
productPro: string;
trial: boolean;
offline: boolean;
status: string;
message: string;
}
export interface LicenseStatus {
productPro: string;
trial: boolean;
status: string;
}
}
-2
View File
@@ -373,8 +373,6 @@ export namespace Website {
content?: string;
proxyAddress?: string;
proxyProtocol?: string;
sni: boolean;
proxySSLName: string;
}
export interface ProxReplace {
+1 -1
View File
@@ -43,7 +43,7 @@ export const containerRename = (params: Container.ContainerRename) => {
return http.post(`/containers/rename`, params);
};
export const containerOperator = (params: Container.ContainerOperate) => {
return http.post(`/containers/operate`, params, TimeoutEnum.T_60S);
return http.post(`/containers/operate`, params);
};
export const containerPrune = (params: Container.ContainerPrune) => {
return http.post<Container.ContainerPruneReport>(`/containers/prune`, params);
-3
View File
@@ -196,9 +196,6 @@ export const snapshotRollback = (param: Setting.SnapshotRecover) => {
export const searchSnapshotPage = (param: SearchWithPage) => {
return http.post<ResPage<Setting.SnapshotInfo>>(`/settings/snapshot/search`, param);
};
export const loadSnapshotSize = (param: SearchWithPage) => {
return http.post<Array<Setting.SnapshotFile>>(`/settings/snapshot/size`, param);
};
// upgrade
export const loadUpgradeInfo = () => {
+16 -5
View File
@@ -107,7 +107,7 @@
<script lang="ts" setup>
import { CheckAppInstalled, InstalledOp } from '@/api/modules/app';
import router from '@/routers';
import { onMounted, reactive, ref } from 'vue';
import { onMounted, reactive, ref, watch } from 'vue';
import Status from '@/components/status/index.vue';
import { ElMessageBox } from 'element-plus';
import i18n from '@/lang';
@@ -125,6 +125,21 @@ const props = defineProps({
},
});
watch(
() => props.appKey,
(val) => {
key.value = val;
onCheck();
},
);
watch(
() => props.appName,
(val) => {
name.value = val;
onCheck();
},
);
let key = ref('');
let name = ref('');
@@ -235,8 +250,4 @@ onMounted(() => {
name.value = props.appName;
onCheck();
});
defineExpose({
onCheck,
});
</script>
@@ -95,14 +95,15 @@ const logSearch = reactive({
const handleClose = () => {
terminalSocket.value?.send('close conn');
open.value = false;
globalStore.isFullScreen = false;
};
function toggleFullscreen() {
globalStore.isFullScreen = !globalStore.isFullScreen;
if (screenfull.isEnabled) {
screenfull.toggle();
}
}
const loadTooltip = () => {
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
return i18n.global.t('commons.button.' + (screenfull.isFullscreen ? 'quitFullscreen' : 'fullscreen'));
};
watch(logVisible, (val) => {
@@ -213,9 +214,6 @@ defineExpose({
</script>
<style scoped lang="scss">
.fullScreen {
border: none;
}
.selectWidth {
width: 200px;
}
+6 -6
View File
@@ -141,7 +141,7 @@ const data = ref([]);
const loading = ref(false);
const paths = ref<string[]>([]);
const req = reactive({ path: '/', expand: true, page: 1, pageSize: 300, showHidden: true });
const selectRow = ref({ path: '', name: '' });
const selectRow = ref();
const rowRefs = ref();
const popoverVisible = ref(false);
const newFolder = ref();
@@ -183,12 +183,12 @@ const selectFile = () => {
const closePage = () => {
popoverVisible.value = false;
selectRow.value = { path: '', name: '' };
selectRow.value = {};
};
const openPage = () => {
popoverVisible.value = true;
selectRow.value.path = props.dir ? props.path || '/' : '';
selectRow.value = {};
rowName.value = '';
};
@@ -216,7 +216,7 @@ const open = async (row: File.File) => {
}
await search(req);
}
selectRow.value.path = props.dir ? req.path : '';
selectRow.value = {};
rowName.value = '';
};
@@ -230,7 +230,7 @@ const jump = async (index: number) => {
}
path = path || '/';
req.path = path;
selectRow.value.path = props.dir ? req.path : '';
selectRow.value = {};
rowName.value = '';
await search(req);
popoverVisible.value = true;
@@ -286,7 +286,7 @@ const cancelFolder = (row: any) => {
data.value.shift();
row.isCreate = false;
disBtn.value = false;
selectRow.value.path = props.dir ? req.path : '';
selectRow.value = {};
rowName.value = '';
newFolder.value = '';
};
@@ -108,9 +108,6 @@ const showBack = computed(() => {
.el-button + .el-button {
margin: 0 !important;
}
.el-button-group .el-button {
margin: 0 !important;
}
}
.content-container_form {
@@ -0,0 +1,119 @@
<template>
<div>
<el-dialog class="level-up-pro" v-model="open" :close-on-click-modal="false" @close="handleClose">
<div style="text-align: center" v-loading="loading">
<span class="text-3xl font-medium title">{{ $t('license.levelUpPro') }}</span>
<el-row type="flex" justify="center" class="mt-6">
<el-col :span="22">
<el-upload
action="#"
:auto-upload="false"
ref="uploadRef"
class="upload-demo"
drag
:limit="1"
:on-change="fileOnChange"
:on-exceed="handleExceed"
v-model:file-list="uploaderFiles"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
{{ $t('license.importHelper') }}
</div>
</el-upload>
</el-col>
</el-row>
<el-button
type="primary"
class="mt-3 w-52"
:disabled="loading || uploaderFiles.length == 0"
plain
@click="submit"
>
{{ $t('license.power') }}
</el-button>
<div class="mt-3 mb-5">
<el-button text type="primary" @click="toHalo">{{ $t('license.knowMorePro') }}</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import i18n from '@/lang';
import { ref } from 'vue';
import { MsgSuccess } from '@/utils/message';
import { UploadFileData } from '@/api/modules/setting';
import { GlobalStore } from '@/store';
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile, genFileId } from 'element-plus';
import { useTheme } from '@/hooks/use-theme';
import { getXpackSetting } from '@/utils/xpack';
const globalStore = GlobalStore();
const { switchTheme } = useTheme();
const loading = ref(false);
const open = ref(false);
const uploadRef = ref<UploadInstance>();
const uploaderFiles = ref<UploadFiles>([]);
const handleClose = () => {
open.value = false;
uploadRef.value!.clearFiles();
};
const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => {
uploaderFiles.value = uploadFiles;
};
const handleExceed: UploadProps['onExceed'] = (files) => {
uploadRef.value!.clearFiles();
const file = files[0] as UploadRawFile;
file.uid = genFileId();
uploadRef.value!.handleStart(file);
};
const toHalo = () => {
window.open('https://www.lxware.cn/1panel' + '', '_blank', 'noopener,noreferrer');
};
const submit = async () => {
if (uploaderFiles.value.length !== 1) {
return;
}
const file = uploaderFiles.value[0];
const formData = new FormData();
formData.append('file', file.raw);
loading.value = true;
await UploadFileData(formData)
.then(async () => {
globalStore.isProductPro = true;
const xpackRes = await getXpackSetting();
if (xpackRes) {
globalStore.themeConfig.isGold = xpackRes.data.theme === 'dark-gold';
}
loading.value = false;
switchTheme();
uploadRef.value!.clearFiles();
uploaderFiles.value = [];
open.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
window.location.reload();
})
.catch(() => {
loading.value = false;
uploadRef.value!.clearFiles();
uploaderFiles.value = [];
});
};
const acceptParams = () => {
uploaderFiles.value = [];
uploadRef.value?.clearFiles();
open.value = true;
};
defineExpose({
acceptParams,
});
</script>
+8 -12
View File
@@ -4,7 +4,6 @@
:destroy-on-close="true"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="handleClose"
:size="globalStore.isFullScreen ? '100%' : '50%'"
>
<template #header>
@@ -22,10 +21,10 @@
</el-drawer>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { computed, ref } from 'vue';
import LogFile from '@/components/log-file/index.vue';
import screenfull from 'screenfull';
import { GlobalStore } from '@/store';
import screenfull from 'screenfull';
import i18n from '@/lang';
const globalStore = GlobalStore();
@@ -46,22 +45,19 @@ const open = ref(false);
const config = ref();
const em = defineEmits(['close']);
const handleClose = () => {
const handleClose = (search: boolean) => {
open.value = false;
em('close', false);
globalStore.isFullScreen = false;
em('close', search);
};
watch(open, (val) => {
if (screenfull.isEnabled && !val && !mobile.value) screenfull.exit();
});
function toggleFullscreen() {
globalStore.isFullScreen = !globalStore.isFullScreen;
if (screenfull.isEnabled) {
screenfull.toggle();
}
}
const loadTooltip = () => {
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
return i18n.global.t('commons.button.' + (screenfull.isFullscreen ? 'quitFullscreen' : 'fullscreen'));
};
const acceptParams = (props: LogProps) => {
+222 -202
View File
@@ -1,41 +1,41 @@
<template>
<div v-loading="firstLoading">
<div v-loading="isLoading">
<div v-if="defaultButton">
<el-checkbox border v-model="tailLog" class="float-left" @change="changeTail(false)">
{{ $t('commons.button.watch') }}
</el-checkbox>
<el-button class="ml-2.5" @click="onDownload" icon="Download" :disabled="logs.length === 0">
<el-button class="ml-2.5" @click="onDownload" icon="Download" :disabled="data.content === ''">
{{ $t('file.download') }}
</el-button>
<span v-if="$slots.button" class="ml-2.5">
<slot name="button"></slot>
</span>
</div>
<div class="log-container" ref="logContainer" @scroll="onScroll">
<div class="log-spacer" :style="{ height: `${totalHeight}px` }"></div>
<div
v-for="(log, index) in visibleLogs"
:key="startIndex + index"
class="log-item"
:style="{ top: `${(startIndex + index) * logHeight}px` }"
>
<span>{{ log }}</span>
</div>
<div class="mt-2.5">
<highlightjs
ref="editorRef"
class="editor-main"
language="JavaScript"
:autodetect="false"
:code="content"
></highlightjs>
</div>
</div>
</template>
<script lang="ts" setup>
import { ReadByLine } from '@/api/modules/files';
import { ref, computed, onMounted, watch, nextTick, reactive } from 'vue';
import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
import { downloadFile } from '@/utils/util';
import { ReadByLine } from '@/api/modules/files';
import { watch } from 'vue';
const editorRef = ref();
interface LogProps {
id?: number;
type: string;
name?: string;
tail?: boolean;
}
const props = defineProps({
config: {
type: Object as () => LogProps | null,
@@ -63,6 +63,46 @@ const props = defineProps({
default: false,
},
});
const data = ref({
enable: false,
content: '',
path: '',
});
let timer: NodeJS.Timer | null = null;
const tailLog = ref(false);
const content = ref('');
const end = ref(false);
const scrollerElement = ref<HTMLElement | null>(null);
const minPage = ref(1);
const maxPage = ref(1);
const logs = ref([]);
const isLoading = ref(false);
const readReq = reactive({
id: 0,
type: '',
name: '',
page: 1,
pageSize: 500,
latest: false,
});
const emit = defineEmits(['update:loading', 'update:hasContent', 'update:isReading']);
const loading = ref(props.loading);
watch(
() => props.loading,
(newLoading) => {
loading.value = newLoading;
},
);
const changeLoading = () => {
loading.value = !loading.value;
emit('update:loading', loading.value);
};
const stopSignals = [
'docker-compose up failed!',
'docker-compose up successful!',
@@ -73,63 +113,123 @@ const stopSignals = [
'image push failed!',
'image push successful!',
];
const emit = defineEmits(['update:loading', 'update:hasContent', 'update:isReading']);
const tailLog = ref(false);
const loading = ref(props.loading);
const readReq = reactive({
id: 0,
type: '',
name: '',
page: 1,
pageSize: 500,
latest: false,
});
const isLoading = ref(false);
const end = ref(false);
const lastLogs = ref([]);
const maxPage = ref(0);
const minPage = ref(0);
let timer: NodeJS.Timer | null = null;
const logPath = ref('');
const firstLoading = ref(false);
const logs = ref<string[]>([]);
const logContainer = ref<HTMLElement | null>(null);
const logHeight = 20;
const logCount = ref(0);
const totalHeight = computed(() => logHeight * logCount.value);
const containerHeight = ref(500);
const visibleCount = computed(() => Math.ceil(containerHeight.value / logHeight)); // 计算可见日志条数(容器高度 / 日志高度)
const startIndex = ref(0);
const getContent = (pre: boolean) => {
if (isLoading.value) {
return;
}
emit('update:isReading', true);
readReq.id = props.config.id;
readReq.type = props.config.type;
readReq.name = props.config.name;
if (readReq.page < 1) {
readReq.page = 1;
}
isLoading.value = true;
ReadByLine(readReq).then((res) => {
if (!end.value && res.data.end) {
lastLogs.value = [...logs.value];
}
const visibleLogs = computed(() => {
return logs.value.slice(startIndex.value, startIndex.value + visibleCount.value);
});
data.value = res.data;
if (res.data.lines && res.data.lines.length > 0) {
res.data.lines = res.data.lines.map((line) =>
line.replace(/\\u(\w{4})/g, function (match, grp) {
return String.fromCharCode(parseInt(grp, 16));
}),
);
const newLogs = res.data.lines;
if (newLogs.length === readReq.pageSize && readReq.page < res.data.total) {
readReq.page++;
}
const onScroll = () => {
if (logContainer.value) {
const scrollTop = logContainer.value.scrollTop;
if (scrollTop == 0) {
readReq.page = minPage.value - 1;
if (readReq.page < 1) {
if (
readReq.type == 'php' &&
logs.value.length > 0 &&
newLogs.length > 0 &&
newLogs[newLogs.length - 1] === logs.value[logs.value.length - 1]
) {
isLoading.value = false;
return;
}
minPage.value = readReq.page;
getContent(true);
if (stopSignals.some((signal) => newLogs[newLogs.length - 1].endsWith(signal))) {
onCloseLog();
}
if (end.value) {
if ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
logs.value = pre ? [...newLogs, ...lastLogs.value] : [...lastLogs.value, ...newLogs];
}
} else {
if ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
logs.value = pre ? [...newLogs, ...logs.value] : [...logs.value, ...newLogs];
}
}
}
startIndex.value = Math.floor(scrollTop / logHeight);
}
end.value = res.data.end;
content.value = logs.value.join('\n');
emit('update:hasContent', content.value !== '');
nextTick(() => {
if (pre) {
if (scrollerElement.value.scrollHeight > 2000) {
scrollerElement.value.scrollTop = 2000;
}
} else {
scrollerElement.value.scrollTop = scrollerElement.value.scrollHeight;
}
});
if (readReq.latest) {
readReq.page = res.data.total;
readReq.latest = false;
maxPage.value = res.data.total;
minPage.value = res.data.total;
}
if (logs.value && logs.value.length > 3000) {
logs.value.splice(0, readReq.pageSize);
if (minPage.value > 1) {
minPage.value--;
}
}
isLoading.value = false;
});
};
const changeLoading = () => {
loading.value = !loading.value;
emit('update:loading', loading.value);
};
function throttle<T extends (...args: any[]) => any>(func: T, limit: number): (...args: Parameters<T>) => void {
let inThrottle: boolean;
let lastFunc: ReturnType<typeof setTimeout>;
let lastRan: number;
return function (this: any, ...args: Parameters<T>) {
if (!inThrottle) {
func.apply(this, args);
lastRan = Date.now();
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if (Date.now() - lastRan >= limit) {
func.apply(this, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
const onDownload = async () => {
changeLoading();
downloadFile(logPath.value);
changeLoading();
const throttledGetContent = throttle(getContent, 3000);
const search = () => {
throttledGetContent(false);
};
const changeTail = (fromOutSide: boolean) => {
@@ -138,127 +238,36 @@ const changeTail = (fromOutSide: boolean) => {
}
if (tailLog.value) {
timer = setInterval(() => {
getContent(false);
search();
}, 1000 * 3);
} else {
onCloseLog();
}
};
const clearLog = (): void => {
logs.value = [];
readReq.page = 1;
lastLogs.value = [];
};
const getContent = async (pre: boolean) => {
if (isLoading.value) {
return;
}
readReq.id = props.config.id;
readReq.type = props.config.type;
readReq.name = props.config.name;
if (readReq.page < 1) {
readReq.page = 1;
}
isLoading.value = true;
emit('update:isReading', true);
const res = await ReadByLine(readReq);
logPath.value = res.data.path;
firstLoading.value = false;
if (!end.value && res.data.end) {
lastLogs.value = [...logs.value];
}
if (res.data.lines && res.data.lines.length > 0) {
res.data.lines = res.data.lines.map((line) =>
line.replace(/\\u(\w{4})/g, function (match, grp) {
return String.fromCharCode(parseInt(grp, 16));
}),
);
const newLogs = res.data.lines;
if (newLogs.length === readReq.pageSize && readReq.page < res.data.total) {
readReq.page++;
}
if (
readReq.type == 'php' &&
logs.value.length > 0 &&
newLogs.length > 0 &&
newLogs[newLogs.length - 1] === logs.value[logs.value.length - 1]
) {
isLoading.value = false;
return;
}
if (stopSignals.some((signal) => newLogs[newLogs.length - 1].endsWith(signal))) {
onCloseLog();
}
if (end.value) {
if ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
logs.value = pre ? [...newLogs, ...lastLogs.value] : [...lastLogs.value, ...newLogs];
}
} else {
if ((logs.value.length = 0)) {
logs.value = newLogs;
} else {
logs.value = pre ? [...newLogs, ...logs.value] : [...logs.value, ...newLogs];
}
}
nextTick(() => {
if (pre) {
logContainer.value.scrollTop = 2000;
} else {
logContainer.value.scrollTop = totalHeight.value;
containerHeight.value = logContainer.value.getBoundingClientRect().height;
}
});
}
logCount.value = logs.value.length;
end.value = res.data.end;
emit('update:hasContent', logs.value.length > 0);
if (readReq.latest) {
readReq.page = res.data.total;
readReq.latest = false;
maxPage.value = res.data.total;
minPage.value = res.data.total;
}
if (logs.value && logs.value.length > 3000) {
if (pre) {
logs.value.splice(logs.value.length - readReq.pageSize, readReq.pageSize);
if (maxPage.value > 1) {
maxPage.value--;
}
} else {
logs.value.splice(0, readReq.pageSize);
if (minPage.value > 1) {
minPage.value++;
}
}
}
isLoading.value = false;
const onDownload = async () => {
changeLoading();
downloadFile(data.value.path);
changeLoading();
};
const onCloseLog = async () => {
emit('update:isReading', false);
tailLog.value = false;
clearInterval(Number(timer));
timer = null;
isLoading.value = false;
emit('update:isReading', false);
};
watch(
() => props.loading,
(newLoading) => {
loading.value = newLoading;
},
);
function isScrolledToBottom(element: HTMLElement): boolean {
return element.scrollTop + element.clientHeight + 1 >= element.scrollHeight;
}
const init = async () => {
function isScrolledToTop(element: HTMLElement): boolean {
return element.scrollTop === 0;
}
const init = () => {
if (props.config.tail) {
tailLog.value = props.config.tail;
} else {
@@ -268,49 +277,60 @@ const init = async () => {
changeTail(false);
}
readReq.latest = true;
await getContent(false);
search();
nextTick(() => {});
};
onMounted(async () => {
firstLoading.value = true;
await init();
const clearLog = (): void => {
content.value = '';
};
const initCodemirror = () => {
nextTick(() => {
if (logContainer.value) {
logContainer.value.scrollTop = totalHeight.value;
containerHeight.value = logContainer.value.getBoundingClientRect().height;
if (editorRef.value) {
scrollerElement.value = editorRef.value.$el as HTMLElement;
scrollerElement.value.addEventListener('scroll', function () {
if (tailLog.value) {
return;
}
if (isScrolledToBottom(scrollerElement.value)) {
if (maxPage.value > 1) {
readReq.page = maxPage.value;
}
search();
}
if (isScrolledToTop(scrollerElement.value)) {
readReq.page = minPage.value - 1;
if (readReq.page < 1) {
return;
}
minPage.value = readReq.page;
throttledGetContent(true);
}
});
let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;
hljsDom.style['min-height'] = '500px';
}
});
};
onUnmounted(() => {
onCloseLog();
});
onMounted(() => {
initCodemirror();
init();
});
defineExpose({ changeTail, onDownload, clearLog });
</script>
<style scoped>
.log-container {
height: calc(100vh - 405px);
overflow-y: auto;
overflow-x: auto;
position: relative;
background-color: #1e1e1e;
margin-top: 10px;
}
.log-spacer {
position: relative;
<style lang="scss" scoped>
.editor-main {
height: calc(100vh - 480px);
width: 100%;
}
.log-item {
position: absolute;
width: 100%;
padding: 5px;
color: #f5f5f5;
box-sizing: border-box;
white-space: nowrap;
}
.log-item span {
font-size: 14px;
font-weight: 300;
color: #cccccc;
min-height: 600px;
overflow: auto;
}
</style>
@@ -1,6 +1,6 @@
<template>
<el-card class="router_card">
<div class="flex w-full flex-col md:justify-between md:flex-row">
<div class="flex w-full justify-between items-center md:justify-between md:flex-row">
<el-radio-group v-model="activeName" @change="handleChange">
<el-radio-button
class="router_card_button"
@@ -76,6 +76,7 @@ onMounted(() => {
<style lang="scss">
.router_card {
--el-card-padding: 0;
width: 100%;
.el-card__body {
display: flex;
justify-content: space-between;
@@ -16,6 +16,11 @@
<el-divider v-if="!mobile" direction="vertical" />
</div>
<div class="flex flex-wrap">
<el-button type="primary" link @click="toHalo">
<span class="font-normal">
{{ isProductPro ? $t('license.pro') : $t('license.community') }}
</span>
</el-button>
<span class="version" @click="copyText(version)">{{ version }}</span>
<el-badge is-dot style="margin-top: -3px" v-if="version !== 'Waiting' && globalStore.hasNewVersion">
<el-button type="primary" link @click="onLoadUpgradeInfo">
@@ -121,6 +126,10 @@ const handleClose = () => {
drawerVisible.value = false;
};
const toHalo = () => {
window.open('https://www.lxware.cn/1panel' + '', '_blank', 'noopener,noreferrer');
};
const toDoc = () => {
window.open('https://1panel.cn/docs/', '_blank', 'noopener,noreferrer');
};
+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.recoverHelper', [row.name]),
i18n.global.t('commons.button.recover'),
i18n.global.t('commons.msg.backupHelper', [name.value + '( ' + detailName.value + ' )']),
i18n.global.t('commons.button.backup'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
-4
View File
@@ -164,10 +164,6 @@ export const DNSTypes = [
label: i18n.global.t('website.tencentCloud'),
value: 'TencentCloud',
},
{
label: i18n.global.t('website.huaweicloud'),
value: 'HuaweiCloud',
},
{
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
value: 'DnsPod',
+54 -37
View File
@@ -56,8 +56,8 @@ const message = {
copy: 'Copy',
random: 'Random',
uninstall: 'Uninstall',
fullscreen: 'WebsiteFullscreen',
quitFullscreen: 'Quit WebsiteFullscreen',
fullscreen: 'Fullscreen',
quitFullscreen: 'Quit Fullscreen',
update: 'Edit',
showAll: 'Show All',
hideSome: 'Hide Some',
@@ -575,13 +575,8 @@ const message = {
container: {
create: 'Create',
edit: 'Edit container',
updateHelper1: 'Detected that this container comes from the app store. Please note the following two points:',
updateHelper2:
'1. The current modifications will not be synchronized to the installed applications in the app store.',
updateHelper3:
'2. If you modify the application on the installed page, the currently edited content will become invalid.',
updateHelper4:
'Editing the container requires rebuilding, and any non-persistent data will be lost. Do you want to continue?',
updateContainerHelper:
'Container editing requires rebuilding the container. Any data that has not been persisted will be lost. Do you want to continue?',
containerList: 'Container list',
operatorHelper: '{0} will be performed on the following container, Do you want to continue?',
operatorAppHelper:
@@ -648,7 +643,7 @@ const message = {
containerFromAppHelper:
'Detected that this container originates from the app store. App operations may cause current edits to be invalidated.',
containerFromAppHelper1:
'Click the [Param] button in the installed applications list to enter the editing page and modify the container name.',
'Click the `Settings` button in the installed applications list to enter the editing page and modify the container name.',
command: 'Command',
console: 'Console Interaction',
tty: 'TTY (-t)',
@@ -660,7 +655,7 @@ const message = {
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.',
'The environment variables manually entered in the menu will override existing variables with the same name. If they do not exist, they will be added.',
upgradeHelper: 'Repository Name/Image Name: Image Version',
upgradeWarning2:
@@ -736,8 +731,6 @@ const message = {
urlWarning: 'The URL prefix does not need to include http:// or https://. Please modify.',
network: 'Network',
networkHelper:
'Deleting the 1panel-network container network will affect the normal use of some applications and runtime environments. Do you want to continue?',
createNetwork: 'Create',
networkName: 'Name',
driver: 'Driver',
@@ -779,12 +772,11 @@ const message = {
deleteFile: 'Delete file',
allDelete: 'Permanently Delete',
deleteComposeHelper:
'Delete all files related to container compose, including configuration files and persistent files. Please proceed with caution!',
'1. Delete container orchestration records \n2. Delete all container orchestration files, including configuration and persistent files',
deleteCompose: '" Delete this composition.',
apps: 'Apps',
local: 'Local',
createCompose: 'Create',
composeDirectory: 'Compose Directory',
template: 'Template',
composeTemplate: 'Compose template',
createComposeTemplate: 'Create',
@@ -800,8 +792,6 @@ const message = {
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?',
@@ -942,7 +932,6 @@ const message = {
requestExpirationTime: 'Upload Request Expiration Time(Hours)',
unitHours: 'Unit: Hours',
alertTitle: 'Planned Task - {0} 「{1}」 Task Failure Alert',
},
monitor: {
monitor: 'Monitor',
@@ -1101,6 +1090,7 @@ const message = {
clam: {
clam: 'Virus Scan',
cron: 'Scheduled scan',
cronHelper: 'Professional version supports scheduled scan feature',
specErr: 'Execution schedule format error, please check and retry!',
disableMsg:
'Stopping scheduled execution will prevent this scan task from running automatically. Do you want to continue?',
@@ -1139,8 +1129,6 @@ const message = {
clamLog: 'Scan Logs',
freshClam: 'Update Virus Definitions',
freshClamLog: 'Update Virus Definitions Logs',
alertHelper: 'Professional version supports SMS alert',
alertTitle: 'Virus scanning 「{0}」 task failed alert',
},
},
logs: {
@@ -1524,11 +1512,8 @@ const message = {
bindDomain: 'Bind Domain',
unBindDomain: 'Unbind domain',
panelSSL: 'Panel SSL',
sslAutoRestart: 'Restart 1Panel service after certificate auto-renewal',
sslChangeHelper1:
'Currently, automatic restart of 1Panel service is not selected. The certificate auto-renewal will not take effect immediately and will still require a manual restart of 1Panel.',
sslChangeHelper2:
'The 1Panel service will automatically restart after setting the panel SSL. Do you want to continue?',
panelSSLHelper:
'After the automatic renewal of the panel SSL, you need to manually restart the 1Panel service for the changes to take effect.',
unBindDomainHelper:
'The action of unbinding a domain name may cause system insecurity. Do you want to continue?',
bindDomainHelper:
@@ -1664,6 +1649,49 @@ const message = {
compressPassword: 'Compression Password',
backupRecoverMessage: 'Please enter the compression or decompression password (leave blank to not set)',
},
license: {
community: 'Community Edition: ',
community2: 'Community Edition',
pro: 'Professional Edition: ',
trial: 'Trial Edition',
office: 'Official Edition',
trialInfo: 'Version',
authorizationId: 'Subscription Authorization ID',
authorizedUser: 'Authorized User',
expiresAt: 'Expiry Date',
productName: 'Product Name',
productStatus: 'Product Status',
Lost01: 'Lost * 1',
Lost02: 'Lost * 2',
Lost03: 'Lost',
Enable: 'Enabled',
Disable: 'Disabled',
lostHelper:
'The License needs to be periodically synchronized for availability. Please ensure normal external network access. After three losses of connection, the License binding will be released.',
quickUpdate: 'Quick Update',
import: 'Import',
power: 'Authorize',
unbind: 'Unbind License',
unbindHelper: 'All Pro related Settings will be cleared after unbinding. Do you want to continue? ',
importLicense: 'Import License',
importHelper: 'Please click or drag the license file here',
technicalAdvice: 'Technical Advice',
advice: 'Consultation',
indefinitePeriod: 'Indefinite Period',
levelUpPro: 'Upgrade to Professional Edition',
licenseSync: 'License Sync',
knowMorePro: 'Learn More',
closeAlert: 'The current page can be closed in the panel settings',
introduce: 'Feature Introduction',
waf: 'Upgrading to the professional version can provide features such as interception map, logs, block records, geographical location blocking, custom rules, custom interception pages, etc.',
tamper: 'Upgrading to the professional version can protect websites from unauthorized modifications or tampering.',
gpu: 'Upgrading to the professional version can help users visually monitor important parameters of GPU such as workload, temperature, memory usage in real time.',
setting:
'Upgrading to the professional version allows customization of panel logo, welcome message, and other information.',
monitor:
'Upgrade to the professional version to view the real-time status of the website, visitor trends, visitor sources, request logs and other information. ',
alert: 'Upgrade to the professional version to receive alarm information via SMS and view alarm logs, fully control various key events, and ensure worry-free system operation',
},
clean: {
scan: 'Start Scanning',
scanHelper: 'Easily tidy up junk files accumulated during 1Panel runtime',
@@ -1884,7 +1912,7 @@ const message = {
applySSL: 'Certificate Application',
SSLList: 'Certificate List',
createDnsAccount: 'DNS Account',
aliyun: 'Aliyun',
aliyun: 'Aliyun DNS',
manual: 'Manual parsing',
key: 'Key',
check: 'View',
@@ -2107,7 +2135,6 @@ const message = {
sni: 'Origin SNI',
sniHelper:
"When the reverse proxy backend is HTTPS, you might need to set the origin SNI. Please refer to the CDN service provider's documentation for details.",
huaweicloud: 'Huawei Cloud',
},
php: {
short_open_tag: 'Short tag support',
@@ -2358,8 +2385,6 @@ const message = {
javaDirHelper: 'The directory must contain jar files, subdirectories are also acceptable',
goHelper: 'Please provide a complete start command, for example: go run main.go or ./main',
goDirHelper: 'The directory must contain go files or binary files, subdirectories are also acceptable',
pythonHelper:
'Please fill in the complete startup command, for example: pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000',
},
process: {
pid: 'Process ID',
@@ -2430,14 +2455,6 @@ const message = {
manage: 'Management',
},
},
alert: {
isAlert: 'Alert',
alertCount: 'Alert Count',
clamHelper: 'Trigger SMS alert when scanning infected files',
cronJobHelper: 'Trigger SMS alert when scheduled task execution fails',
licenseHelper: 'Professional version supports SMS alert',
alertCountHelper: 'Maximum daily alarm frequency',
},
};
export default {
+51 -33
View File
@@ -55,8 +55,8 @@ const message = {
copy: '復製',
random: '隨機密碼',
uninstall: '卸載',
fullscreen: '網頁全屏',
quitFullscreen: '退出網頁全屏',
fullscreen: '全屏',
quitFullscreen: '退出全屏',
update: '編輯',
showAll: '顯示所有',
hideSome: '隱藏部分',
@@ -557,10 +557,7 @@ const message = {
container: {
create: '創建容器',
edit: '編輯容器',
updateHelper1: '檢測到該容器來源於應用商店,請注意以下兩點:',
updateHelper2: '1. 當前修改內容不會同步到應用商店的已安裝應用。',
updateHelper3: '2. 如果在已安裝頁面修改應用,當前編輯的部分內容將失效。',
updateHelper4: '編輯容器需要重建,任何未持久化的數據將丟失,是否繼續?',
updateContainerHelper: '容器編輯需要重建容器,任何未持久化的數據將會丟失,是否繼續?',
containerList: '容器列表',
operatorHelper: '將對以下容器進行 {0} 操作,是否繼續?',
operatorAppHelper:
@@ -622,7 +619,7 @@ const message = {
inputIpv6: '請輸入 IPv6 地址',
containerFromAppHelper: '檢測到該容器來源於應用商店,應用操作可能會導致當前編輯失效',
containerFromAppHelper1: '在已安裝應用程式列表點擊 [參數] 按鈕,進入編輯頁面即可修改容器名稱。',
containerFromAppHelper1: '在已安裝應用程式列表點擊 `參數` 按鈕,進入編輯頁面即可修改容器名稱。',
command: '命令',
console: '控製臺交互',
tty: '偽終端 ( -t )',
@@ -632,8 +629,7 @@ const message = {
emptyUser: '為空時,將使用容器默認的用戶登錄',
privileged: '特權模式',
privilegedHelper: '允許容器在主機上執行某些特權操作,可能會增加容器風險,請謹慎開啟!',
editComposeHelper:
'注意:設置的環境變數會默認寫入 1panel.env 文件。\n若需在容器中使用這些參數,還需在 compose 文件中手動添加 env_file 引用。',
editComposeHelper: '在菜單中手動輸入的環境變量會覆蓋原有的同名變量,若不存在則新增。',
upgradeHelper: '倉庫名稱/鏡像名稱:鏡像版本',
upgradeWarning2: '升級操作需要重建容器,任何未持久化的數據將會丟失,是否繼續?',
@@ -710,7 +706,6 @@ const message = {
urlWarning: '路徑前綴不需要添加 http:// 或 https://,請修改',
network: '網絡',
networkHelper: '刪除 1panel-network 容器網絡將影響部分應用和運行環境的正常使用,是否繼續?',
createNetwork: '創建網絡',
networkName: '網絡名',
driver: '模式',
@@ -746,12 +741,11 @@ const message = {
composeHelper: '通過 1Panel 編輯或者模版創建的編排,將保存在 {0}/docker/compose 路徑下',
deleteFile: '刪除文件',
allDelete: '徹底刪除',
deleteComposeHelper: '刪除容器編排的所有文件,包括配置文件和持久化文件,請謹慎操作!',
deleteComposeHelper: '1. 刪除容器編排記錄 \n2. 刪除容器編排的所有文件,包括配置文件和持久化文件',
deleteCompose: '" 刪除此編排',
apps: '應用商店',
local: '本地',
createCompose: '創建編排',
composeDirectory: '編排目錄',
template: '模版',
composeTemplate: '編排模版',
createComposeTemplate: '創建編排模版',
@@ -766,7 +760,6 @@ const message = {
up: '啟動',
composeDetailHelper: '該 compose 為 1Panel 編排外部創建。暫不支持啟停操作。',
composeOperatorHelper: '將對 {0} 進行 {1} 操作,是否繼續?',
composeDownHelper: '將停止並刪除 {0} 編排下所有容器及網絡,是否繼續?',
setting: '配置',
operatorStatusHelper: '此操作將{0}Docker 服務,是否繼續?',
@@ -893,7 +886,6 @@ const message = {
requestExpirationTime: '上傳請求過期時間(小時)',
unitHours: '單位:小時',
alertTitle: '計畫任務-{0}「{1}」任務失敗告警',
},
monitor: {
monitor: '監控',
@@ -1038,6 +1030,7 @@ const message = {
clam: {
clam: '病毒掃描',
cron: '定時掃描',
cronHelper: '專業版支持定時掃描功能',
specErr: '執行周期格式錯誤,請檢查後重試!',
disableMsg: '停止定時執行會導致該掃描任務不再自動執行。是否繼續?',
enableMsg: '啟用定時執行會讓該掃描任務定期自動執行。是否繼續?',
@@ -1073,8 +1066,6 @@ const message = {
clamLog: '掃描日誌',
freshClam: '病毒庫刷新配置',
freshClamLog: '病毒庫刷新日誌',
alertHelper: '專業版支持短信告警功能',
alertTitle: '病毒掃描「{0}」任務失敗告警',
},
},
logs: {
@@ -1311,8 +1302,8 @@ const message = {
systemIP: '伺服器地址',
proxy: '代理伺服器',
proxyHelper: '設置代理伺服器後,將在以下場景中生效:',
proxyHelper1: '應用商店的安裝包下載和同步',
proxyHelper2: '系統版本升級及獲取更新說明',
proxyHelper1: '應用商店的安裝包下載和同步(專業版功能)',
proxyHelper2: '系統版本升級及獲取更新說明(專業版功能)',
proxyHelper3: '系統許可證的驗證和同步',
proxyType: '代理類型',
proxyUrl: '代理地址',
@@ -1471,9 +1462,7 @@ const message = {
bindDomain: '域名綁定',
unBindDomain: '域名解綁',
panelSSL: '面板 SSL',
sslAutoRestart: '證書自動續期後重啟 1Panel 服務',
sslChangeHelper1: '當前未勾選自動重啟 1Panel 服務,證書自動續期後不會立即生效,仍需手動重啟 1Panel。',
sslChangeHelper2: '設置面板 SSL 後將自動重啟 1Panel 服務,是否繼續?',
panelSSLHelper: '面板 SSL 自动续期后需要手动重启 1Panel 服务才可生效',
unBindDomainHelper: '解除域名綁定可能造成系統不安全,是否繼續?',
bindDomainHelper: '設置域名綁定後,僅能通過設置中域名訪問 1Panel 服務',
bindDomainHelper1: '綁定域名為空時,則取消域名綁定',
@@ -1543,6 +1532,46 @@ const message = {
compressPassword: '壓縮密碼',
backupRecoverMessage: '請輸入壓縮或解壓縮密碼(留空則不設定)',
},
license: {
community: '社區版:',
community2: '社區版',
pro: '專業版:',
trial: '試用版',
office: '正式版',
trialInfo: '版本',
authorizationId: '訂閱授權 ID',
authorizedUser: '被授權方',
expiresAt: '到期時間',
productName: '產品名稱',
productStatus: '產品狀態',
Lost01: '失聯 * 1',
Lost02: '失聯 * 2',
Lost03: '已失聯',
Enable: '已啟用',
Disable: '未啟用',
lostHelper: '許可證需要定時同步是否可用,請保證正常外網訪問,失聯三次後將解除許可證綁定',
quickUpdate: '快速更新',
import: '導入',
power: '授 權',
unbind: '解除綁定',
unbindHelper: '解除綁定後將清除所有專業版相關設置,是否繼續?',
importLicense: '導入許可證',
importHelper: '請點擊或拖動許可文件到此處',
technicalAdvice: '技術諮詢',
advice: '諮詢',
indefinitePeriod: '無限期',
levelUpPro: '升級專業版',
licenseSync: '許可證同步',
knowMorePro: '了解更多',
closeAlert: '當前頁面可在面板設置中關閉顯示',
introduce: '功能介紹',
waf: '升級專業版可以獲得攔截地圖、日誌、封鎖記錄、地理位置封禁、自定義規則、自定義攔截頁面等功能。',
tamper: '升級專業版可以保護網站免受未經授權的修改或篡改。',
gpu: '升級專業版可以幫助用戶實時直觀查看到 GPU 的工作負載、溫度、顯存等重要參數。',
setting: '升級專業版可以自定義面板 Logo、歡迎簡介等信息。',
monitor: '升級專業版可以查看網站的即時狀態、訪客趨勢、訪客來源、請求日誌等資訊。 ',
alert: '陞級專業版可通過簡訊接收告警資訊,並查看告警日誌,全面掌控各類關鍵事件,確保系統運行無憂。',
},
clean: {
scan: '開始掃描',
scanHelper: '輕鬆梳理 1Panel 運行期間積累的垃圾文件',
@@ -1746,7 +1775,7 @@ const message = {
applySSL: '證書申請',
SSLList: '證書列表',
createDnsAccount: 'DNS賬戶',
aliyun: '阿裏雲',
aliyun: '阿裏雲DNS',
manual: '手動解析',
key: '密鑰',
check: '查看',
@@ -1955,7 +1984,6 @@ const message = {
website404Helper: '網站 404 錯誤頁僅支援 PHP 運行環境網站和靜態網站',
sni: '回源 SNI',
sniHelper: '反代後端為 https 的時候可能需要設置回源 SNI,具體需要看 CDN 服務商文檔',
huaweicloud: '華為雲',
},
php: {
short_open_tag: '短標簽支持',
@@ -2183,8 +2211,6 @@ const message = {
javaDirHelper: '目錄中要包含 jar 包,子目錄中包含也可',
goHelper: '請填寫完整啟動命令,例如:go run main.go 或 ./main',
goDirHelper: '目錄中要包含 go 文件或者二進制文件,子目錄中包含也可',
pythonHelper:
'請填入完整啟動指令,例如:pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000',
},
process: {
pid: '進程ID',
@@ -2253,14 +2279,6 @@ const message = {
manage: '管理',
},
},
alert: {
isAlert: '是否告警',
alertCount: '告警次數',
clamHelper: '掃描到感染檔案時觸發簡訊告警',
cronJobHelper: '定時執行計畫任務失敗時觸發簡訊告警',
licenseHelper: '專業版支持簡訊告警功能',
alertCountHelper: '每日最大告警次數',
},
};
export default {
...fit2cloudTwLocale,
+53 -33
View File
@@ -55,8 +55,8 @@ const message = {
copy: '复制',
random: '随机密码',
uninstall: '卸载',
fullscreen: '网页全屏',
quitFullscreen: '退出网页全屏',
fullscreen: '全屏',
quitFullscreen: '退出全屏',
update: '编辑',
showAll: '显示所有',
hideSome: '隐藏部分',
@@ -559,10 +559,7 @@ const message = {
container: {
create: '创建容器',
edit: '编辑容器',
updateHelper1: '检测到该容器来源于应用商店,请注意以下两点:',
updateHelper2: '1. 当前修改内容不会同步到应用商店的已安装应用。',
updateHelper3: '2. 如果在已安装页面修改应用,当前编辑的部分内容将失效。',
updateHelper4: '编辑容器需要重建,任何未持久化的数据将丢失,是否继续?',
updateContainerHelper: '容器编辑需要重建容器,任何未持久化的数据将会丢失,是否继续?',
containerList: '容器列表',
operatorHelper: '将对以下容器进行 {0} 操作,是否继续?',
operatorAppHelper:
@@ -625,7 +622,7 @@ const message = {
inputIpv6: '请输入 ipv6 地址',
containerFromAppHelper: '检测到该容器来源于应用商店,应用操作可能会导致当前编辑失效',
containerFromAppHelper1: '在应用商店的已安装页面,点击 [参数] 按钮,进入编辑页面修改容器名称。',
containerFromAppHelper1: '在已安装应用列表点击 `参数` 按钮,进入编辑页面即可修改容器名称。',
command: '命令',
console: '控制台交互',
tty: '伪终端 ( -t )',
@@ -635,8 +632,8 @@ const message = {
emptyUser: '为空时,将使用容器默认的用户登录',
privileged: '特权模式',
privilegedHelper: '允许容器在主机上执行某些特权操作,可能会增加容器风险,谨慎开启!',
editComposeHelper:
'注意:设置的环境变量会默认写入 1panel.env 文件。\n如需在容器中使用这些参数,还需在 compose 文件中手动添加 env_file 引用。',
editComposeHelper: '菜单中手动输入的环境变量会覆盖原有的同名变量,如果不存在则新增',
upgradeHelper: '仓库名称/镜像名称:镜像版本',
upgradeWarning2: '升级操作需要重建容器,任何未持久化的数据将会丢失,是否继续?',
oldImage: '当前镜像',
@@ -712,7 +709,6 @@ const message = {
urlWarning: '路径前缀不需要添加 http:// 或 https://, 请修改',
network: '网络',
networkHelper: '删除 1panel-network 容器网络将影响部分应用和运行环境的正常使用,是否继续?',
createNetwork: '创建网络',
networkName: '网络名',
driver: '模式',
@@ -747,12 +743,11 @@ const message = {
composeHelper: '通过 1Panel 编辑或者模版创建的编排,将保存在 {0}/docker/compose 路径下',
deleteFile: '删除文件',
allDelete: '彻底删除',
deleteComposeHelper: '删除容器编排的所有文件,包括配置文件和持久化文件,请谨慎操作!',
deleteComposeHelper: '1. 删除容器编排记录 \n2. 删除容器编排的所有文件,包括配置文件和持久化文件',
deleteCompose: '" 删除此编排',
apps: '应用商店',
local: '本地',
createCompose: '创建编排',
composeDirectory: '编排目录',
template: '模版',
composeTemplate: '编排模版',
createComposeTemplate: '创建编排模版',
@@ -767,7 +762,6 @@ const message = {
up: '启动',
composeDetailHelper: '该 compose 为 1Panel 编排外部创建。暂不支持启停操作。',
composeOperatorHelper: '将对 {0} 进行 {1} 操作,是否继续?',
composeDownHelper: '将停止并删除 {0} 编排下所有容器及网络,是否继续?',
setting: '配置',
operatorStatusHelper: '此操作将{0}Docker 服务,是否继续?',
@@ -894,7 +888,6 @@ const message = {
requestExpirationTime: '上传请求过期时间(小时)',
unitHours: '单位:小时',
alertTitle: '计划任务-{0}「 {1} 」任务失败告警',
},
monitor: {
monitor: '监控',
@@ -1039,6 +1032,7 @@ const message = {
clam: {
clam: '病毒扫描',
cron: '定时扫描',
cronHelper: '专业版支持定时扫描功能 ',
specErr: '执行周期格式错误,请检查后重试!',
disableMsg: '停止定时执行会导致该扫描任务不再自动执行。是否继续?',
enableMsg: '启用定时执行会让该扫描任务定期自动执行。是否继续?',
@@ -1075,8 +1069,6 @@ const message = {
clamLog: '扫描日志',
freshClam: '病毒库刷新配置',
freshClamLog: '病毒库刷新日志',
alertHelper: '专业版支持短信告警功能',
alertTitle: '病毒扫描「 {0} 」任务失败告警',
},
},
logs: {
@@ -1313,8 +1305,8 @@ const message = {
systemIP: '服务器地址',
proxy: '代理服务器',
proxyHelper: '设置代理服务器后,将在以下场景中生效:',
proxyHelper1: '应用商店的安装包下载和同步',
proxyHelper2: '系统版本升级及获取更新说明',
proxyHelper1: '应用商店的安装包下载和同步(专业版功能)',
proxyHelper2: '系统版本升级及获取更新说明(专业版功能)',
proxyHelper3: '系统许可证的验证和同步',
proxyType: '代理类型',
proxyUrl: '代理地址',
@@ -1473,9 +1465,7 @@ const message = {
bindDomain: '域名绑定',
unBindDomain: '域名解绑',
panelSSL: '面板 SSL',
sslAutoRestart: '证书自动续期后重启 1Panel 服务',
sslChangeHelper1: '当前未勾选自动重启 1Panel 服务,证书自动续期后不会立即生效,仍需手动重启 1Panel。',
sslChangeHelper2: '设置面板 SSL 后将自动重启 1Panel 服务,是否继续?',
panelSSLHelper: '面板 SSL 自动续期后需要手动重启 1Panel 服务才可生效',
unBindDomainHelper: '解除域名绑定可能造成系统不安全,是否继续?',
bindDomainHelper: '设置域名绑定后,仅能通过设置中域名访问 1Panel 服务',
bindDomainHelper1: '绑定域名为空时,则取消域名绑定',
@@ -1499,6 +1489,7 @@ const message = {
mfaInterval: '刷新时间(秒)',
mfaTitleHelper: '用于区分不同 1Panel 主机,修改后请重新扫描或手动添加密钥信息!',
mfaIntervalHelper: '修改刷新时间后,请重新扫描或手动添加密钥信息!',
sslChangeHelper: 'https 设置修改需要重启服务,是否继续?',
sslDisable: '禁用',
sslDisableHelper: '禁用 https 服务,需要重启面板才能生效,是否继续?',
noAuthSetting: '未认证设置',
@@ -1544,6 +1535,46 @@ const message = {
compressPassword: '压缩密码',
backupRecoverMessage: '请输入压缩或解压缩密码(留空则不设置)',
},
license: {
community: '社区版:',
community2: '社区版',
pro: '专业版:',
trial: '试用版',
office: '正式版',
trialInfo: '版本',
authorizationId: '订阅授权 ID',
authorizedUser: '被授权方',
expiresAt: '到期时间',
productName: '产品名称',
productStatus: '产品状态',
Lost01: '失联 * 1',
Lost02: '失联 * 2',
Lost03: '已失联',
Enable: '已激活',
Disable: '未激活',
lostHelper: '许可证需要定时同步是否可用,请保证正常外网访问,失联三次后将解除许可证绑定',
quickUpdate: '快速更新',
import: '导入',
power: '授 权',
unbind: '解除绑定',
unbindHelper: '解除绑定后将清除所有专业版相关设置,是否继续?',
importLicense: '导入许可证',
importHelper: '请点击或拖动许可文件到此处',
technicalAdvice: '技术咨询',
advice: '咨询',
indefinitePeriod: '无限期',
levelUpPro: '升级专业版',
licenseSync: '许可证同步',
knowMorePro: '了解更多',
closeAlert: '当前页面可在面板设置中关闭显示',
introduce: '功能介绍',
waf: '升级专业版可以获得拦截地图、日志、封锁记录、地理位置封禁、自定义规则、自定义拦截页面等功能。',
tamper: '升级专业版可以保护网站免受未经授权的修改或篡改。',
gpu: '升级专业版可以帮助用户实时直观查看到 GPU 的工作负载、温度、显存等重要参数。',
setting: '升级专业版可以自定义面板 Logo、欢迎简介等信息。',
monitor: '升级专业版可以查看网站的实时状态、访客趋势、访客来源、请求日志等信息。',
alert: '升级专业版可通过短信接收告警信息,并查看告警日志,全面掌控各类关键事件,确保系统运行无忧。',
},
clean: {
scan: '开始扫描',
scanHelper: '轻松梳理 1Panel 运行期间积累的垃圾文件',
@@ -1747,7 +1778,7 @@ const message = {
applySSL: '证书申请',
SSLList: '证书列表',
createDnsAccount: 'DNS账户',
aliyun: '阿里云',
aliyun: '阿里云DNS',
manual: '手动解析',
key: '密钥',
check: '查看',
@@ -1956,7 +1987,6 @@ const message = {
website404Helper: '网站 404 错误页仅支持 PHP 运行环境网站和静态网站',
sni: '回源 SNI',
sniHelper: '反代后端为 https 的时候可能需要设置回源 SNI,具体需要看 CDN 服务商文档',
huaweicloud: '华为云',
},
php: {
short_open_tag: '短标签支持',
@@ -2185,8 +2215,6 @@ const message = {
javaDirHelper: '目录中要包含 jar 包,子目录中包含也可',
goHelper: '请填写完整启动命令,例如:go run main.go 或 ./main',
goDirHelper: '目录中要包含 go 文件或者二进制文件,子目录中包含也可',
pythonHelper:
'请填写完整启动命令,例如:pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000',
},
process: {
pid: '进程ID',
@@ -2255,14 +2283,6 @@ const message = {
manage: '管理',
},
},
alert: {
isAlert: '是否告警',
alertCount: '告警次数',
clamHelper: '扫描到感染文件时触发短信告警',
cronJobHelper: '定时执行计划任务失败时触发短信告警',
licenseHelper: '专业版支持短信告警功能',
alertCountHelper: '每日最大告警次数',
},
};
export default {
...fit2cloudZhLocale,
+1 -1
View File
@@ -100,7 +100,7 @@ onMounted(() => {
loadStatus();
loadProductProFromDB();
globalStore.isFullScreen = false;
const mqList = window.matchMedia('(prefers-color-scheme: dark)');
if (mqList.addEventListener) {
mqList.addEventListener('change', () => {
+10
View File
@@ -37,6 +37,16 @@ const settingRouter = {
activeMenu: 'Setting',
},
},
{
path: 'license',
name: 'License',
component: () => import('@/views/setting/license/index.vue'),
hidden: true,
meta: {
requiresAuth: true,
activeMenu: 'Setting',
},
},
{
path: 'about',
name: 'About',
+2 -12
View File
@@ -64,7 +64,7 @@ const webSiteRouter = {
hidden: true,
component: () => import('@/views/website/runtime/java/index.vue'),
meta: {
activeMenu: '/websites/runtimes/php',
activeMenu: '/websites/runtimes/java',
requiresAuth: false,
},
},
@@ -74,17 +74,7 @@ const webSiteRouter = {
hidden: true,
component: () => import('@/views/website/runtime/go/index.vue'),
meta: {
activeMenu: '/websites/runtimes/php',
requiresAuth: false,
},
},
{
path: '/websites/runtimes/python',
name: 'python',
hidden: true,
component: () => import('@/views/website/runtime/python/index.vue'),
meta: {
activeMenu: '/websites/runtimes/php',
activeMenu: '/websites/runtimes/go',
requiresAuth: false,
},
},
-1
View File
@@ -34,7 +34,6 @@ export interface GlobalState {
defaultNetwork: string;
isProductPro: boolean;
isTrial: boolean;
productProExpires: number;
errStatus: string;
-1
View File
@@ -38,7 +38,6 @@ const GlobalStore = defineStore({
defaultNetwork: 'all',
isProductPro: false,
isTrial: false,
productProExpires: 0,
errStatus: '',
+1 -1
View File
@@ -22,4 +22,4 @@ body,
.el-switch--small,.is-checked .el-switch__core::after {
margin-left: 12px !important;
}
}
-2
View File
@@ -61,7 +61,6 @@ 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();
@@ -82,7 +81,6 @@ 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;
+7 -2
View File
@@ -224,11 +224,16 @@ const search = async (req: App.AppReq) => {
const openInstall = (app: App.App) => {
switch (app.type) {
case 'php':
router.push({ path: '/websites/runtimes/php' });
break;
case 'node':
router.push({ path: '/websites/runtimes/node' });
break;
case 'java':
router.push({ path: '/websites/runtimes/java' });
break;
case 'go':
case 'python':
router.push({ path: '/websites/runtimes/' + app.type });
router.push({ path: '/websites/runtimes/go' });
break;
default:
const params = {
@@ -142,11 +142,16 @@ const toLink = (link: string) => {
const openInstall = () => {
switch (app.value.type) {
case 'php':
router.push({ path: '/websites/runtimes/php' });
break;
case 'node':
router.push({ path: '/websites/runtimes/node' });
break;
case 'java':
router.push({ path: '/websites/runtimes/java' });
break;
case 'go':
case 'python':
router.push({ path: '/websites/runtimes/' + app.value.type });
router.push({ path: '/websites/runtimes/go' });
break;
default:
const params = {
@@ -4,12 +4,14 @@
:title="$t('app.composeDiff')"
:destroy-on-close="true"
:close-on-click-modal="false"
width="90%"
width="60%"
>
<div>
<el-text type="warning">{{ $t('app.diffHelper') }}</el-text>
<div ref="container" class="compose-diff"></div>
</div>
<el-row :gutter="10">
<el-col :span="22" :offset="1">
<el-text type="warning">{{ $t('app.diffHelper') }}</el-text>
<div ref="container" class="compose-diff"></div>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
@@ -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 - 400px)"
style="width: 100%; height: calc(100vh - 376px)"
:lineWrapping="true"
:matchBrackets="true"
theme="cobalt"
@@ -94,18 +94,6 @@
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>
@@ -165,7 +153,6 @@ const form = reactive({
template: null as number,
env: [],
envStr: '',
envFileContent: `env_file:\n - 1panel.env`,
});
const rules = reactive({
name: [Rules.requiredInput, Rules.imageName],
@@ -7,7 +7,7 @@
>
<el-form ref="deleteForm" v-loading="loading">
<el-form-item>
<el-checkbox v-model="deleteFile" :label="$t('container.deleteFile')" />
<el-checkbox v-model="deleteFile" :label="$t('container.allDelete')" />
<span class="input-help whitespace-break-spaces">
{{ $t('container.deleteComposeHelper') }}
</span>
@@ -69,7 +69,7 @@ const submit = async () => {
let params = {
name: composeName.value,
path: composePath.value,
operation: 'delete',
operation: 'down',
withFile: deleteFile.value,
};
await composeOperator(params)
@@ -143,6 +143,7 @@ const dialogContainerLogRef = ref();
const opRef = ref();
const emit = defineEmits<{ (e: 'back'): void }>();
interface DialogProps {
createdBy: string;
name: string;
@@ -259,18 +260,15 @@ const onOperate = async (op: string) => {
};
const onComposeOperate = async (operation: string) => {
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 () => {
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 params = {
name: composeName.value,
path: composePath.value,
@@ -282,7 +280,11 @@ const onComposeOperate = async (operation: string) => {
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
search();
if (operation === 'down') {
emit('back');
} else {
search();
}
})
.catch(() => {
loading.value = false;
@@ -19,7 +19,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 - ${loadHeight()})` }"
style="width: 100%; height: calc(100vh - 175px)"
:lineWrapping="true"
:matchBrackets="true"
theme="cobalt"
@@ -28,34 +28,15 @@
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-item :label="$t('container.env')" prop="environmentStr">
<el-input
type="textarea"
:placeholder="$t('container.tagHelper')"
:rows="3"
v-model="environmentStr"
/>
</el-form-item>
<span class="input-help">{{ $t('container.editComposeHelper') }}</span>
</el-form>
</el-col>
</el-row>
@@ -83,7 +64,6 @@ 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];
@@ -91,9 +71,6 @@ 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 = {
@@ -101,7 +78,6 @@ const onSubmitEdit = async () => {
path: path.value,
content: content.value,
env: environmentStr.value,
createdBy: createdBy.value,
};
if (environmentStr.value != undefined) {
param.env = environmentStr.value.split('\n');
@@ -112,7 +88,6 @@ const onSubmitEdit = async () => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
composeVisible.value = false;
emit('search');
})
.catch(() => {
loading.value = false;
@@ -123,22 +98,14 @@ 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');
environmentStr.value = '';
};
const handleClose = () => {
composeVisible.value = false;
+30 -16
View File
@@ -1,7 +1,7 @@
<template>
<div v-loading="loading">
<div v-show="isOnDetail">
<ComposeDetail ref="composeDetailRef" />
<ComposeDetail @back="backList" ref="composeDetailRef" />
</div>
<el-card v-if="dockerStatus != 'Running'" class="mask-prompt">
<span>{{ $t('container.serviceUnavailable') }}</span>
@@ -10,6 +10,20 @@
</el-card>
<LayoutContent v-if="!isOnDetail" :title="$t('container.compose')" :class="{ mask: dockerStatus != 'Running' }">
<template #prompt>
<el-alert type="info" :closable="false">
<template #title>
<span class="flx-align-center">
<span>{{ $t('container.composeHelper', [baseDir]) }}</span>
<el-button type="primary" link @click="toFolder">
<el-icon>
<FolderOpened />
</el-icon>
</el-button>
</span>
</template>
</el-alert>
</template>
<template #toolbar>
<div class="flex justify-between gap-2 flex-wrap sm:flex-row">
<div class="flex flex-wrap gap-3">
@@ -34,7 +48,6 @@
:label="$t('commons.table.name')"
width="170"
prop="name"
sortable
fix
show-overflow-tooltip
>
@@ -51,15 +64,6 @@
<span v-if="row.createdBy === '1Panel'">1Panel</span>
</template>
</el-table-column>
<el-table-column :label="$t('container.composeDirectory')" min-width="80" fix>
<template #default="{ row }">
<el-button type="primary" link @click="toComposeFolder(row)">
<el-icon>
<FolderOpened />
</el-icon>
</el-button>
</template>
</el-table-column>
<el-table-column :label="$t('container.containerStatus')" min-width="80" fix>
<template #default="scope">
<div>
@@ -79,7 +83,7 @@
</template>
</LayoutContent>
<EditDialog @search="search" ref="dialogEditRef" />
<EditDialog ref="dialogEditRef" />
<CreateDialog @search="search" ref="dialogRef" />
<DeleteDialog @search="search" ref="dialogDelRef" />
</div>
@@ -94,6 +98,7 @@ import ComposeDetail from '@/views/container/compose/detail/index.vue';
import { loadContainerLog, loadDockerStatus, searchCompose } from '@/api/modules/container';
import i18n from '@/lang';
import { Container } from '@/api/interface/container';
import { loadBaseDir } from '@/api/modules/setting';
import router from '@/routers';
const data = ref();
@@ -101,6 +106,7 @@ const selects = ref<any>([]);
const loading = ref(false);
const isOnDetail = ref(false);
const baseDir = ref();
const paginationConfig = reactive({
cacheSizeKey: 'container-compose-page-size',
@@ -130,8 +136,13 @@ const goSetting = async () => {
router.push({ name: 'ContainerSetting' });
};
const toComposeFolder = async (row: Container.ComposeInfo) => {
router.push({ path: '/hosts/files', query: { path: row.workdir } });
const toFolder = async () => {
router.push({ path: '/hosts/files', query: { path: baseDir.value + '/docker/compose' } });
};
const loadPath = async () => {
const pathRes = await loadBaseDir();
baseDir.value = pathRes.data;
};
const search = async () => {
@@ -174,6 +185,10 @@ const getContainerStatus = (containers) => {
return i18n.global.t('container.running') + ` (${runningCount}/${totalCount})`;
}
};
const backList = async () => {
isOnDetail.value = false;
search();
};
const dialogRef = ref();
const onOpenDialog = async () => {
@@ -196,8 +211,6 @@ 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);
};
@@ -223,6 +236,7 @@ const buttons = [
},
];
onMounted(() => {
loadPath();
loadStatus();
});
</script>
@@ -369,7 +369,7 @@ const dialogUpgradeRef = ref();
const dialogCommitRef = ref();
const dialogPortJumpRef = ref();
const opRef = ref();
const includeAppStore = ref();
const includeAppStore = ref(true);
const columns = ref([]);
const dockerStatus = ref('Running');
@@ -423,7 +423,6 @@ 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;
@@ -768,8 +767,6 @@ const buttons = [
];
onMounted(() => {
let includeItem = localStorage.getItem('includeAppStore');
includeAppStore.value = !includeItem || includeItem === 'true';
loadStatus();
});
</script>
@@ -122,16 +122,17 @@ const timeOptions = ref([
]);
function toggleFullscreen() {
globalStore.isFullScreen = !globalStore.isFullScreen;
if (screenfull.isEnabled) {
screenfull.toggle();
}
}
const loadTooltip = () => {
return i18n.global.t('commons.button.' + (globalStore.isFullScreen ? 'quitFullscreen' : 'fullscreen'));
return i18n.global.t('commons.button.' + (screenfull.isFullscreen ? 'quitFullscreen' : 'fullscreen'));
};
const handleClose = async () => {
terminalSocket.value?.send('close conn');
logVisible.value = false;
globalStore.isFullScreen = false;
};
watch(logVisible, (val) => {
if (screenfull.isEnabled && !val && !mobile.value) screenfull.exit();
@@ -161,8 +162,10 @@ const searchLogs = async () => {
};
const onDownload = async () => {
logSearch.tail = 0;
let msg = i18n.global.t('container.downLogHelper1', [logSearch.container]);
let msg =
logSearch.tail === 0
? i18n.global.t('container.downLogHelper1', [logSearch.container])
: i18n.global.t('container.downLogHelper2', [logSearch.container, logSearch.tail]);
ElMessageBox.confirm(msg, i18n.global.t('file.download'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
@@ -25,6 +25,12 @@
>
<el-row type="flex" justify="center">
<el-col :span="22">
<el-alert
v-if="dialogData.title === 'edit' && isFromApp(dialogData.rowData!)"
:title="$t('container.containerFromAppHelper')"
:closable="false"
type="error"
/>
<el-form-item class="mt-5" :label="$t('commons.table.name')" prop="name">
<el-input
:disabled="isFromApp(dialogData.rowData!)"
@@ -290,39 +296,13 @@
</span>
</template>
</el-drawer>
<el-dialog v-model="dialogVisible" width="30%" :title="$t('commons.button.edit')">
<div v-if="dialogData.title === 'edit' && isFromApp(dialogData.rowData!)" class="leading-6">
<div>
<span>{{ $t('container.updateHelper1') }}</span>
</div>
<br />
<div>
<span>{{ $t('container.updateHelper2') }}</span>
</div>
<div>
<span>{{ $t('container.updateHelper3') }}</span>
</div>
<br />
</div>
<div>
<span>{{ $t('container.updateHelper4') }}</span>
</div>
<template #footer>
<el-button :disabled="loading" @click="dialogVisible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="submit()">
{{ $t('commons.button.confirm') }}
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { Rules, checkFloatNumberRange, checkNumberRange } from '@/global/form-rules';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { ElForm, ElMessageBox } from 'element-plus';
import DrawerHeader from '@/components/drawer-header/index.vue';
import {
listImage,
@@ -347,7 +327,6 @@ interface DialogProps {
const title = ref<string>('');
const drawerVisible = ref(false);
const dialogVisible = ref(false);
const dialogData = ref<DialogProps>({
title: '',
@@ -407,7 +386,6 @@ const limits = ref<Container.ResourceLimit>({
const handleClose = () => {
emit('search');
drawerVisible.value = false;
dialogVisible.value = false;
};
const rules = reactive({
@@ -470,7 +448,6 @@ const loadNetworkOptions = async () => {
const res = await listNetwork();
networks.value = res.data;
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (dialogData.value.rowData!.volumes.length !== 0) {
for (const item of dialogData.value.rowData!.volumes) {
@@ -483,87 +460,93 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
dialogVisible.value = true;
});
};
const submit = async () => {
dialogVisible.value = false;
if (dialogData.value.rowData?.envStr) {
dialogData.value.rowData.env = dialogData.value.rowData!.envStr.split('\n');
}
if (dialogData.value.rowData?.labelsStr) {
dialogData.value.rowData!.labels = dialogData.value.rowData!.labelsStr.split('\n');
}
dialogData.value.rowData!.cmd = [];
if (dialogData.value.rowData?.cmdStr) {
if (dialogData.value.rowData?.cmdStr.indexOf(`'`) !== -1) {
let itemCmd = dialogData.value.rowData!.cmdStr.split(`'`);
for (const cmd of itemCmd) {
if (cmd && cmd !== ' ') {
if (dialogData.value.rowData?.envStr) {
dialogData.value.rowData.env = dialogData.value.rowData!.envStr.split('\n');
}
if (dialogData.value.rowData?.labelsStr) {
dialogData.value.rowData!.labels = dialogData.value.rowData!.labelsStr.split('\n');
}
dialogData.value.rowData!.cmd = [];
if (dialogData.value.rowData?.cmdStr) {
if (dialogData.value.rowData?.cmdStr.indexOf(`'`) !== -1) {
let itemCmd = dialogData.value.rowData!.cmdStr.split(`'`);
for (const cmd of itemCmd) {
if (cmd && cmd !== ' ') {
dialogData.value.rowData!.cmd.push(cmd);
}
}
} else {
let itemCmd = dialogData.value.rowData!.cmdStr.split(` `);
for (const cmd of itemCmd) {
dialogData.value.rowData!.cmd.push(cmd);
}
}
} else {
let itemCmd = dialogData.value.rowData!.cmdStr.split(` `);
for (const cmd of itemCmd) {
dialogData.value.rowData!.cmd.push(cmd);
}
}
}
dialogData.value.rowData!.entrypoint = [];
if (dialogData.value.rowData?.entrypointStr) {
if (dialogData.value.rowData?.entrypointStr.indexOf(`'`) !== -1) {
let itemEntrypoint = dialogData.value.rowData!.entrypointStr.split(`'`);
for (const entry of itemEntrypoint) {
if (entry && entry !== ' ') {
dialogData.value.rowData!.entrypoint = [];
if (dialogData.value.rowData?.entrypointStr) {
if (dialogData.value.rowData?.entrypointStr.indexOf(`'`) !== -1) {
let itemEntrypoint = dialogData.value.rowData!.entrypointStr.split(`'`);
for (const entry of itemEntrypoint) {
if (entry && entry !== ' ') {
dialogData.value.rowData!.entrypoint.push(entry);
}
}
} else {
let itemEntrypoint = dialogData.value.rowData!.entrypointStr.split(` `);
for (const entry of itemEntrypoint) {
dialogData.value.rowData!.entrypoint.push(entry);
}
}
}
if (dialogData.value.rowData!.publishAllPorts) {
dialogData.value.rowData!.exposedPorts = [];
} else {
let itemEntrypoint = dialogData.value.rowData!.entrypointStr.split(` `);
for (const entry of itemEntrypoint) {
dialogData.value.rowData!.entrypoint.push(entry);
if (!checkPortValid()) {
return;
}
}
}
if (dialogData.value.rowData!.publishAllPorts) {
dialogData.value.rowData!.exposedPorts = [];
} else {
if (!checkPortValid()) {
return;
}
}
dialogData.value.rowData!.memory = Number(dialogData.value.rowData!.memory);
dialogData.value.rowData!.nanoCPUs = Number(dialogData.value.rowData!.nanoCPUs);
dialogData.value.rowData!.memory = Number(dialogData.value.rowData!.memory);
dialogData.value.rowData!.nanoCPUs = Number(dialogData.value.rowData!.nanoCPUs);
loading.value = true;
if (dialogData.value.title === 'create') {
await createContainer(dialogData.value.rowData!)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
dialogVisible.value = false;
})
.catch(() => {
loading.value = false;
});
return;
}
await updateContainer(dialogData.value.rowData!)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
dialogVisible.value = false;
})
.catch(() => {
updateContainerID();
loading.value = false;
});
loading.value = true;
if (dialogData.value.title === 'create') {
await createContainer(dialogData.value.rowData!)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
loading.value = false;
});
} else {
ElMessageBox.confirm(
i18n.global.t('container.updateContainerHelper'),
i18n.global.t('commons.button.edit'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
},
)
.then(async () => {
await updateContainer(dialogData.value.rowData!)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
emit('search');
drawerVisible.value = false;
})
.catch(() => {
updateContainerID();
loading.value = false;
});
})
.catch(() => {
loading.value = false;
});
}
});
};
const updateContainerID = async () => {
+4 -13
View File
@@ -198,29 +198,20 @@ const search = async () => {
const batchDelete = async (row: Container.NetworkInfo | null) => {
let names: Array<string> = [];
let hasPanelNetwork;
if (row === null) {
selects.value.forEach((item: Container.NetworkInfo) => {
if (item.name === '1panel-network') {
hasPanelNetwork = true;
}
names.push(item.name);
});
} else {
if (row.name === '1panel-network') {
hasPanelNetwork = true;
}
names.push(row.name);
}
opRef.value.acceptParams({
title: i18n.global.t('commons.button.delete'),
names: names,
msg: hasPanelNetwork
? i18n.global.t('container.networkHelper')
: i18n.global.t('commons.msg.operatorHelper', [
i18n.global.t('container.network'),
i18n.global.t('commons.button.delete'),
]),
msg: i18n.global.t('commons.msg.operatorHelper', [
i18n.global.t('container.network'),
i18n.global.t('commons.button.delete'),
]),
api: deleteNetwork,
params: { names: names },
});
@@ -35,7 +35,6 @@
:label="$t('commons.table.name')"
min-width="100"
prop="name"
sortable
fix
show-overflow-tooltip
>
+1 -5
View File
@@ -271,11 +271,7 @@
>
<el-input v-model="dialogData.rowData!.sourceDir">
<template #prepend>
<FileList
@choose="loadDir"
:dir="true"
:path="dialogData.rowData!.sourceDir"
></FileList>
<FileList @choose="loadDir" :dir="true"></FileList>
</template>
</el-input>
</el-form-item>
@@ -21,7 +21,6 @@
v-model:mask-show="maskShow"
@setting="onSetting"
@is-exist="checkExist"
ref="appStatusRef"
></AppStatus>
</template>
@@ -328,8 +327,6 @@ const dashboardName = ref();
const dashboardKey = ref();
const dashboardVisible = ref(false);
const appStatusRef = ref();
const dialogPortJumpRef = ref();
const data = ref();
@@ -387,9 +384,6 @@ const onSetting = async () => {
};
const changeDatabase = async () => {
if (currentDB.value.from === 'local') {
appStatusRef.value.onCheck();
}
for (const item of dbOptionsLocal.value) {
if (item.database == currentDBName.value) {
currentDB.value = item;
@@ -19,7 +19,6 @@
v-model:mask-show="maskShow"
@setting="onSetting"
@is-exist="checkExist"
ref="appStatusRef"
></AppStatus>
</template>
@@ -306,7 +305,6 @@ const postgresqlContainer = ref();
const postgresqlStatus = ref();
const postgresqlVersion = ref();
const appStatusRef = ref();
const dialogRef = ref();
const onOpenDialog = async () => {
let params = {
@@ -350,9 +348,6 @@ const onSetting = async () => {
};
const changeDatabase = async () => {
if (currentDB.value.from === 'local') {
appStatusRef.value.onCheck();
}
for (const item of dbOptionsLocal.value) {
if (item.database == currentDBName.value) {
currentDB.value = item;
@@ -128,7 +128,7 @@ const onOpenDialog = async (
rowData: Partial<Database.DatabaseInfo> = {
name: '',
type: 'postgresql',
version: '17.x',
version: '16.x',
address: '',
port: 5432,
username: '',
@@ -27,7 +27,6 @@
</el-form-item>
<el-form-item :label="$t('database.version')" prop="version">
<el-radio-group v-model="dialogData.rowData!.version" @change="isOK = false">
<el-radio label="17.x" value="17.x" />
<el-radio label="16.x" value="16.x" />
<el-radio label="15.x" value="15.x" />
<el-radio label="14.x" value="14.x" />
@@ -19,7 +19,6 @@
@before="onBefore"
@after="onAfter"
@setting="onSetting"
ref="appStatusRef"
></AppStatus>
</template>
<template #search v-if="!isOnSetting && currentDB">
@@ -165,7 +164,6 @@ const redisIsExist = ref(false);
const redisStatus = ref();
const terminalShow = ref(false);
const appStatusRef = ref();
const commandVisible = ref(false);
const redisCliExist = ref();
@@ -222,9 +220,6 @@ const goRouter = async (target: string) => {
};
const changeDatabase = async () => {
if (currentDB.value.from === 'local') {
appStatusRef.value.onCheck();
}
for (const item of dbOptionsLocal.value) {
if (item.database == currentDBName.value) {
currentDB.value = item;
+96 -69
View File
@@ -7,7 +7,17 @@
path: '/',
},
]"
/>
>
<template #route-button>
<div class="router-button">
<template v-if="!isProductPro">
<el-button link type="primary" @click="toUpload">
{{ $t('license.levelUpPro') }}
</el-button>
</template>
</div>
</template>
</RouterButton>
<el-alert
v-if="!isSafety && globalStore.showEntranceWarn"
@@ -32,39 +42,37 @@
<el-row :gutter="20" style="margin-top: 20px">
<el-col :xs="24" :sm="24" :md="16" :lg="16" :xl="16">
<CardWithHeader :header="$t('home.overview')" height="166px">
<CardWithHeader :header="$t('home.overview')">
<template #body>
<div class="h-overview">
<el-row>
<el-col :span="6">
<span>{{ $t('menu.website') }}</span>
<div class="count">
<span @click="goRouter('/websites')">{{ baseInfo?.websiteNumber }}</span>
</div>
</el-col>
<el-col :span="6">
<span>{{ $t('menu.database') }} - {{ $t('database.all') }}</span>
<div class="count">
<span @click="goRouter('/databases')">{{ baseInfo?.databaseNumber }}</span>
</div>
</el-col>
<el-col :span="6">
<span>{{ $t('menu.cronjob') }}</span>
<div class="count">
<span @click="goRouter('/cronjobs')">
{{ baseInfo?.cronjobNumber }}
</span>
</div>
</el-col>
<el-col :span="6">
<span>{{ $t('home.appInstalled') }}</span>
<div class="count">
<span @click="goRouter('/apps/installed')">
{{ baseInfo?.appInstalledNumber }}
</span>
</div>
</el-col>
</el-row>
<div>
<span>{{ $t('menu.website') }}</span>
<div class="count">
<span @click="goRouter('/websites')">{{ baseInfo?.websiteNumber }}</span>
</div>
</div>
<div>
<span>{{ $t('menu.database') }} - {{ $t('database.all') }}</span>
<div class="count">
<span @click="goRouter('/databases')">{{ baseInfo?.databaseNumber }}</span>
</div>
</div>
<div>
<span>{{ $t('menu.cronjob') }}</span>
<div class="count">
<span @click="goRouter('/cronjobs')">
{{ baseInfo?.cronjobNumber }}
</span>
</div>
</div>
<div>
<span>{{ $t('home.appInstalled') }}</span>
<div class="count">
<span @click="goRouter('/apps/installed')">
{{ baseInfo?.appInstalledNumber }}
</span>
</div>
</div>
</div>
</template>
</CardWithHeader>
@@ -75,42 +83,44 @@
</CardWithHeader>
<CardWithHeader :header="$t('menu.monitor')" style="margin-top: 20px; margin-bottom: 20px">
<template #header-r>
<el-radio-group
style="float: right; margin-left: 5px"
v-model="chartOption"
@change="changeOption"
<div
class="float-right flex flex-col items-end flex-wrap sm:items-center gap-2 sm:flex-row sm:gap-4"
>
<el-radio-button value="network">{{ $t('home.network') }}</el-radio-button>
<el-radio-button value="io">{{ $t('home.io') }}</el-radio-button>
</el-radio-group>
<el-select
v-if="chartOption === 'network'"
@change="onLoadBaseInfo(false, 'network')"
v-model="searchInfo.netOption"
class="p-w-200 float-right"
>
<template #prefix>{{ $t('home.networkCard') }}</template>
<el-option
v-for="item in netOptions"
:key="item"
:label="item == 'all' ? $t('commons.table.all') : item"
:value="item"
/>
</el-select>
<el-select
v-if="chartOption === 'io'"
v-model="searchInfo.ioOption"
@change="onLoadBaseInfo(false, 'io')"
class="p-w-200 float-right"
>
<template #prefix>{{ $t('home.disk') }}</template>
<el-option
v-for="item in ioOptions"
:key="item"
:label="item == 'all' ? $t('commons.table.all') : item"
:value="item"
/>
</el-select>
<el-radio-group v-model="chartOption" @change="changeOption">
<el-radio-button value="network">{{ $t('home.network') }}</el-radio-button>
<el-radio-button value="io">{{ $t('home.io') }}</el-radio-button>
</el-radio-group>
<div>
<el-select
v-if="chartOption === 'network'"
@change="onLoadBaseInfo(false, 'network')"
v-model="searchInfo.netOption"
class="p-w-200 float-right"
>
<template #prefix>{{ $t('home.networkCard') }}</template>
<el-option
v-for="item in netOptions"
:key="item"
:label="item == 'all' ? $t('commons.table.all') : item"
:value="item"
/>
</el-select>
<el-select
v-if="chartOption === 'io'"
v-model="searchInfo.ioOption"
@change="onLoadBaseInfo(false, 'io')"
class="p-w-200 float-right"
>
<template #prefix>{{ $t('home.disk') }}</template>
<el-option
v-for="item in ioOptions"
:key="item"
:label="item == 'all' ? $t('commons.table.all') : item"
:value="item"
/>
</el-select>
</div>
</div>
</template>
<template #body>
<div style="position: relative; margin-top: 20px">
@@ -249,6 +259,8 @@
</CardWithHeader>
</el-col>
</el-row>
<LicenseImport ref="licenseRef" />
</div>
</template>
@@ -257,6 +269,7 @@ import { onMounted, onBeforeUnmount, ref, reactive } from 'vue';
import Status from '@/views/home/status/index.vue';
import App from '@/views/home/app/index.vue';
import VCharts from '@/components/v-charts/index.vue';
import LicenseImport from '@/components/license-import/index.vue';
import CardWithHeader from '@/components/card-with-header/index.vue';
import i18n from '@/lang';
import { Dashboard } from '@/api/interface/dashboard';
@@ -290,6 +303,9 @@ const timeNetDatas = ref<Array<string>>([]);
const ioOptions = ref();
const netOptions = ref();
const licenseRef = ref();
const isProductPro = ref();
const searchInfo = reactive({
ioOption: 'all',
netOption: 'all',
@@ -643,7 +659,12 @@ const onBlur = () => {
isActive.value = false;
};
const toUpload = () => {
licenseRef.value.acceptParams();
};
onMounted(() => {
isProductPro.value = globalStore.isProductPro;
window.addEventListener('focus', onFocus);
window.addEventListener('blur', onBlur);
loadSafeStatus();
@@ -664,14 +685,20 @@ onBeforeUnmount(() => {
<style lang="scss">
.h-overview {
text-align: center;
display: flex;
align-items: center;
justify-content: space-around;
flex-direction: row;
span:first-child {
font-size: 14px;
color: var(--el-text-color-regular);
}
@media only screen and (max-width: 1300px) {
display: grid;
gap: 1rem;
grid-template-columns: repeat(2, minmax(0, 1fr));
span:first-child {
font-size: 12px;
font-size: 16px;
color: var(--el-text-color-regular);
}
}
@@ -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 || props.files[0].gid;
addForm.user = props.files[0].user || props.files[0].uid;
addForm.group = props.files[0].group;
addForm.user = props.files[0].user;
addForm.sub = true;
mode.value = String(props.files[0].mode);
@@ -19,7 +19,6 @@
v-if="!mobile"
class="!border-none !bg-transparent !text-base !font-semibold !py-2 !px-1"
icon="FullScreen"
style="margin-right: 10px"
></el-button>
</el-tooltip>
<el-button
@@ -478,10 +478,11 @@ import {
SearchFavorite,
} from '@/api/modules/files';
import { computeSize, copyText, dateFormat, downloadFile, getFileType, getIcon, getRandomStr } from '@/utils/util';
import { StarFilled, Star, Top, Right, Close } from '@element-plus/icons-vue';
import { File } from '@/api/interface/file';
import { Mimetypes, Languages } from '@/global/mimetype';
import { useRouter } from 'vue-router';
import { Back, Refresh, StarFilled, Star, Top, Right, Close } from '@element-plus/icons-vue';
import { Back, Refresh } from '@element-plus/icons-vue';
import { MsgWarning } from '@/utils/message';
import { useSearchable } from './hooks/searchable';
import { ResultData } from '@/api/interface';
@@ -611,7 +612,7 @@ const handleFilePatch = (command: string) => {
openBatchRole(selects.value);
break;
case 'batchDelFiles':
batchDelFiles();
openDetail(selects.value);
break;
default:
break;
@@ -126,8 +126,6 @@
<span class="agree" v-html="$t('commons.login.licenseHelper')"></span>
</template>
</el-checkbox>
</el-form-item>
<div class="agree-helper">
<span
v-if="!loginForm.agreeLicense && !_isMobile()"
class="input-error"
@@ -135,7 +133,7 @@
>
{{ $t('commons.login.errorAgree') }}
</span>
</div>
</el-form-item>
</el-form>
<div class="demo">
<span v-if="isDemo">
@@ -516,11 +514,5 @@ onMounted(() => {
:deep(.el-checkbox__input.is-checked .el-checkbox__inner::after) {
border-color: #fff !important;
}
.agree-helper {
min-height: 20px;
margin-top: -20px;
margin-left: 20px;
}
}
</style>
+4
View File
@@ -27,6 +27,10 @@ const buttons = [
label: i18n.global.t('setting.snapshot'),
path: '/settings/snapshot',
},
{
label: i18n.global.t('setting.license'),
path: '/settings/license',
},
{
label: i18n.global.t('setting.about'),
path: '/settings/about',
@@ -0,0 +1,251 @@
<template>
<div>
<LayoutContent v-loading="loading" :title="$t('setting.license')" :divider="true">
<template #main>
<el-row :gutter="20" class="mt-5; mb-10">
<el-col :xs="24" :sm="24" :md="15" :lg="15" :xl="15">
<div class="descriptions" v-if="hasLicense">
<el-descriptions :column="1" direction="horizontal" size="large" border>
<el-descriptions-item :label="$t('license.authorizationId')">
{{ license.licenseName || '-' }}
<el-button
type="primary"
class="ml-3"
plain
@click="onSync"
size="small"
v-if="showSync()"
>
{{ $t('commons.button.sync') }}
</el-button>
<el-button type="primary" class="ml-3" plain @click="onUnBind()" size="small">
{{ $t('license.unbind') }}
</el-button>
</el-descriptions-item>
<el-descriptions-item :label="$t('license.authorizedUser')">
{{ license.assigneeName || '-' }}
</el-descriptions-item>
<el-descriptions-item :label="$t('license.productName')">
{{ license.productName || '-' }}
</el-descriptions-item>
<el-descriptions-item :label="$t('license.trialInfo')">
{{ license.trial ? $t('license.trial') : $t('license.office') }}
</el-descriptions-item>
<el-descriptions-item :label="$t('license.expiresAt')">
{{ license.expiresAt || '-' }}
</el-descriptions-item>
<el-descriptions-item :label="$t('license.productStatus')">
<div v-if="license.status">
<el-tooltip
v-if="license.status.indexOf('Lost') !== -1"
:content="$t('license.lostHelper')"
>
<el-tag type="info">
{{ $t('license.' + license.status) }}
</el-tag>
</el-tooltip>
<el-tag v-else>{{ $t('license.' + license.status) }}</el-tag>
</div>
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item class="descriptions" :label="$t('commons.table.message')">
{{ license.message }}
</el-descriptions-item>
</el-descriptions>
</div>
<CardWithHeader :header="$t('home.overview')" height="160px" v-if="!hasLicense">
<template #body>
<div class="h-app-card">
<el-row>
<el-col :span="6">
<span>{{ $t('setting.license') }}</span>
</el-col>
<el-col :span="6">
<span>{{ $t('license.community2') }}</span>
</el-col>
</el-row>
</div>
</template>
</CardWithHeader>
</el-col>
<el-col :xs="24" :sm="24" :md="9" :lg="9" :xl="9">
<CardWithHeader :header="$t('license.quickUpdate')" height="160px">
<template #body>
<div class="h-app-card">
<el-row>
<el-col :span="15">
<div class="h-app-content">{{ $t('license.importLicense') }}:</div>
</el-col>
<el-col :span="5">
<el-button type="primary" plain round size="small" @click="toUpload">
{{ $t('license.import') }}
</el-button>
</el-col>
</el-row>
</div>
<div class="h-app-card">
<el-row>
<el-col :span="15">
<div class="h-app-content">{{ $t('license.technicalAdvice') }}:</div>
</el-col>
<el-col :span="5">
<el-button type="primary" plain round size="small" @click="toHalo()">
{{ $t('license.advice') }}
</el-button>
</el-col>
</el-row>
</div>
</template>
</CardWithHeader>
</el-col>
</el-row>
</template>
</LayoutContent>
<LicenseImport ref="licenseRef" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { getLicense, syncLicense, unbindLicense } from '@/api/modules/setting';
import CardWithHeader from '@/components/card-with-header/index.vue';
import LicenseImport from '@/components/license-import/index.vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { GlobalStore } from '@/store';
const loading = ref();
const licenseRef = ref();
const globalStore = GlobalStore();
const hasLicense = ref();
const license = reactive({
licenseName: '',
trial: true,
expiresAt: '',
assigneeName: '',
productName: '',
status: '',
message: '',
});
const toHalo = () => {
window.open('https://www.lxware.cn/1panel' + '', '_blank', 'noopener,noreferrer');
};
const onSync = async () => {
loading.value = true;
await syncLicense()
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
window.location.reload();
})
.catch(() => {
loading.value = false;
});
};
const onUnBind = async () => {
ElMessageBox.confirm(i18n.global.t('license.unbindHelper'), i18n.global.t('license.unbind'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
loading.value = true;
await unbindLicense()
.then(() => {
loading.value = false;
globalStore.isProductPro = false;
globalStore.themeConfig.isGold = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
window.location.reload();
})
.catch(() => {
loading.value = false;
});
});
};
const timestampToDate = (timestamp: number) => {
const date = new Date(timestamp * 1000);
const y = date.getFullYear();
let m: string | number = date.getMonth() + 1;
m = m < 10 ? `0${String(m)}` : m;
let d: string | number = date.getDate();
d = d < 10 ? `0${String(d)}` : d;
let h: string | number = date.getHours();
h = h < 10 ? `0${String(h)}` : h;
let minute: string | number = date.getMinutes();
minute = minute < 10 ? `0${String(minute)}` : minute;
let second: string | number = date.getSeconds();
second = second < 10 ? `0${String(second)}` : second;
return `${y}-${m}-${d} ${h}:${minute}:${second}`;
};
const search = async () => {
loading.value = true;
await getLicense()
.then((res) => {
loading.value = false;
license.status = res.data.status;
globalStore.isProductPro =
res.data.status === 'Enable' || res.data.status === 'Lost01' || res.data.status === 'Lost02';
if (res.data.status === '') {
hasLicense.value = false;
return;
}
hasLicense.value = true;
if (globalStore.isProductPro) {
globalStore.productProExpires = Number(res.data.productPro);
}
license.licenseName = res.data.licenseName;
license.message = res.data.message;
license.assigneeName = res.data.assigneeName;
license.trial = res.data.trial;
if (res.data.productPro) {
license.productName = 'product-1panel-pro';
license.expiresAt =
res.data.productPro === '0'
? i18n.global.t('license.indefinitePeriod')
: timestampToDate(Number(res.data.productPro));
}
})
.catch(() => {
loading.value = false;
});
};
const showSync = () => {
return license.status.indexOf('Lost') !== -1 || license.status === 'Disable';
};
const toUpload = () => {
licenseRef.value.acceptParams();
};
onMounted(() => {
search();
});
</script>
<style scoped lang="scss">
.h-app-card {
padding: 10px 15px;
margin-right: 10px;
line-height: 18px;
&:hover {
background-color: rgba(0, 94, 235, 0.03);
}
}
:deep(.el-descriptions__content) {
max-width: 300px;
}
.descriptions {
word-break: break-all;
word-wrap: break-word;
}
</style>
@@ -126,6 +126,21 @@
</el-input>
</el-form-item>
<el-form-item :label="$t('setting.developerMode')" prop="developerMode">
<el-radio-group
@change="onSave('DeveloperMode', form.developerMode)"
v-model="form.developerMode"
>
<el-radio-button value="enable">
<span>{{ $t('commons.button.enable') }}</span>
</el-radio-button>
<el-radio-button value="disable">
<span>{{ $t('commons.button.disable') }}</span>
</el-radio-button>
</el-radio-group>
<span class="input-help">{{ $t('setting.developerModeHelper') }}</span>
</el-form-item>
<el-form-item :label="$t('setting.advancedMenuHide')">
<el-input disabled v-model="form.proHideMenus">
<template #append>
@@ -251,6 +266,7 @@ const search = async () => {
form.defaultNetworkVal = res.data.defaultNetwork === 'all' ? i18n.t('commons.table.all') : res.data.defaultNetwork;
form.proHideMenus = res.data.xpackHideMenu;
form.hideMenuList = res.data.xpackHideMenu;
form.developerMode = res.data.developerMode;
form.proxyUrl = res.data.proxyUrl;
form.proxyType = res.data.proxyType;
@@ -19,6 +19,7 @@
<ul style="margin-left: -20px">
<li v-if="isProductPro">{{ $t('setting.proxyHelper1') }}</li>
<li v-if="isProductPro">{{ $t('setting.proxyHelper2') }}</li>
<li>{{ $t('setting.proxyHelper3') }}</li>
</ul>
</template>
</el-alert>
+1 -5
View File
@@ -114,7 +114,7 @@
<div v-if="form.ssl === 'enable' && sslInfo">
<el-tag>{{ $t('setting.domainOrIP') }} {{ sslInfo.domain }}</el-tag>
<el-tag style="margin-left: 5px">
{{ $t('setting.timeOut') }} {{ dateFormat('', '', sslInfo.timeout) }}
{{ $t('setting.timeOut') }} {{ sslInfo.timeout }}
</el-tag>
<div>
<el-button link type="primary" @click="handleSSL">
@@ -194,7 +194,6 @@ import TimeoutSetting from '@/views/setting/safe/timeout/index.vue';
import EntranceSetting from '@/views/setting/safe/entrance/index.vue';
import DomainSetting from '@/views/setting/safe/domain/index.vue';
import AllowIPsSetting from '@/views/setting/safe/allowips/index.vue';
import { dateFormat } from '@/utils/util';
import { updateSetting, getSettingInfo, getSystemAvailable, updateSSL, loadSSLInfo } from '@/api/modules/setting';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
@@ -225,7 +224,6 @@ const form = reactive({
bindAddress: '',
ssl: 'disable',
sslType: 'self',
autoRestart: 'disable',
securityEntrance: '',
expirationDays: 0,
expirationTime: '',
@@ -250,7 +248,6 @@ const search = async () => {
if (form.ssl === 'enable') {
loadInfo();
}
form.autoRestart = res.data.autoRestart;
form.securityEntrance = res.data.securityEntrance;
form.expirationDays = Number(res.data.expirationDays);
form.expirationTime = res.data.expirationTime;
@@ -330,7 +327,6 @@ const handleSSL = async () => {
ssl: form.ssl,
sslType: form.sslType,
sslInfo: sslInfo.value,
autoRestart: form.autoRestart,
};
sslRef.value!.acceptParams(params);
return;
+10 -20
View File
@@ -11,6 +11,13 @@
<template #header>
<DrawerHeader :header="$t('setting.panelSSL')" :back="handleClose" />
</template>
<el-alert class="common-prompt" :closable="false" type="error">
<template #default>
<span>
<span>{{ $t('setting.panelSSLHelper') }}</span>
</span>
</template>
</el-alert>
<el-form ref="formRef" label-position="top" :model="form" :rules="rules" v-loading="loading">
<el-row type="flex" justify="center">
<el-col :span="22">
@@ -33,9 +40,7 @@
<el-form-item v-if="form.timeout">
<el-tag>{{ $t('setting.domainOrIP') }} {{ form.domain }}</el-tag>
<el-tag style="margin-left: 5px">
{{ $t('setting.timeOut') }} {{ dateFormat('', '', form.timeout) }}
</el-tag>
<el-tag style="margin-left: 5px">{{ $t('setting.timeOut') }} {{ form.timeout }}</el-tag>
<el-button
@click="onDownload"
style="margin-left: 5px"
@@ -112,14 +117,6 @@
</el-descriptions-item>
</el-descriptions>
</div>
<el-form-item v-if="form.sslType !== 'import'">
<el-checkbox true-value="enable" false-value="disable" v-model="form.autoRestart">
{{ $t('setting.sslAutoRestart') }}
</el-checkbox>
<span v-if="form.autoRestart === 'disable'" class="input-help">
{{ $t('setting.sslChangeHelper1') }}
</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
@@ -136,7 +133,7 @@
</template>
<script lang="ts" setup>
import { Website } from '@/api/interface/website';
import { dateFormatSimple, dateFormat, getProvider } from '@/utils/util';
import { dateFormatSimple, getProvider } from '@/utils/util';
import { ListSSL } from '@/api/modules/website';
import { reactive, ref } from 'vue';
import i18n from '@/lang';
@@ -162,7 +159,6 @@ const form = reactive({
key: '',
rootPath: '',
timeout: '',
autoRestart: 'disable',
});
const rules = reactive({
@@ -179,7 +175,6 @@ const itemSSL = ref();
interface DialogProps {
sslType: string;
sslInfo?: Setting.SSLInfo;
autoRestart: string;
}
const acceptParams = async (params: DialogProps): Promise<void> => {
if (params.sslType.indexOf('-') !== -1) {
@@ -202,7 +197,6 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
} else {
loadSSLs();
}
form.autoRestart = params.autoRestart;
drawerVisible.value = true;
};
const emit = defineEmits<{ (e: 'search'): void }>();
@@ -243,10 +237,7 @@ const onSaveSSL = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
let msg = !form.autoRestart
? i18n.global.t('setting.sslChangeHelper1') + '\n\n\n' + 'qwdqwdqwd'
: i18n.global.t('setting.sslChangeHelper2');
ElMessageBox.confirm(msg, i18n.global.t('setting.panelSSL'), {
ElMessageBox.confirm(i18n.global.t('setting.sslChangeHelper'), 'https', {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
@@ -262,7 +253,6 @@ const onSaveSSL = async (formEl: FormInstance | undefined) => {
sslID: form.sslID,
cert: form.cert,
key: form.key,
autoRestart: form.autoRestart,
};
let href = window.location.href;
param.domain = href.split('//')[1].split(':')[0];
+6 -40
View File
@@ -75,15 +75,10 @@
</el-table-column>
<el-table-column :label="$t('file.size')" prop="size" min-width="60" show-overflow-tooltip>
<template #default="{ row }">
<div v-if="row.hasLoad">
<span v-if="row.size">
{{ computeSize(row.size) }}
</span>
<span v-else>-</span>
</div>
<div v-if="!row.hasLoad">
<el-button link loading></el-button>
</div>
<span v-if="row.size">
{{ computeSize(row.size) }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.status')" min-width="80" prop="status">
@@ -110,7 +105,7 @@
</template>
</el-table-column>
<el-table-column
prop="created_at"
prop="createdAt"
sortable
:label="$t('commons.table.date')"
:formatter="dateFormat"
@@ -202,13 +197,7 @@
<script setup lang="ts">
import DrawerHeader from '@/components/drawer-header/index.vue';
import {
snapshotCreate,
searchSnapshotPage,
snapshotDelete,
updateSnapshotDescription,
loadSnapshotSize,
} from '@/api/modules/setting';
import { snapshotCreate, searchSnapshotPage, snapshotDelete, updateSnapshotDescription } from '@/api/modules/setting';
import { onMounted, reactive, ref } from 'vue';
import { computeSize, dateFormat } from '@/utils/util';
import { ElForm } from 'element-plus';
@@ -429,7 +418,6 @@ const search = async (column?: any) => {
await searchSnapshotPage(params)
.then((res) => {
loading.value = false;
loadSize(params);
cleanData.value = false;
data.value = res.data.items || [];
paginationConfig.total = res.data.total;
@@ -439,28 +427,6 @@ const search = async (column?: any) => {
});
};
const loadSize = async (params: any) => {
await loadSnapshotSize(params)
.then((res) => {
let stats = res.data || [];
if (stats.length === 0) {
return;
}
for (const snap of data.value) {
for (const item of stats) {
if (snap.id === item.id) {
snap.hasLoad = true;
snap.size = item.size;
break;
}
}
}
})
.catch(() => {
loading.value = false;
});
};
onMounted(() => {
search();
loadBackups();
+1 -1
View File
@@ -261,7 +261,7 @@ const toDoc = async () => {
};
const onChange = async (row: any) => {
await updateClam(row);
await await updateClam(row);
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
};

Some files were not shown because too many files have changed in this diff Show More