Compare 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
106 changed files with 1044 additions and 2472 deletions
-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 从系统快照恢复
-20
View File
@@ -1,20 +0,0 @@
package dto
type CreateOrUpdateAlert struct {
AlertTitle string `json:"alertTitle"`
AlertType string `json:"alertType"`
AlertCount uint `json:"alertCount"`
EntryID uint `json:"entryID"`
}
type AlertBase struct {
AlertType string `json:"alertType"`
EntryID uint `json:"entryID"`
}
type PushAlert struct {
TaskName string `json:"taskName"`
AlertType string `json:"alertType"`
EntryID uint `json:"entryID"`
Param string `json:"param"`
}
-5
View File
@@ -33,7 +33,6 @@ type ClamInfo struct {
LastHandleDate string `json:"lastHandleDate"`
Spec string `json:"spec"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
}
type ClamLogSearch struct {
@@ -72,8 +71,6 @@ type ClamCreate struct {
InfectedDir string `json:"infectedDir"`
Spec string `json:"spec"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
}
type ClamUpdate struct {
@@ -85,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 {
+1 -2
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"`
@@ -219,7 +218,7 @@ type ComposeCreate struct {
type ComposeOperation struct {
Name string `json:"name" validate:"required"`
Path string `json:"path" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=up start stop down delete"`
Operation string `json:"operation" validate:"required,oneof=up start stop down"`
WithFile bool `json:"withFile"`
}
type ComposeUpdate struct {
-6
View File
@@ -31,13 +31,10 @@ type CronjobCreate struct {
DefaultDownload string `json:"defaultDownload"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
Secret string `json:"secret"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
}
type CronjobUpdate struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required"`
Name string `json:"name" validate:"required"`
Spec string `json:"spec" validate:"required"`
@@ -56,8 +53,6 @@ type CronjobUpdate struct {
DefaultDownload string `json:"defaultDownload"`
RetainCopies int `json:"retainCopies" validate:"number,min=1"`
Secret string `json:"secret"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
}
type CronjobUpdateStatus struct {
@@ -104,7 +99,6 @@ type CronjobInfo struct {
LastRecordTime string `json:"lastRecordTime"`
Status string `json:"status"`
Secret string `json:"secret"`
AlertCount uint `json:"alertCount"`
}
type SearchRecord 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 {
-68
View File
@@ -8,7 +8,6 @@ import (
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
@@ -155,16 +154,6 @@ func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interfa
}
datas[i].LastHandleDate = t1.Format(constant.DateTimeLayout)
}
alertBase := dto.AlertBase{
AlertType: "clams",
EntryID: datas[i].ID,
}
alertCount := xpack.GetAlert(alertBase)
if alertCount != 0 {
datas[i].AlertCount = alertCount
} else {
datas[i].AlertCount = 0
}
}
return total, datas, err
}
@@ -191,19 +180,6 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
if err := clamRepo.Create(&clam); err != nil {
return err
}
if req.AlertCount != 0 {
createAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertCount: req.AlertCount,
AlertType: "clams",
EntryID: clam.ID,
}
err := xpack.CreateAlert(createAlert)
if err != nil {
return err
}
}
return nil
}
@@ -249,16 +225,6 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
if err := clamRepo.Update(req.ID, upMap); err != nil {
return err
}
updateAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertType: "clams",
AlertCount: req.AlertCount,
EntryID: clam.ID,
}
err := xpack.UpdateAlert(updateAlert)
if err != nil {
return err
}
return nil
}
@@ -299,14 +265,6 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
if err := clamRepo.Delete(commonRepo.WithByID(id)); err != nil {
return err
}
alertBase := dto.AlertBase{
AlertType: "clams",
EntryID: clam.ID,
}
err := xpack.DeleteAlert(alertBase)
if err != nil {
return err
}
}
return nil
}
@@ -347,7 +305,6 @@ func (c *ClamService) HandleOnce(req dto.OperateByID) error {
}
global.LOG.Debugf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
stdout, err := cmd.Execf("clamdscan --fdpass %s %s -l %s", strategy, clam.Path, logFile)
handleAlert(stdout, clam.Name, clam.ID)
if err != nil {
global.LOG.Errorf("clamdscan failed, stdout: %v, err: %v", stdout, err)
}
@@ -628,28 +585,3 @@ func (c *ClamService) loadLogPath(name string) string {
return ""
}
func handleAlert(stdout, clamName string, clamId uint) {
if strings.Contains(stdout, "- SCAN SUMMARY -") {
lines := strings.Split(stdout, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "Infected files: ") {
var infectedFiles = 0
infectedFiles, _ = strconv.Atoi(strings.TrimPrefix(line, "Infected files: "))
if infectedFiles > 0 {
pushAlert := dto.PushAlert{
TaskName: clamName,
AlertType: "clams",
EntryID: clamId,
Param: strconv.Itoa(infectedFiles),
}
err := xpack.PushAlert(pushAlert)
if err != nil {
global.LOG.Errorf("clamdscan push failed, err: %v", err)
}
break
}
}
}
}
}
+143 -72
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()
@@ -258,16 +293,6 @@ func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
if _, err := os.Stat(req.Path); err != nil {
return fmt.Errorf("load file with path %s failed, %v", req.Path, err)
}
if 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))
@@ -278,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
}
@@ -289,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)
@@ -351,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
}
+1 -47
View File
@@ -13,7 +13,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
@@ -54,16 +53,6 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
} else {
item.LastRecordTime = "-"
}
alertBase := dto.AlertBase{
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
alertCount := xpack.GetAlert(alertBase)
if alertCount != 0 {
item.AlertCount = alertCount
} else {
item.AlertCount = 0
}
dtoCronjobs = append(dtoCronjobs, item)
}
return total, dtoCronjobs, err
@@ -201,18 +190,6 @@ func (u *CronjobService) Create(cronjobDto dto.CronjobCreate) error {
if err := cronjobRepo.Create(&cronjob); err != nil {
return err
}
if cronjobDto.AlertCount != 0 {
createAlert := dto.CreateOrUpdateAlert{
AlertTitle: cronjobDto.AlertTitle,
AlertCount: cronjobDto.AlertCount,
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
err := xpack.CreateAlert(createAlert)
if err != nil {
return err
}
}
return nil
}
@@ -255,14 +232,6 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
if err := cronjobRepo.Delete(commonRepo.WithByID(id)); err != nil {
return err
}
alertBase := dto.AlertBase{
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
err := xpack.DeleteAlert(alertBase)
if err != nil {
return err
}
}
return nil
@@ -312,21 +281,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobUpdate) error {
upMap["default_download"] = req.DefaultDownload
upMap["retain_copies"] = req.RetainCopies
upMap["secret"] = req.Secret
err = cronjobRepo.Update(id, upMap)
if err != nil {
return err
}
updateAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertType: cronModel.Type,
AlertCount: req.AlertCount,
EntryID: cronModel.ID,
}
err = xpack.UpdateAlert(updateAlert)
if err != nil {
return err
}
return nil
return cronjobRepo.Update(id, upMap)
}
func (u *CronjobService) UpdateStatus(id uint, status string) error {
@@ -338,7 +293,6 @@ func (u *CronjobService) UpdateStatus(id uint, status string) error {
entryIDs string
err error
)
if status == constant.StatusEnable {
entryIDs, err = u.StartJob(&cronjob, false)
if err != 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 -17
View File
@@ -12,7 +12,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/constant"
@@ -21,7 +20,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/ntp"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
"github.com/pkg/errors"
)
@@ -87,12 +85,12 @@ 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)
}
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
handleCronJobAlert(cronjob)
return
}
if len(message) != 0 {
@@ -394,17 +392,3 @@ func (u *CronjobService) generateLogsPath(cronjob model.Cronjob, startTime time.
func hasBackup(cronjobType string) bool {
return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log"
}
func handleCronJobAlert(cronjob *model.Cronjob) {
pushAlert := dto.PushAlert{
TaskName: cronjob.Name,
AlertType: cronjob.Type,
EntryID: cronjob.ID,
Param: cronjob.Type,
}
err := xpack.PushAlert(pushAlert)
if err != nil {
global.LOG.Errorf("cronjob alert push failed, err: %v", err)
return
}
}
+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 {
+7 -10
View File
@@ -86,11 +86,9 @@ func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
}
if req.Protocol == "http" {
_ = u.handleRegistries(req.DownloadUrl, "", "create")
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))
}
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
@@ -161,13 +159,12 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if repo.Auth {
_, _ = cmd.ExecWithCheck("docker", "logout", repo.DownloadUrl)
}
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))
}
}
upMap := make(map[string]interface{})
upMap["download_url"] = req.DownloadUrl
upMap["protocol"] = req.Protocol
+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 -7
View File
@@ -352,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)
}
}
@@ -1667,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"})
}
@@ -1752,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)
}
+5 -37
View File
@@ -7,7 +7,6 @@ import (
"path"
"strings"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/constant"
@@ -15,7 +14,6 @@ import (
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/encrypt"
"github.com/1Panel-dev/1Panel/backend/utils/xpack"
)
func Init() {
@@ -128,24 +126,11 @@ func handleSnapStatus() {
}
func handleCronjobStatus() {
var jobRecords []model.JobRecords
_ = global.DB.Where("status = ?", constant.StatusWaiting).Find(&jobRecords).Error
for _, record := range jobRecords {
err := global.DB.Model(&model.JobRecords{}).Where("status = ?", constant.StatusWaiting).
Updates(map[string]interface{}{
"status": constant.StatusFailed,
"message": "the task was interrupted due to the restart of the 1panel service",
}).Error
if err != nil {
global.LOG.Errorf("Failed to update job ID: %v, Error:%v", record.ID, err)
continue
}
var cronjob *model.Cronjob
_ = global.DB.Where("id = ?", record.CronjobID).First(&cronjob).Error
handleCronJobAlert(cronjob)
}
_ = global.DB.Model(&model.JobRecords{}).Where("status = ?", constant.StatusWaiting).
Updates(map[string]interface{}{
"status": constant.StatusFailed,
"message": "the task was interrupted due to the restart of the 1panel service",
}).Error
}
func loadLocalDir() {
@@ -225,20 +210,3 @@ func initDir() {
}
}
}
func handleCronJobAlert(cronjob *model.Cronjob) {
if cronjob.Type == "snapshot" {
return
}
pushAlert := dto.PushAlert{
TaskName: cronjob.Name,
AlertType: cronjob.Type,
EntryID: cronjob.ID,
Param: cronjob.Type,
}
err := xpack.PushAlert(pushAlert)
if err != nil {
global.LOG.Errorf("cronjob alert push failed, err: %v", err)
return
}
}
-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),
)
}
-21
View File
@@ -8,7 +8,6 @@ import (
"net/http"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
@@ -40,23 +39,3 @@ func LoadXpuInfo() []interface{} {
func StartClam(startClam model.Clam, isUpdate bool) (int, error) {
return 0, buserr.New(constant.ErrXpackNotFound)
}
func CreateAlert(createAlert dto.CreateOrUpdateAlert) error {
return nil
}
func UpdateAlert(updateAlert dto.CreateOrUpdateAlert) error {
return nil
}
func DeleteAlert(alertBase dto.AlertBase) error {
return nil
}
func GetAlert(alertBase dto.AlertBase) uint {
return 0
}
func PushAlert(pushAlert dto.PushAlert) error {
return nil
}
-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 {
-3
View File
@@ -30,9 +30,6 @@ export namespace Cronjob {
retainCopies: number;
status: string;
secret: string;
hasAlert: boolean;
alertCount: number;
alertTitle: string;
}
export interface CronjobCreate {
name: string;
+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;
}
}
-3
View File
@@ -139,9 +139,6 @@ export namespace Toolbox {
spec: string;
specObj: Cronjob.SpecObj;
description: string;
hasAlert: boolean;
alertCount: number;
alertTitle: string;
}
export interface ClamCreate {
name: 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 {
+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;
+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',
+10 -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',
@@ -1140,8 +1129,6 @@ const message = {
clamLog: 'Scan Logs',
freshClam: 'Update Virus Definitions',
freshClamLog: 'Update Virus Definitions Logs',
alertHelper: 'Professional version supports scheduled scan and SMS alert',
alertTitle: 'Virus scan task 「{0}」 detected infected file alert',
},
},
logs: {
@@ -1525,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:
@@ -1928,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',
@@ -2151,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',
@@ -2402,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',
@@ -2474,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 {
+8 -31
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: '監控',
@@ -1074,8 +1066,6 @@ const message = {
clamLog: '掃描日誌',
freshClam: '病毒庫刷新配置',
freshClamLog: '病毒庫刷新日誌',
alertHelper: '專業版支持定時掃描和短信告警功能',
alertTitle: '病毒掃描「{0}」任務检测到感染文件告警',
},
},
logs: {
@@ -1472,9 +1462,7 @@ const message = {
bindDomain: '域名綁定',
unBindDomain: '域名解綁',
panelSSL: '面板 SSL',
sslAutoRestart: '證書自動續期後重啟 1Panel 服務',
sslChangeHelper1: '當前未勾選自動重啟 1Panel 服務,證書自動續期後不會立即生效,仍需手動重啟 1Panel。',
sslChangeHelper2: '設置面板 SSL 後將自動重啟 1Panel 服務,是否繼續?',
panelSSLHelper: '面板 SSL 自动续期后需要手动重启 1Panel 服务才可生效',
unBindDomainHelper: '解除域名綁定可能造成系統不安全,是否繼續?',
bindDomainHelper: '設置域名綁定後,僅能通過設置中域名訪問 1Panel 服務',
bindDomainHelper1: '綁定域名為空時,則取消域名綁定',
@@ -1787,7 +1775,7 @@ const message = {
applySSL: '證書申請',
SSLList: '證書列表',
createDnsAccount: 'DNS賬戶',
aliyun: '阿裏雲',
aliyun: '阿裏雲DNS',
manual: '手動解析',
key: '密鑰',
check: '查看',
@@ -1996,7 +1984,6 @@ const message = {
website404Helper: '網站 404 錯誤頁僅支援 PHP 運行環境網站和靜態網站',
sni: '回源 SNI',
sniHelper: '反代後端為 https 的時候可能需要設置回源 SNI,具體需要看 CDN 服務商文檔',
huaweicloud: '華為雲',
},
php: {
short_open_tag: '短標簽支持',
@@ -2224,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',
@@ -2294,14 +2279,6 @@ const message = {
manage: '管理',
},
},
alert: {
isAlert: '是否告警',
alertCount: '告警次數',
clamHelper: '掃描到感染檔案時觸發簡訊告警',
cronJobHelper: '定時任務執行失敗時將觸發簡訊告警',
licenseHelper: '專業版支持簡訊告警功能',
alertCountHelper: '每日最大告警次數',
},
};
export default {
...fit2cloudTwLocale,
+10 -31
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: '监控',
@@ -1076,8 +1069,6 @@ const message = {
clamLog: '扫描日志',
freshClam: '病毒库刷新配置',
freshClamLog: '病毒库刷新日志',
alertHelper: '专业版支持定时扫描和短信告警功能',
alertTitle: '病毒扫描「 {0} 」任务检测到感染文件告警',
},
},
logs: {
@@ -1474,9 +1465,7 @@ const message = {
bindDomain: '域名绑定',
unBindDomain: '域名解绑',
panelSSL: '面板 SSL',
sslAutoRestart: '证书自动续期后重启 1Panel 服务',
sslChangeHelper1: '当前未勾选自动重启 1Panel 服务,证书自动续期后不会立即生效,仍需手动重启 1Panel。',
sslChangeHelper2: '设置面板 SSL 后将自动重启 1Panel 服务,是否继续?',
panelSSLHelper: '面板 SSL 自动续期后需要手动重启 1Panel 服务才可生效',
unBindDomainHelper: '解除域名绑定可能造成系统不安全,是否继续?',
bindDomainHelper: '设置域名绑定后,仅能通过设置中域名访问 1Panel 服务',
bindDomainHelper1: '绑定域名为空时,则取消域名绑定',
@@ -1500,6 +1489,7 @@ const message = {
mfaInterval: '刷新时间(秒)',
mfaTitleHelper: '用于区分不同 1Panel 主机,修改后请重新扫描或手动添加密钥信息!',
mfaIntervalHelper: '修改刷新时间后,请重新扫描或手动添加密钥信息!',
sslChangeHelper: 'https 设置修改需要重启服务,是否继续?',
sslDisable: '禁用',
sslDisableHelper: '禁用 https 服务,需要重启面板才能生效,是否继续?',
noAuthSetting: '未认证设置',
@@ -1788,7 +1778,7 @@ const message = {
applySSL: '证书申请',
SSLList: '证书列表',
createDnsAccount: 'DNS账户',
aliyun: '阿里云',
aliyun: '阿里云DNS',
manual: '手动解析',
key: '密钥',
check: '查看',
@@ -1997,7 +1987,6 @@ const message = {
website404Helper: '网站 404 错误页仅支持 PHP 运行环境网站和静态网站',
sni: '回源 SNI',
sniHelper: '反代后端为 https 的时候可能需要设置回源 SNI,具体需要看 CDN 服务商文档',
huaweicloud: '华为云',
},
php: {
short_open_tag: '短标签支持',
@@ -2226,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',
@@ -2296,14 +2283,6 @@ const message = {
manage: '管理',
},
},
alert: {
isAlert: '是否告警',
alertCount: '告警次数',
clamHelper: '扫描到感染文件时触发短信告警',
cronJobHelper: '定时任务执行失败时将触发短信告警',
licenseHelper: '专业版支持短信告警功能',
alertCountHelper: '每日最大告警次数',
},
};
export default {
...fit2cloudZhLocale,
+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: '',
+2 -7
View File
@@ -20,11 +20,6 @@ body,
height: 12px;
}
.el-switch--small.is-checked .el-switch__core::after {
margin-left: -13px;
}
.el-alert__title {
display: flex;
align-items: center;
.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 -66
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>
@@ -320,31 +316,6 @@
</el-form-item>
</div>
<el-form-item prop="hasAlert">
<el-checkbox v-model="dialogData.rowData!.hasAlert" :label="$t('alert.isAlert')" />
<span class="input-help">{{ $t('alert.cronJobHelper') }}</span>
</el-form-item>
<el-form-item
prop="alertCount"
v-if="dialogData.rowData!.hasAlert && isProductPro"
:label="$t('alert.alertCount')"
>
<el-input-number
style="width: 200px"
:min="1"
step-strictly
:step="1"
v-model.number="dialogData.rowData!.alertCount"
></el-input-number>
<span class="input-help">{{ $t('alert.alertCountHelper') }}</span>
</el-form-item>
<el-form-item v-if="dialogData.rowData!.hasAlert && !isProductPro">
<span>{{ $t('alert.licenseHelper') }}</span>
<el-button link type="primary" @click="toUpload">
{{ $t('license.levelUpPro') }}
</el-button>
</el-form-item>
<el-form-item :label="$t('cronjob.retainCopies')" prop="retainCopies">
<el-input-number
style="width: 200px"
@@ -386,7 +357,6 @@
</el-button>
</span>
</template>
<LicenseImport ref="licenseRef" />
</el-drawer>
</template>
@@ -408,9 +378,6 @@ import { listContainer } from '@/api/modules/container';
import { Database } from '@/api/interface/database';
import { ListAppInstalled } from '@/api/modules/app';
import { loadDefaultSpec, specOptions, transObjToSpec, transSpecToObj, weekOptions } from './../helper';
import { storeToRefs } from 'pinia';
import { GlobalStore } from '@/store';
import LicenseImport from '@/components/license-import/index.vue';
const router = useRouter();
@@ -426,10 +393,6 @@ const dialogData = ref<DialogProps>({
title: '',
});
const globalStore = GlobalStore();
const licenseRef = ref();
const { isProductPro } = storeToRefs(globalStore);
const acceptParams = (params: DialogProps): void => {
dialogData.value = params;
if (dialogData.value.rowData?.spec) {
@@ -456,8 +419,6 @@ const acceptParams = (params: DialogProps): void => {
if (dialogData.value.rowData.dbName) {
dialogData.value.rowData.dbNameList = dialogData.value.rowData.dbName.split(',');
}
dialogData.value.rowData.hasAlert = dialogData.value.rowData!.alertCount > 0;
dialogData.value.rowData!.alertCount = dialogData.value.rowData!.alertCount || 3;
dialogData.value.rowData!.command = dialogData.value.rowData!.command || 'sh';
dialogData.value.rowData!.isCustom =
dialogData.value.rowData!.command !== 'sh' &&
@@ -595,18 +556,6 @@ const verifySpec = (rule: any, value: any, callback: any) => {
callback();
};
function checkSendCount(rule: any, value: any, callback: any) {
if (value === '') {
callback();
}
const regex = /^(?:[1-9]|[12][0-9]|30)$/;
if (!regex.test(value)) {
return callback(new Error(i18n.global.t('commons.rule.numberRange', [1, 30])));
}
callback();
}
const rules = reactive({
name: [Rules.requiredInput, Rules.noSpace],
type: [Rules.requiredSelect],
@@ -625,7 +574,6 @@ const rules = reactive({
backupAccounts: [Rules.requiredSelect],
defaultDownload: [Rules.requiredSelect],
retainCopies: [Rules.number],
alertCount: [Rules.integerNumber, { validator: checkSendCount, trigger: 'blur' }],
});
type FormInstance = InstanceType<typeof ElForm>;
@@ -804,15 +752,6 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (dialogData.value?.rowData?.exclusionRules) {
dialogData.value.rowData.exclusionRules = dialogData.value.rowData.exclusionRules.replaceAll('\n', ',');
}
dialogData.value.rowData.alertCount =
dialogData.value.rowData!.hasAlert && isProductPro.value ? dialogData.value.rowData.alertCount : 0;
dialogData.value.rowData.alertTitle =
dialogData.value.rowData!.hasAlert && isProductPro.value
? i18n.global.t('cronjob.alertTitle', [
i18n.global.t('cronjob.' + dialogData.value.rowData.type),
dialogData.value.rowData.name,
])
: '';
if (!dialogData.value.rowData) return;
if (dialogData.value.title === 'create') {
await addCronjob(dialogData.value.rowData);
@@ -827,10 +766,6 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
});
};
const toUpload = () => {
licenseRef.value.acceptParams();
};
defineExpose({
acceptParams,
});
@@ -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,7 +384,6 @@ const onSetting = async () => {
};
const changeDatabase = async () => {
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,7 +348,6 @@ const onSetting = async () => {
};
const changeDatabase = async () => {
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,7 +220,6 @@ const goRouter = async (target: string) => {
};
const changeDatabase = async () => {
appStatusRef.value.onCheck();
for (const item of dbOptionsLocal.value) {
if (item.database == currentDBName.value) {
currentDB.value = item;
+74 -68
View File
@@ -42,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>
@@ -85,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">
@@ -685,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>
+2 -12
View File
@@ -18,14 +18,7 @@
>
{{ $t('commons.button.sync') }}
</el-button>
<el-button
v-if="!license.offline"
type="primary"
class="ml-3"
plain
@click="onUnBind()"
size="small"
>
<el-button type="primary" class="ml-3" plain @click="onUnBind()" size="small">
{{ $t('license.unbind') }}
</el-button>
</el-descriptions-item>
@@ -131,7 +124,6 @@ const hasLicense = ref();
const license = reactive({
licenseName: '',
trial: true,
offline: true,
expiresAt: '',
assigneeName: '',
productName: '',
@@ -209,13 +201,11 @@ const search = async () => {
hasLicense.value = true;
if (globalStore.isProductPro) {
globalStore.productProExpires = Number(res.data.productPro);
globalStore.isTrial = res.data.trial;
}
license.licenseName = res.data.licenseName;
license.message = res.data.message;
license.assigneeName = res.data.assigneeName;
license.trial = res.data.trial;
license.offline = res.data.offline;
if (res.data.productPro) {
license.productName = 'product-1panel-pro';
license.expiresAt =
@@ -230,7 +220,7 @@ const search = async () => {
};
const showSync = () => {
return (license.status.indexOf('Lost') !== -1 || license.status === 'Disable') && !license.offline;
return license.status.indexOf('Lost') !== -1 || license.status === 'Disable';
};
const toUpload = () => {
+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'));
};
@@ -53,6 +53,12 @@
<el-form-item prop="hasSpec">
<el-checkbox v-model="dialogData.rowData!.hasSpec" :label="$t('toolbox.clam.cron')" />
</el-form-item>
<el-form-item v-if="dialogData.rowData!.hasSpec && !isProductPro">
<span>{{ $t('toolbox.clam.cronHelper') }}</span>
<el-button link type="primary" @click="toUpload">
{{ $t('license.levelUpPro') }}
</el-button>
</el-form-item>
<el-form-item prop="spec" v-if="dialogData.rowData!.hasSpec && isProductPro">
<el-select
class="specTypeClass"
@@ -115,30 +121,6 @@
</template>
</el-input>
</el-form-item>
<el-form-item prop="hasAlert">
<el-checkbox v-model="dialogData.rowData!.hasAlert" :label="$t('alert.isAlert')" />
<span class="input-help">{{ $t('alert.clamHelper') }}</span>
</el-form-item>
<el-form-item v-if="(dialogData.rowData!.hasAlert || dialogData.rowData!.hasSpec) && !isProductPro">
<span>{{ $t('toolbox.clam.alertHelper') }}</span>
<el-button link type="primary" @click="toUpload">
{{ $t('license.levelUpPro') }}
</el-button>
</el-form-item>
<el-form-item
prop="alertCount"
v-if="dialogData.rowData!.hasAlert && isProductPro"
:label="$t('alert.alertCount')"
>
<el-input-number
style="width: 200px"
:min="1"
step-strictly
:step="1"
v-model.number="dialogData.rowData!.alertCount"
></el-input-number>
<span class="input-help">{{ $t('alert.alertCountHelper') }}</span>
</el-form-item>
<el-form-item :label="$t('commons.table.description')" prop="description">
<el-input type="textarea" :rows="3" clearable v-model="dialogData.rowData!.description" />
</el-form-item>
@@ -202,8 +184,6 @@ const acceptParams = (params: DialogProps): void => {
second: 30,
};
}
dialogData.value.rowData.hasAlert = dialogData.value.rowData!.alertCount > 0;
dialogData.value.rowData!.alertCount = dialogData.value.rowData!.alertCount || 3;
title.value = i18n.global.t('commons.button.' + dialogData.value.title);
drawerVisible.value = true;
};
@@ -297,19 +277,6 @@ const verifySpec = (rule: any, value: any, callback: any) => {
}
callback();
};
function checkSendCount(rule: any, value: any, callback: any) {
if (value === '') {
callback();
}
const regex = /^(?:[1-9]|[12][0-9]|30)$/;
if (!regex.test(value)) {
return callback(new Error(i18n.global.t('commons.rule.numberRange', [1, 30])));
}
callback();
}
const rules = reactive({
name: [Rules.simpleName],
path: [Rules.requiredInput, Rules.noSpace],
@@ -317,7 +284,6 @@ const rules = reactive({
{ validator: verifySpec, trigger: 'blur', required: true },
{ validator: verifySpec, trigger: 'change', required: true },
],
alertCount: [Rules.integerNumber, { validator: checkSendCount, trigger: 'blur' }],
});
type FormInstance = InstanceType<typeof ElForm>;
@@ -390,19 +356,6 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
}
dialogData.value.rowData.spec = spec;
if (dialogData.value.rowData!.hasAlert) {
dialogData.value.rowData.alertCount = dialogData.value.rowData!.hasAlert
? dialogData.value.rowData.alertCount
: 0;
dialogData.value.rowData.alertTitle = i18n.global.t('toolbox.clam.alertTitle', [
dialogData.value.rowData.name,
]);
} else {
dialogData.value.rowData.alertTitle = '';
dialogData.value.rowData.alertCount = 0;
dialogData.value.rowData.hasAlert = false;
}
if (dialogData.value.title === 'edit') {
await updateClam(dialogData.value.rowData)
.then(() => {
@@ -92,19 +92,17 @@ let timer: NodeJS.Timer | null = null;
const em = defineEmits(['search']);
watch(open, (val) => {
if (screenfull.isEnabled && !val && !mobile.value) screenfull.exit();
});
const mobile = computed(() => {
return globalStore.isMobile();
});
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 getContent = () => {
@@ -136,7 +134,6 @@ const changeTail = () => {
const handleClose = () => {
content.value = '';
open.value = false;
globalStore.isFullScreen = false;
};
const submit = () => {
@@ -76,12 +76,16 @@
{{ $t('runtime.goDirHelper') }}
</span>
</el-form-item>
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<span class="input-help">
{{ $t('runtime.goHelper') }}
</span>
</el-form-item>
<el-row :gutter="20">
<el-col :span="18">
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<span class="input-help">
{{ $t('runtime.goHelper') }}
</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="7">
<el-form-item :label="$t('runtime.appPort')" prop="params.GO_APP_PORT">
@@ -25,9 +25,5 @@ const buttons = [
label: 'Go',
path: '/websites/runtimes/go',
},
{
label: 'Python',
path: '/websites/runtimes/python',
},
];
</script>
@@ -76,12 +76,16 @@
{{ $t('runtime.javaDirHelper') }}
</span>
</el-form-item>
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<span class="input-help">
{{ $t('runtime.javaScriptHelper') }}
</span>
</el-form-item>
<el-row :gutter="20">
<el-col :span="18">
<el-form-item :label="$t('runtime.runScript')" prop="params.EXEC_SCRIPT">
<el-input v-model="runtime.params['EXEC_SCRIPT']"></el-input>
<span class="input-help">
{{ $t('runtime.javaScriptHelper') }}
</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="7">
<el-form-item :label="$t('runtime.appPort')" prop="params.JAVA_APP_PORT">
@@ -1,307 +0,0 @@
<template>
<div>
<RouterMenu />
<LayoutContent :title="'Python'" v-loading="loading">
<template #prompt>
<el-alert type="info" :closable="false">
<template #title>
<span class="input-help whitespace-break-spaces">
{{ $t('runtime.statusHelper') }}
</span>
</template>
</el-alert>
</template>
<template #toolbar>
<div class="flex flex-wrap gap-3">
<el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }}
</el-button>
<el-button type="primary" plain @click="onOpenBuildCache()">
{{ $t('container.cleanBuildCache') }}
</el-button>
</div>
</template>
<template #main>
<ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()">
<el-table-column
:label="$t('commons.table.name')"
fix
prop="name"
min-width="120px"
show-overflow-tooltip
>
<template #default="{ row }">
<el-text type="primary" class="cursor-pointer" @click="openDetail(row)">
{{ row.name }}
</el-text>
</template>
</el-table-column>
<el-table-column :label="$t('website.runDir')" prop="codeDir">
<template #default="{ row }">
<el-button type="primary" link @click="toFolder(row.codeDir)">
<el-icon>
<FolderOpened />
</el-icon>
</el-button>
</template>
</el-table-column>
<el-table-column :label="$t('runtime.version')" prop="version"></el-table-column>
<el-table-column :label="$t('runtime.externalPort')" prop="port">
<template #default="{ row }">
{{ row.port }}
<el-button link :icon="Promotion" @click="goDashboard(row.port, 'http')"></el-button>
</template>
</el-table-column>
<el-table-column :label="$t('commons.table.status')" prop="status">
<template #default="{ row }">
<el-popover
v-if="row.status === 'error'"
placement="bottom"
:width="400"
trigger="hover"
:content="row.message"
popper-class="max-h-[300px] overflow-auto"
>
<template #reference>
<Status :key="row.status" :status="row.status"></Status>
</template>
</el-popover>
<div v-else>
<Status :key="row.status" :status="row.status"></Status>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('commons.button.log')" prop="path">
<template #default="{ row }">
<el-button @click="openLog(row)" link type="primary">{{ $t('website.check') }}</el-button>
</template>
</el-table-column>
<el-table-column
prop="createdAt"
:label="$t('commons.table.date')"
:formatter="dateFormat"
show-overflow-tooltip
min-width="120"
fix
/>
<fu-table-operations
:ellipsis="mobile ? 0 : 10"
:min-width="mobile ? 'auto' : 300"
:buttons="buttons"
:label="$t('commons.table.operate')"
fixed="right"
fix
/>
</ComplexTable>
</template>
</LayoutContent>
<Operate ref="operateRef" @close="search" />
<Delete ref="deleteRef" @close="search" />
<ComposeLogs ref="composeLogRef" />
<PortJumpDialog ref="dialogPortJumpRef" />
<AppResources ref="checkRef" @close="search" />
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref, computed } from 'vue';
import { Runtime } from '@/api/interface/runtime';
import { OperateRuntime, RuntimeDeleteCheck, SearchRuntimes, SyncRuntime } from '@/api/modules/runtime';
import { dateFormat } from '@/utils/util';
import Operate from '@/views/website/runtime/python/operate/index.vue';
import Status from '@/components/status/index.vue';
import Delete from '@/views/website/runtime/delete/index.vue';
import i18n from '@/lang';
import RouterMenu from '../index.vue';
import router from '@/routers/router';
import ComposeLogs from '@/components/compose-log/index.vue';
import { Promotion } from '@element-plus/icons-vue';
import PortJumpDialog from '@/components/port-jump/index.vue';
import AppResources from '@/views/website/runtime/php/check/index.vue';
import { ElMessageBox } from 'element-plus';
import { containerPrune } from '@/api/modules/container';
import { MsgSuccess } from '@/utils/message';
import { GlobalStore } from '@/store';
let timer: NodeJS.Timer | null = null;
const loading = ref(false);
const items = ref<Runtime.RuntimeDTO[]>([]);
const operateRef = ref();
const deleteRef = ref();
const dialogPortJumpRef = ref();
const composeLogRef = ref();
const checkRef = ref();
const globalStore = GlobalStore();
const mobile = computed(() => {
return globalStore.isMobile();
});
const paginationConfig = reactive({
cacheSizeKey: 'runtime-page-size',
currentPage: 1,
pageSize: 10,
total: 0,
});
const req = reactive<Runtime.RuntimeReq>({
name: '',
page: 1,
pageSize: 40,
type: 'python',
});
const buttons = [
{
label: i18n.global.t('container.stop'),
click: function (row: Runtime.Runtime) {
operateRuntime('down', row.id);
},
disabled: function (row: Runtime.Runtime) {
return row.status === 'recreating' || row.status === 'stopped';
},
},
{
label: i18n.global.t('container.start'),
click: function (row: Runtime.Runtime) {
operateRuntime('up', row.id);
},
disabled: function (row: Runtime.Runtime) {
return row.status === 'starting' || row.status === 'recreating' || row.status === 'running';
},
},
{
label: i18n.global.t('container.restart'),
click: function (row: Runtime.Runtime) {
operateRuntime('restart', row.id);
},
disabled: function (row: Runtime.Runtime) {
return row.status === 'recreating';
},
},
{
label: i18n.global.t('commons.button.edit'),
click: function (row: Runtime.Runtime) {
openDetail(row);
},
disabled: function (row: Runtime.Runtime) {
return row.status === 'recreating';
},
},
{
label: i18n.global.t('commons.button.delete'),
click: function (row: Runtime.Runtime) {
openDelete(row);
},
},
];
const search = async () => {
req.page = paginationConfig.currentPage;
req.pageSize = paginationConfig.pageSize;
loading.value = true;
try {
const res = await SearchRuntimes(req);
items.value = res.data.items;
paginationConfig.total = res.data.total;
} catch (error) {
} finally {
loading.value = false;
}
};
const sync = () => {
SyncRuntime();
};
const openCreate = () => {
operateRef.value.acceptParams({ type: 'python', mode: 'create' });
};
const openDetail = (row: Runtime.Runtime) => {
operateRef.value.acceptParams({ type: row.type, mode: 'edit', id: row.id });
};
const openDelete = async (row: Runtime.Runtime) => {
RuntimeDeleteCheck(row.id).then(async (res) => {
const items = res.data;
if (res.data && res.data.length > 0) {
checkRef.value.acceptParams({ items: items, key: 'website', installID: row.id });
} else {
deleteRef.value.acceptParams(row.id, row.name);
}
});
};
const onOpenBuildCache = () => {
ElMessageBox.confirm(i18n.global.t('container.delBuildCacheHelper'), i18n.global.t('container.cleanBuildCache'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
loading.value = true;
let params = {
pruneType: 'buildcache',
withTagAll: false,
};
await containerPrune(params)
.then((res) => {
loading.value = false;
MsgSuccess(i18n.global.t('container.cleanSuccess', [res.data.deletedNumber]));
search();
})
.catch(() => {
loading.value = false;
});
});
};
const openLog = (row: any) => {
composeLogRef.value.acceptParams({ compose: row.path + '/docker-compose.yml', resource: row.name });
};
const goDashboard = async (port: any, protocol: string) => {
dialogPortJumpRef.value.acceptParams({ port: port, protocol: protocol });
};
const operateRuntime = async (operate: string, ID: number) => {
try {
const action = await ElMessageBox.confirm(
i18n.global.t('runtime.operatorHelper', [i18n.global.t('commons.operate.' + operate)]),
i18n.global.t('commons.operate.' + operate),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
},
);
if (action === 'confirm') {
loading.value = true;
await OperateRuntime({ operate: operate, ID: ID });
search();
}
} catch (error) {
} finally {
loading.value = false;
}
};
const toFolder = (folder: string) => {
router.push({ path: '/hosts/files', query: { path: folder } });
};
onMounted(() => {
sync();
search();
timer = setInterval(() => {
search();
sync();
}, 1000 * 10);
});
onUnmounted(() => {
clearInterval(Number(timer));
timer = null;
});
</script>
<style lang="scss" scoped></style>

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