Compare commits

...
24 Commits
Author SHA1 Message Date
ssongliu fe717d993b fix: 快照重试后更新列表状态 (#4086) 2024-03-06 07:58:07 +00:00
zhengkunwang 703da8a09a fix: 解决网站日志查看报错的问题 (#4082)
Refs https://github.com/1Panel-dev/1Panel/issues/3789
2024-03-06 03:56:07 +00:00
ssongliu a99f19471b fix: 优化计划任务脚本执行输出 (#4080) 2024-03-06 02:36:07 +00:00
zhengkunwang ca3be03ae7 feat: 本地应用同步增加校验 (#4071) 2024-03-05 10:10:08 +00:00
ssongliu 71ca8016e1 fix: 解决命令执行超时未生效的问题 (#4070) 2024-03-05 10:06:08 +00:00
ssongliu 5633bdd41e fix: 快照前清空服务器地址 (#4068) 2024-03-05 04:04:07 +00:00
ssongliu ab11d7257c fix: 快照重试时忽略已成功步骤 (#4067) 2024-03-05 03:14:07 +00:00
ssongliu 39b8de7ada fix: 限制快照跨架构恢复 (#4064)
Refs #4056
2024-03-04 14:59:11 +00:00
zhengkunwang c49c4b7863 feat(waf): 增加概览页 (#4060) 2024-03-04 08:53:06 +00:00
ssongliu d42df7184c style: 修改主机记住密码显示标题 (#4061) 2024-03-04 08:37:05 +00:00
ssongliu e7f172520a pref: 优化 MySQL 备份恢复方式 (#4059)
Refs #3955
2024-03-04 08:35:05 +00:00
zhengkunwang dca29174ea feat: 同步本地应用增加 data.yml 正确性验证 (#4058)
Refs https://github.com/1Panel-dev/1Panel/issues/4052
2024-03-04 03:53:03 +00:00
ssongliu 94181538a9 fix: 解决单位转换导致的内存限制问题 (#4055)
Refs #4040
2024-03-04 03:29:04 +00:00
zhengkunwang e9cc7bf1cf feat(waf):增加请求日志统计 (#4048) 2024-03-02 14:45:02 +00:00
ssongliu fa1e945b8d pref: 处理代码警告 (#4036) 2024-03-01 08:55:02 +00:00
zhengkunwang 94702a79a4 feat(waf): 修改攻击日志格式 (#4024) 2024-03-01 05:53:02 +00:00
ssongliu 24f573f9fd fix: 修改快照恢复逻辑 (#4031) 2024-03-01 13:51:34 +08:00
ssongliu 7e4606eda5 feat: 增加防篡改功能 (#4016) 2024-03-01 05:51:01 +00:00
ssongliu 73e118a3e5 style: 封装表格搜索组件 (#4028)
Refs #4017
2024-03-01 03:21:01 +00:00
ssongliu 514d68d395 fix: 解决容器资源使用率获取错误的问题 (#4023)
Refs #4022
2024-02-29 03:08:18 +00:00
QuentinHsu d0bb07796b fix: 初始化版本号变量为字符串类型,默认为空字符串 (#4003) 2024-02-28 17:10:47 +08:00
ssongliu 66a451fc1f fix: 解决登陆页刷新跳转的问题 (#4014)
Refs #3739
2024-02-28 09:06:15 +00:00
zhengkunwang c13e2a36cc feat: 增加 x-Pack 菜单 (#4009) 2024-02-28 08:44:15 +00:00
zhengkunwang 2affd2bc79 feat: 增加 waf 模块 (#4008) 2024-02-28 06:34:09 +00:00
145 changed files with 75133 additions and 1337 deletions
+5
View File
@@ -41,6 +41,11 @@ frontend/src/xpack
backend/xpack
backend/router/entry_xpack.go
backend/server/init_xpack.go
plugins/openresty/waf/db
plugins/openresty/waf/data
plugins/openresty/waf/sites
plugins/openresty/waf/conf/websites.json
.history/
dist/
+1 -5
View File
@@ -101,11 +101,7 @@ func (b *BaseApi) LoadRecordLog(c *gin.Context) {
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
content, err := cronjobService.LoadRecordLog(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
content := cronjobService.LoadRecordLog(req)
helper.SuccessWithData(c, content)
}
+16
View File
@@ -8,6 +8,22 @@ import (
"github.com/gin-gonic/gin"
)
// @Tags Dashboard
// @Summary Load os info
// @Description 获取服务器基础数据
// @Accept json
// @Success 200 {object} dto.OsInfo
// @Security ApiKeyAuth
// @Router /dashboard/base/os [get]
func (b *BaseApi) LoadDashboardOsInfo(c *gin.Context) {
data, err := dashboardService.LoadOsInfo()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, data)
}
// @Tags Dashboard
// @Summary Load dashboard base info
// @Description 获取首页基础数据
+2 -2
View File
@@ -38,8 +38,8 @@ type ContainerInfo struct {
}
type ResourceLimit struct {
CPU int `json:"cpu"`
Memory int `json:"memory"`
CPU int `json:"cpu"`
Memory uint64 `json:"memory"`
}
type ContainerOperate struct {
+8
View File
@@ -24,6 +24,14 @@ type DashboardBase struct {
CurrentInfo DashboardCurrent `json:"currentInfo"`
}
type OsInfo struct {
OS string `json:"os"`
Platform string `json:"platform"`
PlatformFamily string `json:"platformFamily"`
KernelArch string `json:"kernelArch"`
KernelVersion string `json:"kernelVersion"`
}
type DashboardCurrent struct {
Uptime uint64 `json:"uptime"`
TimeSinceUptime string `json:"timeSinceUptime"`
+2 -2
View File
@@ -527,7 +527,7 @@ func (a *AppInstallService) ChangeAppPort(req request.PortUpdate) error {
return nil
}
if err := updateInstallInfoInDB(req.Key, req.Name, "port", true, strconv.FormatInt(req.Port, 10)); err != nil {
if err := updateInstallInfoInDB(req.Key, req.Name, "port", strconv.FormatInt(req.Port, 10)); err != nil {
return nil
}
@@ -776,7 +776,7 @@ func syncAppInstallStatus(appInstall *model.AppInstall) error {
return nil
}
func updateInstallInfoInDB(appKey, appName, param string, isRestart bool, value interface{}) error {
func updateInstallInfoInDB(appKey, appName, param string, value interface{}) error {
if param != "password" && param != "port" && param != "user-password" {
return nil
}
+58 -25
View File
@@ -849,38 +849,34 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
func upApp(appInstall *model.AppInstall, pullImages bool) {
upProject := func(appInstall *model.AppInstall) (err error) {
if err == nil {
var (
out string
errMsg string
)
if pullImages && appInstall.App.Type != "php" {
out, err = compose.Pull(appInstall.GetComposePath())
if err != nil {
if out != "" {
if strings.Contains(out, "no such host") {
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
}
if strings.Contains(out, "timeout") {
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
}
appInstall.Message = errMsg + out
}
return err
}
}
out, err = compose.Up(appInstall.GetComposePath())
var (
out string
errMsg string
)
if pullImages && appInstall.App.Type != "php" {
out, err = compose.Pull(appInstall.GetComposePath())
if err != nil {
if out != "" {
if strings.Contains(out, "no such host") {
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
}
if strings.Contains(out, "timeout") {
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
}
appInstall.Message = errMsg + out
}
return err
}
return
} else {
return
}
out, err = compose.Up(appInstall.GetComposePath())
if err != nil {
if out != "" {
appInstall.Message = errMsg + out
}
return err
}
return
}
if err := upProject(appInstall); err != nil {
appInstall.Status = constant.Error
@@ -996,6 +992,43 @@ func handleLocalAppDetail(versionDir string, appDetail *model.AppDetail) error {
if err != nil {
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
}
var appParam dto.AppForm
if err = json.Unmarshal(dataJson, &appParam); err != nil {
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
}
for _, formField := range appParam.FormFields {
if strings.Contains(formField.EnvKey, " ") {
return buserr.WithName(constant.ErrAppParamKey, formField.EnvKey)
}
}
var dataMap map[string]interface{}
err = yaml.Unmarshal(paramByte, &dataMap)
if err != nil {
return buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
}
additionalProperties, ok := dataMap["additionalProperties"].(map[string]interface{})
if !ok {
return buserr.WithName(constant.ErrAppParamKey, "additionalProperties")
}
formFieldsInterface, ok := additionalProperties["formFields"]
if ok {
formFields, ok := formFieldsInterface.([]interface{})
if !ok {
return buserr.WithName(constant.ErrAppParamKey, "formFields")
}
for _, item := range formFields {
field := item.(map[string]interface{})
for key, value := range field {
if value == nil {
return buserr.WithName(constant.ErrAppParamKey, key)
}
}
}
}
appDetail.Params = string(dataJson)
return nil
}
+1 -1
View File
@@ -115,7 +115,7 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
if err != nil {
return err
}
if err := handleMysqlBackup(db.MysqlName, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
if err := handleMysqlBackup(db.MysqlName, resource.Key, db.Name, tmpDir, fmt.Sprintf("%s.sql.gz", install.Name)); err != nil {
return err
}
case constant.AppPostgresql:
+12 -4
View File
@@ -29,7 +29,7 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
targetDir := path.Join(localDir, itemDir)
fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow+common.RandStrAndNum(5))
if err := handleMysqlBackup(req.Name, req.DetailName, targetDir, fileName); err != nil {
if err := handleMysqlBackup(req.Name, req.Type, req.DetailName, targetDir, fileName); err != nil {
return err
}
@@ -100,18 +100,20 @@ func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error {
return nil
}
func handleMysqlBackup(database, dbName, targetDir, fileName string) error {
func handleMysqlBackup(database, dbType, dbName, targetDir, fileName string) error {
dbInfo, err := mysqlRepo.Get(commonRepo.WithByName(dbName), mysqlRepo.WithByMysqlName(database))
if err != nil {
return err
}
cli, _, err := LoadMysqlClientByFrom(database)
cli, version, err := LoadMysqlClientByFrom(database)
if err != nil {
return err
}
backupInfo := client.BackupInfo{
Name: dbName,
Type: dbType,
Version: version,
Format: dbInfo.Format,
TargetDir: targetDir,
FileName: fileName,
@@ -134,7 +136,7 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
if err != nil {
return err
}
cli, _, err := LoadMysqlClientByFrom(req.Name)
cli, version, err := LoadMysqlClientByFrom(req.Name)
if err != nil {
return err
}
@@ -143,6 +145,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
rollbackFile := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format("20060102150405")))
if err := cli.Backup(client.BackupInfo{
Name: req.DetailName,
Type: req.Type,
Version: version,
Format: dbInfo.Format,
TargetDir: path.Dir(rollbackFile),
FileName: path.Base(rollbackFile),
@@ -156,6 +160,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
global.LOG.Info("recover failed, start to rollback now")
if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName,
Type: req.Type,
Version: version,
Format: dbInfo.Format,
SourceFile: rollbackFile,
@@ -172,6 +178,8 @@ func handleMysqlRecover(req dto.CommonRecover, isRollback bool) error {
}
if err := cli.Recover(client.RecoverInfo{
Name: req.DetailName,
Type: req.Type,
Version: version,
Format: dbInfo.Format,
SourceFile: req.File,
+5 -5
View File
@@ -5,7 +5,6 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"io"
"os"
"os/exec"
@@ -18,6 +17,8 @@ import (
"time"
"unicode/utf8"
"github.com/pkg/errors"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
@@ -336,7 +337,7 @@ func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
data := dto.ResourceLimit{
CPU: cpuCounts,
Memory: int(memoryInfo.Total),
Memory: memoryInfo.Total,
}
return &data, nil
}
@@ -683,7 +684,6 @@ func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType,
global.LOG.Errorf("read bytes from log failed, err: %v", err)
continue
}
// check if the bytes is valid utf8
if !utf8.Valid(buffer[:n]) {
continue
}
@@ -723,7 +723,6 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
if cache, ok := stats.MemoryStats.Stats["cache"]; ok {
data.Cache = float64(cache) / 1024 / 1024
}
data.Memory = data.Memory - data.Cache
data.NetworkRX, data.NetworkTX = calculateNetwork(stats.Networks)
data.ShotTime = stats.Read
return &data, nil
@@ -791,7 +790,7 @@ func calculateCPUPercentUnix(stats *types.StatsJSON) float64 {
}
func calculateMemPercentUnix(memStats types.MemoryStats) float64 {
memPercent := 0.0
memUsage := float64(memStats.Usage - memStats.Stats["cache"])
memUsage := float64(memStats.Usage)
memLimit := float64(memStats.Limit)
if memUsage > 0.0 && memLimit > 0.0 {
memPercent = (memUsage / memLimit) * 100.0
@@ -895,6 +894,7 @@ func loadCpuAndMem(client *client.Client, container string) dto.ContainerListSta
data.MemoryCache = stats.MemoryStats.Stats["cache"]
data.MemoryUsage = stats.MemoryStats.Usage
data.MemoryLimit = stats.MemoryStats.Limit
data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats)
return data
}
+6 -7
View File
@@ -11,7 +11,6 @@ import (
"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"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/jinzhu/copier"
@@ -33,7 +32,7 @@ type ICronjobService interface {
StartJob(cronjob *model.Cronjob) (string, error)
CleanRecord(req dto.CronjobClean) error
LoadRecordLog(req dto.OperateByID) (string, error)
LoadRecordLog(req dto.OperateByID) string
}
func NewICronjobService() ICronjobService {
@@ -77,19 +76,19 @@ func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interfac
return total, dtoCronjobs, err
}
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) (string, error) {
func (u *CronjobService) LoadRecordLog(req dto.OperateByID) string {
record, err := cronjobRepo.GetRecord(commonRepo.WithByID(req.ID))
if err != nil {
return "", err
return ""
}
if _, err := os.Stat(record.Records); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
return ""
}
content, err := os.ReadFile(record.Records)
if err != nil {
return "", err
return ""
}
return string(content), nil
return string(content)
}
func (u *CronjobService) CleanRecord(req dto.CronjobClean) error {
+1 -1
View File
@@ -109,7 +109,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
backupDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name))
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format("20060102150405")+common.RandStrAndNum(5))
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
if err := handleMysqlBackup(dbInfo.Database, dbInfo.Name, backupDir, record.FileName); err != nil {
if err := handleMysqlBackup(dbInfo.Database, dbInfo.DBType, dbInfo.Name, backupDir, record.FileName); err != nil {
return err
}
} else {
+4 -1
View File
@@ -82,7 +82,10 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
}
if err != nil {
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message))
if len(message) != 0 {
record.Records, _ = mkdirAndWriteFile(cronjob, record.StartTime, message)
}
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
return
}
if len(message) != 0 {
+26 -1
View File
@@ -22,6 +22,7 @@ import (
type DashboardService struct{}
type IDashboardService interface {
LoadOsInfo() (*dto.OsInfo, error)
LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error)
LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent
@@ -49,6 +50,27 @@ func (u *DashboardService) Restart(operation string) error {
return nil
}
func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) {
var baseInfo dto.OsInfo
hostInfo, err := host.Info()
if err != nil {
return nil, err
}
baseInfo.OS = hostInfo.OS
baseInfo.Platform = hostInfo.Platform
baseInfo.PlatformFamily = hostInfo.PlatformFamily
baseInfo.KernelArch = hostInfo.KernelArch
baseInfo.KernelVersion = hostInfo.KernelVersion
if baseInfo.KernelArch == "armv7l" {
baseInfo.KernelArch = "armv7"
}
if baseInfo.KernelArch == "x86_64" {
baseInfo.KernelArch = "amd64"
}
return &baseInfo, nil
}
func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) {
var baseInfo dto.DashboardBase
hostInfo, err := host.Info()
@@ -183,7 +205,10 @@ func loadDiskInfo() []dto.DiskInfo {
var datas []dto.DiskInfo
stdout, err := cmd.ExecWithTimeOut("df -hT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev", 2*time.Second)
if err != nil {
return datas
stdout, err = cmd.ExecWithTimeOut("df -lhT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev", 1*time.Second)
if err != nil {
return datas
}
}
lines := strings.Split(stdout, "\n")
+2 -2
View File
@@ -342,7 +342,7 @@ func (u *MysqlService) ChangePassword(req dto.ChangeDBInfo) error {
}
global.LOG.Infof("start to update mysql password used by app %s-%s", appModel.Key, appInstall.Name)
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, req.Value); err != nil {
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", req.Value); err != nil {
return err
}
}
@@ -355,7 +355,7 @@ func (u *MysqlService) ChangePassword(req dto.ChangeDBInfo) error {
return nil
}
if err := updateInstallInfoInDB(req.Type, req.Database, "password", false, req.Value); err != nil {
if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil {
return err
}
return nil
+2 -2
View File
@@ -393,7 +393,7 @@ func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
}
global.LOG.Infof("start to update postgresql password used by app %s-%s", appModel.Key, appInstall.Name)
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", true, req.Value); err != nil {
if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", req.Value); err != nil {
return err
}
}
@@ -406,7 +406,7 @@ func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error {
return nil
}
if err := updateInstallInfoInDB(req.Type, req.Database, "password", false, req.Value); err != nil {
if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil {
return err
}
return nil
+2 -2
View File
@@ -55,10 +55,10 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
}
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
if err := updateInstallInfoInDB("redis", "", "password", true, req.Value); err != nil {
if err := updateInstallInfoInDB("redis", "", "password", req.Value); err != nil {
return err
}
if err := updateInstallInfoInDB("redis-commander", "", "password", true, req.Value); err != nil {
if err := updateInstallInfoInDB("redis-commander", "", "password", req.Value); err != nil {
return err
}
+2 -2
View File
@@ -280,7 +280,7 @@ func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error {
if err := fileOp.WriteFile(path.Join(secretDir, "server.key.tmp"), strings.NewReader(key), 0600); err != nil {
return err
}
if err := checkCertValid(req.Domain); err != nil {
if err := checkCertValid(); err != nil {
return err
}
if err := fileOp.Rename(path.Join(secretDir, "server.crt.tmp"), path.Join(secretDir, "server.crt")); err != nil {
@@ -418,7 +418,7 @@ func loadInfoFromCert() (*dto.SSLInfo, error) {
}, nil
}
func checkCertValid(domain string) error {
func checkCertValid() error {
certificate, err := os.ReadFile(path.Join(global.CONF.System.BaseDir, "1panel/secret/server.crt.tmp"))
if err != nil {
return err
+69 -661
View File
@@ -6,8 +6,6 @@ import (
"fmt"
"os"
"path"
"regexp"
"strconv"
"strings"
"sync"
"time"
@@ -18,10 +16,10 @@ import (
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/compose"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/shirou/gopsutil/v3/host"
)
type SnapshotService struct {
@@ -74,7 +72,7 @@ func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error {
for _, snap := range req.Names {
shortName := strings.TrimPrefix(snap, "snapshot_")
nameItems := strings.Split(shortName, "_")
if !strings.HasPrefix(shortName, "1panel_v") || !strings.HasSuffix(shortName, ".tar.gz") || len(nameItems) != 3 {
if !strings.HasPrefix(shortName, "1panel_v") || !strings.HasSuffix(shortName, ".tar.gz") || len(nameItems) < 3 {
return fmt.Errorf("incorrect snapshot name format of %s", shortName)
}
if strings.HasSuffix(snap, ".tar.gz") {
@@ -137,26 +135,13 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
if err != nil {
return err
}
if hasOs(snap.Name) && !strings.Contains(snap.Name, loadOs()) {
return fmt.Errorf("Restoring snapshots(%s) between different server architectures(%s) is not supported.", snap.Name, loadOs())
}
if !req.IsNew && len(snap.InterruptStep) != 0 && len(snap.RollbackStatus) != 0 {
return fmt.Errorf("the snapshot has been rolled back and cannot be restored again")
}
isNewSnapshot := isNewSnapVersion(snap.Version)
isReTry := false
if len(snap.InterruptStep) != 0 && !req.IsNew {
isReTry = true
}
backup, err := backupRepo.Get(commonRepo.WithByType(snap.DefaultDownload))
if err != nil {
return err
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return err
}
localDir, err := loadLocalDir()
if err != nil {
return err
}
baseDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("system/%s", snap.Name))
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
_ = os.MkdirAll(baseDir, os.ModePerm)
@@ -164,152 +149,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"recover_status": constant.StatusWaiting})
_ = settingRepo.Update("SystemStatus", "Recovering")
go func() {
_ = global.Cron.Stop()
defer func() {
global.Cron.Start()
}()
operation := "recover"
if isReTry {
operation = "re-recover"
}
if !isReTry || snap.InterruptStep == "Download" || (isReTry && req.ReDownload) {
itemBackupPath := strings.TrimPrefix(backup.BackupPath, "/")
itemBackupPath = strings.TrimSuffix(itemBackupPath, "/")
ok, err := client.Download(fmt.Sprintf("%s/system_snapshot/%s.tar.gz", itemBackupPath, snap.Name), fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name))
if err != nil || !ok {
if req.ReDownload {
updateRecoverStatus(snap.ID, snap.InterruptStep, constant.StatusFailed, fmt.Sprintf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err))
return
}
updateRecoverStatus(snap.ID, "Download", constant.StatusFailed, fmt.Sprintf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err))
return
}
isReTry = false
}
fileOp := files.NewFileOp()
if !isReTry || snap.InterruptStep == "Decompress" || (isReTry && req.ReDownload) {
if err := handleUnTar(fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name), baseDir); err != nil {
if req.ReDownload {
updateRecoverStatus(snap.ID, snap.InterruptStep, constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
return
}
updateRecoverStatus(snap.ID, "Decompress", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
return
}
isReTry = false
}
rootDir := fmt.Sprintf("%s/%s", baseDir, snap.Name)
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", rootDir))
if err != nil {
updateRecoverStatus(snap.ID, "Readjson", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
return
}
if snap.InterruptStep == "Readjson" {
isReTry = false
}
u.OriginalPath = fmt.Sprintf("%s/1panel_original/original_%s", snapJson.BaseDir, snap.Name)
_ = os.MkdirAll(u.OriginalPath, os.ModePerm)
snapJson.OldBaseDir = global.CONF.System.BaseDir
snapJson.OldPanelDataDir = path.Join(global.CONF.System.BaseDir, "1panel")
snapJson.OldBackupDataDir = localDir
recoverPanelDir := fmt.Sprintf("%s/%s/1panel", baseDir, snap.Name)
liveRestore := false
if !isNewSnapshot {
if !isReTry || snap.InterruptStep == "LoadDockerJson" {
snapJson.OldDockerDataDir, liveRestore, err = u.loadDockerDataDir()
if err != nil {
updateRecoverStatus(snap.ID, "LoadDockerJson", constant.StatusFailed, fmt.Sprintf("load docker data dir failed, err: %v", err))
return
}
isReTry = false
}
if liveRestore {
if err := u.updateLiveRestore(false); err != nil {
updateRecoverStatus(snap.ID, "UpdateLiveRestore", constant.StatusFailed, fmt.Sprintf("update docker daemon.json live-restore conf failed, err: %v", err))
return
}
isReTry = false
}
_ = u.saveJson(snapJson, u.OriginalPath)
_, _ = cmd.Exec("systemctl stop docker")
if !isReTry || snap.InterruptStep == "DockerDir" {
if err := u.handleDockerDatas(fileOp, operation, rootDir, snapJson.DockerDataDir); err != nil {
updateRecoverStatus(snap.ID, "DockerDir", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
} else {
_ = u.saveJson(snapJson, u.OriginalPath)
if !isReTry || snap.InterruptStep == "DockerDir" {
if err := u.handleDockerDatasWithSave(fileOp, operation, rootDir, ""); err != nil {
updateRecoverStatus(snap.ID, "DockerDir", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
}
if !isReTry || snap.InterruptStep == "DaemonJson" {
if err := u.handleDaemonJson(fileOp, operation, rootDir+"/docker/daemon.json", u.OriginalPath); err != nil {
updateRecoverStatus(snap.ID, "DaemonJson", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
_, _ = cmd.Exec("systemctl restart docker")
if !isReTry || snap.InterruptStep == "1PanelBinary" {
if err := u.handlePanelBinary(fileOp, operation, recoverPanelDir+"/1panel", u.OriginalPath+"/1panel"); err != nil {
updateRecoverStatus(snap.ID, "1PanelBinary", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
if !isReTry || snap.InterruptStep == "1PctlBinary" {
if err := u.handlePanelctlBinary(fileOp, operation, recoverPanelDir+"/1pctl", u.OriginalPath+"/1pctl"); err != nil {
updateRecoverStatus(snap.ID, "1PctlBinary", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
if !isReTry || snap.InterruptStep == "1PanelService" {
if err := u.handlePanelService(fileOp, operation, recoverPanelDir+"/1panel.service", u.OriginalPath+"/1panel.service"); err != nil {
updateRecoverStatus(snap.ID, "1PanelService", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
if !isReTry || snap.InterruptStep == "1PanelBackups" {
if err := u.handleBackupDatas(fileOp, operation, rootDir, snapJson.BackupDataDir); err != nil {
updateRecoverStatus(snap.ID, "1PanelBackups", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
if !isReTry || snap.InterruptStep == "1PanelData" {
if err := u.handlePanelDatas(snap.ID, fileOp, operation, rootDir, snapJson.PanelDataDir, localDir, snapJson.OldDockerDataDir); err != nil {
updateRecoverStatus(snap.ID, "1PanelData", constant.StatusFailed, err.Error())
return
}
isReTry = false
}
if isNewSnapshot {
_ = rebuildAllAppInstall()
}
_ = os.RemoveAll(rootDir)
global.LOG.Info("recover successful")
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
}()
go u.HandleSnapshotRecover(snap, true, req)
return nil
}
@@ -319,133 +159,9 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
if err != nil {
return err
}
if snap.InterruptStep == "Download" || snap.InterruptStep == "Decompress" || snap.InterruptStep == "Readjson" {
return nil
}
localDir, err := loadLocalDir()
if err != nil {
return err
}
fileOp := files.NewFileOp()
isNewSnapshot := isNewSnapVersion(snap.Version)
rootDir := path.Join(localDir, fmt.Sprintf("system/%s/%s", snap.Name, snap.Name))
_ = settingRepo.Update("SystemStatus", "Rollbacking")
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"rollback_status": constant.StatusWaiting})
go func() {
_ = global.Cron.Stop()
defer func() {
global.Cron.Start()
}()
u.OriginalPath = fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
if _, err := os.Stat(u.OriginalPath); err != nil && os.IsNotExist(err) {
return
}
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", u.OriginalPath))
if err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, fmt.Sprintf("read snapjson file failed, err: %v", err))
return
}
if !isNewSnapshot {
_, _ = cmd.Exec("systemctl stop docker")
if err := u.handleDockerDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldDockerDataDir); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
defer func() {
_, _ = cmd.Exec("systemctl restart docker")
}()
if snap.InterruptStep == "DockerDir" {
return
}
if snapJson.LiveRestoreEnabled {
if err := u.updateLiveRestore(true); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
}
if snap.InterruptStep == "UpdateLiveRestore" {
return
}
} else {
if err := u.handleDockerDatasWithSave(fileOp, "rollback", u.OriginalPath, ""); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
defer func() {
_ = rebuildAllAppInstall()
}()
if snap.InterruptStep == "DockerDir" {
return
}
}
if err := u.handleDaemonJson(fileOp, "rollback", u.OriginalPath+"/daemon.json", ""); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if snap.InterruptStep == "DaemonJson" {
return
}
if err := u.handlePanelBinary(fileOp, "rollback", u.OriginalPath+"/1panel", ""); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if snap.InterruptStep == "1PanelBinary" {
return
}
if err := u.handlePanelctlBinary(fileOp, "rollback", u.OriginalPath+"/1pctl", ""); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if snap.InterruptStep == "1PctlBinary" {
return
}
if err := u.handlePanelService(fileOp, "rollback", u.OriginalPath+"/1panel.service", ""); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if snap.InterruptStep == "1PanelService" {
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
return
}
if err := u.handleBackupDatas(fileOp, "rollback", u.OriginalPath, snapJson.OldBackupDataDir); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if snap.InterruptStep == "1PanelBackups" {
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
return
}
if err := u.handlePanelDatas(snap.ID, fileOp, "rollback", u.OriginalPath, snapJson.OldPanelDataDir, "", ""); err != nil {
updateRollbackStatus(snap.ID, constant.StatusFailed, err.Error())
return
}
if snap.InterruptStep == "1PanelData" {
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
return
}
_ = os.RemoveAll(rootDir)
global.LOG.Info("rollback successful")
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
}()
return nil
}
func (u *SnapshotService) saveJson(snapJson SnapshotJson, path string) error {
remarkInfo, _ := json.MarshalIndent(snapJson, "", "\t")
if err := os.WriteFile(fmt.Sprintf("%s/snapshot.json", path), remarkInfo, 0640); err != nil {
return err
}
req.IsNew = false
snap.InterruptStep = "Readjson"
go u.HandleSnapshotRecover(snap, false, req)
return nil
}
@@ -477,9 +193,10 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
if req.ID == 0 {
versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion"))
name := fmt.Sprintf("1panel_%s_%s", versionItem.Value, timeNow)
name := fmt.Sprintf("1panel_%s_%s_%s", versionItem.Value, loadOs(), timeNow)
if isCronjob {
name = fmt.Sprintf("snapshot_1panel_%s_%s", versionItem.Value, timeNow)
name = fmt.Sprintf("snapshot_1panel_%s_%s_%s", versionItem.Value, loadOs(), timeNow)
}
rootDir = path.Join(localDir, "system", name)
@@ -499,6 +216,7 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
if err != nil {
return "", err
}
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusWaiting})
snapStatus, _ = snapshotRepo.GetStatus(snap.ID)
if snapStatus.ID == 0 {
snapStatus.SnapID = snap.ID
@@ -548,17 +266,23 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
return
}
snapPanelData(itemHelper, localDir, backupPanelDir)
if snapStatus.PanelData != constant.StatusDone {
snapPanelData(itemHelper, localDir, backupPanelDir)
}
if snapStatus.PanelData != constant.StatusDone {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
return
}
snapCompress(itemHelper, rootDir)
if snapStatus.Compress != constant.StatusDone {
snapCompress(itemHelper, rootDir)
}
if snapStatus.Compress != constant.StatusDone {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
return
}
snapUpload(itemHelper, req.From, fmt.Sprintf("%s.tar.gz", rootDir))
if snapStatus.Upload != constant.StatusDone {
snapUpload(itemHelper, req.From, fmt.Sprintf("%s.tar.gz", rootDir))
}
if snapStatus.Upload != constant.StatusDone {
_ = snapshotRepo.Update(snap.ID, map[string]interface{}{"status": constant.StatusFailed})
return
@@ -599,263 +323,6 @@ func (u *SnapshotService) HandleSnapshot(isCronjob bool, logPath string, req dto
return snap.Name, nil
}
func (u *SnapshotService) handleDockerDatas(fileOp files.FileOp, operation string, source, target string) error {
switch operation {
case "snapshot":
if err := handleSnapTar(source, target, "docker_data.tar.gz", ""); err != nil {
return fmt.Errorf("backup docker data failed, err: %v", err)
}
case "recover":
if err := handleSnapTar(target, u.OriginalPath, "docker_data.tar.gz", ""); err != nil {
return fmt.Errorf("backup docker data failed, err: %v", err)
}
if err := u.handleUnTar(source+"/docker/docker_data.tar.gz", target); err != nil {
return fmt.Errorf("recover docker data failed, err: %v", err)
}
case "re-recover":
if err := u.handleUnTar(source+"/docker/docker_data.tar.gz", target); err != nil {
return fmt.Errorf("re-recover docker data failed, err: %v", err)
}
case "rollback":
if err := u.handleUnTar(source+"/docker_data.tar.gz", target); err != nil {
return fmt.Errorf("rollback docker data failed, err: %v", err)
}
}
global.LOG.Info("handle docker data dir successful!")
return nil
}
func (u *SnapshotService) handleDockerDatasWithSave(fileOp files.FileOp, operation, source, target string) error {
switch operation {
case "snapshot":
var wg sync.WaitGroup
wg.Add(1)
var status model.SnapshotStatus
go snapAppData(snapHelper{Wg: &wg, Status: &status}, u.OriginalPath)
wg.Wait()
if status.AppData != constant.StatusDone {
return errors.New(status.AppData)
}
case "recover":
if err := u.handleDockerDatasWithSave(fileOp, "snapshot", "", u.OriginalPath); err != nil {
return fmt.Errorf("backup docker data failed, err: %v", err)
}
if _, err := os.Stat(path.Join(source, "docker/docker_image.tar")); err != nil {
global.LOG.Debug("no such docker images in snapshot")
return nil
}
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker/docker_image.tar"))
if err != nil {
return errors.New(std)
}
case "re-recover":
if _, err := os.Stat(path.Join(source, "docker/docker_image.tar")); err != nil {
global.LOG.Debug("no such docker images in snapshot")
return nil
}
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker/docker_image.tar"))
if err != nil {
return errors.New(std)
}
case "rollback":
if _, err := os.Stat(path.Join(source, "docker/docker_image.tar")); err != nil {
global.LOG.Debug("no such docker images in snapshot")
return nil
}
std, err := cmd.Execf("docker load < %s", path.Join(source, "docker_image.tar"))
if err != nil {
return errors.New(std)
}
}
global.LOG.Info("handle docker data successful!")
return nil
}
func (u *SnapshotService) handleDaemonJson(fileOp files.FileOp, operation string, source, target string) error {
daemonJsonPath := "/etc/docker/daemon.json"
if operation == "snapshot" || operation == "recover" {
_, err := os.Stat(daemonJsonPath)
if os.IsNotExist(err) {
global.LOG.Info("no daemon.json in snapshot and system now, nothing happened")
}
if err == nil {
if err := fileOp.CopyFile(daemonJsonPath, target); err != nil {
return fmt.Errorf("backup docker daemon.json failed, err: %v", err)
}
}
}
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
_, sourceErr := os.Stat(source)
if os.IsNotExist(sourceErr) {
_ = os.Remove(daemonJsonPath)
}
if sourceErr == nil {
if err := fileOp.CopyFile(source, "/etc/docker"); err != nil {
return fmt.Errorf("recover docker daemon.json failed, err: %v", err)
}
}
}
global.LOG.Info("handle docker daemon.json successful!")
return nil
}
func (u *SnapshotService) handlePanelBinary(fileOp files.FileOp, operation string, source, target string) error {
panelPath := "/usr/local/bin/1panel"
if operation == "snapshot" || operation == "recover" {
if _, err := os.Stat(panelPath); err != nil {
return fmt.Errorf("1panel binary is not found in %s, err: %v", panelPath, err)
} else {
if err := cpBinary([]string{panelPath}, target); err != nil {
return fmt.Errorf("backup 1panel binary failed, err: %v", err)
}
}
}
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
if _, err := os.Stat(source); err != nil {
return fmt.Errorf("1panel binary is not found in snapshot, err: %v", err)
} else {
if err := cpBinary([]string{source}, "/usr/local/bin/1panel"); err != nil {
return fmt.Errorf("recover 1panel binary failed, err: %v", err)
}
}
}
global.LOG.Info("handle binary panel successful!")
return nil
}
func (u *SnapshotService) handlePanelctlBinary(fileOp files.FileOp, operation string, source, target string) error {
panelctlPath := "/usr/local/bin/1pctl"
if operation == "snapshot" || operation == "recover" {
if _, err := os.Stat(panelctlPath); err != nil {
return fmt.Errorf("1pctl binary is not found in %s, err: %v", panelctlPath, err)
} else {
if err := cpBinary([]string{panelctlPath}, target); err != nil {
return fmt.Errorf("backup 1pctl binary failed, err: %v", err)
}
}
}
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
if _, err := os.Stat(source); err != nil {
return fmt.Errorf("1pctl binary is not found in snapshot, err: %v", err)
} else {
if err := cpBinary([]string{source}, "/usr/local/bin/1pctl"); err != nil {
return fmt.Errorf("recover 1pctl binary failed, err: %v", err)
}
}
}
global.LOG.Info("handle binary 1pactl successful!")
return nil
}
func (u *SnapshotService) handlePanelService(fileOp files.FileOp, operation string, source, target string) error {
panelServicePath := "/etc/systemd/system/1panel.service"
if operation == "snapshot" || operation == "recover" {
if _, err := os.Stat(panelServicePath); err != nil {
return fmt.Errorf("1panel service is not found in %s, err: %v", panelServicePath, err)
} else {
if err := cpBinary([]string{panelServicePath}, target); err != nil {
return fmt.Errorf("backup 1panel service failed, err: %v", err)
}
}
}
if operation == "recover" || operation == "rollback" || operation == "re-recover" {
if _, err := os.Stat(source); err != nil {
return fmt.Errorf("1panel service is not found in snapshot, err: %v", err)
} else {
if err := cpBinary([]string{source}, "/etc/systemd/system/1panel.service"); err != nil {
return fmt.Errorf("recover 1panel service failed, err: %v", err)
}
}
}
global.LOG.Info("handle panel service successful!")
return nil
}
func (u *SnapshotService) handleBackupDatas(fileOp files.FileOp, operation string, source, target string) error {
switch operation {
case "recover":
if err := handleSnapTar(target, u.OriginalPath, "1panel_backup.tar.gz", "./system;"); err != nil {
return fmt.Errorf("restore original local backup dir data failed, err: %v", err)
}
if err := u.handleUnTar(source+"/1panel/1panel_backup.tar.gz", target); err != nil {
return fmt.Errorf("recover local backup dir data failed, err: %v", err)
}
case "re-recover":
if err := u.handleUnTar(source+"/1panel/1panel_backup.tar.gz", target); err != nil {
return fmt.Errorf("retry recover local backup dir data failed, err: %v", err)
}
case "rollback":
if err := u.handleUnTar(source+"/1panel_backup.tar.gz", target); err != nil {
return fmt.Errorf("rollback local backup dir data failed, err: %v", err)
}
}
global.LOG.Info("handle backup data successful!")
return nil
}
func (u *SnapshotService) handlePanelDatas(snapID uint, fileOp files.FileOp, operation string, source, target, backupDir, dockerDir string) error {
switch operation {
case "snapshot":
exclusionRules := "./tmp;./log;./cache;"
if strings.Contains(backupDir, source) {
exclusionRules += ("." + strings.ReplaceAll(backupDir, source, "") + ";")
}
if strings.Contains(dockerDir, source) {
exclusionRules += ("." + strings.ReplaceAll(dockerDir, source, "") + ";")
}
if err := handleSnapTar(source, target, "1panel_data.tar.gz", exclusionRules); err != nil {
return fmt.Errorf("backup panel data failed, err: %v", err)
}
case "recover":
exclusionRules := "./tmp;./log;./cache;./db/1Panel.db-*;"
if strings.Contains(backupDir, target) {
exclusionRules += ("." + strings.ReplaceAll(backupDir, target, "") + ";")
}
if strings.Contains(dockerDir, target) {
exclusionRules += ("." + strings.ReplaceAll(dockerDir, target, "") + ";")
}
_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": ""})
checkPointOfWal()
if err := handleSnapTar(target, u.OriginalPath, "1panel_data.tar.gz", exclusionRules); err != nil {
return fmt.Errorf("restore original panel data failed, err: %v", err)
}
_ = snapshotRepo.Update(snapID, map[string]interface{}{"recover_status": constant.StatusWaiting})
_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
checkPointOfWal()
if err := u.handleUnTar(source+"/1panel/1panel_data.tar.gz", target); err != nil {
return fmt.Errorf("recover panel data failed, err: %v", err)
}
case "re-recover":
_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
checkPointOfWal()
if err := u.handleUnTar(source+"/1panel/1panel_data.tar.gz", target); err != nil {
return fmt.Errorf("retry recover panel data failed, err: %v", err)
}
case "rollback":
_ = fileOp.Fs.RemoveAll(path.Join(target, "apps"))
checkPointOfWal()
if err := u.handleUnTar(source+"/1panel_data.tar.gz", target); err != nil {
return fmt.Errorf("rollback panel data failed, err: %v", err)
}
}
return nil
}
func (u *SnapshotService) loadDockerDataDir() (string, bool, error) {
client, err := docker.NewDockerClient()
if err != nil {
return "", false, fmt.Errorf("new docker client failed, err: %v", err)
}
info, err := client.Info(context.Background())
if err != nil {
return "", false, fmt.Errorf("load docker info failed, err: %v", err)
}
return info.DockerRootDir, info.LiveRestoreEnabled, nil
}
func (u *SnapshotService) Delete(req dto.BatchDeleteReq) error {
backups, _ := snapshotRepo.GetList(commonRepo.WithIdsIn(req.Ids))
localDir, err := loadLocalDir()
@@ -878,21 +345,22 @@ func (u *SnapshotService) Delete(req dto.BatchDeleteReq) error {
return nil
}
func updateRecoverStatus(id uint, interruptStep, status string, message string) {
if status != constant.StatusSuccess {
global.LOG.Errorf("recover failed, err: %s", message)
func updateRecoverStatus(id uint, isRecover bool, interruptStep, status, message string) {
if isRecover {
if status != constant.StatusSuccess {
global.LOG.Errorf("recover failed, err: %s", message)
}
if err := snapshotRepo.Update(id, map[string]interface{}{
"interrupt_step": interruptStep,
"recover_status": status,
"recover_message": message,
"last_recovered_at": time.Now().Format("2006-01-02 15:04:05"),
}); err != nil {
global.LOG.Errorf("update snap recover status failed, err: %v", err)
}
_ = settingRepo.Update("SystemStatus", "Free")
return
}
if err := snapshotRepo.Update(id, map[string]interface{}{
"interrupt_step": interruptStep,
"recover_status": status,
"recover_message": message,
"last_recovered_at": time.Now().Format("2006-01-02 15:04:05"),
}); err != nil {
global.LOG.Errorf("update snap recover status failed, err: %v", err)
}
_ = settingRepo.Update("SystemStatus", "Free")
}
func updateRollbackStatus(id uint, status string, message string) {
_ = settingRepo.Update("SystemStatus", "Free")
if status == constant.StatusSuccess {
if err := snapshotRepo.Update(id, map[string]interface{}{
@@ -926,53 +394,6 @@ func cpBinary(src []string, dst string) error {
return nil
}
func (u *SnapshotService) updateLiveRestore(enabled bool) error {
if _, err := os.Stat(constant.DaemonJsonPath); err != nil {
return fmt.Errorf("load docker daemon.json conf failed, err: %v", err)
}
file, err := os.ReadFile(constant.DaemonJsonPath)
if err != nil {
return err
}
daemonMap := make(map[string]interface{})
_ = json.Unmarshal(file, &daemonMap)
if !enabled {
delete(daemonMap, "live-restore")
} else {
daemonMap["live-restore"] = enabled
}
newJson, err := json.MarshalIndent(daemonMap, "", "\t")
if err != nil {
return err
}
if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil {
return err
}
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
return errors.New(stdout)
}
ticker := time.NewTicker(3 * time.Second)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
for range ticker.C {
select {
case <-ctx.Done():
return errors.New("the docker service cannot be restarted")
default:
stdout, err := cmd.Exec("systemctl is-active docker")
if string(stdout) == "active\n" && err == nil {
global.LOG.Info("docker restart with new live-restore successful!")
return nil
}
}
}
return nil
}
func (u *SnapshotService) handleUnTar(sourceDir, targetDir string) error {
if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(targetDir, os.ModePerm); err != nil {
@@ -1025,64 +446,32 @@ func rebuildAllAppInstall() error {
return nil
}
func isNewSnapVersion(version string) bool {
versionItem := "v1.5.0"
if version == versionItem {
return true
}
version1s := strings.Split(version, ".")
version2s := strings.Split(versionItem, ".")
n := min(len(version1s), len(version2s))
re := regexp.MustCompile("[0-9]+")
for i := 0; i < n; i++ {
sVersion1s := re.FindAllString(version1s[i], -1)
sVersion2s := re.FindAllString(version2s[i], -1)
if len(sVersion1s) == 0 {
return false
}
if len(sVersion2s) == 0 {
return false
}
v1num, _ := strconv.Atoi(sVersion1s[0])
v2num, _ := strconv.Atoi(sVersion2s[0])
if v1num == v2num {
continue
} else {
return v1num > v2num
}
}
return true
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func checkIsAllDone(snapID uint) bool {
status, err := snapshotRepo.GetStatus(snapID)
if err != nil {
return false
}
isOK, _ := checkAllDone(status)
return isOK
}
func checkAllDone(status model.SnapshotStatus) (bool, string) {
if status.Panel != constant.StatusDone {
return false
return false, status.Panel
}
if status.PanelInfo != constant.StatusDone {
return false
return false, status.PanelInfo
}
if status.DaemonJson != constant.StatusDone {
return false
return false, status.DaemonJson
}
if status.AppData != constant.StatusDone {
return false
return false, status.AppData
}
if status.BackupData != constant.StatusDone {
return false
return false, status.BackupData
}
return true
return true, ""
}
func loadLogByStatus(status model.SnapshotStatus, logPath string) {
@@ -1099,9 +488,28 @@ func loadLogByStatus(status model.SnapshotStatus, logPath string) {
file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
global.LOG.Errorf("write snapshot logs failed, err: %v", err)
return
}
defer file.Close()
_, _ = file.Write([]byte(logs))
}
func hasOs(name string) bool {
return strings.Contains(name, "amd64") ||
strings.Contains(name, "arm64") ||
strings.Contains(name, "armv7") ||
strings.Contains(name, "ppc64le") ||
strings.Contains(name, "s390x")
}
func loadOs() string {
hostInfo, _ := host.Info()
switch hostInfo.KernelArch {
case "x86_64":
return "amd64"
case "armv7l":
return "armv7"
default:
return hostInfo.KernelArch
}
}
+3
View File
@@ -140,6 +140,8 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
}
_ = snapshotRepo.Update(snap.SnapID, map[string]interface{}{"status": "OnSaveData"})
sysIP, _ := settingRepo.Get(settingRepo.WithByKey("SystemIP"))
_ = settingRepo.Update("SystemIP", "")
checkPointOfWal()
if err := handleSnapTar(dataDir, targetDir, "1panel_data.tar.gz", exclusionRules); err != nil {
status = err.Error()
@@ -148,6 +150,7 @@ func snapPanelData(snap snapHelper, localDir, targetDir string) {
snap.Status.PanelData = status
_ = snapshotRepo.UpdateStatus(snap.Status.ID, map[string]interface{}{"panel_data": status})
_ = settingRepo.Update("SystemIP", sysIP.Value)
}
func snapCompress(snap snapHelper, rootDir string) {
+263
View File
@@ -0,0 +1,263 @@
package service
import (
"context"
"fmt"
"os"
"path"
"strings"
"sync"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"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/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/pkg/errors"
)
func (u *SnapshotService) HandleSnapshotRecover(snap model.Snapshot, isRecover bool, req dto.SnapshotRecover) {
_ = global.Cron.Stop()
defer func() {
global.Cron.Start()
}()
snapFileDir := ""
if isRecover {
baseDir := path.Join(global.CONF.System.TmpDir, fmt.Sprintf("system/%s", snap.Name))
if _, err := os.Stat(baseDir); err != nil && os.IsNotExist(err) {
_ = os.MkdirAll(baseDir, os.ModePerm)
}
if req.IsNew || snap.InterruptStep == "Backup" {
if err := backupBeforeRecover(snap); err != nil {
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, fmt.Sprintf("handle backup before recover failed, err: %v", err))
return
}
global.LOG.Debug("handle backup before recover successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "Download" || req.ReDownload {
if err := handleDownloadSnapshot(snap, baseDir); err != nil {
updateRecoverStatus(snap.ID, isRecover, "Backup", constant.StatusFailed, err.Error())
return
}
global.LOG.Debugf("download snapshot file to %s successful!", baseDir)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "Decompress" {
if err := handleUnTar(fmt.Sprintf("%s/%s.tar.gz", baseDir, snap.Name), baseDir); err != nil {
updateRecoverStatus(snap.ID, isRecover, "Decompress", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
return
}
global.LOG.Debug("decompress snapshot file successful!", baseDir)
req.IsNew = true
}
snapFileDir = fmt.Sprintf("%s/%s", baseDir, snap.Name)
} else {
snapFileDir = fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
if _, err := os.Stat(snapFileDir); err != nil {
updateRecoverStatus(snap.ID, isRecover, "", constant.StatusFailed, fmt.Sprintf("cannot find the backup file %s, please try to recover again.", snapFileDir))
return
}
}
snapJson, err := u.readFromJson(fmt.Sprintf("%s/snapshot.json", snapFileDir))
if err != nil {
updateRecoverStatus(snap.ID, isRecover, "Readjson", constant.StatusFailed, fmt.Sprintf("decompress file failed, err: %v", err))
return
}
if snap.InterruptStep == "Readjson" {
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "AppData" {
if err := recoverAppData(snapFileDir); err != nil {
updateRecoverStatus(snap.ID, isRecover, "DockerDir", constant.StatusFailed, fmt.Sprintf("handle recover app data failed, err: %v", err))
return
}
global.LOG.Debug("recover app data from snapshot file successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "DaemonJson" {
fileOp := files.NewFileOp()
if err := recoverDaemonJson(snapFileDir, fileOp); err != nil {
updateRecoverStatus(snap.ID, isRecover, "DaemonJson", constant.StatusFailed, err.Error())
return
}
global.LOG.Debug("recover daemon.json from snapshot file successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "1PanelBinary" {
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel"), "/usr/local/bin/1panel"); err != nil {
updateRecoverStatus(snap.ID, isRecover, "1PanelBinary", constant.StatusFailed, err.Error())
return
}
global.LOG.Debug("recover 1panel binary from snapshot file successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "1PctlBinary" {
if err := recoverPanel(path.Join(snapFileDir, "1panel/1pctl"), "/usr/local/bin/1pctl"); err != nil {
updateRecoverStatus(snap.ID, isRecover, "1PctlBinary", constant.StatusFailed, err.Error())
return
}
global.LOG.Debug("recover 1pctl from snapshot file successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "1PanelService" {
if err := recoverPanel(path.Join(snapFileDir, "1panel/1panel.service"), "/etc/systemd/system/1panel.service"); err != nil {
updateRecoverStatus(snap.ID, isRecover, "1PanelService", constant.StatusFailed, err.Error())
return
}
global.LOG.Debug("recover 1panel service from snapshot file successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "1PanelBackups" {
if err := u.handleUnTar(path.Join(snapFileDir, "/1panel/1panel_backup.tar.gz"), snapJson.BackupDataDir); err != nil {
updateRecoverStatus(snap.ID, isRecover, "1PanelBackups", constant.StatusFailed, err.Error())
return
}
global.LOG.Debug("recover 1panel backups from snapshot file successful!")
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "1PanelData" {
checkPointOfWal()
if err := u.handleUnTar(path.Join(snapFileDir, "/1panel/1panel_data.tar.gz"), path.Join(snapJson.BaseDir, "1panel")); err != nil {
updateRecoverStatus(snap.ID, isRecover, "1PanelData", constant.StatusFailed, err.Error())
return
}
global.LOG.Debug("recover 1panel data from snapshot file successful!")
req.IsNew = true
}
_ = rebuildAllAppInstall()
restartCompose(path.Join(snapJson.BaseDir, "1panel/docker/compose"))
global.LOG.Info("recover successful")
if !isRecover {
oriPath := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
global.LOG.Debugf("remove the file %s after the operation is successful", oriPath)
_ = os.RemoveAll(oriPath)
} else {
global.LOG.Debugf("remove the file %s after the operation is successful", path.Dir(snapFileDir))
_ = os.RemoveAll(path.Dir(snapFileDir))
}
_, _ = cmd.Exec("systemctl daemon-reload && systemctl restart 1panel.service")
}
func backupBeforeRecover(snap model.Snapshot) error {
baseDir := fmt.Sprintf("%s/1panel_original/original_%s", global.CONF.System.BaseDir, snap.Name)
var wg sync.WaitGroup
var status model.SnapshotStatus
itemHelper := snapHelper{SnapID: 0, Status: &status, Wg: &wg, FileOp: files.NewFileOp(), Ctx: context.Background()}
jsonItem := SnapshotJson{
BaseDir: global.CONF.System.BaseDir,
BackupDataDir: global.CONF.System.Backup,
PanelDataDir: path.Join(global.CONF.System.BaseDir, "1panel"),
}
_ = os.MkdirAll(path.Join(baseDir, "1panel"), os.ModePerm)
_ = os.MkdirAll(path.Join(baseDir, "docker"), os.ModePerm)
wg.Add(5)
itemHelper.Wg = &wg
go snapJson(itemHelper, jsonItem, baseDir)
go snapPanel(itemHelper, path.Join(baseDir, "1panel"))
go snapDaemonJson(itemHelper, path.Join(baseDir, "docker"))
go snapAppData(itemHelper, path.Join(baseDir, "docker"))
go snapBackup(itemHelper, global.CONF.System.Backup, path.Join(baseDir, "1panel"))
wg.Wait()
allDone, msg := checkAllDone(status)
if !allDone {
return errors.New(msg)
}
snapPanelData(itemHelper, global.CONF.System.BaseDir, path.Join(baseDir, "1panel"))
if status.PanelData != constant.StatusDone {
return errors.New(status.PanelData)
}
return nil
}
func handleDownloadSnapshot(snap model.Snapshot, targetDir string) error {
backup, err := backupRepo.Get(commonRepo.WithByType(snap.DefaultDownload))
if err != nil {
return err
}
client, err := NewIBackupService().NewClient(&backup)
if err != nil {
return err
}
pathItem := backup.BackupPath
if backup.BackupPath != "/" {
pathItem = strings.TrimPrefix(backup.BackupPath, "/")
}
filePath := fmt.Sprintf("%s/%s.tar.gz", targetDir, snap.Name)
_ = os.RemoveAll(filePath)
ok, err := client.Download(path.Join(pathItem, fmt.Sprintf("system_snapshot/%s.tar.gz", snap.Name)), filePath)
if err != nil || !ok {
return fmt.Errorf("download file %s from %s failed, err: %v", snap.Name, backup.Type, err)
}
return nil
}
func recoverAppData(src string) error {
if _, err := os.Stat(path.Join(src, "docker/docker_image.tar")); err != nil {
global.LOG.Debug("no such docker images in snapshot")
return nil
}
std, err := cmd.Execf("docker load < %s", path.Join(src, "docker/docker_image.tar"))
if err != nil {
return errors.New(std)
}
return err
}
func recoverDaemonJson(src string, fileOp files.FileOp) error {
daemonJsonPath := "/etc/docker/daemon.json"
_, errSrc := os.Stat(src)
_, errPath := os.Stat(daemonJsonPath)
if os.IsNotExist(errSrc) && os.IsNotExist(errPath) {
global.LOG.Debug("the daemon.json file does not exist, nothing happens.")
return nil
}
if errSrc == nil {
if err := fileOp.CopyFile(path.Join(src, "docker/daemon.json"), "/etc/docker"); err != nil {
return fmt.Errorf("recover docker daemon.json failed, err: %v", err)
}
}
_, _ = cmd.Exec("systemctl restart docker")
return nil
}
func recoverPanel(src string, dst string) error {
if _, err := os.Stat(src); err != nil {
return fmt.Errorf("file is not found in %s, err: %v", src, err)
}
global.LOG.Debugf(fmt.Sprintf("\\cp -f %s %s", src, dst))
stdout, err := cmd.Exec(fmt.Sprintf("\\cp -f %s %s", src, dst))
if err != nil {
return fmt.Errorf("cp file failed, stdout: %v, err: %v", stdout, err)
}
return nil
}
func restartCompose(composePath string) {
composes, err := composeRepo.ListRecord()
if err != nil {
return
}
for _, compose := range composes {
pathItem := path.Join(composePath, compose.Name, "docker-compose.yml")
if _, err := os.Stat(pathItem); err != nil {
continue
}
upCmd := fmt.Sprintf("docker-compose -f %s up -d", pathItem)
stdout, err := cmd.Exec(upCmd)
if err != nil {
global.LOG.Debugf("%s failed, err: %v", upCmd, stdout)
}
}
global.LOG.Debug("restart all compose successful!")
}
+5 -5
View File
@@ -133,24 +133,24 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error {
if err := cpBinary([]string{tmpDir + "/1panel"}, "/usr/local/bin/1panel"); err != nil {
global.LOG.Errorf("upgrade 1panel failed, err: %v", err)
u.handleRollback(fileOp, originalDir, 1)
u.handleRollback(originalDir, 1)
return
}
if err := cpBinary([]string{tmpDir + "/1pctl"}, "/usr/local/bin/1pctl"); err != nil {
global.LOG.Errorf("upgrade 1pctl failed, err: %v", err)
u.handleRollback(fileOp, originalDir, 2)
u.handleRollback(originalDir, 2)
return
}
if _, err := cmd.Execf("sed -i -e 's#BASE_DIR=.*#BASE_DIR=%s#g' /usr/local/bin/1pctl", global.CONF.System.BaseDir); err != nil {
global.LOG.Errorf("upgrade basedir in 1pctl failed, err: %v", err)
u.handleRollback(fileOp, originalDir, 2)
u.handleRollback(originalDir, 2)
return
}
if err := cpBinary([]string{tmpDir + "/1panel.service"}, "/etc/systemd/system/1panel.service"); err != nil {
global.LOG.Errorf("upgrade 1panel.service failed, err: %v", err)
u.handleRollback(fileOp, originalDir, 3)
u.handleRollback(originalDir, 3)
return
}
@@ -181,7 +181,7 @@ func (u *UpgradeService) handleBackup(fileOp files.FileOp, originalDir string) e
return nil
}
func (u *UpgradeService) handleRollback(fileOp files.FileOp, originalDir string, errStep int) {
func (u *UpgradeService) handleRollback(originalDir string, errStep int) {
dbPath := global.CONF.System.DbPath + "/1Panel.db"
_ = settingRepo.Update("SystemStatus", "Free")
if err := cpBinary([]string{originalDir + "/1Panel.db"}, dbPath); err != nil {
+10 -9
View File
@@ -2,13 +2,6 @@ package service
import (
"fmt"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
"gopkg.in/yaml.v3"
"log"
"os"
"path"
@@ -18,6 +11,14 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/i18n"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/1Panel-dev/1Panel/backend/utils/nginx/components"
"gopkg.in/yaml.v3"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@@ -114,7 +115,7 @@ func createIndexFile(website *model.Website, runtime *model.Runtime) error {
return nil
}
func createProxyFile(website *model.Website, runtime *model.Runtime) error {
func createProxyFile(website *model.Website) error {
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
if err != nil {
return err
@@ -191,7 +192,7 @@ func createWebsiteFolder(nginxInstall model.AppInstall, website *model.Website,
}
}
if website.Type == constant.Proxy {
if err := createProxyFile(website, runtime); err != nil {
if err := createProxyFile(website); err != nil {
return err
}
}
+1
View File
@@ -62,6 +62,7 @@ var (
ErrAppNameExist = "ErrAppNameExist"
ErrFileNotFound = "ErrFileNotFound"
ErrFileParseApp = "ErrFileParseApp"
ErrAppParamKey = "ErrAppParamKey"
)
// website
+1
View File
@@ -60,6 +60,7 @@ ErrAppNameExist: 'App name is already exist'
AppStoreIsSyncing: 'The App Store is syncing, please try again later'
ErrGetCompose: "Failed to obtain docker-compose.yml file! {{ .detail }}"
ErrAppWarn: "Abnormal status, please check the log"
ErrAppParamKey: "Parameter {{ .name }} field exception"
#file
ErrFileCanNotRead: "File can not read"
+1
View File
@@ -60,6 +60,7 @@ ErrAppNameExist: '應用名稱已存在'
AppStoreIsSyncing: '應用程式商店正在同步中,請稍後再試'
ErrGetCompose: "docker-compose.yml 檔案取得失敗!{{ .detail }}"
ErrAppWarn: "狀態異常,請查看日誌"
ErrAppParamKey: "參數 {{ .name }} 欄位異常"
#file
ErrFileCanNotRead: "此文件不支持預覽"
+1
View File
@@ -60,6 +60,7 @@ ErrAppNameExist: '应用名称已存在'
AppStoreIsSyncing: '应用商店正在同步中,请稍后再试'
ErrGetCompose: "docker-compose.yml 文件获取失败!{{ .detail }}"
ErrAppWarn: "状态异常,请查看日志"
ErrAppParamKey: "参数 {{ .name }} 字段异常"
#file
ErrFileCanNotRead: "此文件不支持预览"
+1
View File
@@ -16,6 +16,7 @@ func (s *DashboardRouter) InitRouter(Router *gin.RouterGroup) {
Use(middleware.PasswordExpired())
baseApi := v1.ApiGroupApp.BaseApi
{
cmdRouter.GET("/base/os", baseApi.LoadDashboardOsInfo)
cmdRouter.GET("/base/:ioOption/:netOption", baseApi.LoadDashboardBaseInfo)
cmdRouter.GET("/current/:ioOption/:netOption", baseApi.LoadDashboardCurrentInfo)
cmdRouter.POST("/system/restart/:operation", baseApi.SystemRestart)
@@ -116,7 +116,7 @@ func (o oneDriveClient) Upload(src, target string) (bool, error) {
}
var isOk bool
if fileInfo.Size() < 4*1024*1024 {
isOk, err = o.upSmall(ctx, src, folderID, fileInfo.Size())
isOk, err = o.upSmall(src, folderID, fileInfo.Size())
} else {
isOk, err = o.upBig(ctx, src, folderID, fileInfo.Size())
}
@@ -300,7 +300,7 @@ func (o *oneDriveClient) NewSessionFileUploadRequest(absoluteUrl string, grandOf
return req, err
}
func (o *oneDriveClient) upSmall(ctx context.Context, srcPath, folderID string, fileSize int64) (bool, error) {
func (o *oneDriveClient) upSmall(srcPath, folderID string, fileSize int64) (bool, error) {
file, err := os.Open(srcPath)
if err != nil {
return false, err
+51 -39
View File
@@ -2,7 +2,6 @@ package cmd
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
@@ -14,20 +13,7 @@ import (
)
func Exec(cmdStr string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
cmd := exec.Command("bash", "-c", cmdStr)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
return "", buserr.New(constant.ErrCmdTimeout)
}
if err != nil {
return handleErr(stdout, stderr, err)
}
return stdout.String(), nil
return ExecWithTimeOut(cmdStr, 20*time.Second)
}
func handleErr(stdout, stderr bytes.Buffer, err error) (string, error) {
@@ -46,19 +32,28 @@ func handleErr(stdout, stderr bytes.Buffer, err error) (string, error) {
}
func ExecWithTimeOut(cmdStr string, timeout time.Duration) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.Command("bash", "-c", cmdStr)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
if err := cmd.Start(); err != nil {
return "", err
}
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
after := time.After(timeout)
select {
case <-after:
_ = cmd.Process.Kill()
return "", buserr.New(constant.ErrCmdTimeout)
case err := <-done:
if err != nil {
return handleErr(stdout, stderr, err)
}
}
if err != nil {
return handleErr(stdout, stderr, err)
}
return stdout.String(), nil
}
@@ -75,9 +70,6 @@ func ExecContainerScript(containerName, cmdStr string, timeout time.Duration) er
}
func ExecCronjobWithTimeOut(cmdStr, workdir, outPath string, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
file, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return err
@@ -88,13 +80,24 @@ func ExecCronjobWithTimeOut(cmdStr, workdir, outPath string, timeout time.Durati
cmd.Dir = workdir
cmd.Stdout = file
cmd.Stderr = file
err = cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
return buserr.New(constant.ErrCmdTimeout)
if err := cmd.Start(); err != nil {
return err
}
return err
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
after := time.After(timeout)
select {
case <-after:
_ = cmd.Process.Kill()
return buserr.New(constant.ErrCmdTimeout)
case err := <-done:
if err != nil {
return err
}
}
return nil
}
func Execf(cmdStr string, a ...interface{}) (string, error) {
@@ -122,20 +125,29 @@ func ExecWithCheck(name string, a ...string) (string, error) {
}
func ExecScript(scriptPath, workDir string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
cmd := exec.Command("bash", scriptPath)
cmd.Dir = workDir
var stdout, stderr bytes.Buffer
cmd.Dir = workDir
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if ctx.Err() == context.DeadlineExceeded {
if err := cmd.Start(); err != nil {
return "", err
}
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
after := time.After(10 * time.Minute)
select {
case <-after:
_ = cmd.Process.Kill()
return "", buserr.New(constant.ErrCmdTimeout)
case err := <-done:
if err != nil {
return handleErr(stdout, stderr, err)
}
}
if err != nil {
return handleErr(stdout, stderr, err)
}
return stdout.String(), nil
}
+8 -7
View File
@@ -3,6 +3,7 @@ package files
import (
"bufio"
"github.com/spf13/afero"
"io"
"net/http"
"os"
"os/user"
@@ -87,16 +88,20 @@ func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error)
}
defer file.Close()
scanner := bufio.NewScanner(file)
reader := bufio.NewReaderSize(file, 8192)
var lines []string
currentLine := 0
startLine := (page - 1) * pageSize
endLine := startLine + pageSize
for scanner.Scan() {
for {
line, _, err := reader.ReadLine()
if err == io.EOF {
break
}
if currentLine >= startLine && currentLine < endLine {
lines = append(lines, scanner.Text())
lines = append(lines, string(line))
}
currentLine++
if currentLine >= endLine {
@@ -104,10 +109,6 @@ func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error)
}
}
if err := scanner.Err(); err != nil {
return nil, false, err
}
isEndOfFile := currentLine < endLine
return lines, isEndOfFile, nil
+4
View File
@@ -70,6 +70,8 @@ type AccessChangeInfo struct {
type BackupInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Version string `json:"version"`
Format string `json:"format"`
TargetDir string `json:"targetDir"`
FileName string `json:"fileName"`
@@ -79,6 +81,8 @@ type BackupInfo struct {
type RecoverInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Version string `json:"version"`
Format string `json:"format"`
SourceFile string `json:"sourceFile"`
+90 -37
View File
@@ -1,11 +1,14 @@
package client
import (
"compress/gzip"
"context"
"database/sql"
"errors"
"fmt"
"os"
"os/exec"
"path"
"strings"
"time"
@@ -13,7 +16,8 @@ import (
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/1Panel-dev/1Panel/backend/utils/mysql/helper"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
)
type Remote struct {
@@ -229,56 +233,63 @@ func (r *Remote) Backup(info BackupInfo) error {
return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err)
}
}
fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz")
tlsItem, err := ConnWithSSL(r.SSL, r.SkipVerify, r.ClientKey, r.ClientCert, r.RootCert)
outfile, _ := os.OpenFile(path.Join(info.TargetDir, info.FileName), os.O_RDWR|os.O_CREATE, 0755)
global.LOG.Infof("start to mysqldump | gzip > %s.gzip", info.TargetDir+"/"+info.FileName)
image, err := loadImage(info.Type, info.Version)
if err != nil {
return err
}
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai%s", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F", tlsItem)
backupCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'mysqldump -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'",
image, r.Address, r.Port, r.User, r.Password, sslSkip(info.Version), info.Format, info.Name)
f, _ := os.OpenFile(fileNameItem, os.O_RDWR|os.O_CREATE, 0755)
defer f.Close()
if err := helper.Dump(dns, helper.WithData(), helper.WithDropTable(), helper.WithWriter(f)); err != nil {
return err
}
global.LOG.Debug(backupCmd)
cmd := exec.Command("bash", "-c", backupCmd)
gzipCmd := exec.Command("gzip", fileNameItem)
stdout, err := gzipCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("gzip file %s failed, stdout: %v, err: %v", strings.TrimSuffix(info.FileName, ".gz"), string(stdout), err)
}
gzipCmd := exec.Command("gzip", "-cf")
gzipCmd.Stdin, _ = cmd.StdoutPipe()
gzipCmd.Stdout = outfile
_ = gzipCmd.Start()
_ = cmd.Run()
_ = gzipCmd.Wait()
return nil
}
func (r *Remote) Recover(info RecoverInfo) error {
fileName := info.SourceFile
if strings.HasSuffix(info.SourceFile, ".sql.gz") {
fileName = strings.TrimSuffix(info.SourceFile, ".gz")
gzipCmd := exec.Command("gunzip", info.SourceFile)
stdout, err := gzipCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("gunzip file %s failed, stdout: %v, err: %v", info.SourceFile, string(stdout), err)
}
defer func() {
gzipCmd := exec.Command("gzip", fileName)
_, _ = gzipCmd.CombinedOutput()
}()
}
tlsItem, err := ConnWithSSL(r.SSL, r.SkipVerify, r.ClientKey, r.ClientCert, r.RootCert)
if err != nil {
return err
}
dns := fmt.Sprintf("%s:%s@tcp(%s:%v)/%s?charset=%s&parseTime=true&loc=Asia%sShanghai%s", r.User, r.Password, r.Address, r.Port, info.Name, info.Format, "%2F", tlsItem)
fi, _ := os.Open(info.SourceFile)
defer fi.Close()
f, err := os.Open(fileName)
image, err := loadImage(info.Type, info.Version)
if err != nil {
return err
}
defer f.Close()
if err := helper.Source(dns, f, helper.WithMergeInsert(1000)); err != nil {
return err
recoverCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'mysql -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'",
image, r.Address, r.Port, r.User, r.Password, sslSkip(info.Version), info.Format, info.Name)
global.LOG.Debug(recoverCmd)
cmd := exec.Command("bash", "-c", recoverCmd)
if strings.HasSuffix(info.SourceFile, ".gz") {
gzipFile, err := os.Open(info.SourceFile)
if err != nil {
return err
}
defer gzipFile.Close()
gzipReader, err := gzip.NewReader(gzipFile)
if err != nil {
return err
}
defer gzipReader.Close()
cmd.Stdin = gzipReader
} else {
cmd.Stdin = fi
}
stdout, err := cmd.CombinedOutput()
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
return errors.New(stdStr)
}
return nil
}
@@ -389,3 +400,45 @@ func (r *Remote) ExecSQLForHosts(timeout uint) ([]string, error) {
}
return rows, nil
}
func loadImage(dbType, version string) (string, error) {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
fmt.Println(err)
}
images, err := cli.ImageList(context.Background(), types.ImageListOptions{})
if err != nil {
fmt.Println(err)
}
for _, image := range images {
for _, tag := range image.RepoTags {
if !strings.HasPrefix(tag, dbType+":") {
continue
}
if dbType == "mariadb" && strings.HasPrefix(tag, "mariadb:") {
return tag, nil
}
if strings.HasPrefix(version, "5.6") && strings.HasPrefix(tag, "mysql:5.6") {
return tag, nil
}
if strings.HasPrefix(version, "5.7") && strings.HasPrefix(tag, "mysql:5.7") {
return tag, nil
}
if strings.HasPrefix(version, "8.") && strings.HasPrefix(tag, "mysql:8.") {
return tag, nil
}
}
}
if dbType == "mariadb" || version == "8.x" {
return "mysql:8.2.0", nil
}
return "mysql:" + version, nil
}
func sslSkip(version string) string {
if strings.HasPrefix(version, "5.6") || strings.HasPrefix(version, "5.7") {
return "--skip-ssl"
}
return "--ssl-mode=DISABLED"
}
+45 -1
View File
@@ -3830,6 +3830,31 @@ const docTemplate = `{
}
}
},
"/dashboard/base/os": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取服务器基础数据",
"consumes": [
"application/json"
],
"tags": [
"Dashboard"
],
"summary": "Load os info",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.OsInfo"
}
}
}
}
},
"/dashboard/current/:ioOption/:netOption": {
"get": {
"security": [
@@ -16828,6 +16853,26 @@ const docTemplate = `{
}
}
},
"dto.OsInfo": {
"type": "object",
"properties": {
"kernelArch": {
"type": "string"
},
"kernelVersion": {
"type": "string"
},
"os": {
"type": "string"
},
"platform": {
"type": "string"
},
"platformFamily": {
"type": "string"
}
}
},
"dto.PageContainer": {
"type": "object",
"required": [
@@ -18967,7 +19012,6 @@ const docTemplate = `{
"request.FileEdit": {
"type": "object",
"required": [
"content",
"path"
],
"properties": {
+45 -1
View File
@@ -3823,6 +3823,31 @@
}
}
},
"/dashboard/base/os": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取服务器基础数据",
"consumes": [
"application/json"
],
"tags": [
"Dashboard"
],
"summary": "Load os info",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/dto.OsInfo"
}
}
}
}
},
"/dashboard/current/:ioOption/:netOption": {
"get": {
"security": [
@@ -16821,6 +16846,26 @@
}
}
},
"dto.OsInfo": {
"type": "object",
"properties": {
"kernelArch": {
"type": "string"
},
"kernelVersion": {
"type": "string"
},
"os": {
"type": "string"
},
"platform": {
"type": "string"
},
"platformFamily": {
"type": "string"
}
}
},
"dto.PageContainer": {
"type": "object",
"required": [
@@ -18960,7 +19005,6 @@
"request.FileEdit": {
"type": "object",
"required": [
"content",
"path"
],
"properties": {
+28 -1
View File
@@ -1887,6 +1887,19 @@ definitions:
option:
type: string
type: object
dto.OsInfo:
properties:
kernelArch:
type: string
kernelVersion:
type: string
os:
type: string
platform:
type: string
platformFamily:
type: string
type: object
dto.PageContainer:
properties:
excludeAppStore:
@@ -3319,7 +3332,6 @@ definitions:
path:
type: string
required:
- content
- path
type: object
request.FileMove:
@@ -7412,6 +7424,21 @@ paths:
summary: Load dashboard base info
tags:
- Dashboard
/dashboard/base/os:
get:
consumes:
- application/json
description: 获取服务器基础数据
responses:
"200":
description: OK
schema:
$ref: '#/definitions/dto.OsInfo'
security:
- ApiKeyAuth: []
summary: Load os info
tags:
- Dashboard
/dashboard/current/:ioOption/:netOption:
get:
consumes:
+7
View File
@@ -1,4 +1,11 @@
export namespace Dashboard {
export interface OsInfo {
os: string;
platform: string;
platformFamily: string;
kernelArch: string;
kernelVersion: string;
}
export interface BaseInfo {
websiteNumber: number;
databaseNumber: number;
+4
View File
@@ -1,6 +1,10 @@
import http from '@/api';
import { Dashboard } from '../interface/dashboard';
export const loadOsInfo = () => {
return http.get<Dashboard.OsInfo>(`/dashboard/base/os`);
};
export const loadBaseInfo = (ioOption: string, netOption: string) => {
return http.get<Dashboard.BaseInfo>(`/dashboard/base/${ioOption}/${netOption}`);
};
+36
View File
@@ -0,0 +1,36 @@
{
"Anhui": "安徽",
"Beijing": "北京",
"Fujian": "福建",
"Gansu": "甘肃",
"Guangdong": "广东",
"Guangxi": "广西",
"Guizhou": "贵州",
"Hainan": "海南",
"Hebei": "河北",
"Henan": "河南",
"Heilongjiang": "黑龙江",
"Hubei": "湖北",
"Hunan": "湖南",
"Jilin": "吉林",
"Jiangsu": "江苏",
"Jiangxi": "江西",
"Liaoning": "辽宁",
"Inner Mongolia": "内蒙古",
"Ningxia": "宁夏",
"Qinghai": "青海",
"Shandong": "山东",
"Shanxi": "山西",
"Shaanxi": "陕西",
"Shanghai": "上海",
"Sichuan": "四川",
"Tianjin": "天津",
"Tibet": "西藏",
"Xinjiang": "新疆",
"Yunnan": "云南",
"Zhejiang": "浙江",
"Chongqing": "重庆",
"HongKong": "香港",
"Macao": "澳门",
"Taiwan": "台湾"
}
File diff suppressed because it is too large Load Diff
+192
View File
@@ -0,0 +1,192 @@
{
"Afghanistan": "阿富汗",
"Albania": "阿尔巴尼亚",
"Algeria": "阿尔及利亚",
"Angola": "安哥拉",
"Argentina": "阿根廷",
"Armenia": "亚美尼亚",
"Australia": "澳大利亚",
"Austria": "奥地利",
"Azerbaijan": "阿塞拜疆",
"Bahamas": "巴哈马",
"Bahrain": "巴林",
"Bangladesh": "孟加拉国",
"Belarus": "白俄罗斯",
"Belgium": "比利时",
"Belize": "伯利兹",
"Benin": "贝宁",
"Bhutan": "不丹",
"Bolivia": "玻利维亚",
"Bosnia and Herz.": "波斯尼亚和黑塞哥维那",
"Botswana": "博茨瓦纳",
"Brazil": "巴西",
"British Virgin Islands": "英属维京群岛",
"Brunei": "文莱",
"Bulgaria": "保加利亚",
"Burkina Faso": "布基纳法索",
"Burundi": "布隆迪",
"Cambodia": "柬埔寨",
"Cameroon": "喀麦隆",
"Canada": "加拿大",
"Cape Verde": "佛得角",
"Cayman Islands": "开曼群岛",
"Central African Rep.": "中非共和国",
"Chad": "乍得",
"Chile": "智利",
"China": "中国",
"Colombia": "哥伦比亚",
"Comoros": "科摩罗",
"Congo": "刚果",
"Costa Rica": "哥斯达黎加",
"Croatia": "克罗地亚",
"Cuba": "古巴",
"Cyprus": "塞浦路斯",
"Czech Rep.": "捷克共和国",
"Côte d'Ivoire": "科特迪瓦",
"Dem. Rep. Congo": "刚果民主共和国",
"Dem. Rep. Korea": "朝鲜",
"Denmark": "丹麦",
"Djibouti": "吉布提",
"Dominican Rep.": "多米尼加共和国",
"Ecuador": "厄瓜多尔",
"Egypt": "埃及",
"El Salvador": "萨尔瓦多",
"Equatorial Guinea": "赤道几内亚",
"Eritrea": "厄立特里亚",
"Estonia": "爱沙尼亚",
"Ethiopia": "埃塞俄比亚",
"Falkland Is.": "福克兰群岛",
"Fiji": "斐济",
"Finland": "芬兰",
"Fr. S. Antarctic Lands": "所罗门群岛",
"France": "法国",
"Gabon": "加蓬",
"Gambia": "冈比亚",
"Georgia": "格鲁吉亚",
"Germany": "德国",
"Ghana": "加纳",
"Greece": "希腊",
"Greenland": "格陵兰",
"Guatemala": "危地马拉",
"Guinea": "几内亚",
"Guinea-Bissau": "几内亚比绍",
"Guyana": "圭亚那",
"Haiti": "海地",
"Honduras": "洪都拉斯",
"Hungary": "匈牙利",
"Iceland": "冰岛",
"India": "印度",
"Indonesia": "印度尼西亚",
"Iran": "伊朗",
"Iraq": "伊拉克",
"Ireland": "爱尔兰",
"Isle of Man": "马恩岛",
"Israel": "以色列",
"Italy": "意大利",
"Jamaica": "牙买加",
"Japan": "日本",
"Jordan": "约旦",
"Kazakhstan": "哈萨克斯坦",
"Kenya": "肯尼亚",
"Korea": "韩国",
"Kuwait": "科威特",
"Kyrgyzstan": "吉尔吉斯斯坦",
"Lao PDR": "老挝",
"Latvia": "拉脱维亚",
"Lebanon": "黎巴嫩",
"Lesotho": "莱索托",
"Liberia": "利比里亚",
"Libya": "利比亚",
"Lithuania": "立陶宛",
"Luxembourg": "卢森堡",
"Macedonia": "马其顿",
"Madagascar": "马达加斯加",
"Malawi": "马拉维",
"Malaysia": "马来西亚",
"Maldives": "马尔代夫",
"Mali": "马里",
"Malta": "马耳他",
"Mauritania": "毛利塔尼亚",
"Mauritius": "毛里求斯",
"Mexico": "墨西哥",
"Moldova": "摩尔多瓦",
"Monaco": "摩纳哥",
"Mongolia": "蒙古",
"Montenegro": "黑山共和国",
"Morocco": "摩洛哥",
"Mozambique": "莫桑比克",
"Myanmar": "缅甸",
"Namibia": "纳米比亚",
"Nepal": "尼泊尔",
"Netherlands": "荷兰",
"New Caledonia": "新喀里多尼亚",
"New Zealand": "新西兰",
"Nicaragua": "尼加拉瓜",
"Niger": "尼日尔",
"Nigeria": "尼日利亚",
"Norway": "挪威",
"Oman": "阿曼",
"Pakistan": "巴基斯坦",
"Panama": "巴拿马",
"Papua New Guinea": "巴布亚新几内亚",
"Paraguay": "巴拉圭",
"Peru": "秘鲁",
"Philippines": "菲律宾",
"Poland": "波兰",
"Portugal": "葡萄牙",
"Puerto Rico": "波多黎各",
"Qatar": "卡塔尔",
"Reunion": "留尼旺",
"Romania": "罗马尼亚",
"Russia": "俄罗斯",
"Rwanda": "卢旺达",
"S. Geo. and S. Sandw. Is.": "南乔治亚和南桑威奇群岛",
"S. Sudan": "南苏丹",
"San Marino": "圣马力诺",
"Saudi Arabia": "沙特阿拉伯",
"Senegal": "塞内加尔",
"Serbia": "塞尔维亚",
"Sierra Leone": "塞拉利昂",
"Singapore": "新加坡",
"Slovakia": "斯洛伐克",
"Slovenia": "斯洛文尼亚",
"Solomon Is.": "所罗门群岛",
"Somalia": "索马里",
"South Africa": "南非",
"Spain": "西班牙",
"Sri Lanka": "斯里兰卡",
"Sudan": "苏丹",
"Suriname": "苏里南",
"Swaziland": "斯威士兰",
"Sweden": "瑞典",
"Switzerland": "瑞士",
"Syria": "叙利亚",
"Taiwan": "中国台湾",
"Tajikistan": "塔吉克斯坦",
"Tanzania": "坦桑尼亚",
"Thailand": "泰国",
"Togo": "多哥",
"Tonga": "汤加",
"Trinidad and Tobago": "特立尼达和多巴哥",
"Tunisia": "突尼斯",
"Turkey": "土耳其",
"Turkmenistan": "土库曼斯坦",
"U.S. Virgin Islands": "美属维尔京群岛",
"Uganda": "乌干达",
"Ukraine": "乌克兰",
"United Arab Emirates": "阿拉伯联合酋长国",
"United Kingdom": "英国",
"United States": "美国",
"Uruguay": "乌拉圭",
"Uzbekistan": "乌兹别克斯坦",
"Vanuatu": "瓦努阿图",
"Vatican City": "梵蒂冈城",
"Venezuela": "委内瑞拉",
"Vietnam": "越南",
"W. Sahara": "西撒哈拉",
"Yemen": "也门",
"Yugoslavia": "南斯拉夫",
"Zaire": "扎伊尔",
"Zambia": "赞比亚",
"Zimbabwe": "津巴布韦"
}
File diff suppressed because it is too large Load Diff
-1
View File
@@ -59,7 +59,6 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import OpDialog from '@/components/del-dialog/index.vue';
import { computeSize, dateFormat, downloadFile } from '@/utils/util';
import { handleBackup, handleRecover } from '@/api/modules/setting';
import i18n from '@/lang';
+4
View File
@@ -4,6 +4,8 @@ import RouterButton from './router-button/index.vue';
import ComplexTable from './complex-table/index.vue';
import ErrPrompt from './error-prompt/index.vue';
import OpDialog from './del-dialog/index.vue';
import TableSearch from './table-search/index.vue';
import TableSetting from './table-setting/index.vue';
import Tooltip from '@/components/tooltip/index.vue';
import CopyButton from '@/components/copy-button/index.vue';
export default {
@@ -15,5 +17,7 @@ export default {
app.component(OpDialog.name, OpDialog);
app.component(Tooltip.name, Tooltip);
app.component(CopyButton.name, CopyButton);
app.component(TableSearch.name, TableSearch);
app.component(TableSetting.name, TableSetting);
},
};
@@ -77,7 +77,7 @@ import { GlobalStore } from '@/store';
import { ElMessageBox } from 'element-plus';
const globalStore = GlobalStore();
const version = ref();
const version = ref<string>('');
const loading = ref(false);
const drawerVisible = ref(false);
const upgradeInfo = ref();
@@ -0,0 +1,30 @@
<template>
<div>
<div class="search-button">
<el-input
clearable
v-model="searchInfo"
@clear="search()"
suffix-icon="Search"
@change="search()"
:placeholder="props.placeholder || $t('commons.button.search')"
></el-input>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
defineOptions({ name: 'TableSearch' });
const emit = defineEmits(['search', 'update:searchName']);
const searchInfo = ref();
const props = defineProps({
placeholder: String,
});
const search = () => {
emit('update:searchName', searchInfo.value);
emit('search');
};
</script>
-1
View File
@@ -90,7 +90,6 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { computeSize } from '@/utils/util';
import OpDialog from '@/components/del-dialog/index.vue';
import { handleRecoverByUpload } from '@/api/modules/setting';
import i18n from '@/lang';
import { UploadFile, UploadFiles, UploadInstance } from 'element-plus';
+64 -4
View File
@@ -291,6 +291,7 @@ const message = {
process: 'Process',
network: 'Network',
supervisor: 'Supervisor',
name: 'Tamper Proof',
},
home: {
restart_1panel: 'Restart Panel',
@@ -805,7 +806,7 @@ const message = {
database: 'Backup database',
missBackupAccount: 'The backup account could not be found',
syncDate: 'Synchronization time ',
releaseMemory: 'Free memory',
clean: 'Cache Clean',
curl: 'Access URL',
taskName: 'Cronjob name',
cronSpec: 'Lifecycle',
@@ -908,7 +909,7 @@ const message = {
ip: 'Address',
authMode: 'Auth Mode',
passwordMode: 'Password',
rememberPassword: 'Remember password',
rememberPassword: 'Remember Authentication Information',
keyMode: 'PrivateKey',
key: 'Private key',
keyPassword: 'Private key password',
@@ -1404,14 +1405,15 @@ const message = {
createSnapshot: 'Create snapshot',
importSnapshot: 'Sync snapshot',
recover: 'Recover',
noRecoverRecord: 'No recovery record has been recorded',
lastRecoverAt: 'Last recovery time',
lastRollbackAt: 'Last rollback time',
noRollbackRecord: 'No rollback record has been recorded',
reDownload: 'Download the backup file again',
recoverRecord: 'Recover record',
recoverHelper:
'The recovery is about to start from snapshot {0}, and the recovery needs to restart docker and 1panel service, do you want to continue?',
recoverHelper1:
'Will start restoring from snapshot {0}, please ensure that the server architecture matches the one where the snapshot was created.',
recoverHelper2: 'Restoring snapshots between different server architectures is not supported.',
rollback: 'Rollback',
rollbackHelper:
'This recovery is about to be rolled back, which will replace all the files recovered this time. In the process, docker and 1panel services may need to be restarted. Do you want to continue?',
@@ -2141,6 +2143,64 @@ const message = {
statusCode: 'Status code',
manage: 'Management',
},
xpack: {
name: 'Professional Edition',
waf: {
name: 'WAF',
blackWhite: 'black and white list',
globalSetting: 'Global Setting',
websiteSetting: 'Website Settings',
blockRecords: 'Block records',
world: 'world',
china: 'China',
intercept: 'interception',
request: 'request',
count4xx: '4xx quantity',
count5xx: '5xx quantity',
todayStatus: 'Today status',
reqMap: 'Request map (30 days)',
resource: 'source',
count: 'Quantity',
hight: 'high',
low: 'low',
reqCount: 'Number of requests',
interceptCount: 'Interception number',
requestTrends: 'Request Trends (7 days)',
interceptTrends: 'Intercept Trends (7 days)',
},
monitor: {
name: 'Website Monitoring',
},
tamper: {
tamperHelper1:
'One-click deployment type of website, it is recommended to enable the application directory anti-tampering function;',
tamperHelper2:
'If the website cannot be used normally or backup and restore fail after enabling the anti-tampering function, please disable the anti-tampering function first;',
tamperHelper3:
'Enabling tamper-proofing will restrict reading, writing, deleting, permission, and owner modification operations of protected files under non-excluded directories. Please choose carefully when setting [Exclude Directory] and [Protect].',
op: 'Operation',
create: 'Create',
file: 'File',
tamperPath: 'Protection Directory',
tamperPathEdit: 'Modify Path',
log: 'Intercept Logs',
totalProtect: 'Total Protection',
todayProtect: 'Today’s Protection',
addRule: 'Add Rule',
ignore: 'Exclude Directory',
ignoreHelper: 'One per line, e.g., \ntmp\n./tmp',
ignoreHelper1: 'Add folder names or specific paths to ignore',
ignoreHelper2: 'To ignore specific folders, use relative paths starting with ./',
protect: 'Protect',
protectHelper: 'One per line, e.g., \npng\n./test.css',
protectHelper1: 'Specify file names, suffixes, or specific files for protection',
protectHelper2: 'To protect specific files, use relative paths starting with ./',
enableHelper:
'The anti-tampering function of website {0} is about to be enabled to enhance website security. Do you want to continue?',
disableHelper:
'The anti-tampering function of website {0} is about to be disabled. Do you want to continue?',
},
},
},
};
+59 -7
View File
@@ -288,6 +288,7 @@ const message = {
process: '進程',
network: '網絡',
supervisor: '進程守護',
tamper: '防篡改',
},
home: {
restart_1panel: '重啟面板',
@@ -767,7 +768,7 @@ const message = {
database: '備份數據庫',
missBackupAccount: '未能找到備份賬號',
syncDate: '同步時間 ',
releaseMemory: '釋放內存',
clean: '缓存清理',
curl: '訪問 URL',
taskName: '任務名稱',
cronSpec: '執行周期',
@@ -866,7 +867,7 @@ const message = {
ip: '主機地址',
authMode: '認證方式',
passwordMode: '密碼認證',
rememberPassword: '記住密碼',
rememberPassword: '記住認證資訊',
keyMode: '私鑰認證',
key: '私鑰',
keyPassword: '私鑰密碼',
@@ -1245,17 +1246,14 @@ const message = {
createSnapshot: '創建快照',
importSnapshot: '同步快照',
recover: '恢復',
noRecoverRecord: '暫無恢復記錄',
lastRecoverAt: '上次恢復時間',
lastRollbackAt: '上次回滾時間',
noRollbackRecord: '暫無回滾記錄',
reDownload: '重新下載備份文件',
statusAll: '全部',
statusSuccess: '成功',
statusFailed: '失敗',
versionChange: '版本變化',
snapshotFrom: '快照存儲位置',
recoverHelper: '即將從快照 {0} 開始恢復,恢復需要重啟 docker 以及 1panel 服務,是否繼續?',
recoverHelper1: '即將從快照 {0} 開始恢復,請確保伺服器架構與創建快照伺服器架構信息保持一致。',
recoverHelper2: '不支持在不同伺服器架構之間進行快照恢復操作。',
rollback: '回滾',
rollbackHelper:
'即將回滾本次恢復,回滾將替換所有本次恢復的文件,過程中可能需要重啟 docker 以及 1panel 服務,是否繼續?',
@@ -2003,6 +2001,60 @@ const message = {
statusCode: '狀態碼',
manage: '管理',
},
xpack: {
name: '專業版',
waf: {
name: 'WAF',
blackWhite: '黑白名單',
globalSetting: '全域設定',
websiteSetting: '網站設定',
blockRecords: '封鎖記錄',
world: '世界',
china: '中國',
intercept: '攔截',
request: '請求',
count4xx: '4xx 數量',
count5xx: '5xx 數量',
todayStatus: '今日狀態',
reqMap: '請求地圖(30日)',
resource: '來源',
count: '數量',
hight: '高',
low: '低',
reqCount: '請求數',
interceptCount: '攔截數',
requestTrends: '請求趨勢(7天)',
interceptTrends: '攔截趨勢(7天)',
},
monitor: {
name: '網站監控',
},
tamper: {
tamperHelper1: '一鍵部署類型的網站,建議啟用應用目錄防篡改功能;',
tamperHelper2: '如果在啟用防篡改功能後出現網站無法正常使用或備份、恢復失敗的情況,請先關閉防篡改功能;',
tamperHelper3:
'啟用防篡改,將限制非排除目錄下受保護文件的讀寫、刪除、權限和所有者修改操作,請在設定 [排除目錄] 和 [保護] 時謹慎選擇。',
op: '操作',
create: '創建',
file: '文件',
tamperPath: '防護目錄',
tamperPathEdit: '修改路徑',
log: '攔截日誌',
totalProtect: '總防護',
todayProtect: '今日防護',
addRule: '添加規則',
ignore: '排除目錄',
ignoreHelper: '一行一個,例: \ntmp\n./tmp',
ignoreHelper1: '添加要忽略的文件夾名或特定路徑',
ignoreHelper2: '要忽略特定文件夾,請使用以 ./ 開頭的相對路徑',
protect: '保護',
protectHelper: '一行一個,例: \npng\n./test.css',
protectHelper1: '可指定文件名、後綴名或特定文件進行保護',
protectHelper2: '要保護特定文件,請使用以 ./ 開頭的相對路徑',
enableHelper: '即將啟用 {0} 網站的防窜改功能,以提升網站安全性,是否繼續?',
disableHelper: '即將關閉 {0} 網站的防窜改功能,是否繼續?',
},
},
},
};
export default {
+60 -7
View File
@@ -288,6 +288,7 @@ const message = {
process: '进程',
network: '网络',
supervisor: '进程守护',
tamper: '防篡改',
},
home: {
restart_1panel: '重启面板',
@@ -768,7 +769,7 @@ const message = {
database: '备份数据库',
missBackupAccount: '未能找到备份账号',
syncDate: '同步时间 ',
releaseMemory: '释放内存',
clean: '缓存清理',
curl: '访问 URL',
taskName: '任务名称',
cronSpec: '执行周期',
@@ -867,7 +868,7 @@ const message = {
ip: '主机地址',
authMode: '认证方式',
passwordMode: '密码认证',
rememberPassword: '记住密码',
rememberPassword: '记住认证信息',
keyMode: '私钥认证',
key: '私钥',
keyPassword: '私钥密码',
@@ -1246,17 +1247,14 @@ const message = {
createSnapshot: '创建快照',
importSnapshot: '同步快照',
recover: '恢复',
noRecoverRecord: '暂无恢复记录',
lastRecoverAt: '上次恢复时间',
lastRollbackAt: '上次回滚时间',
noRollbackRecord: '暂无回滚记录',
reDownload: '重新下载备份文件',
statusAll: '全部',
statusSuccess: '成功',
statusFailed: '失败',
versionChange: '版本变化',
snapshotFrom: '快照存储位置',
recoverHelper: '即将从快照 {0} 开始恢复,恢复需要重启 docker 以及 1panel 服务,是否继续?',
recoverHelper1: '即将从快照 {0} 开始恢复,请确保服务器架构与创建快照服务器架构信息保持一致。',
recoverHelper2: '不支持在不同服务器架构之间进行快照恢复操作。',
rollback: '回滚',
rollbackHelper:
'即将回滚本次恢复,回滚将替换所有本次恢复的文件,过程中可能需要重启 docker 以及 1panel 服务,是否继续?',
@@ -2004,6 +2002,61 @@ const message = {
manage: '管理',
},
},
xpack: {
name: '专业版',
waf: {
name: 'WAF',
blackWhite: '黑白名单',
globalSetting: '全局设置',
websiteSetting: '网站设置',
blockRecords: '封锁记录',
world: '世界',
china: '中国',
intercept: '拦截',
request: '请求',
count4xx: '4xx 数量',
count5xx: '5xx 数量',
todayStatus: '今日状态',
reqMap: '请求地图(30日)',
resource: '来源',
count: '数量',
hight: '高',
low: '低',
reqCount: '请求数',
interceptCount: '拦截数',
requestTrends: '请求趋势(7日)',
interceptTrends: '拦截趋势(7日)',
},
monitor: {
name: '网站监控',
},
tamper: {
tamper: '防篡改',
tamperHelper1: '一键部署类型的网站,建议启用应用目录防篡改功能;',
tamperHelper2: '如果在启用防篡改功能后出现网站无法正常使用或备份、恢复失败的情况,请先关闭防篡改功能;',
tamperHelper3:
'启用防篡改,将限制非排除目录下受保护文件的读写、删除、权限和所有者修改操作,请在设定 [排除目录] 和 [保护] 时谨慎选择。',
op: '操作',
create: '创建',
file: '文件',
tamperPath: '防护目录',
tamperPathEdit: '修改路径',
log: '拦截日志',
totalProtect: '总防护',
todayProtect: '今日防护',
addRule: '添加规则',
ignore: '排除目录',
ignoreHelper: '一行一个,例: \ntmp\n./tmp',
ignoreHelper1: '添加要忽略的文件夹名或特定路径',
ignoreHelper2: '要忽略特定文件夹,请使用以 ./ 开头的相对路径',
protect: '保护',
protectHelper: '一行一个,例: \npng\n./test.css',
protectHelper1: '可指定文件名、后缀名或特定文件进行保护',
protectHelper2: '要保护特定文件,请使用以 ./ 开头的相对路径',
enableHelper: '即将启用 {0} 网站的防窜改功能,以提升网站安全性,是否继续?',
disableHelper: '即将关闭 {0} 网站的防窜改功能,是否继续?',
},
},
};
export default {
...fit2cloudZhLocale,
+8
View File
@@ -13,6 +13,14 @@ router.beforeEach((to, from, next) => {
axiosCanceler.removeAllPending();
const globalStore = GlobalStore();
if (to.name === 'home' && !globalStore.isLogin) {
next({
name: 'entrance',
params: { code: globalStore.entrance },
});
NProgress.done();
return;
}
if (to.name === 'entrance' && globalStore.isLogin) {
if (to.params.code === globalStore.entrance) {
next({
+45
View File
@@ -0,0 +1,45 @@
import i18n from '@/lang';
export const shortcuts = [
{
text: i18n.global.t('monitor.today'),
value: () => {
const end = new Date();
const start = new Date(new Date().setHours(0, 0, 0, 0));
return [start, end];
},
},
{
text: i18n.global.t('monitor.yesterday'),
value: () => {
const yesterday = new Date(new Date().getTime() - 3600 * 1000 * 24 * 1);
const end = new Date(yesterday.setHours(23, 59, 59, 999));
const start = new Date(yesterday.setHours(0, 0, 0, 0));
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [3]),
value: () => {
const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 3);
const end = new Date();
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [7]),
value: () => {
const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 7);
const end = new Date();
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [30]),
value: () => {
const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 30);
const end = new Date();
return [start, end];
},
},
];
+2 -12
View File
@@ -50,16 +50,7 @@
</div>
</el-col>
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
<div class="search-button">
<el-input
v-model="req.name"
clearable
@clear="searchByName('')"
suffix-icon="Search"
@change="searchByName(req.name)"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="searchByName()" v-model:searchName="req.name" />
</el-col>
</el-row>
</template>
@@ -298,8 +289,7 @@ const getTagValue = (key: string) => {
}
};
const searchByName = (name: string) => {
req.name = name;
const searchByName = () => {
search(req);
};
@@ -53,18 +53,7 @@
</el-col>
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
<div class="search-button">
<el-input
class="table-button"
v-model="searchReq.name"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchReq.name" />
</el-col>
</el-row>
</template>
@@ -114,8 +114,6 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import Tooltip from '@/components/tooltip/index.vue';
import OpDialog from '@/components/del-dialog/index.vue';
import MonitorDialog from '@/views/container/container/monitor/index.vue';
import ContainerLogDialog from '@/views/container/container/log/index.vue';
import TerminalDialog from '@/views/container/container/terminal/index.vue';
+1 -13
View File
@@ -33,17 +33,7 @@
</el-col>
<el-col :span="8">
<TableSetting @search="search()" />
<div class="search-button">
<el-input
clearable
v-model="searchName"
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -91,8 +81,6 @@
</template>
<script lang="ts" setup>
import Tooltip from '@/components/tooltip/index.vue';
import TableSetting from '@/components/table-setting/index.vue';
import { reactive, onMounted, ref } from 'vue';
import EditDialog from '@/views/container/compose/edit/index.vue';
import CreateDialog from '@/views/container/compose/create/index.vue';
@@ -56,17 +56,7 @@
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<TableSetting title="container-refresh" @search="refresh()" />
<div class="search-button">
<el-input
clearable
v-model="searchName"
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -320,9 +310,6 @@
</template>
<script lang="ts" setup>
import OpDialog from '@/components/del-dialog/index.vue';
import Tooltip from '@/components/tooltip/index.vue';
import TableSetting from '@/components/table-setting/index.vue';
import PruneDialog from '@/views/container/container/prune/index.vue';
import RenameDialog from '@/views/container/container/rename/index.vue';
import OperateDialog from '@/views/container/container/operate/index.vue';
@@ -48,7 +48,6 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { ElForm } from 'element-plus';
import OpDialog from '@/components/del-dialog/index.vue';
import { imageRemove } from '@/api/modules/container';
import DrawerHeader from '@/components/drawer-header/index.vue';
import i18n from '@/lang';
+1 -14
View File
@@ -25,17 +25,7 @@
</el-col>
<el-col :span="8">
<TableSetting @search="search()" />
<div class="search-button">
<el-input
clearable
v-model="searchName"
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -110,9 +100,6 @@
</template>
<script lang="ts" setup>
import OpDialog from '@/components/del-dialog/index.vue';
import TableSetting from '@/components/table-setting/index.vue';
import Tooltip from '@/components/tooltip/index.vue';
import { reactive, onMounted, ref, computed } from 'vue';
import { dateFormat } from '@/utils/util';
import { Container } from '@/api/interface/container';
+1 -14
View File
@@ -22,17 +22,7 @@
</el-col>
<el-col :span="8">
<TableSetting @search="search()" />
<div class="search-button">
<el-input
clearable
v-model="searchName"
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -95,9 +85,6 @@
</template>
<script lang="ts" setup>
import Tooltip from '@/components/tooltip/index.vue';
import OpDialog from '@/components/del-dialog/index.vue';
import TableSetting from '@/components/table-setting/index.vue';
import CreateDialog from '@/views/container/network/create/index.vue';
import CodemirrorDialog from '@/components/codemirror-dialog/index.vue';
import { reactive, onMounted, ref } from 'vue';
+1 -13
View File
@@ -16,17 +16,7 @@
</el-col>
<el-col :span="8">
<TableSetting @search="search()" />
<div class="search-button">
<el-input
clearable
v-model="searchName"
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -77,8 +67,6 @@
</template>
<script lang="ts" setup>
import OpDialog from '@/components/del-dialog/index.vue';
import TableSetting from '@/components/table-setting/index.vue';
import OperatorDialog from '@/views/container/repo/operator/index.vue';
import { reactive, onMounted, ref } from 'vue';
import { dateFormat } from '@/utils/util';
@@ -19,17 +19,7 @@
</el-col>
<el-col :span="8">
<TableSetting @search="search()" />
<div class="search-button">
<el-input
clearable
v-model="searchName"
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -64,9 +54,6 @@
</template>
<script lang="ts" setup>
import Tooltip from '@/components/tooltip/index.vue';
import OpDialog from '@/components/del-dialog/index.vue';
import TableSetting from '@/components/table-setting/index.vue';
import { reactive, onMounted, ref } from 'vue';
import { dateFormatSimple } from '@/utils/util';
import { Container } from '@/api/interface/container';
+1 -14
View File
@@ -22,17 +22,7 @@
</el-col>
<el-col :span="8">
<TableSetting @search="search()" />
<div class="search-button">
<el-input
clearable
v-model="searchName"
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -95,9 +85,6 @@
</template>
<script lang="ts" setup>
import Tooltip from '@/components/tooltip/index.vue';
import OpDialog from '@/components/del-dialog/index.vue';
import TableSetting from '@/components/table-setting/index.vue';
import CreateDialog from '@/views/container/volume/create/index.vue';
import CodemirrorDialog from '@/components/codemirror-dialog/index.vue';
import { reactive, onMounted, ref, computed } from 'vue';
-46
View File
@@ -2,52 +2,6 @@ import { Cronjob } from '@/api/interface/cronjob';
import i18n from '@/lang';
import { loadZero } from '@/utils/util';
export const shortcuts = [
{
text: i18n.global.t('monitor.today'),
value: () => {
const end = new Date(new Date().setHours(23, 59, 59, 999));
const start = new Date(new Date().setHours(0, 0, 0, 0));
return [start, end];
},
},
{
text: i18n.global.t('monitor.yesterday'),
value: () => {
const itemDate = new Date(new Date().getTime() - 3600 * 1000 * 24 * 1);
const end = new Date(itemDate.setHours(23, 59, 59, 999));
const start = new Date(itemDate.setHours(0, 0, 0, 0));
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [3]),
value: () => {
const itemDate = new Date(new Date().getTime() - 3600 * 1000 * 24 * 3);
const end = new Date(new Date().setHours(23, 59, 59, 999));
const start = new Date(itemDate.setHours(0, 0, 0, 0));
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [7]),
value: () => {
const itemDate = new Date(new Date().getTime() - 3600 * 1000 * 24 * 7);
const end = new Date(new Date().setHours(23, 59, 59, 999));
const start = new Date(itemDate.setHours(0, 0, 0, 0));
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [30]),
value: () => {
const itemDate = new Date(new Date().getTime() - 3600 * 1000 * 24 * 30);
const end = new Date(new Date().setHours(23, 59, 59, 999));
const start = new Date(itemDate.setHours(0, 0, 0, 0));
return [start, end];
},
},
];
export const specOptions = [
{ label: i18n.global.t('cronjob.perMonth'), value: 'perMonth' },
{ label: i18n.global.t('cronjob.perWeek'), value: 'perWeek' },
+1 -14
View File
@@ -29,17 +29,7 @@
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<TableSetting @search="search()" />
<div class="search-button">
<el-input
clearable
v-model="searchName"
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -174,9 +164,6 @@
</template>
<script lang="ts" setup>
import OpDialog from '@/components/del-dialog/index.vue';
import TableSetting from '@/components/table-setting/index.vue';
import Tooltip from '@/components/tooltip/index.vue';
import OperateDialog from '@/views/cronjob/operate/index.vue';
import Records from '@/views/cronjob/record/index.vue';
import Backups from '@/views/cronjob/backup/index.vue';
+1 -5
View File
@@ -245,7 +245,7 @@ import { oneDark } from '@codemirror/theme-one-dark';
import { MsgSuccess } from '@/utils/message';
import { listDbItems } from '@/api/modules/database';
import { ListAppInstalled } from '@/api/modules/app';
import { shortcuts } from './../helper';
import { shortcuts } from '@/utils/shortcuts';
const loading = ref();
const refresh = ref(false);
@@ -405,10 +405,6 @@ const forDetail = async (row: Cronjob.Record) => {
loadRecord(row);
};
const loadRecord = async (row: Cronjob.Record) => {
if (row.status === 'Failed') {
currentRecordDetail.value = row.records;
return;
}
if (row.records) {
const res = await getRecordLog(row.id);
currentRecordDetail.value = res.data;
+1 -11
View File
@@ -107,17 +107,7 @@
</el-dropdown>
</el-col>
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
<div class="search-button">
<el-input
v-model="searchName"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -1,19 +1,19 @@
<template>
<el-dialog
v-model="dialogVisible"
:title="$t('commons.button.delete') + ' - ' + deleteReq.database"
:title="$t('commons.button.delete') + ' - ' + deleteMysqlReq.database"
width="30%"
:close-on-click-modal="false"
>
<el-form ref="deleteForm" v-loading="loading" @submit.prevent>
<el-form-item>
<el-checkbox v-model="deleteReq.forceDelete" :label="$t('app.forceDelete')" />
<el-checkbox v-model="deleteMysqlReq.forceDelete" :label="$t('app.forceDelete')" />
<span class="input-help">
{{ $t('app.forceDeleteHelper') }}
</span>
</el-form-item>
<el-form-item>
<el-checkbox v-model="deleteReq.deleteBackup" :label="$t('app.deleteBackup')" />
<el-checkbox v-model="deleteMysqlReq.deleteBackup" :label="$t('app.deleteBackup')" />
<span class="input-help">
{{ $t('database.deleteBackupHelper') }}
</span>
@@ -21,10 +21,10 @@
<el-form-item>
<div>
<span style="font-size: 12px">{{ $t('database.delete') }}</span>
<span style="font-size: 12px; color: red; font-weight: 500">{{ deleteReq.database }}</span>
<span style="font-size: 12px; color: red; font-weight: 500">{{ deleteMysqlReq.database }}</span>
<span style="font-size: 12px">{{ $t('database.deleteHelper') }}</span>
</div>
<el-input v-model="deleteInfo" :placeholder="deleteReq.database"></el-input>
<el-input v-model="delMysqlInfo" :placeholder="deleteMysqlReq.database"></el-input>
</el-form-item>
</el-form>
<template #footer>
@@ -32,7 +32,11 @@
<el-button @click="dialogVisible = false" :disabled="loading">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button type="primary" @click="submit" :disabled="deleteInfo != deleteReq.database || loading">
<el-button
type="primary"
@click="submit"
:disabled="delMysqlInfo != deleteMysqlReq.database || loading"
>
{{ $t('commons.button.confirm') }}
</el-button>
</span>
@@ -46,7 +50,7 @@ import i18n from '@/lang';
import { deleteDatabase } from '@/api/modules/database';
import { MsgSuccess } from '@/utils/message';
let deleteReq = ref({
let deleteMysqlReq = ref({
id: 0,
database: '',
deleteBackup: false,
@@ -54,7 +58,7 @@ let deleteReq = ref({
});
let dialogVisible = ref(false);
let loading = ref(false);
let deleteInfo = ref('');
let delMysqlInfo = ref('');
const deleteForm = ref<FormInstance>();
@@ -66,7 +70,8 @@ interface DialogProps {
const emit = defineEmits<{ (e: 'search'): void }>();
const acceptParams = async (prop: DialogProps) => {
deleteReq.value = {
delMysqlInfo.value = '';
deleteMysqlReq.value = {
id: prop.id,
database: prop.database,
deleteBackup: false,
@@ -77,7 +82,7 @@ const acceptParams = async (prop: DialogProps) => {
const submit = async () => {
loading.value = true;
deleteDatabase(deleteReq.value)
deleteDatabase(deleteMysqlReq.value)
.then(() => {
loading.value = false;
emit('search');
@@ -12,17 +12,7 @@
</el-button>
</el-col>
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
<div class="search-button">
<el-input
v-model="searchName"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -84,17 +84,7 @@
<el-button @click="goDashboard()" type="primary" plain>PGAdmin4</el-button>
</el-col>
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
<div class="search-button">
<el-input
v-model="searchName"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -12,17 +12,7 @@
</el-button>
</el-col>
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
<div class="search-button">
<el-input
v-model="searchName"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -113,7 +113,6 @@
</template>
<script lang="ts" setup>
import OpDialog from '@/components/del-dialog/index.vue';
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { Database } from '@/api/interface/database';
import { redisPersistenceConf, updateRedisPersistenceConf } from '@/api/modules/database';
+1 -11
View File
@@ -31,15 +31,7 @@
</el-col>
<el-col :span="8">
<TableSetting @search="search()" />
<div class="search-button">
<el-input
clearable
v-model="searchName"
suffix-icon="Search"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -129,10 +121,8 @@
</template>
<script lang="ts" setup>
import OpDialog from '@/components/del-dialog/index.vue';
import OperateDialog from '@/views/host/firewall/ip/operate/index.vue';
import FireRouter from '@/views/host/firewall/index.vue';
import TableSetting from '@/components/table-setting/index.vue';
import FireStatus from '@/views/host/firewall/status/index.vue';
import { onMounted, reactive, ref } from 'vue';
import { batchOperateRule, searchFireRule, updateAddrRule, updateFirewallDescription } from '@/api/modules/host';
@@ -63,15 +63,7 @@
</el-col>
<el-col :span="8">
<TableSetting @search="search()" />
<div class="search-button">
<el-input
clearable
v-model="searchName"
suffix-icon="Search"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -180,8 +172,6 @@
<script lang="ts" setup>
import FireRouter from '@/views/host/firewall/index.vue';
import OpDialog from '@/components/del-dialog/index.vue';
import TableSetting from '@/components/table-setting/index.vue';
import OperateDialog from '@/views/host/firewall/port/operate/index.vue';
import FireStatus from '@/views/host/firewall/status/index.vue';
import { onMounted, reactive, ref } from 'vue';
@@ -208,6 +208,7 @@ import { computeSizeFromKBs, dateFormatWithoutYear } from '@/utils/util';
import i18n from '@/lang';
import MonitorRouter from '@/views/host/monitor/index.vue';
import { GlobalStore } from '@/store';
import { shortcuts } from '@/utils/shortcuts';
const globalStore = GlobalStore();
@@ -227,49 +228,6 @@ const networkChoose = ref();
const netOptions = ref();
const chartsOption = ref({ loadLoadChart: null, loadCPUChart: null, loadMemoryChart: null, loadNetworkChart: null });
const shortcuts = [
{
text: i18n.global.t('monitor.today'),
value: () => {
const end = new Date();
const start = new Date(new Date().setHours(0, 0, 0, 0));
return [start, end];
},
},
{
text: i18n.global.t('monitor.yesterday'),
value: () => {
const yesterday = new Date(new Date().getTime() - 3600 * 1000 * 24 * 1);
const end = new Date(yesterday.setHours(23, 59, 59, 999));
const start = new Date(yesterday.setHours(0, 0, 0, 0));
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [3]),
value: () => {
const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 3);
const end = new Date();
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [7]),
value: () => {
const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 7);
const end = new Date();
return [start, end];
},
},
{
text: i18n.global.t('monitor.lastNDay', [30]),
value: () => {
const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 30);
const end = new Date();
return [start, end];
},
},
];
const searchTime = ref();
const searchInfo = reactive<Monitor.MonitorSearch>({
param: '',
@@ -9,7 +9,6 @@
<script lang="ts" setup>
import i18n from '@/lang';
import RouterButton from '@/components/router-button/index.vue';
const buttons = [
{
@@ -9,45 +9,25 @@
<el-form-item style="float: right">
<el-row :gutter="20">
<el-col :span="8">
<div class="search-button">
<el-input
type="number"
v-model.number="netSearch.processID"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('process.pid')"
></el-input>
</div>
<TableSearch
@search="search()"
:placeholder="$t('process.pid')"
v-model:searchName="netSearch.processID"
/>
</el-col>
<el-col :span="8">
<div class="search-button">
<el-input
v-model.trim="netSearch.processName"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('process.processName')"
></el-input>
</div>
<TableSearch
@search="search()"
:placeholder="$t('process.processName')"
v-model:searchName="netSearch.processName"
/>
</el-col>
<el-col :span="8">
<div class="search-button">
<el-input
type="number"
v-model.number="netSearch.port"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.table.port')"
></el-input>
</div>
<TableSearch
@search="search()"
:placeholder="$t('commons.table.port')"
v-model:searchName="netSearch.port"
/>
</el-col>
</el-row>
</el-form-item>
@@ -210,10 +190,10 @@ const sendMsg = () => {
const search = () => {
if (isWsOpen()) {
if (typeof netSearch.processID === 'string') {
netSearch.processID = undefined;
netSearch.processID = Number(netSearch.processID);
}
if (typeof netSearch.port === 'string') {
netSearch.port = undefined;
netSearch.port = Number(netSearch.port);
}
processSocket.send(JSON.stringify(netSearch));
}
@@ -9,44 +9,25 @@
<el-form-item style="float: right">
<el-row :gutter="20">
<el-col :span="8">
<div class="search-button">
<el-input
type="number"
v-model.number="processSearch.pid"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('process.pid')"
></el-input>
</div>
<TableSearch
@search="search()"
:placeholder="$t('process.pid')"
v-model:searchName="processSearch.pid"
/>
</el-col>
<el-col :span="8">
<div class="search-button">
<el-input
v-model.trim="processSearch.name"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.table.name')"
></el-input>
</div>
<TableSearch
@search="search()"
:placeholder="$t('commons.table.name')"
v-model:searchName="processSearch.name"
/>
</el-col>
<el-col :span="8">
<div class="search-button">
<el-input
v-model.trim="processSearch.username"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.table.user')"
></el-input>
</div>
<TableSearch
@search="search()"
:placeholder="$t('commons.table.user')"
v-model:searchName="processSearch.username"
/>
</el-col>
</el-row>
</el-form-item>
@@ -126,7 +107,6 @@
<script setup lang="ts">
import FireRouter from '@/views/host/process/index.vue';
import OpDialog from '@/components/del-dialog/index.vue';
import { ref, onMounted, onUnmounted, nextTick, reactive } from 'vue';
import ProcessDetail from './detail/index.vue';
import i18n from '@/lang';
@@ -274,7 +254,7 @@ const search = () => {
if (isWsOpen() && !isGetData.value) {
isGetData.value = true;
if (typeof processSearch.pid === 'string') {
processSearch.pid = undefined;
processSearch.pid = Number(processSearch.pid);
}
processSocket.send(JSON.stringify(processSearch));
}
-1
View File
@@ -9,7 +9,6 @@
<script lang="ts" setup>
import i18n from '@/lang';
import RouterButton from '@/components/router-button/index.vue';
const buttons = [
{
+1 -12
View File
@@ -18,17 +18,7 @@
<el-col :xs="24" :sm="16" :md="16" :lg="16" :xl="16"></el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<TableSetting @search="search()" />
<div class="search-button">
<el-input
clearable
v-model="searchInfo"
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchInfo" />
</el-col>
</el-row>
</template>
@@ -69,7 +59,6 @@
</template>
<script setup lang="ts">
import TableSetting from '@/components/table-setting/index.vue';
import { dateFormat } from '@/utils/util';
import { onMounted, reactive, ref } from '@vue/runtime-core';
import { loadSSHLogs } from '@/api/modules/host';
+1 -11
View File
@@ -8,17 +8,7 @@
<el-col :span="8"></el-col>
<el-col :span="8"></el-col>
<el-col :span="8">
<div class="search-button">
<el-input
v-model.trim="sshSearch.loginUser"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.table.user')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="sshSearch.loginUser" />
</el-col>
</el-row>
</div>
@@ -111,7 +111,6 @@
<script setup lang="ts">
import { Command } from '@/api/interface/command';
import GroupDialog from '@/components/group/index.vue';
import OpDialog from '@/components/del-dialog/index.vue';
import GroupChangeDialog from '@/components/group/change.vue';
import { addCommand, editCommand, deleteCommand, getCommandPage } from '@/api/modules/host';
import { reactive, ref } from 'vue';
@@ -15,17 +15,7 @@
</el-button>
</el-col>
<el-col :span="4">
<div class="search-button">
<el-input
v-model="info"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="info" />
</el-col>
</el-row>
</template>
@@ -75,7 +65,6 @@
<script setup lang="ts">
import GroupDialog from '@/components/group/index.vue';
import OpDialog from '@/components/del-dialog/index.vue';
import GroupChangeDialog from '@/components/group/change.vue';
import OperateDialog from '@/views/host/terminal/host/operate/index.vue';
import { deleteHost, editHostGroup, searchHosts } from '@/api/modules/host';
+1 -12
View File
@@ -16,18 +16,8 @@
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<div class="flx-align-center">
<div class="search-button">
<el-input
v-model="searchIP"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search') + ' ip'"
></el-input>
</div>
<TableSetting @search="search()" />
<TableSearch @search="search()" v-model:searchName="searchIP" />
</div>
</el-col>
</el-row>
@@ -78,7 +68,6 @@
</template>
<script setup lang="ts">
import TableSetting from '@/components/table-setting/index.vue';
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { dateFormat } from '@/utils/util';
import { cleanLogs, getLoginLogs } from '@/api/modules/log';
+1 -12
View File
@@ -16,17 +16,7 @@
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<TableSetting @search="search()" />
<div class="search-button">
<el-input
clearable
v-model="searchName"
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -109,7 +99,6 @@
</template>
<script setup lang="ts">
import TableSetting from '@/components/table-setting/index.vue';
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
import { dateFormat } from '@/utils/util';
import { cleanLogs, getOperationLogs } from '@/api/modules/log';
@@ -464,7 +464,6 @@
<script setup lang="ts">
import { dateFormat } from '@/utils/util';
import { onMounted, ref } from 'vue';
import OpDialog from '@/components/del-dialog/index.vue';
import { getBackupList, deleteBackup, refreshOneDrive } from '@/api/modules/setting';
import localDialog from '@/views/setting/backup-account/local/index.vue';
import s3Dialog from '@/views/setting/backup-account/s3/index.vue';
+1 -13
View File
@@ -16,17 +16,7 @@
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<TableSetting ref="timerRef" @search="search()" />
<div class="search-button">
<el-input
clearable
v-model="searchName"
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="searchName" />
</el-col>
</el-row>
</template>
@@ -177,8 +167,6 @@
</template>
<script setup lang="ts">
import OpDialog from '@/components/del-dialog/index.vue';
import TableSetting from '@/components/table-setting/index.vue';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { snapshotCreate, searchSnapshotPage, snapshotDelete, updateSnapshotDescription } from '@/api/modules/setting';
import { onMounted, reactive, ref } from 'vue';
@@ -172,7 +172,8 @@ import { ElMessageBox } from 'element-plus';
import i18n from '@/lang';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { snapshotRecover, snapshotRollback } from '@/api/modules/setting';
import { MsgSuccess } from '@/utils/message';
import { MsgError, MsgSuccess } from '@/utils/message';
import { loadOsInfo } from '@/api/modules/dashboard';
const drawerVisible = ref(false);
const snapInfo = ref();
@@ -210,7 +211,27 @@ const doRecover = async (isNew: boolean) => {
};
const recoverSnapshot = async (isNew: boolean) => {
ElMessageBox.confirm(i18n.global.t('setting.recoverHelper', [snapInfo.value.name]), {
let msg = i18n.global.t('setting.recoverHelper', [snapInfo.value.name]);
if (
snapInfo.value.name.indexOf('amd64') === -1 &&
snapInfo.value.name.indexOf('arm64') === -1 &&
snapInfo.value.name.indexOf('armv7') === -1 &&
snapInfo.value.name.indexOf('ppc64le') === -1 &&
snapInfo.value.name.indexOf('s390x') === -1
) {
msg = i18n.global.t('setting.recoverHelper1', [snapInfo.value.name]);
} else {
const res = await loadOsInfo();
let osVal = res.data.kernelArch;
if (osVal === '') {
msg = i18n.global.t('setting.recoverHelper1', [snapInfo.value.name]);
} else if (snapInfo.value.name.indexOf(osVal) === -1) {
MsgError(i18n.global.t('setting.recoverHelper2'));
return;
}
}
ElMessageBox.confirm(msg, i18n.global.t('commons.button.recover'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
@@ -261,6 +282,7 @@ defineExpose({
font-size: 14px;
font-weight: 500;
line-height: 25px;
color: var(--el-button-text-color, var(--el-text-color-regular));
}
.card-logo {
font-size: 7px;
@@ -49,7 +49,6 @@
import { Codemirror } from 'vue-codemirror';
import { javascript } from '@codemirror/lang-javascript';
import { oneDark } from '@codemirror/theme-one-dark';
import OpDialog from '@/components/del-dialog/index.vue';
import { onUnmounted, reactive, ref, shallowRef } from 'vue';
import { OperateSupervisorProcessFile } from '@/api/modules/host-tool';
import i18n from '@/lang';
@@ -84,7 +84,6 @@
<script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref } from 'vue';
import OpDialog from '@/components/del-dialog/index.vue';
import { Runtime } from '@/api/interface/runtime';
import { DeleteRuntime, SearchRuntimes } from '@/api/modules/runtime';
import { dateFormat, toLowerCase } from '@/utils/util';
@@ -42,7 +42,6 @@
</template>
<script lang="ts" setup>
import OpDialog from '@/components/del-dialog/index.vue';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { Website } from '@/api/interface/website';
import { DeleteAcmeAccount, SearchAcmeAccount } from '@/api/modules/website';
@@ -35,7 +35,6 @@
</template>
<script lang="ts" setup>
import OpDialog from '@/components/del-dialog/index.vue';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { Website } from '@/api/interface/website';
import { DeleteCA, SearchCAs } from '@/api/modules/website';
-1
View File
@@ -154,7 +154,6 @@
<script lang="ts" setup>
import { onMounted, reactive, ref, computed } from 'vue';
import OpDialog from '@/components/del-dialog/index.vue';
import { DeleteSSL, DownloadFile, SearchSSL, UpdateSSL } from '@/api/modules/website';
import DnsAccount from './dns-account/index.vue';
import AcmeAccount from './acme-account/index.vue';
@@ -26,7 +26,6 @@
<script lang="ts" setup name="proxy">
import { Website } from '@/api/interface/website';
import OpDialog from '@/components/del-dialog/index.vue';
import { OperateAuthConfig, GetAuthConfig } from '@/api/modules/website';
import { computed, onMounted, ref } from 'vue';
import i18n from '@/lang';
@@ -25,7 +25,6 @@
<script lang="ts" setup>
import Domain from './create/index.vue';
import { Website } from '@/api/interface/website';
import OpDialog from '@/components/del-dialog/index.vue';
import { DeleteDomain, GetWebsite, ListDomains } from '@/api/modules/website';
import { computed, onMounted, ref } from 'vue';
import i18n from '@/lang';
@@ -38,7 +38,6 @@
<script lang="ts" setup name="proxy">
import { Website } from '@/api/interface/website';
import OpDialog from '@/components/del-dialog/index.vue';
import { OperateProxyConfig, GetProxyConfig } from '@/api/modules/website';
import { computed, onMounted, ref } from 'vue';
import Create from './create/index.vue';
@@ -55,7 +55,6 @@
<script lang="ts" setup name="proxy">
import { Website } from '@/api/interface/website';
import OpDialog from '@/components/del-dialog/index.vue';
import { OperateRedirectConfig, GetRedirectConfig } from '@/api/modules/website';
import { computed, onMounted, ref } from 'vue';
import Create from './create/index.vue';
+1 -18
View File
@@ -32,17 +32,7 @@
</el-button>
</el-col>
<el-col :xs="24" :sm="4" :md="4" :lg="4" :xl="4">
<div class="search-button">
<el-input
v-model="req.name"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
<TableSearch @search="search()" v-model:searchName="req.name" />
</el-col>
</el-row>
</template>
@@ -185,7 +175,6 @@
</template>
<script lang="ts" setup>
import Tooltip from '@/components/tooltip/index.vue';
import Backups from '@/components/backup/index.vue';
import UploadDialog from '@/components/upload/index.vue';
import DefaultServer from '@/views/website/website/default/index.vue';
@@ -451,9 +440,3 @@ onMounted(() => {
listGroup();
});
</script>
<style lang="scss" scoped>
.search-button {
float: right;
display: inline;
}
</style>
+1 -1
View File
@@ -311,7 +311,7 @@ replace (
github.com/containerd/containerd => github.com/containerd/containerd v1.7.13
github.com/cucumber/godog => github.com/cucumber/godog v0.12.6
github.com/docker/buildx => github.com/docker/buildx v0.12.0-rc2.0.20231219140829-617f538cb315
github.com/mattn/go-sqlite3 => github.com/jgiannuzzi/go-sqlite3 v1.14.17-0.20230719111531-6e53453ccbd3
github.com/mattn/go-sqlite3 => github.com/jgiannuzzi/go-sqlite3 v1.14.17-0.20240122133042-fb824c8e339e
github.com/moby/buildkit => github.com/moby/buildkit v0.13.0-beta3
golang.org/x/net => golang.org/x/net v0.17.0
k8s.io/api => k8s.io/api v0.29.2
+2 -4
View File
@@ -268,8 +268,6 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/i18n v1.1.1 h1:2wwCkrU/Zsu91fwZBhpXAPTQbHnjengAs7TNLbtJmew=
github.com/gin-contrib/i18n v1.1.1/go.mod h1:BAC1NwrM9ApRxS5zehG/OSoM3DTXqTVoJs+6VTdnIDo=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
@@ -487,8 +485,8 @@ github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s=
github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jgiannuzzi/go-sqlite3 v1.14.17-0.20230719111531-6e53453ccbd3 h1:Iqr8ZWwijosAcawoD9IZ7cwj61WH50Rysm+RQ+ZIB4I=
github.com/jgiannuzzi/go-sqlite3 v1.14.17-0.20230719111531-6e53453ccbd3/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/jgiannuzzi/go-sqlite3 v1.14.17-0.20240122133042-fb824c8e339e h1:0s+RSCZSEaZ3IM4fqMH1Nf5UDpttrn1R2QDutqiL2Zc=
github.com/jgiannuzzi/go-sqlite3 v1.14.17-0.20240122133042-fb824c8e339e/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=

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