Compare commits

...
Author SHA1 Message Date
ssongliu 2b7562f546 feat: Add snapshot restore timeout duration (#11660) 2026-01-16 11:09:05 +08:00
ssongliu e7f4e312b9 fix: Fix container image cleanup uninitialized issue (#11655) 2026-01-15 10:09:59 +00:00
ssongliu 98d3870477 fix: Fix incomplete scanning of unassociated backups issue (#11654) 2026-01-15 10:08:00 +00:00
CityFun b1ceb2e53d feat: fix redirection issue caused by incorrect PHP PATH_INFO config (#11650)
Refs https://github.com/1Panel-dev/1Panel/issues/11651
2026-01-15 07:26:01 +00:00
ssongliu 075d2468ea feat: Add file copy command timeout duration (#11652)
Refs #11639
2026-01-15 07:23:59 +00:00
ssongliu 8b9e7aef4a fix: Fix container image cleanup count display abnormality issue (#11649) 2026-01-15 14:47:57 +08:00
ssongliu 15e499c1e9 perf: Optimize cache cleanup logic (#11648) 2026-01-15 06:45:59 +00:00
55ccb9fe61 fix: DNS Manual SSL certificate issues for wildcard domains (#11642)
This fix addresses three bugs in the DNS Manual SSL certificate flow:

1. **Order caching fails when Expires is zero**: ACME orders often have
   zero Expires initially. The condition `!Expires.IsZero()` caused valid
   cached orders to be deleted and recreated with different TXT values.
   Fixed by checking `Expires.IsZero() || Expires.After(now)`.

2. **Wildcard and base domain TXT records overwrite each other**: When
   requesting SSL for both `example.com` and `*.example.com`, both
   authorizations have identifier `example.com`, causing one TXT value
   to overwrite the other. Fixed by using `*.domain` as the map key.

3. **Only first TXT record checked**: When multiple TXT records exist,
   only the first was checked. Fixed by returning all TXT values and
   checking if expected value exists in any of them.

```release-note
Fix DNS Manual SSL certificate issues for wildcard domains
```

Co-authored-by: DeployThemAll <deploythemall@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 10:42:59 +08:00
蘭 a5816f1dc9 fix: Fix alert log issues (#11637) 2026-01-14 08:47:56 +00:00
ssongliu 95a613f59f fix: Fix GPU monitoring page refresh abnormality issue (#11635) 2026-01-14 06:57:57 +00:00
J.Knight 6777941dee feat: add support for Porkbun DNS provider (#11614) 2026-01-14 10:38:12 +08:00
ssongliu e55128b7fa perf: Optimize security portal prompt information (#11613) 2026-01-09 16:50:01 +08:00
CityFun 9ac11002f8 perf: Optimize app store icon display (#11612) 2026-01-09 08:41:52 +00:00
ssongliu 1cc7bfb4a9 fix: Fix cache cleanup checkbox abnormality issue (#11611) 2026-01-09 16:18:37 +08:00
CityFun e59534e54f feat: change appstore sync logic for v2.0.17 (#11610) 2026-01-09 07:17:52 +00:00
ssongliu 8c2368c1d2 fix: Fix snapshot cleanup abnormality issue (#11608) 2026-01-09 05:47:52 +00:00
CityFun 8e8f181cae fix: Fixed issue with website log display error in clean cache (#11607) 2026-01-09 04:01:51 +00:00
蘭 2fcab77565 fix: Fix more button overflow screen issues (#11605) 2026-01-09 11:17:26 +08:00
ssongliu d29f0c6c31 style: Modify container image pull style (#11604) 2026-01-09 11:14:15 +08:00
CityFun 79f1b1458b fix: Fix infinite log reading issue (#11603) 2026-01-09 11:14:07 +08:00
蘭 b279ee6b82 fix: Fix the issue of opening folder path errors (#11600) 2026-01-08 22:01:54 +08:00
蘭 c64a58d814 fix: Fix the issue of not sending SMS alerts (#11599) 2026-01-08 17:33:27 +08:00
ssongliu 57c60ab3f8 perf: MySQL backup parameter deduplication (#11597) 2026-01-08 15:27:00 +08:00
ssongliu e37d90d6e1 fix: Fix node overview cannot operate container terminal issue (#11593) 2026-01-08 06:17:49 +00:00
KOMATA 22055ed99a refactor: enhance Mux connection handling in server (#11590)
* refactor: enhance Mux connection handling in server

* fix: Increase maximum allowed length for RequestURI in Mux connection handling

* feat: Add support for HTTP/2 in Mux mode
2026-01-08 14:03:04 +08:00
KOMATA e263331b94 feat: Enhance entrance pattern validation in settings API to exclude specific segments and assets (#11592) 2026-01-08 14:01:43 +08:00
ssongliu eb3d613e3d feat: Set overview as default menu for multi-node management (#11588) 2026-01-07 22:06:13 +08:00
CityFun cc23444776 feat: change listNodes api timeout (#11586) 2026-01-07 09:29:48 +00:00
ssongliu 1931d3f262 perf: Optimize SSL selection style (#11585) 2026-01-07 16:08:56 +08:00
CityFun 79cbbbdf0a perf: Optimize display for long website names (#11581) 2026-01-07 07:09:47 +00:00
ssongliu 38cd785aa3 fix: Fix node overview operation abnormality issue (#11580) 2026-01-07 07:07:48 +00:00
ssongliu dabec3f87a fix: Fix cache cleanup abnormality issue (#11577) 2026-01-06 22:09:11 +08:00
ssongliu 6c94cb2bdf feat: MySQL backup supports custom parameters (#11575) 2026-01-06 16:41:55 +08:00
KOMATA f67bdf0c22 feat: Implement connection closure handling in status 444 (#11574)
* feat: Implement connection closure handling in security module

* refactor: Rename closeConn to CloseDirectly for clarity in security module
2026-01-06 16:41:20 +08:00
蘭 6c51b31270 fix: Fix more button overflow screen issues (#11573) 2026-01-06 07:39:44 +00:00
ssongliu ef6e342737 fix: Fix ICMP ping disable status update abnormality issue (#11571) 2026-01-06 04:01:43 +00:00
蘭 ac0ef98dde feat: Add panel name and IP address to email alert content (#11567) 2026-01-05 22:10:27 +08:00
CityFun 523775e56b fix: Fix log viewing errors when switching nodes (#11565) 2026-01-05 18:06:36 +08:00
CityFun b6e741a150 feat: change node select params rules (#11563) 2026-01-05 17:29:51 +08:00
CityFun 54357fbe2b feat: Add backend api timeout (#11562) 2026-01-05 08:41:42 +00:00
ssongliu 040dda5bec feat: Add node overview description information (#11561) 2026-01-05 08:25:42 +00:00
CityFun 77b4d58fb8 feat: Add required validation for pushNodes (#11560) 2026-01-05 08:23:42 +00:00
CityFun 0c9e551492 fix: Fix API to prevent frontend code startup errors (#11559) 2026-01-05 07:47:42 +00:00
KOMATA 2ba1e2e22e fix: Update regex patterns for log highlighting in custom component (#11556)
* fix: Update regex patterns for log highlighting in custom component

* feat: Add new regex pattern for HTTP status codes highlighting in custom log component

* refactor: Update HTTP status code regex patterns in custom log component for improved highlighting
2026-01-05 14:49:09 +08:00
ssongliu d8a08a6c0c fix: Fix iptables rule display abnormality issue (#11555) 2026-01-05 14:48:49 +08:00
101 changed files with 1408 additions and 527 deletions
+7 -1
View File
@@ -62,4 +62,10 @@ cmd/server/fileList.txt
1Panel.code-workspace
core/.golangci.yml
agent/.golangci.yml
agent/.golangci.yml
.opencode
openspec
CLAUDE.md
AGENTS.md
opencode.json
+3 -3
View File
@@ -203,14 +203,14 @@ func (b *BaseApi) GetAppListUpdate(c *gin.Context) {
// @Success 200 {file} file "app icon"
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/icon/:appId [get]
// @Router /apps/icon/:key [get]
func (b *BaseApi) GetAppIcon(c *gin.Context) {
appID, err := helper.GetIntParamByKey(c, "appID")
appKey, err := helper.GetStrParamByKey(c, "key")
if err != nil {
helper.BadRequest(c, err)
return
}
iconBytes, err := appService.GetAppIcon(appID)
iconBytes, err := appService.GetAppIcon(appKey)
if err != nil {
helper.InternalServer(c, err)
return
+5
View File
@@ -330,3 +330,8 @@ type AlertEmailConfig struct {
Encryption string `json:"encryption"` // "ssl" / "tls" / "none"
Recipient string `json:"recipient"`
}
type AgentInfo struct {
NodeName string `json:"nodeName"`
NodeAddr string `json:"nodeAddr"`
}
+7 -6
View File
@@ -65,12 +65,13 @@ type UploadForRecover struct {
}
type CommonBackup struct {
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mysql-cluster postgresql-cluster redis-cluster"`
Name string `json:"name"`
DetailName string `json:"detailName"`
Secret string `json:"secret"`
TaskID string `json:"taskID"`
FileName string `json:"fileName"`
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mysql-cluster postgresql-cluster redis-cluster"`
Name string `json:"name"`
DetailName string `json:"detailName"`
Secret string `json:"secret"`
TaskID string `json:"taskID"`
FileName string `json:"fileName"`
Args []string `json:"args"`
Description string `json:"description"`
}
+3
View File
@@ -49,6 +49,7 @@ type CronjobOperate struct {
Timeout uint `json:"timeout" validate:"number,min=1"`
IgnoreErr bool `json:"ignoreErr"`
Secret string `json:"secret"`
Args string `json:"args"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
@@ -123,6 +124,7 @@ type CronjobInfo struct {
LastRecordTime string `json:"lastRecordTime"`
Status string `json:"status"`
Secret string `json:"secret"`
Args string `json:"args"`
AlertCount uint `json:"alertCount"`
}
@@ -162,6 +164,7 @@ type CronjobTrans struct {
IgnoreErr bool `json:"ignoreErr"`
SnapshotRule SnapshotTransHelper `json:"snapshotRule"`
Secret string `json:"secret"`
Args string `json:"args"`
SourceAccounts []string `json:"sourceAccounts"`
DownloadAccount string `json:"downloadAccount"`
+1
View File
@@ -37,6 +37,7 @@ type Cronjob struct {
Timeout uint `json:"timeout"`
IgnoreErr bool `json:"ignoreErr"`
RetainCopies uint64 `json:"retainCopies"`
Args string `json:"args"`
IsExecuting bool `json:"isExecuting"`
Status string `json:"status"`
+11 -2
View File
@@ -580,7 +580,11 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
AlertId: alert.ID,
Count: todayCount + 1,
}
_ = xpack.CreateSMSAlertLog(alertType, alert, create, quotaType, params, constant.SMS)
alertErr := xpack.CreateSMSAlertLog(alertType, alert, create, quotaType, params, constant.SMS)
if alertErr != nil {
global.LOG.Infof("%s alert sms push faild, err: %v", alertType, alertErr.Error())
continue
}
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.SMS)
global.LOG.Infof("%s alert sms push successful", alertType)
@@ -599,7 +603,12 @@ func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params [
create.AlertRule = alertUtil.ProcessAlertRule(alert)
create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, constant.Email)
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alertInfo, params, transport)
agentInfo, _ := xpack.GetAgentInfo()
alertErr := alertUtil.CreateEmailAlertLog(create, alertInfo, params, transport, agentInfo)
if alertErr != nil {
global.LOG.Infof("%s alert email push faild, err: %v", alertType, alertErr.Error())
continue
}
alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.Email)
global.LOG.Infof("%s alert email push successful", alertType)
}
+4 -2
View File
@@ -85,7 +85,8 @@ func (s *AlertSender) sendEmail(quota string, params []dto.Param) {
}
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, s.alert, params, transport)
agentInfo, _ := xpack.GetAgentInfo()
_ = alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo)
alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email)
global.LOG.Infof("%s alert email push successful", s.alert.Type)
}
@@ -131,7 +132,8 @@ func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) {
}
transport := xpack.LoadRequestTransport()
if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport); err != nil {
agentInfo, _ := xpack.GetAgentInfo()
if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo); err != nil {
global.LOG.Errorf("failed to send Email alert: %v", err)
return
}
+8 -3
View File
@@ -46,7 +46,7 @@ type IAppService interface {
GetAppUpdate() (*response.AppUpdateRes, error)
GetAppDetailByID(id uint) (*response.AppDetailDTO, error)
SyncAppListFromLocal(taskID string)
GetAppIcon(appID uint) ([]byte, error)
GetAppIcon(key string) ([]byte, error)
GetAppDetailByKey(appKey, version string) (response.AppDetailSimpleDTO, error)
}
@@ -802,6 +802,11 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
res := &response.AppUpdateRes{
CanUpdate: false,
}
mysql, _ := appRepo.GetFirst(appRepo.WithKey("mysql"))
if !mysql.BatchInstallSupport {
res.CanUpdate = true
return res, nil
}
versionUrl := fmt.Sprintf("%s/%s/1panel.json.version.txt", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode)
_, versionRes, err := req_helper.HandleRequest(versionUrl, http.MethodGet, constant.TimeOut20s)
@@ -1172,8 +1177,8 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
return nil
}
func (a AppService) GetAppIcon(appID uint) ([]byte, error) {
app, err := appRepo.GetFirst(repo.WithByID(appID))
func (a AppService) GetAppIcon(key string) ([]byte, error) {
app, err := appRepo.GetFirst(appRepo.WithKey(key))
if err != nil {
return nil, err
}
+8 -2
View File
@@ -44,7 +44,7 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error {
return err
}
databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName}
databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName, Args: req.Args}
if err := handleMysqlBackup(databaseHelper, nil, record.ID, targetDir, fileName, req.TaskID, req.Secret); err != nil {
backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()})
return err
@@ -87,7 +87,12 @@ func handleMysqlBackup(db DatabaseHelper, parentTask *task.Task, recordID uint,
}
}
itemHandler := func() error { return doMysqlBackup(db, targetDir, fileName, secret) }
itemHandler := func() error {
if len(db.Args) != 0 {
backupTask.Logf("%s: %v", i18n.GetMsgByKey("Arg"), db.Args)
}
return doMysqlBackup(db, targetDir, fileName, secret)
}
if parentTask != nil {
return itemHandler()
}
@@ -225,6 +230,7 @@ func doMysqlBackup(db DatabaseHelper, targetDir, fileName, secret string) error
Format: dbInfo.Format,
TargetDir: targetDir,
FileName: fileName,
Args: db.Args,
}
if err := cli.Backup(backupInfo); err != nil {
return err
+2 -8
View File
@@ -435,7 +435,6 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
}
}
taskItem.Log(i18n.GetMsgByKey("PruneStart"))
DeletedNumber := 0
SpaceReclaimed := 0
switch req.PruneType {
case "container":
@@ -443,21 +442,18 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
if err != nil {
return err
}
DeletedNumber = len(rep.ContainersDeleted)
SpaceReclaimed = int(rep.SpaceReclaimed)
case "image":
rep, err := client.ImagesPrune(context.Background(), pruneFilters)
if err != nil {
return err
}
DeletedNumber = len(rep.ImagesDeleted)
SpaceReclaimed = int(rep.SpaceReclaimed)
case "network":
rep, err := client.NetworksPrune(context.Background(), pruneFilters)
_, err := client.NetworksPrune(context.Background(), pruneFilters)
if err != nil {
return err
}
DeletedNumber = len(rep.NetworksDeleted)
case "volume":
versions, err := client.ServerVersion(context.Background())
if err != nil {
@@ -470,7 +466,6 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
if err != nil {
return err
}
DeletedNumber = len(rep.VolumesDeleted)
SpaceReclaimed = int(rep.SpaceReclaimed)
case "buildcache":
opts := build.CachePruneOptions{}
@@ -479,10 +474,9 @@ func (u *ContainerService) Prune(req dto.ContainerPrune) error {
if err != nil {
return err
}
DeletedNumber = len(rep.CachesDeleted)
SpaceReclaimed = int(rep.SpaceReclaimed)
}
taskItem.Log(i18n.GetMsgWithMap("PruneHelper", map[string]interface{}{"name": i18n.GetMsgByKey(name), "count": DeletedNumber, "size": common.LoadSizeUnit2F(float64(SpaceReclaimed))}))
taskItem.Log(i18n.GetMsgWithMap("PruneHelper", map[string]interface{}{"name": i18n.GetMsgByKey(name), "size": common.LoadSizeUnit2F(float64(SpaceReclaimed))}))
return nil
}, nil)
go func() {
+3
View File
@@ -143,6 +143,7 @@ func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) {
Timeout: cronjob.Timeout,
IgnoreErr: cronjob.IgnoreErr,
Secret: cronjob.Secret,
Args: cronjob.Args,
}
switch cronjob.Type {
case "app":
@@ -237,6 +238,7 @@ func (u *CronjobService) Import(req []dto.CronjobTrans) error {
Timeout: item.Timeout,
IgnoreErr: item.IgnoreErr,
Secret: item.Secret,
Args: item.Args,
}
hasNotFound := false
switch item.Type {
@@ -733,6 +735,7 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
upMap["timeout"] = req.Timeout
upMap["ignore_err"] = req.IgnoreErr
upMap["secret"] = req.Secret
upMap["args"] = req.Args
err = cronjobRepo.Update(id, upMap)
if err != nil {
return err
+5
View File
@@ -379,6 +379,7 @@ type DatabaseHelper struct {
DBType string
Database string
Name string
Args []string
}
func addSkipTask(source string, taskItem *task.Task) {
@@ -400,6 +401,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
DBType: cronjob.DBType,
Database: mysql.Database,
Name: mysql.Name,
Args: strings.Split(cronjob.Args, ","),
})
}
} else {
@@ -410,6 +412,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
DBType: cronjob.DBType,
Database: pg.PostgresqlName,
Name: pg.Name,
Args: strings.Split(cronjob.Args, ","),
})
}
}
@@ -425,6 +428,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
DBType: cronjob.DBType,
Database: mysqlItem.MysqlName,
Name: mysqlItem.Name,
Args: strings.Split(cronjob.Args, ","),
})
} else {
pgItem, _ := postgresqlRepo.Get(repo.WithByID(uint(itemID)))
@@ -433,6 +437,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
DBType: cronjob.DBType,
Database: pgItem.PostgresqlName,
Name: pgItem.Name,
Args: strings.Split(cronjob.Args, ","),
})
}
}
+87 -53
View File
@@ -105,7 +105,11 @@ func (u *DeviceService) Clean(req []dto.Clean) {
case "tmp_backup":
dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp"))
case "unknown_backup":
dropFileOrDir(item.Name)
if strings.HasPrefix(item.Name, path.Join(global.Dir.LocalBackupDir, "log/website")) {
dropFileOrDir(item.Name)
} else {
dropFile(item.Name)
}
case "rollback":
dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "app"))
@@ -182,6 +186,7 @@ func (u *DeviceService) Clean(req []dto.Clean) {
}
}
_ = cleanEmptyDirs(global.Dir.LocalBackupDir)
_ = settingRepo.Update("LastCleanTime", time.Now().Format(constant.DateTimeLayout))
_ = settingRepo.Update("LastCleanSize", fmt.Sprintf("%v", size))
_ = settingRepo.Update("LastCleanData", fmt.Sprintf("%v", len(req)))
@@ -295,7 +300,7 @@ func loadAgentPackage(fileOp fileUtils.FileOp) dto.CleanTree {
itemTree := dto.CleanTree{
ID: uuid.NewString(),
Label: "agent_packages",
IsCheck: true,
IsCheck: false,
IsRecommend: true,
Type: "agent",
}
@@ -314,17 +319,19 @@ func loadAgentPackage(fileOp fileUtils.FileOp) dto.CleanTree {
Type: "agent",
})
} else {
if strings.HasPrefix(file.Name(), fmt.Sprintf("1panel-agent_%s_a", global.CONF.Base.Version)) {
itemSize, _ := file.Info()
itemName := file.Name()
isCurrentVersion := strings.HasPrefix(itemName, fmt.Sprintf("1panel-agent_%s_", global.CONF.Base.Version))
if isCurrentVersion {
continue
}
itemSize, _ := file.Info()
itemTree.Size += uint64(itemSize.Size())
itemTree.Children = append(itemTree.Children, dto.CleanTree{
ID: uuid.NewString(),
Label: file.Name(),
Name: file.Name(),
Label: itemName,
Name: itemName,
Size: uint64(itemSize.Size()),
IsCheck: true,
IsCheck: !isCurrentVersion,
IsRecommend: true,
Type: "agent",
})
@@ -363,7 +370,7 @@ func loadBackupTree(fileOp fileUtils.FileOp) []dto.CleanTree {
treeData = append(treeData, loadUnknownDbs(fileOp, recordMap))
treeData = append(treeData, loadUnknownWebsites(fileOp, recordMap))
treeData = append(treeData, loadUnknownSnapshot(fileOp))
treeData = append(treeData, loadUnknownWebsiteLog(fileOp, recordMap))
treeData = append(treeData, loadUnknownWebsiteLog(fileOp))
return treeData
}
@@ -376,70 +383,68 @@ func loadUnknownApps(fileOp fileUtils.FileOp, recordMap map[string][]string) dto
for _, item := range val {
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName, item))
}
} else {
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName))
}
}
backupPath := path.Join(global.Dir.LocalBackupDir, "app")
treeData := dto.CleanTree{
ID: uuid.NewString(),
Label: "unknown_app",
IsCheck: false,
IsRecommend: false,
Name: backupPath,
Type: "unknown_backup",
}
_ = loadFileOrDirWithExclude(fileOp, 0, path.Join(global.Dir.LocalBackupDir, "app"), &treeData, excludePaths)
loadBackupIsCheck(&treeData)
_ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths)
return treeData
}
func loadUnknownDbs(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree {
apps, _ := appInstallRepo.ListBy(context.Background())
dbs, _ := databaseRepo.GetList()
var excludePaths []string
appMap := make(map[string]struct{})
for _, app := range apps {
appMap[fmt.Sprintf("database/%s/%s", app.App.Key, app.Name)] = struct{}{}
dbMap := make(map[string]struct{})
for _, db := range dbs {
dbMap[fmt.Sprintf("database/%s/%s", db.Type, db.Name)] = struct{}{}
}
for key, val := range recordMap {
itemName := path.Dir(key)
if _, ok := appMap[itemName]; ok {
if _, ok := dbMap[itemName]; ok {
for _, item := range val {
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, key, item))
}
}
}
backupPath := path.Join(global.Dir.LocalBackupDir, "database")
treeData := dto.CleanTree{
ID: uuid.NewString(),
Label: "unknown_database",
Name: backupPath,
IsCheck: false,
IsRecommend: false,
Type: "unknown_backup",
}
_ = loadFileOrDirWithExclude(fileOp, 0, path.Join(global.Dir.LocalBackupDir, "database"), &treeData, excludePaths)
loadBackupIsCheck(&treeData)
_ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths)
return treeData
}
func loadUnknownWebsites(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree {
websites, _ := websiteRepo.List()
var excludePaths []string
for _, website := range websites {
itemName := fmt.Sprintf("website/%s", website.PrimaryDomain)
itemName := fmt.Sprintf("website/%s", website.Alias)
if val, ok := recordMap[itemName]; ok {
for _, item := range val {
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName, item))
}
} else {
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName))
}
}
backupPath := path.Join(global.Dir.LocalBackupDir, "website")
treeData := dto.CleanTree{
ID: uuid.NewString(),
Label: "unknown_website",
Name: backupPath,
IsCheck: false,
IsRecommend: false,
Type: "unknown_backup",
}
_ = loadFileOrDirWithExclude(fileOp, 0, path.Join(global.Dir.LocalBackupDir, "website"), &treeData, excludePaths)
loadBackupIsCheck(&treeData)
_ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths)
return treeData
}
func loadUnknownSnapshot(fileOp fileUtils.FileOp) dto.CleanTree {
@@ -448,21 +453,21 @@ func loadUnknownSnapshot(fileOp fileUtils.FileOp) dto.CleanTree {
for _, item := range snaps {
excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, "system_snapshot", item.Name+".tar.gz"))
}
backupPath := path.Join(global.Dir.LocalBackupDir, "system_snapshot")
treeData := dto.CleanTree{
ID: uuid.NewString(),
Label: "unknown_snapshot",
Name: backupPath,
IsCheck: false,
IsRecommend: false,
Type: "unknown_backup",
}
dir := path.Join(global.Dir.LocalBackupDir, "system_snapshot")
entries, _ := os.ReadDir(dir)
entries, _ := os.ReadDir(backupPath)
for _, entry := range entries {
childPath := filepath.Join(dir, entry.Name())
childPath := filepath.Join(backupPath, entry.Name())
if isExactPathMatch(childPath, excludePaths) {
continue
}
childNode := dto.CleanTree{
ID: uuid.NewString(),
Label: entry.Name(),
@@ -488,7 +493,7 @@ func loadUnknownSnapshot(fileOp fileUtils.FileOp) dto.CleanTree {
return treeData
}
func loadUnknownWebsiteLog(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree {
func loadUnknownWebsiteLog(fileOp fileUtils.FileOp) dto.CleanTree {
treeData := dto.CleanTree{
ID: uuid.NewString(),
Label: "unknown_website_log",
@@ -497,14 +502,19 @@ func loadUnknownWebsiteLog(fileOp fileUtils.FileOp, recordMap map[string][]strin
Type: "unknown_backup",
}
dir := path.Join(global.Dir.LocalBackupDir, "log/website")
websites, _ := websiteRepo.List()
websiteMap := make(map[string]struct{})
for _, website := range websites {
websiteMap[website.Alias] = struct{}{}
}
entries, _ := os.ReadDir(dir)
for _, entry := range entries {
if !entry.IsDir() {
continue
}
dirName := entry.Name()
itemName := fmt.Sprintf("log/website/%s", dirName)
if _, ok := recordMap[itemName]; !ok {
if _, ok := websiteMap[dirName]; !ok {
dirPath := path.Join(dir, dirName)
itemSize, _ := fileOp.GetDirSize(dirPath)
childData := dto.CleanTree{
@@ -537,7 +547,6 @@ func loadFileOrDirWithExclude(fileOp fileUtils.FileOp, index uint, dir string, r
if isExactPathMatch(childPath, excludes) {
continue
}
childNode := dto.CleanTree{
ID: uuid.NewString(),
Label: entry.Name(),
@@ -572,26 +581,6 @@ func loadFileOrDirWithExclude(fileOp fileUtils.FileOp, index uint, dir string, r
return nil
}
func loadBackupIsCheck(treeData *dto.CleanTree) {
for i := 0; i < len(treeData.Children); i++ {
if len(treeData.Children[i].Children) == 0 {
treeData.Children[i].IsCheck = true
treeData.Children[i].IsRecommend = true
continue
}
if treeData.Label != "unknown_database" && treeData.Label != "unknown_app" {
continue
}
for j := 0; j < len(treeData.Children[i].Children); j++ {
if len(treeData.Children[i].Children[j].Children) == 0 {
treeData.Children[i].Children[j].IsCheck = true
treeData.Children[i].Children[j].IsRecommend = true
continue
}
}
}
}
func isExactPathMatch(path string, excludePaths []string) bool {
cleanPath := filepath.Clean(path)
@@ -903,6 +892,18 @@ func dropFileOrDir(itemPath string) {
global.LOG.Errorf("drop file %s failed, err %v", itemPath, err)
}
}
func dropFile(itemPath string) {
info, err := os.Stat(itemPath)
if err != nil {
return
}
if info.IsDir() {
return
}
if err := os.Remove(itemPath); err != nil {
global.LOG.Errorf("drop file %s failed, err %v", itemPath, err)
}
}
func dropBuildCache() (int, int) {
client, err := docker.NewDockerClient()
@@ -1101,3 +1102,36 @@ func scanFile(pathItem string, size *int64, count *int) {
}
}
}
func cleanEmptyDirs(root string) error {
dirsToCheck := make([]string, 0)
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil {
return nil
}
if d.IsDir() {
dirsToCheck = append(dirsToCheck, path)
}
return nil
})
if err != nil {
return err
}
for i := len(dirsToCheck) - 1; i >= 0; i-- {
dir := dirsToCheck[i]
if dir == root {
continue
}
entries, err := os.ReadDir(dir)
if err != nil {
continue
}
if len(entries) == 0 {
_ = os.Remove(dir)
}
}
return nil
}
+2 -2
View File
@@ -203,15 +203,15 @@ func (u *FirewallService) OperateFirewall(req dto.FirewallOperation) error {
needRestartDocker = true
case "disableBanPing":
if err := firewall.UpdatePingStatus("0"); err != nil {
_ = settingRepo.Update("BanPing", constant.StatusDisable)
return err
}
_ = settingRepo.Update("BanPing", constant.StatusDisable)
return nil
case "enableBanPing":
if err := firewall.UpdatePingStatus("1"); err != nil {
_ = settingRepo.Update("BanPing", constant.StatusEnable)
return err
}
_ = settingRepo.Update("BanPing", constant.StatusEnable)
return nil
default:
return fmt.Errorf("not supported operation: %s", req.Operation)
+12
View File
@@ -230,6 +230,18 @@ func (s *IptablesService) Operate(req dto.IptablesOp) error {
}
_ = settingRepo.Update("IptablesStatus", constant.StatusEnable)
return nil
case "bind-base-without-init":
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicBefore, 1); err != nil {
return err
}
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasic, 2); err != nil {
return err
}
if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter, 3); err != nil {
return err
}
_ = settingRepo.Update("IptablesStatus", constant.StatusEnable)
return nil
case "unbind-base":
if err := iptables.UnbindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter); err != nil {
return err
+30 -28
View File
@@ -101,15 +101,15 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
}()
if req.IsNew || snap.InterruptStep == "RecoverDownload" || req.ReDownload {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverDownload",
func(t *task.Task) error { return handleDownloadSnapshot(&itemHelper, snap, rootDir) },
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverDecompress" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverDecompress",
func(t *task.Task) error {
itemHelper.Task.Log("---------------------- 2 / 11 ----------------------")
@@ -118,54 +118,54 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
return err
},
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "BackupBeforeRecover" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"BackupBeforeRecover",
func(t *task.Task) error { return backupBeforeRecover(snap.Name, &itemHelper) },
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
var snapJson SnapshotJson
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"Readjson",
func(t *task.Task) error {
snapJson, err = readFromJson(path.Join(rootDir, snap.Name), &itemHelper)
return err
},
nil,
nil, 0, 90*time.Minute,
)
if req.IsNew || snap.InterruptStep == "RecoverApp" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverApp",
func(t *task.Task) error { return recoverAppData(path.Join(rootDir, snap.Name), &itemHelper) },
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverBaseData" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverBaseData",
func(t *task.Task) error { return recoverBaseData(path.Join(rootDir, snap.Name, "base"), &itemHelper) },
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverDBData" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverDBData",
func(t *task.Task) error { return recoverDBData(path.Join(rootDir, snap.Name, "db"), &itemHelper) },
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverBackups" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverBackups",
func(t *task.Task) error {
itemHelper.Task.Log("---------------------- 8 / 11 ----------------------")
@@ -174,12 +174,12 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
return err
},
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverWebsite" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverWebsite",
func(t *task.Task) error {
itemHelper.Task.Log("---------------------- 9 / 11 ----------------------")
@@ -193,12 +193,12 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err)
return err
},
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
if req.IsNew || snap.InterruptStep == "RecoverPanelData" {
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverPanelData",
func(t *task.Task) error {
itemHelper.Task.Log("---------------------- 10 / 11 ----------------------")
@@ -218,16 +218,16 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
}
return err
},
nil,
nil, 0, 90*time.Minute,
)
req.IsNew = true
}
taskItem.AddSubTaskWithAlias(
taskItem.AddSubTaskWithAliasAndOps(
"RecoverDBData",
func(t *task.Task) error {
return restartCompose(path.Join(snapJson.BaseDir, "1panel/docker/compose"), &itemHelper)
},
nil,
nil, 0, 90*time.Minute,
)
if err := taskItem.Execute(); err != nil {
@@ -277,14 +277,15 @@ func backupBeforeRecover(name string, itemHelper *snapRecoverHelper) error {
return err
}
}
err := itemHelper.FileOp.CopyDirWithExclude(global.Dir.DataDir, rootDir, []string{"cache", "tmp"})
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", global.Dir.DataDir), err)
dataDir := global.Dir.DataDir
err := itemHelper.FileOp.CopyDirWithExclude(dataDir, rootDir, []string{"cache", "tmp", "backup"})
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", dataDir), err)
if err != nil {
return err
}
openrestyDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
if len(openrestyDir) != 0 && !strings.Contains(openrestyDir, global.Dir.DataDir) {
if len(openrestyDir) != 0 && !strings.Contains(openrestyDir, dataDir) {
err := itemHelper.FileOp.CopyDirWithExclude(openrestyDir, rootDir, nil)
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", openrestyDir), err)
if err != nil {
@@ -292,9 +293,10 @@ func backupBeforeRecover(name string, itemHelper *snapRecoverHelper) error {
}
}
if len(global.Dir.LocalBackupDir) != 0 && !strings.Contains(global.Dir.LocalBackupDir, global.Dir.DataDir) {
err = itemHelper.FileOp.CopyDirWithExclude(global.Dir.LocalBackupDir, rootDir, []string{"system_snapshot"})
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", global.Dir.LocalBackupDir), err)
backupDir := global.Dir.LocalBackupDir
if len(backupDir) != 0 && (backupDir == dataDir+"/backup" || !strings.HasPrefix(backupDir, dataDir)) {
err = itemHelper.FileOp.CopyDirWithExclude(backupDir, rootDir, []string{"system_snapshot", "tmp"})
itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", backupDir), err)
if err != nil {
return err
}
+3 -2
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path"
"strings"
"github.com/1Panel-dev/1Panel/agent/app/repo"
@@ -88,7 +89,7 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
taskItem.AddSubTask(
i18n.GetWithName("SnapCopy", global.Dir.LocalBackupDir),
func(t *task.Task) error {
return FileOp.CopyDir(path.Join(rootDir, "backup"), global.Dir.LocalBackupDir)
return FileOp.CopyDir(path.Join(rootDir, "backup"), strings.TrimSuffix(global.Dir.LocalBackupDir, "/backup"))
},
nil,
)
@@ -97,7 +98,7 @@ func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error {
taskItem.AddSubTask(
i18n.GetWithName("SnapCopy", openrestyDir),
func(t *task.Task) error {
return FileOp.CopyDir(path.Join(rootDir, "www"), openrestyDir)
return FileOp.CopyDir(path.Join(rootDir, "www"), strings.TrimSuffix(openrestyDir, "/www"))
},
nil,
)
+1
View File
@@ -195,6 +195,7 @@ require (
github.com/nrdcg/freemyip v0.3.0 // indirect
github.com/nrdcg/goacmedns v0.2.0 // indirect
github.com/nrdcg/namesilo v0.5.0 // indirect
github.com/nrdcg/porkbun v0.4.0 // indirect
github.com/nwaples/rardecode/v2 v2.2.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/ovh/go-ovh v1.9.0 // indirect
+2
View File
@@ -764,6 +764,8 @@ github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE=
github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw=
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A=
github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+15 -14
View File
@@ -201,6 +201,7 @@ ErrRemoteExist: 'The remote database already exists with this name, please modif
ErrLocalExist: 'The name already exists in the local database, please modify it and try again'
RemoteBackup: "To back up the remote database, the local container database service needs to be started first using the image {{ .name }}, please wait..."
RemoteRecover: "To restore the remote database, the local container database service needs to be started first using the image {{ .name }}, please wait..."
Arg: "Argument"
#redis
ErrTypeOfRedis: 'The recovery file type does not match the current persistence method, please modify it and try again'
@@ -211,7 +212,7 @@ ErrObjectInUsed: "This object is in use and cannot be deleted!"
ErrObjectBeDependent: "This image is depended on by other images and cannot be deleted!"
ErrPortRules: 'Port number does not match, please re-enter!'
ErrPgImagePull: 'Image pull timed out, please configure image acceleration or manually pull the {{ .name }} image and try again'
PruneHelper: "This cleanup removed {{ .name }} {{ .count }} items, freeing {{ .size }} disk space"
PruneHelper: "This cleanup {{ .name }} freed disk space {{ .size }}"
ImageRemoveHelper: "Deleted image {{ .name }}, freeing {{ .size }} disk space"
BuildCache: "Build cache"
Volume: "Storage volume"
@@ -505,19 +506,19 @@ ErrInstallExtension: "An installation task is already in progress, please wait f
PanelAlertTitle: "Panel Alert Notification"
TestAlertTitle: "Test Email - Verify Email Connectivity"
TestAlert: "This is a test email to verify that your email sending configuration is correct."
LicenseExpirationAlert: "Your 1Panel license will expire in {{ .day }} days. Please log in to the panel for details."
CronJobFailedAlert: "Your 1Panel scheduled task '{{ .name }}' has failed. Please log in to the panel for details."
ClamAlert: "Your 1Panel virus scan task has detected {{ .num }} infected files. Please log in to the panel for details."
WebSiteAlert: "There are {{ .num }} websites on your 1Panel that will expire in {{ .day }} days. Please log in to the panel for details."
SSLAlert: "There are {{ .num }} SSL certificates on your 1Panel that will expire in {{ .day }} days. Please log in to the panel for details."
DiskUsedAlert: "Your 1Panel disk '{{ .name }}' has used {{ .used }}. Please log in to the panel for details."
ResourceAlert: "The average {{ .name }} usage over {{ .time }} minutes on your 1Panel is {{ .used }}. Please log in to the panel for details."
PanelVersionAlert: "A new version of 1Panel is available. Please log in to the panel to upgrade."
PanelPwdExpirationAlert: "Your 1Panel password will expire in {{ .day }} days. Please log in to the panel for details."
CommonAlert: "Your 1Panel, {{ .msg }}, please log in to the panel for details."
NodeExceptionAlert: "Your 1Panel, {{ .num }} nodes are abnormal, please log in to the panel for details."
LicenseExceptionAlert: "Your 1Panel, {{ .num }} licenses are abnormal, please log in to the panel for details."
SSHAndPanelLoginAlert: "Your 1Panel, abnormal panel {{ .name }} login from {{ .ip }}, please log in to the panel for details."
LicenseExpirationAlert: "Your {{ .node }}{{ .ip }} Panel, the license will expire in {{ .day }} days. Please log in to the panel for details."
CronJobFailedAlert: "Your {{ .node }}{{ .ip }} Panel, scheduled task - {{ .name }} execution failed. Please log in to the panel for details."
ClamAlert: "Your {{ .node }}{{ .ip }} Panel, the virus scan task detected {{ .num }} infected files. Please log in to the panel for details."
WebSiteAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .num }} websites will expire in {{ .day }} days. Please log in to the panel for details."
SSLAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .num }} SSL certificates will expire in {{ .day }} days. Please log in to the panel for details."
DiskUsedAlert: "Your {{ .node }}{{ .ip }} Panel, disk {{ .name }} usage has reached {{ .used }}. Please log in to the panel for details."
ResourceAlert: "Your {{ .node }}{{ .ip }} Panel, the average {{ .name }} usage rate in {{ .time }} minutes is {{ .used }}. Please log in to the panel for details."
PanelVersionAlert: "Your {{ .node }}{{ .ip }} Panel, the latest panel version is available for upgrade. Please log in to the panel for details."
PanelPwdExpirationAlert: "Your {{ .node }}{{ .ip }} Panel, the panel password will expire in {{ .day }} days. Please log in to the panel for details."
CommonAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .msg }}. Please log in to the panel for details."
NodeExceptionAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .num }} nodes are abnormal. Please log in to the panel for details."
LicenseExceptionAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .num }} licenses are abnormal. Please log in to the panel for details."
SSHAndPanelLoginAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .name }} login from {{ .loginIp }} is abnormal. Please log in to the panel for details."
#disk
DeviceNotFound: "Device {{ .name }} not found"
+15 -14
View File
@@ -200,6 +200,7 @@ ErrRemoteExist: 'La base de datos remota ya existe con ese nombre, modifíquelo
ErrLocalExist: 'El nombre ya existe en la base de datos local, modifíquelo e intente de nuevo'
RemoteBackup: "Para hacer una copia de seguridad de la base de datos remota, primero debe iniciarse el servicio de base de datos del contenedor local utilizando la imagen {{ .name }}, espere por favor..."
RemoteRecover: "Para restaurar la base de datos remota, primero debe iniciarse el servicio de base de datos del contenedor local utilizando la imagen {{ .name }}, espere por favor..."
Arg: "Argumento"
#redis
ErrTypeOfRedis: 'El tipo de archivo de recuperación no coincide con el método de persistencia actual, modifíquelo e intente'
@@ -210,7 +211,7 @@ ErrObjectInUsed: "Este objeto está en uso y no se puede eliminar!"
ErrObjectBeDependent: "Esta imagen es dependida por otras imágenes y no se puede eliminar!"
ErrPortRules: 'El número de puerto no coincide, ingrese de nuevo'
ErrPgImagePull: 'Tiempo de espera al descargar la imagen, configure la aceleración o descárguela manualmente ({{ .name }}) e intente de nuevo'
PruneHelper: "Esta limpieza eliminó {{ .name }} {{ .count }} elementos, liberando {{ .size }} de espacio en disco"
PruneHelper: "Esta limpieza {{ .name }} liberó espacio en disco {{ .size }}"
ImageRemoveHelper: "Imagen {{ .name }} eliminada, liberando {{ .size }} de espacio en disco"
BuildCache: "Caché de compilación"
Volume: "Volumen de almacenamiento"
@@ -495,10 +496,6 @@ ErrAlertPush: 'Error al enviar la alerta, ¡revise e intente de nuevo!'
ErrAlertSave: 'Error al guardar la alerta, ¡revise e intente de nuevo!'
ErrAlertSync: 'Error en la sincronización de la alerta, ¡revise e intente de nuevo!'
ErrAlertRemote: 'Error remoto de alerta, ¡revise e intente de nuevo!'
CommonAlert: "Su 1Panel: {{ .msg }}, por favor inicie sesión en el panel para más detalles."
NodeExceptionAlert: "Su 1Panel tiene {{ .num }} nodos anómalos, inicie sesión para más detalles."
LicenseExceptionAlert: "Su 1Panel tiene {{ .num }} licencias anómalas, inicie sesión para más detalles."
SSHAndPanelLoginAlert: "Su 1Panel detectó un inicio de sesión anómalo en el panel {{ .name }} desde {{ .ip }}, revise en el panel para más detalles."
#task - runtime
ErrInstallExtension: "Ya hay una tarea de instalación en curso, espere a que termine"
@@ -507,15 +504,19 @@ ErrInstallExtension: "Ya hay una tarea de instalación en curso, espere a que te
PanelAlertTitle: "Notificación de alerta del panel"
TestAlertTitle: "Correo de prueba - Verificar conectividad de correo"
TestAlert: "Este es un correo de prueba para verificar que la configuración de envío de correos es correcta."
LicenseExpirationAlert: "Su licencia de 1Panel expirará en {{ .day }} días. Inicie sesión para más detalles."
CronJobFailedAlert: "Su tarea programada '{{ .name }}' ha fallado. Inicie sesión en el panel para más detalles."
ClamAlert: "La tarea de escaneo de virus detectó {{ .num }} archivos infectados. Inicie sesión para detalles."
WebSiteAlert: "Hay {{ .num }} sitios web en su 1Panel que expirarán en {{ .day }} días. Inicie sesión para más detalles."
SSLAlert: "Hay {{ .num }} certificados SSL en su 1Panel que expirarán en {{ .day }} días. Inicie sesión para detalles."
DiskUsedAlert: "El disco '{{ .name }}' de su 1Panel ha utilizado {{ .used }}. Inicie sesión para más detalles."
ResourceAlert: "El uso promedio de {{ .name }} en {{ .time }} minutos es de {{ .used }}. Inicie sesión para más detalles."
PanelVersionAlert: "Hay una nueva versión de 1Panel disponible. Inicie sesión para actualizar."
PanelPwdExpirationAlert: "Su contraseña de 1Panel expirará en {{ .day }} días. Inicie sesión para más detalles."
LicenseExpirationAlert: "Su Panel {{ .node }}{{ .ip }}, la licencia expirará en {{ .day }} días. Inicie sesión en el panel para ver los detalles."
CronJobFailedAlert: "Su Panel {{ .node }}{{ .ip }}, la ejecución de la tarea programada {{ .name }} falló. Inicie sesión en el panel para ver los detalles."
ClamAlert: "Su Panel {{ .node }}{{ .ip }}, la tarea de escaneo antivirus detectó {{ .num }} archivos infectados. Inicie sesión en el panel para ver los detalles."
WebSiteAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .num }} sitios web expirarán en {{ .day }} días. Inicie sesión en el panel para ver los detalles."
SSLAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .num }} certificados SSL expirarán en {{ .day }} días. Inicie sesión en el panel para ver los detalles."
DiskUsedAlert: "Su Panel {{ .node }}{{ .ip }}, el uso del disco {{ .name }} ha alcanzado {{ .used }}. Inicie sesión en el panel para ver los detalles."
ResourceAlert: "Su Panel {{ .node }}{{ .ip }}, la tasa de uso promedio de {{ .name }} en {{ .time }} minutos es {{ .used }}. Inicie sesión en el panel para ver los detalles."
PanelVersionAlert: "Su Panel {{ .node }}{{ .ip }}, está disponible la última versión del panel para actualizar. Inicie sesión en el panel para ver los detalles."
PanelPwdExpirationAlert: "Su Panel {{ .node }}{{ .ip }}, la contraseña del panel expirará en {{ .day }} días. Inicie sesión en el panel para ver los detalles."
CommonAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .msg }}. Inicie sesión en el panel para ver los detalles."
NodeExceptionAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .num }} nodos son anómalos. Inicie sesión en el panel para ver los detalles."
LicenseExceptionAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .num }} licencias son anómalas. Inicie sesión en el panel para ver los detalles."
SSHAndPanelLoginAlert: "Su Panel {{ .node }}{{ .ip }}, el inicio de sesión {{ .name }} desde {{ .loginIp }} es anómalo. Inicie sesión en el panel para ver los detalles."
#disk
DeviceNotFound: "Dispositivo {{ .name }} no encontrado"
+15 -14
View File
@@ -200,6 +200,7 @@ ErrRemoteExist: 'この名前のリモート データベースは既に存在
ErrLocalExist: '名前はローカル データベースに既に存在します。変更してもう一度お試しください'
RemoteBackup: "リモートデータベースをバックアップするには、まずイメージ {{ .name }} を使用してローカルコンテナデータベースサービスを起動する必要があります。しばらくお待ちください..."
RemoteRecover: "リモートデータベースを復元するには、まずイメージ {{ .name }} を使用してローカルコンテナデータベースサービスを起動する必要があります。しばらくお待ちください..."
Arg: "引数"
#redis
ErrTypeOfRedis: 'リカバリ ファイルの種類が現在の永続化方法と一致しません。変更して再試行してください'
@@ -210,7 +211,7 @@ ErrObjectInUsed: "このオブジェクトは使用中であり、削除でき
ErrObjectBeDependent: "このイメージは他のイメージに依存されているため、削除できません!"
ErrPortRules: 'ポート番号が一致しません。再入力してください。'
ErrPgImagePull: 'イメージのプルがタイムアウトしました。イメージのアクセラレーションを設定するか、{{ .name }} イメージを手動でプルして再試行してください'
PruneHelper: "今回のクリーンアップで{{ .name }} {{ .count }}個を削除し、{{ .size }}のディスク領域を解放しました"
PruneHelper: "今回のクリーンアップ {{ .name }} でディスク領域 {{ .size }} を解放しました"
ImageRemoveHelper: "イメージ{{ .name }}を削除し、{{ .size }}のディスク領域を解放しました"
BuildCache: "ビルドキャッシュ"
Volume: "ストレージボリューム"
@@ -504,19 +505,19 @@ ErrInstallExtension: "インストールタスクが進行中です、タスク
PanelAlertTitle: "パネルアラート通知"
TestAlertTitle: "テストメール - メール接続の確認"
TestAlert: "これはテストメールです。メール送信設定が正しく構成されているかを確認します。"
LicenseExpirationAlert: "お使いの 1Panel ライセンスは {{ .day }} 日後に有効期限が切れます。詳細はパネルにログインしてご確認ください。"
CronJobFailedAlert: "1Panel のスケジュールタスク '{{ .name }}' の実行に失敗しました。詳細はパネルでご確認ください。"
ClamAlert: "1Panel のウイルススキャンで {{ .num }} 件の感染ファイルが検出されました。詳細はパネルでご確認ください。"
WebSiteAlert: "1Panel にある {{ .num }} 件のウェブサイトが {{ .day }} 日後に有効期限を迎えます。詳細はパネルでご確認ください。"
SSLAlert: "1Panel にある {{ .num }} 枚のSSL証明書が {{ .day }} 日後に有効期限を迎えます。詳細はパネルでご確認ください。"
DiskUsedAlert: "1Panel のディスク '{{ .name }}' は {{ .used }} 使用されています。詳細はパネルでご確認ください。"
ResourceAlert: "1Panel の {{ .time }} 分間の平均 {{ .name }} 使用率は {{ .used }} です。詳細はパネルでご確認ください。"
PanelVersionAlert: "1Panel に新しいバージョンが利用可能です。アップグレードはパネルから行ってください。"
PanelPwdExpirationAlert: "1Panel のパスワードは {{ .day }} 日後に期限切れとなります。詳細はパネルでご確認ください。"
CommonAlert: "お使いの1Panel、{{ .msg }}、詳細はパネルにログインしてご確認ください。"
NodeExceptionAlert: "お使いの1Panel、{{ .num }}個のノードに異常があります。詳細はパネルにログインしてご確認ください。"
LicenseExceptionAlert: "お使いの1Panel、{{ .num }}個のライセンスに異常があります。詳細はパネルにログインしてご確認ください。"
SSHAndPanelLoginAlert: "お使いの1Panel、パネル{{ .name }}が{{ .ip }}から異常ログインしました。詳細はパネルにログインしてご確認ください。"
LicenseExpirationAlert: "あなたの {{ .node }}{{ .ip }} パネル、ライセンスは {{ .day }} 日後に期限切れになります。詳細はパネルにログインしてご確認ください。"
CronJobFailedAlert: "あなたの {{ .node }}{{ .ip }} パネル、スケジュールタスク {{ .name }} の実行に失敗しました。詳細はパネルにログインしてご確認ください。"
ClamAlert: "あなたの {{ .node }}{{ .ip }} パネル、ウイルススキャンタスクで {{ .num }} 個の感染ファイルを検出しました。詳細はパネルにログインしてご確認ください。"
WebSiteAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のウェブサイトが {{ .day }} 日後に期限切れになります。詳細はパネルにログインしてご確認ください。"
SSLAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 枚のSSL証明書が {{ .day }} 日後に期限切れになります。詳細はパネルにログインしてご確認ください。"
DiskUsedAlert: "あなたの {{ .node }}{{ .ip }} パネル、ディスク {{ .name }} の使用率が {{ .used }} に達しました。詳細はパネルにログインしてご確認ください。"
ResourceAlert: "あなたの {{ .node }}{{ .ip }} パネル、過去 {{ .time }} 分間の {{ .name }} 平均使用率は {{ .used }} です。詳細はパネルにログインしてご確認ください。"
PanelVersionAlert: "あなたの {{ .node }}{{ .ip }} パネル、最新バージョンがアップグレード可能です。詳細はパネルにログインしてご確認ください。"
PanelPwdExpirationAlert: "あなたの {{ .node }}{{ .ip }} パネル、パネルパスワードは {{ .day }} 日後に期限切れになります。詳細はパネルにログインしてご確認ください。"
CommonAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .msg }}。詳細はパネルにログインしてご確認ください。"
NodeExceptionAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のノードに異常が発生しています。詳細はパネルにログインしてご確認ください。"
LicenseExceptionAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のライセンスに異常が発生しています。詳細はパネルにログインしてご確認ください。"
SSHAndPanelLoginAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .loginIp }} からの {{ .name }} ログインに異常があります。詳細はパネルにログインしてご確認ください。"
#disk
DeviceNotFound: "デバイス {{ .name }} が見つかりません"
+15 -14
View File
@@ -201,6 +201,7 @@ ErrRemoteExist: '이 이름을 가진 원격 데이터베이스가 이미 존재
ErrLocalExist: '이름이 로컬 데이터베이스에 이미 존재합니다. 이름을 수정하고 다시 시도하세요'
RemoteBackup: "원격 데이터베이스를 백업하려면 먼저 이미지 {{ .name }}을(를) 사용하여 로컬 컨테이너 데이터베이스 서비스를 시작해야 합니다. 잠시만 기다려 주세요..."
RemoteRecover: "원격 데이터베이스를 복원하려면 먼저 이미지 {{ .name }}을(를) 사용하여 로컬 컨테이너 데이터베이스 서비스를 시작해야 합니다. 잠시만 기다려 주세요..."
Arg: "인수"
#레디스
ErrTypeOfRedis: '복구 파일 유형이 현재 지속성 방법과 일치하지 않습니다. 수정하고 다시 시도하세요'
@@ -211,7 +212,7 @@ ErrObjectInUsed: "이 객체는 사용 중이어서 삭제할 수 없습니다!"
ErrObjectBeDependent: "이 이미지는 다른 이미지에 의존되어 있어 삭제할 수 없습니다!"
ErrPortRules: '포트 번호가 일치하지 않습니다. 다시 입력하세요!'
ErrPgImagePull: '이미지 풀링 시간이 초과되었습니다. 이미지 가속을 구성하거나 {{ .name }} 이미지를 수동으로 풀링한 다음 다시 시도하세요.'
PruneHelper: "이번 정리에서 {{ .name }} {{ .count }}개를 제거하여 {{ .size }} 디스크 공간을 확보했습니다"
PruneHelper: "이번 정리 {{ .name }}가 디스크 공간 {{ .size }}을(를) 확보했습니다"
ImageRemoveHelper: "이미지 {{ .name }} 삭제, {{ .size }} 디스크 공간 확보"
BuildCache: "빌드 캐시"
Volume: "스토리지 볼륨"
@@ -505,19 +506,19 @@ ErrInstallExtension: "이미 설치 작업이 진행 중입니다. 작업이 완
PanelAlertTitle: "패널 알림 통지"
TestAlertTitle: "테스트 이메일 - 이메일 연결 확인"
TestAlert: "이것은 테스트 이메일이며 이메일 발송 설정이 올바른지 확인합니다."
LicenseExpirationAlert: "귀하의 1Panel 라이선스는 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하세요."
CronJobFailedAlert: "1Panel 예약 작업 '{{ .name }}' 실행에 실패했습니다. 자세한 내용은 패널에서 확인하세요."
ClamAlert: "1Panel 바이러스 검사에서 {{ .num }}개의 감염된 파일이 발견되었습니다. 자세한 내용은 패널에서 확인하세요."
WebSiteAlert: "1Panel 에 있는 {{ .num }}개의 웹사이트가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에서 확인하세요."
SSLAlert: "1Panel 에 있는 {{ .num }}개의 SSL 인증서가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에서 확인하세요."
DiskUsedAlert: "1Panel 디스크 '{{ .name }}'의 사용량은 {{ .used }}입니다. 자세한 내용은 패널에서 확인하세요."
ResourceAlert: "1Panel 의 평균 {{ .time }}분 동안 {{ .name }} 사용률은 {{ .used }}입니다. 자세한 내용은 패널에서 확인하세요."
PanelVersionAlert: "1Panel 의 새로운 버전이 이용 가능합니다. 패널에 로그인하여 업그레이드하세요."
PanelPwdExpirationAlert: "1Panel 비밀번호가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에서 확인하세요."
CommonAlert: "귀하의 1Panel, {{ .msg }}. 자세한 내용은 패널에 로그인하여 확인하세요."
NodeExceptionAlert: "귀하의 1Panel, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
LicenseExceptionAlert: "귀하의 1Panel, {{ .num }}개의 라이선스에 이상이 있습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
SSHAndPanelLoginAlert: "귀하의 1Panel, 패널 {{ .name }}이(가) {{ .ip }}에서 비정상 로그인했습니다. 자세한 내용은 패널에 로그인하여 확인하세요."
LicenseExpirationAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 라이센스가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하십시오."
CronJobFailedAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 예약 작업 {{ .name }} 실행에 실패했습니다. 자세한 내용은 패널에 로그인하십시오."
ClamAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 바이러스 스캔 작업에서 {{ .num }}개의 감염 파일이 감지되었습니다. 자세한 내용은 패널에 로그인하십시오."
WebSiteAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 웹사이트가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하십시오."
SSLAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 SSL 인증서가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하십시오."
DiskUsedAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 디스크 {{ .name }} 사용률이 {{ .used }}에 도달했습니다. 자세한 내용은 패널에 로그인하십시오."
ResourceAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 최근 {{ .time }}분간 {{ .name }} 평균 사용률은 {{ .used }}입니다. 자세한 내용은 패널에 로그인하십시오."
PanelVersionAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 최신 버전으로 업그레이드할 수 있습니다. 자세한 내용은 패널에 로그인하십시오."
PanelPwdExpirationAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 패널 비밀번호가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하십시오."
CommonAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .msg }}。자세한 내용은 패널에 로그인하십시오."
NodeExceptionAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오."
LicenseExceptionAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 라이센스에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오."
SSHAndPanelLoginAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .loginIp }}에서의 {{ .name }} 로그인에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오."
#disk
DeviceNotFound: "장치 {{ .name }} 을(를) 찾을 수 없습니다"
+15 -14
View File
@@ -201,6 +201,7 @@ ErrRemoteExist: 'Pangkalan data jauh sudah wujud dengan nama ini, sila ubah suai
ErrLocalExist: 'Nama sudah wujud dalam pangkalan data tempatan, sila ubah suai dan cuba lagi'
RemoteBackup: "Untuk menyandarkan pangkalan data jauh, perkhidmatan pangkalan data bekas tempatan perlu dimulakan terlebih dahulu menggunakan imej {{ .name }}, sila tunggu..."
RemoteRecover: "Untuk memulihkan pangkalan data jauh, perkhidmatan pangkalan data bekas tempatan perlu dimulakan terlebih dahulu menggunakan imej {{ .name }}, sila tunggu..."
Arg: "Hujah"
#redis
ErrTypeOfRedis: 'Jenis fail pemulihan tidak sepadan dengan kaedah kegigihan semasa, sila ubah suai dan cuba lagi'
@@ -211,7 +212,7 @@ ErrObjectInUsed: "Objek ini sedang digunakan dan tidak boleh dipadam!"
ErrObjectBeDependent: "Imej ini digantungkan oleh imej lain dan tidak boleh dipadam!"
ErrPortRules: 'Nombor port tidak sepadan, sila masukkan semula!'
ErrPgImagePull: 'Tarikh imej tamat masa, sila konfigurasikan pecutan imej atau tarik imej {{ .name }} secara manual dan cuba lagi'
PruneHelper: "Pembersihan ini membuang {{ .name }} {{ .count }} item, membebaskan {{ .size }} ruang cakera"
PruneHelper: "Pembersihan ini {{ .name }} membebaskan ruang cakera {{ .size }}"
ImageRemoveHelper: "Padam imej {{ .name }}, membebaskan {{ .size }} ruang cakera"
BuildCache: "Cache binaan"
Volume: "Jilid storan"
@@ -505,19 +506,19 @@ ErrInstallExtension: "Tugas pemasangan sudah sedang berjalan, silakan tunggu tug
PanelAlertTitle: "Notifikasi Amaran Panel"
TestAlertTitle: "E-mel Ujian - Sahkan Sambungan E-mel"
TestAlert: "Ini adalah e-mel ujian untuk mengesahkan konfigurasi penghantaran e-mel anda betul."
LicenseExpirationAlert: "Lesen 1Panel anda akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
CronJobFailedAlert: "Tugas berjadual 1Panel '{{ .name }}' gagal dilaksanakan. Sila log masuk ke panel untuk butiran lanjut."
ClamAlert: "Tugas imbasan virus 1Panel menemui {{ .num }} fail yang dijangkiti. Sila log masuk ke panel untuk maklumat lanjut."
WebSiteAlert: "Terdapat {{ .num }} laman web dalam 1Panel anda yang akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
SSLAlert: "Terdapat {{ .num }} sijil SSL dalam 1Panel anda yang akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
DiskUsedAlert: "Cakera '{{ .name }}' dalam 1Panel anda telah menggunakan {{ .used }}. Sila log masuk ke panel untuk maklumat lanjut."
ResourceAlert: "Penggunaan purata {{ .name }} selama {{ .time }} minit dalam 1Panel anda ialah {{ .used }}. Sila log masuk ke panel untuk maklumat lanjut."
PanelVersionAlert: "Versi baru 1Panel tersedia. Sila log masuk ke panel untuk menaik taraf."
PanelPwdExpirationAlert: "Kata laluan 1Panel anda akan tamat dalam {{ .day }} hari. Sila log masuk ke panel untuk maklumat lanjut."
CommonAlert: "1Panel anda, {{ .msg }}, sila log masuk ke panel untuk maklumat lanjut."
NodeExceptionAlert: "1Panel anda, {{ .num }} nod bermasalah, sila log masuk ke panel untuk maklumat lanjut."
LicenseExceptionAlert: "1Panel anda, {{ .num }} lesen bermasalah, sila log masuk ke panel untuk maklumat lanjut."
SSHAndPanelLoginAlert: "1Panel anda, log masuk panel {{ .name }} yang tidak normal dari {{ .ip }}, sila log masuk ke panel untuk maklumat lanjut."
LicenseExpirationAlert: "Panel {{ .node }}{{ .ip }} Anda, lesen akan tamat tempoh dalam {{ .day }} hari. Sila log masuk ke panel untuk butiran lanjut."
CronJobFailedAlert: "Panel {{ .node }}{{ .ip }} Anda, tugas jadual {{ .name }} gagal dilaksanakan. Sila log masuk ke panel untuk butiran lanjut."
ClamAlert: "Panel {{ .node }}{{ .ip }} Anda, tugas imbasan virus mengesan {{ .num }} fail yang dijangkiti. Sila log masuk ke panel untuk butiran lanjut."
WebSiteAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .num }} laman web akan tamat tempoh dalam {{ .day }} hari. Sila log masuk ke panel untuk butiran lanjut."
SSLAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .num }} sijil SSL akan tamat tempoh dalam {{ .day }} hari. Sila log masuk ke panel untuk butiran lanjut."
DiskUsedAlert: "Panel {{ .node }}{{ .ip }} Anda, penggunaan cakera {{ .name }} telah mencapai {{ .used }}. Sila log masuk ke panel untuk butiran lanjut."
ResourceAlert: "Panel {{ .node }}{{ .ip }} Anda, kadar penggunaan purata {{ .name }} dalam tempoh {{ .time }} minit ialah {{ .used }}. Sila log masuk ke panel untuk butiran lanjut."
PanelVersionAlert: "Panel {{ .node }}{{ .ip }} Anda, versi panel terkini tersedia untuk dinaik taraf. Sila log masuk ke panel untuk butiran lanjut."
PanelPwdExpirationAlert: "Panel {{ .node }}{{ .ip }} Anda, kata laluan panel akan tamat tempoh dalam {{ .day }} hari. Sila log masuk ke panel untuk butiran lanjut."
CommonAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .msg }}. Sila log masuk ke panel untuk butiran lanjut."
NodeExceptionAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .num }} nod tidak normal. Sila log masuk ke panel untuk butiran lanjut."
LicenseExceptionAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .num }} lesen tidak normal. Sila log masuk ke panel untuk butiran lanjut."
SSHAndPanelLoginAlert: "Panel {{ .node }}{{ .ip }} Anda, log masuk {{ .name }} dari {{ .loginIp }} tidak normal. Sila log masuk ke panel untuk butiran lanjut."
#disk
DeviceNotFound: "Peranti {{ .name }} tidak ditemui"
+15 -14
View File
@@ -201,6 +201,7 @@ ErrRemoteExist: 'O banco de dados remoto já existe com este nome, modifique-o e
ErrLocalExist: 'O nome já existe no banco de dados local, modifique-o e tente novamente'
RemoteBackup: "Para fazer backup do banco de dados remoto, o serviço de banco de dados do contêiner local precisa ser iniciado primeiro usando a imagem {{ .name }}, por favor aguarde..."
RemoteRecover: "Para restaurar o banco de dados remoto, o serviço de banco de dados do contêiner local precisa ser iniciado primeiro usando a imagem {{ .name }}, por favor aguarde..."
Arg: "Argumento"
#redis
ErrTypeOfRedis: 'O tipo de arquivo de recuperação não corresponde ao método de persistência atual, modifique-o e tente novamente'
@@ -211,7 +212,7 @@ ErrObjectInUsed: "Este objeto está em uso e não pode ser excluído!"
ErrObjectBeDependent: "Esta imagem é dependida por outras imagens e não pode ser excluída!"
ErrPortRules: 'O número da porta não corresponde, digite novamente!'
ErrPgImagePull: 'Tempo limite para extração de imagem. Configure a aceleração de imagem ou extraia manualmente a imagem {{ .name }} e tente novamente'
PruneHelper: "Esta limpeza removeu {{ .name }} {{ .count }} itens, liberando {{ .size }} de espaço em disco"
PruneHelper: "Esta limpeza {{ .name }} liberou espaço em disco {{ .size }}"
ImageRemoveHelper: "Excluída a imagem {{ .name }}, liberando {{ .size }} de espaço em disco"
BuildCache: "Cache de construção"
Volume: "Volume de armazenamento"
@@ -505,19 +506,19 @@ ErrInstallExtension: "Já existe uma tarefa de instalação em andamento, aguard
PanelAlertTitle: "Notificação de Alerta do Painel"
TestAlertTitle: "E-mail de Teste - Verificar Conectividade de E-mail"
TestAlert: "Este é um e-mail de teste para verificar se sua configuração de envio de e-mails está correta."
LicenseExpirationAlert: "A licença do seu 1Panel expirará em {{ .day }} dias. Acesse o painel para mais detalhes."
CronJobFailedAlert: "A tarefa agendada '{{ .name }}' do seu 1Panel falhou. Acesse o painel para mais detalhes."
ClamAlert: "A verificação de vírus do 1Panel detectou {{ .num }} arquivos infectados. Acesse o painel para mais detalhes."
WebSiteAlert: "{{ .num }} sites do seu 1Panel expirarão em {{ .day }} dias. Acesse o painel para mais detalhes."
SSLAlert: "{{ .num }} certificados SSL do seu 1Panel expirarão em {{ .day }} dias. Acesse o painel para mais detalhes."
DiskUsedAlert: "O disco '{{ .name }}' do seu 1Panel está com uso de {{ .used }}. Acesse o painel para mais detalhes."
ResourceAlert: "O uso médio de {{ .name }} em {{ .time }} minutos no seu 1Panel é de {{ .used }}. Acesse o painel para mais detalhes."
PanelVersionAlert: "Uma nova versão do 1Panel está disponível. Acesse o painel para atualizá-lo."
PanelPwdExpirationAlert: "A senha do 1Panel expirará em {{ .day }} dias. Acesse o painel para mais detalhes."
CommonAlert: "Seu 1Panel, {{ .msg }}. Para mais detalhes, faça login no painel."
NodeExceptionAlert: "Seu 1Panel, {{ .num }} nós estão com problemas. Para mais detalhes, faça login no painel."
LicenseExceptionAlert: "Seu 1Panel, {{ .num }} licenças estão com problemas. Para mais detalhes, faça login no painel."
SSHAndPanelLoginAlert: "Seu 1Panel, login anormal no painel {{ .name }} a partir de {{ .ip }}. Para mais detalhes, faça login no painel."
LicenseExpirationAlert: "Seu Painel {{ .node }}{{ .ip }}, a licença expirará em {{ .day }} dias. Faça login no painel para obter detalhes."
CronJobFailedAlert: "Seu Painel {{ .node }}{{ .ip }}, a execução da tarefa agendada {{ .name }} falhou. Faça login no painel para obter detalhes."
ClamAlert: "Seu Painel {{ .node }}{{ .ip }}, a tarefa de verificação de vírus detectou {{ .num }} arquivos infectados. Faça login no painel para obter detalhes."
WebSiteAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .num }} sites expirarão em {{ .day }} dias. Faça login no painel para obter detalhes."
SSLAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .num }} certificados SSL expirarão em {{ .day }} dias. Faça login no painel para obter detalhes."
DiskUsedAlert: "Seu Painel {{ .node }}{{ .ip }}, o uso do disco {{ .name }} atingiu {{ .used }}. Faça login no painel para obter detalhes."
ResourceAlert: "Seu Painel {{ .node }}{{ .ip }}, a taxa de uso médio do {{ .name }} nos últimos {{ .time }} minutos é {{ .used }}. Faça login no painel para obter detalhes."
PanelVersionAlert: "Seu Painel {{ .node }}{{ .ip }}, a versão mais recente do painel está disponível para atualização. Faça login no painel para obter detalhes."
PanelPwdExpirationAlert: "Seu Painel {{ .node }}{{ .ip }}, a senha do painel expirará em {{ .day }} dias. Faça login no painel para obter detalhes."
CommonAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .msg }}. Faça login no painel para obter detalhes."
NodeExceptionAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .num }} nós estão anormais. Faça login no painel para obter detalhes."
LicenseExceptionAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .num }} licenças estão anormais. Faça login no painel para obter detalhes."
SSHAndPanelLoginAlert: "Seu Painel {{ .node }}{{ .ip }}, o login {{ .name }} a partir de {{ .loginIp }} é anormal. Faça login no painel para obter detalhes."
#disk
DeviceNotFound: "Dispositivo {{ .name }} não encontrado"
+15 -14
View File
@@ -201,6 +201,7 @@ ErrRemoteExist: 'Удаленная база данных с таким имен
ErrLocalExist: 'Имя уже существует в локальной базе данных, измените его и повторите попытку'
RemoteBackup: "Для резервного копирования удаленной базы данных необходимо сначала запустить службу базы данных локального контейнера с помощью образа {{ .name }}, пожалуйста, подождите..."
RemoteRecover: "Для восстановления удаленной базы данных необходимо сначала запустить службу базы данных локального контейнера с помощью образа {{ .name }}, пожалуйста, подождите..."
Arg: "Аргумент"
#редис
ErrTypeOfRedis: 'Тип файла восстановления не соответствует текущему методу сохранения. Измените его и повторите попытку'
@@ -211,7 +212,7 @@ ErrObjectInUsed: "Этот объект используется и не мож
ErrObjectBeDependent: "Этот образ зависит от других образов и не может быть удален!"
ErrPortRules: 'Номер порта не совпадает, введите заново!'
ErrPgImagePull: 'Время извлечения изображения истекло. Настройте ускорение изображения или вручную извлеките изображение {{ .name }} и повторите попытку'
PruneHelper: "Очистка удалила {{ .name }} в количестве {{ .count }}, освободив {{ .size }} дискового пространства"
PruneHelper: "Эта очистка {{ .name }} освободила дисковое пространство {{ .size }}"
ImageRemoveHelper: "Удалён образ {{ .name }}, освобождено {{ .size }} дискового пространства"
BuildCache: "Кэш сборки"
Volume: "Том хранилища"
@@ -505,19 +506,19 @@ ErrInstallExtension: "Уже выполняется задача установ
PanelAlertTitle: "Уведомление панели"
TestAlertTitle: "Тестовое письмо - проверка подключения"
TestAlert: "Это тестовое письмо для проверки правильности настройки отправки писем."
LicenseExpirationAlert: "Срок действия лицензии на 1Panel истекает через {{ .day }} дней. Подробности смотрите в панели."
CronJobFailedAlert: "Запланированное задание '{{ .name }}' на вашем 1Panel завершилось с ошибкой. Подробности в панели."
ClamAlert: "Антивирусная проверка 1Panel обнаружила {{ .num }} заражённых файлов. Подробности смотрите в панели."
WebSiteAlert: "На вашем 1Panel {{ .num }} веб-сайтов истекают через {{ .day }} дней. Подробности смотрите в панели."
SSLAlert: "На вашем 1Panel {{ .num }} SSL-сертификатов истекают через {{ .day }} дней. Подробности смотрите в панели."
DiskUsedAlert: "Диск '{{ .name }}' на вашем 1Panel использует {{ .used }}. Подробности смотрите в панели."
ResourceAlert: "Средняя загрузка {{ .name }} за {{ .time }} минут составляет {{ .used }}. Подробности смотрите в панели."
PanelVersionAlert: "Доступна новая версия 1Panel. Обновитесь через панель."
PanelPwdExpirationAlert: "Пароль для 1Panel истекает через {{ .day }} дней. Подробности смотрите в панели."
CommonAlert: "Ваш 1Panel, {{ .msg }}. Подробности смотрите, войдя в панель."
NodeExceptionAlert: "Ваш 1Panel, {{ .num }} узлов работают неправильно. Подробности смотрите, войдя в панель."
LicenseExceptionAlert: "Ваш 1Panel, {{ .num }} лицензий работают неправильно. Подробности смотрите, войдя в панель."
SSHAndPanelLoginAlert: "Ваш 1Panel, обнаружен аномальный вход в панель {{ .name }} с {{ .ip }}. Подробности смотрите, войдя в панель."
LicenseExpirationAlert: "Ваш панель {{ .node }}{{ .ip }}, лицензия истечет через {{ .day }} дней. Войдите в панель для получения деталей."
CronJobFailedAlert: "Ваш панель {{ .node }}{{ .ip }}, выполнение запланированной задачи {{ .name }} не удалось. Войдите в панель для получения деталей."
ClamAlert: "Ваш панель {{ .node }}{{ .ip }}, задача сканирования на вирусы обнаружила {{ .num }} зараженных файлов. Войдите в панель для получения деталей."
WebSiteAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .num }} веб-сайтов истечут через {{ .day }} дней. Войдите в панель для получения деталей."
SSLAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .num }} сертификатов SSL истечут через {{ .day }} дней. Войдите в панель для получения деталей."
DiskUsedAlert: "Ваш панель {{ .node }}{{ .ip }}, использование диска {{ .name }} достигло {{ .used }}. Войдите в панель для получения деталей."
ResourceAlert: "Ваш панель {{ .node }}{{ .ip }}, средний уровень использования {{ .name }} за {{ .time }} минут составляет {{ .used }}. Войдите в панель для получения деталей."
PanelVersionAlert: "Ваш панель {{ .node }}{{ .ip }}, доступна последняя версия панели для обновления. Войдите в панель для получения деталей."
PanelPwdExpirationAlert: "Ваш панель {{ .node }}{{ .ip }}, пароль панели истечет через {{ .day }} дней. Войдите в панель для получения деталей."
CommonAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .msg }}. Войдите в панель для получения деталей."
NodeExceptionAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .num }} узлов работают с ошибками. Войдите в панель для получения деталей."
LicenseExceptionAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .num }} лицензий имеют ошибки. Войдите в панель для получения деталей."
SSHAndPanelLoginAlert: "Ваш панель {{ .node }}{{ .ip }}, вход {{ .name }} с адреса {{ .loginIp }} является аномальным. Войдите в панель для получения деталей."
#disk
DeviceNotFound: "Устройство {{ .name }} не найдено"
+15 -14
View File
@@ -202,6 +202,7 @@ ErrRemoteExist: 'Uzak veritabanında bu adla zaten mevcut, lütfen değiştirin
ErrLocalExist: 'Yerel veritabanında bu ad zaten mevcut, lütfen değiştirin ve tekrar deneyin'
RemoteBackup: "Uzak veritabanını yedeklemek için önce {{ .name }} görüntüsü kullanılarak yerel konteyner veritabanı hizmetinin başlatılması gerekiyor, lütfen bekleyin..."
RemoteRecover: "Uzak veritabanını geri yüklemek için önce {{ .name }} görüntüsü kullanılarak yerel konteyner veritabanı hizmetinin başlatılması gerekiyor, lütfen bekleyin..."
Arg: "Argüman"
#redis
ErrTypeOfRedis: 'Kurtarma dosyası türü mevcut kalıcılık yöntemiyle eşleşmiyor, lütfen değiştirin ve tekrar deneyin'
@@ -212,7 +213,7 @@ ErrObjectInUsed: "Bu nesne kullanımda ve silinemez!"
ErrObjectBeDependent: "Bu görüntü diğer görüntüler tarafından bağımlıdır ve silinemez!"
ErrPortRules: 'Port numarası eşleşmiyor, lütfen yeniden girin!'
ErrPgImagePull: 'Image çekme zaman aşımı, lütfen image hızlandırma yapılandırın veya manuel olarak {{ .name }} imageını çekin ve tekrar deneyin'
PruneHelper: "Bu temizleme {{ .name }} {{ .count }} öğe kaldırdı, {{ .size }} disk alanı boşalttı"
PruneHelper: "Bu temizlik {{ .name }} disk alanı {{ .size }} boşalttı"
ImageRemoveHelper: "{{ .name }} imajı silindi, {{ .size }} disk alanı boşalttı"
BuildCache: "Derleme önbelleği"
Volume: "Depolama hacmi"
@@ -506,19 +507,19 @@ ErrInstallExtension: "Zaten devam eden bir kurulum görevi var, görevin bitmesi
PanelAlertTitle: "Panel Uyarı Bildirimi"
TestAlertTitle: "Test E-postası - E-posta Bağlantısını Doğrula"
TestAlert: "Bu, e-posta gönderim ayarlarınızın doğru yapılandırıldığını doğrulamak için gönderilen bir test e-postasıdır."
LicenseExpirationAlert: "1Panel lisansınız {{ .day }} gün içinde sona erecek. Detaylar için panele giriş yapın."
CronJobFailedAlert: "1Panel zamanlanmış görevi '{{ .name }}' başarısız oldu. Detaylar için panele giriş yapın."
ClamAlert: "1Panel virüs taraması {{ .num }} enfekte dosya tespit etti. Detaylar için panele giriş yapın."
WebSiteAlert: "1Panel üzerindeki {{ .num }} web sitesi {{ .day }} gün içinde sona erecek. Detaylar için panele giriş yapın."
SSLAlert: "1Panel üzerindeki {{ .num }} SSL sertifikası {{ .day }} gün içinde sona erecek. Detaylar için panele giriş yapın."
DiskUsedAlert: "1Panel diski '{{ .name }}' {{ .used }} kullanıldı. Detaylar için panele giriş yapın."
ResourceAlert: "1Panel üzerinde ortalama {{ .time }} dakikalık {{ .name }} kullanım oranı {{ .used }}. Detaylar için panele giriş yapın."
PanelVersionAlert: "1Panel için yeni bir sürüm mevcut. Güncellemek için panele giriş yapın."
PanelPwdExpirationAlert: "1Panel şifreniz {{ .day }} gün içinde sona erecek. Detaylar için panele giriş yapın."
CommonAlert: "1Panel'iniz, {{ .msg }}. Detaylar için panele giriş yapınız."
NodeExceptionAlert: "1Panel'iniz, {{ .num }} düğümde sorun var. Detaylar için panele giriş yapınız."
LicenseExceptionAlert: "1Panel'iniz, {{ .num }} lisansda sorun var. Detaylar için panele giriş yapınız."
SSHAndPanelLoginAlert: "1Panel'iniz, {{ .ip }} adresinden {{ .name }} paneline anormal giriş tespit edildi. Detaylar için panele giriş yapınız."
LicenseExpirationAlert: "Paneliniz {{ .node }}{{ .ip }}, lisans {{ .day }} gün içinde süresi dolacak. Detaylar için paneline giriş yapın."
CronJobFailedAlert: "Paneliniz {{ .node }}{{ .ip }}, zamanlanmış görev {{ .name }} çalıştırma başarısız oldu. Detaylar için paneline giriş yapın."
ClamAlert: "Paneliniz {{ .node }}{{ .ip }}, virüs tarama görevi {{ .num }} enfekte dosya tespit etti. Detaylar için paneline giriş yapın."
WebSiteAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .num }} web sitesi {{ .day }} gün içinde süresi dolacak. Detaylar için paneline giriş yapın."
SSLAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .num }} SSL sertifikası {{ .day }} gün içinde süresi dolacak. Detaylar için paneline giriş yapın."
DiskUsedAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .name }} diski kullanımı {{ .used }}'a ulaştı. Detaylar için paneline giriş yapın."
ResourceAlert: "Paneliniz {{ .node }}{{ .ip }}, son {{ .time }} dakika içindeki ortalama {{ .name }} kullanım oranı {{ .used }}'dır. Detaylar için paneline giriş yapın."
PanelVersionAlert: "Paneliniz {{ .node }}{{ .ip }}, en son panel sürümü yükseltme için mevcut. Detaylar için paneline giriş yapın."
PanelPwdExpirationAlert: "Paneliniz {{ .node }}{{ .ip }}, panel şifresi {{ .day }} gün içinde süresi dolacak. Detaylar için paneline giriş yapın."
CommonAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .msg }}. Detaylar için paneline giriş yapın."
NodeExceptionAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .num }} düğüm anormal durumda. Detaylar için paneline giriş yapın."
LicenseExceptionAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .num }} lisans anormal durumda. Detaylar için paneline giriş yapın."
SSHAndPanelLoginAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .loginIp }} adresinden {{ .name }} girişi anormal. Detaylar için paneline giriş yapın."
#disk
DeviceNotFound: "Cihaz {{ .name }} bulunamadı"
+15 -14
View File
@@ -200,6 +200,7 @@ ErrRemoteExist: '遠端資料庫已存在該名稱,請修改後重試'
ErrLocalExist: '本機資料庫已存在該名稱,請修改後重試'
RemoteBackup: "備份遠端資料庫需要先使用映像 {{ .name }} 啟動本機容器資料庫服務,請稍候..."
RemoteRecover: "恢復遠端資料庫需要先使用映像 {{ .name }} 啟動本機容器資料庫服務,請稍候..."
Arg: "參數"
#redis
ErrTypeOfRedis: '復原檔案類型與目前持久化方式不符,請修改後重試'
@@ -210,7 +211,7 @@ ErrObjectInUsed: "該對象正被使用,無法刪除!"
ErrObjectBeDependent: "該映像正被其他映像所依賴,無法刪除!"
ErrPortRules: '連接埠數目不匹配,請重新輸入!'
ErrPgImagePull: '鏡像拉取逾時,請配置鏡像加速或手動拉取{{ .name }} 鏡像後重試'
PruneHelper: "本次清理 {{ .name }} {{ .count }} 個,釋放磁碟空間 {{ .size }}"
PruneHelper: "本次清理 {{ .name }} 釋放磁碟空間 {{ .size }}"
ImageRemoveHelper: "刪除鏡像 {{ .name }} ,釋放磁碟空間 {{ .size }}"
BuildCache: "構建快取"
Volume: "磁碟區"
@@ -504,19 +505,19 @@ ErrInstallExtension: "已有安裝任務正在進行,請等待任務結束"
PanelAlertTitle: "面板警示通知"
TestAlertTitle: "測試郵件 - 驗證信箱連通性"
TestAlert: "這是一封測試郵件,用於確認您的郵件發送設定是否正確。"
LicenseExpirationAlert: "您的 1Panel 授權將於 {{ .day }} 天後到期,詳情請登入面板查看。"
CronJobFailedAlert: "您的 1Panel 面板中,計畫任務 '{{ .name }}' 執行失敗,詳情請登入面板查看。"
ClamAlert: "您的 1Panel 面板病毒掃描任務發現 {{ .num }} 個受感染的檔案,詳情請登入面板查看。"
WebSiteAlert: "您的 1Panel 面板中有 {{ .num }} 個網站將於 {{ .day }} 天後到期,詳情請登入面板查看。"
SSLAlert: "您的 1Panel 面板中有 {{ .num }} 張 SSL 憑證將於 {{ .day }} 天後到期,詳情請登入面板查看。"
DiskUsedAlert: "您的 1Panel 面板磁碟 '{{ .name }}' 已使用 {{ .used }},詳情請登入面板查看。"
ResourceAlert: "您的 1Panel 面板於 {{ .time }} 分鐘內的平均 {{ .name }} 使用率為 {{ .used }},詳情請登入面板查看。"
PanelVersionAlert: "您的 1Panel 面板有可升級的新版本,詳情請登入面板查看。"
PanelPwdExpirationAlert: "您的 1Panel 面板密碼將於 {{ .day }} 天後到期,詳情請登入面板查看。"
CommonAlert: "您的 1Panel 面板,{{ .msg }},詳情請登入面板查看。"
NodeExceptionAlert: "您的 1Panel 面板,{{ .num }} 個節點存在異常,詳情請登入面板查看。"
LicenseExceptionAlert: "您的 1Panel 面板,{{ .num }} 個許可證存在異常,詳情請登入面板查看。"
SSHAndPanelLoginAlert: "您的 1Panel 面板,面板{{ .name }}從 {{ .ip }} 登入異常,詳情請登入面板查看。"
LicenseExpirationAlert: "您的 {{ .node }}{{ .ip }} 面板,授權將於 {{ .day }} 天後到期,詳情請登入面板查看。"
CronJobFailedAlert: "您的 {{ .node }}{{ .ip }} 面板,排程任務{{ .name }}執行失敗,詳情請登入面板查看。"
ClamAlert: "您的 {{ .node }}{{ .ip }} 面板,病毒掃描任務偵測到 {{ .num }} 個感染檔案,詳情請登入面板查看。"
WebSiteAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個網站將於 {{ .day }} 天後到期,詳情請登入面板查看。"
SSLAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 張SSL憑證將於 {{ .day }} 天後到期,詳情請登入面板查看。"
DiskUsedAlert: "您的 {{ .node }}{{ .ip }} 面板,磁碟 {{ .name }} 使用率已達 {{ .used }},詳情請登入面板查看。"
ResourceAlert: "您的 {{ .node }}{{ .ip }} 面板,過去 {{ .time }} 分鐘內 {{ .name }} 平均使用率為 {{ .used }},詳情請登入面板查看。"
PanelVersionAlert: "您的 {{ .node }}{{ .ip }} 面板,已有最新版本可供升級,詳情請登入面板查看。"
PanelPwdExpirationAlert: "您的 {{ .node }}{{ .ip }} 面板,面板密碼將於 {{ .day }} 天後到期,詳情請登入面板查看。"
CommonAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},詳情請登入面板查看。"
NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個節點出現異常,詳情請登入面板查看。"
LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個授權出現異常,詳情請登入面板查看。"
SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,來自 {{ .loginIp }} 的 {{ .name }} 登入出現異常,詳情請登入面板查看。"
#disk
DeviceNotFound: "裝置 {{ .name }} 未找到"
+15 -14
View File
@@ -201,6 +201,7 @@ ErrRemoteExist: "远程数据库已存在该名称,请修改后重试"
ErrLocalExist: "本地数据库已存在该名称,请修改后重试"
RemoteBackup: "备份远程数据库需要先使用镜像 {{ .name }} 启动本地容器数据库服务,请稍候..."
RemoteRecover: "恢复远程数据库需要先使用镜像 {{ .name }} 启动本地容器数据库服务,请稍候..."
Arg: "参数"
#redis
ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后重试"
@@ -211,7 +212,7 @@ ErrObjectInUsed: "该对象正被使用,无法删除!"
ErrObjectBeDependent: "该镜像正被其他镜像所依赖,无法删除!"
ErrPortRules: "端口数目不匹配,请重新输入!"
ErrPgImagePull: "镜像拉取超时,请配置镜像加速或手动拉取 {{ .name }} 镜像后重试"
PruneHelper: "本次清理 {{ .name }} {{ .count }} 个,释放磁盘空间 {{ .size }}"
PruneHelper: "本次清理 {{ .name }} 释放磁盘空间 {{ .size }}"
ImageRemoveHelper: "删除镜像 {{ .name }} ,释放磁盘空间 {{ .size }}"
BuildCache: "构建缓存"
Volume: "存储卷"
@@ -506,19 +507,19 @@ ErrInstallExtension: "已有安装任务正在进行,请等待任务结束"
PanelAlertTitle: "面板告警通知"
TestAlertTitle: "测试邮件 - 验证邮箱连通性"
TestAlert: "这是一封测试邮件,旨在验证您的邮箱发件配置是否正确。"
LicenseExpirationAlert: "您的 1Panel 面板,许可证将在 {{ .day }} 天后到期,详情请登录面板查看。"
CronJobFailedAlert: "您的 1Panel 面板,计划任务-{{ .name }}执行失败,详情请登录面板查看。"
ClamAlert: "您的 1Panel 面板,病毒扫描任务发现 {{ .num }} 个感染文件,详情请登录面板查看。"
WebSiteAlert: "您的 1Panel 面板,有 {{ .num }} 个网站将在 {{ .day }} 天后到期,详情请登录面板查看。"
SSLAlert: "您的 1Panel 面板,有 {{ .num }} 张SSL证书将在 {{ .day }} 天后到期,详情请登录面板查看。"
DiskUsedAlert: "您的 1Panel 面板,磁盘 {{ .name }} 已使用 {{ .used }},详情请登录面板查看。"
ResourceAlert: "您的 1Panel 面板,平均 {{ .time }} 分钟内的 {{ .name }} 使用率为 {{ .used }},详情请登录面板查看。"
PanelVersionAlert: "您的 1Panel 面板,有最新面板版本可供升级,详情请登录面板查看。"
PanelPwdExpirationAlert: "您的 1Panel 面板,面板密码将在 {{ .day }} 天后到期,详情请登录面板查看。"
CommonAlert: "您的 1Panel 面板,{{ .msg }},详情请登录面板查看。"
NodeExceptionAlert: "您的 1Panel 面板,{{ .num }} 个节点存在异常,详情请登录面板查看。"
LicenseExceptionAlert: "您的 1Panel 面板,{{ .num }} 个许可证存在异常,详情请登录面板查看。"
SSHAndPanelLoginAlert: "您的 1Panel 面板,面板{{ .name }}登录{{ .ip }}异常,详情请登录面板查看。"
LicenseExpirationAlert: "您的 {{ .node }}{{ .ip }} 面板,许可证将在 {{ .day }} 天后到期,详情请登录面板查看。"
CronJobFailedAlert: "您的 {{ .node }}{{ .ip }} 面板,计划任务-{{ .name }}执行失败,详情请登录面板查看。"
ClamAlert: "您的 {{ .node }}{{ .ip }} 面板,病毒扫描任务发现 {{ .num }} 个感染文件,详情请登录面板查看。"
WebSiteAlert: "您的 {{ .node }}{{ .ip }} 面板,有 {{ .num }} 个网站将在 {{ .day }} 天后到期,详情请登录面板查看。"
SSLAlert: "您的 {{ .node }}{{ .ip }} 面板,有 {{ .num }} 张SSL证书将在 {{ .day }} 天后到期,详情请登录面板查看。"
DiskUsedAlert: "您的 {{ .node }}{{ .ip }} 面板,磁盘 {{ .name }} 已使用 {{ .used }},详情请登录面板查看。"
ResourceAlert: "您的 {{ .node }}{{ .ip }} 面板,平均 {{ .time }} 分钟内的 {{ .name }} 使用率为 {{ .used }},详情请登录面板查看。"
PanelVersionAlert: "您的 {{ .node }}{{ .ip }} 面板,有最新面板版本可供升级,详情请登录面板查看。"
PanelPwdExpirationAlert: "您的 {{ .node }}{{ .ip }} 面板,面板密码将在 {{ .day }} 天后到期,详情请登录面板查看。"
CommonAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},详情请登录面板查看。"
NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个节点存在异常,详情请登录面板查看。"
LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个许可证存在异常,详情请登录面板查看。"
SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,面板{{ .name }}登录{{ .loginIp }}异常,详情请登录面板查看。"
#disk
DeviceNotFound: "设备 {{ .name }} 未找到"
+1 -1
View File
@@ -79,7 +79,7 @@ func Init() {
iptablesService := service.IptablesService{}
iptablesStatus, _ := settingRepo.GetValueByKey("IptablesStatus")
if iptablesStatus == constant.StatusEnable {
if err := iptablesService.Operate(dto.IptablesOp{Operate: "bind-base"}); err != nil {
if err := iptablesService.Operate(dto.IptablesOp{Operate: "bind-base-without-init"}); err != nil {
global.LOG.Errorf("bind base chains failed, err: %v", err)
return
}
+1
View File
@@ -61,6 +61,7 @@ func InitAgentDB() {
migrations.AddisIPtoWebsiteSSL,
migrations.InitPingStatus,
migrations.UpdateApp,
migrations.AddCronjobArgs,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+7
View File
@@ -807,3 +807,10 @@ var UpdateApp = &gormigrate.Migration{
return tx.AutoMigrate(&model.App{})
},
}
var AddCronjobArgs = &gormigrate.Migration{
ID: "20260106-add-cronjob-args",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.Cronjob{})
},
}
+1 -1
View File
@@ -23,7 +23,7 @@ func (a *AppRouter) InitRouter(Router *gin.RouterGroup) {
appRouter.GET("/details/:id", baseApi.GetAppDetailByID)
appRouter.POST("/install", baseApi.InstallApp)
appRouter.GET("/tags", baseApi.GetAppTags)
appRouter.GET("/icon/:appID", baseApi.GetAppIcon)
appRouter.GET("/icon/:key", baseApi.GetAppIcon)
appRouter.POST("/installed/check", baseApi.CheckAppInstalled)
appRouter.POST("/installed/loadport", baseApi.LoadPort)
+116 -37
View File
@@ -4,16 +4,6 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"sync"
"time"
"github.com/jinzhu/copier"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
@@ -22,21 +12,31 @@ import (
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/email"
"github.com/1Panel-dev/1Panel/agent/utils/psutil"
"github.com/1Panel-dev/1Panel/agent/utils/re"
"github.com/jinzhu/copier"
network "net"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"sync"
"time"
)
var cronJobAlertTypes = []string{"shell", "app", "website", "database", "directory", "log", "snapshot", "curl", "cutWebsiteLog", "clean", "ntp"}
func CreateTaskScanEmailAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport) error {
func CreateTaskScanEmailAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error {
params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param))
alertDetail := ProcessAlertDetail(alert, pushAlert.TaskName, params, method)
alertRule := ProcessAlertRule(alert)
create.AlertRule = alertRule
create.AlertDetail = alertDetail
return CreateEmailAlertLog(create, alert, params, transport)
return CreateEmailAlertLog(create, alert, params, transport, agentInfo)
}
func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, transport *http.Transport) error {
func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, transport *http.Transport, agentInfo *dto.AgentInfo) error {
var alertLog model.AlertLog
alertRepo := repo.NewIAlertRepo()
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.CommonConfig))
@@ -49,7 +49,6 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
return err
}
create.Method = constant.Email
// 获取远端推送信息
if !global.IsMaster && cfg.IsOffline == constant.StatusEnable {
create.Status = constant.AlertPushing
return SaveAlertLog(create, &alertLog)
@@ -77,9 +76,9 @@ func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params [
Encryption: emailInfo.Encryption,
Recipient: emailInfo.Recipient,
}
content := i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
if GetEmailContent(alert.Type, params) != "" {
content = GetEmailContent(alert.Type, params)
content := GetEmailContent(alert.Type, params, agentInfo)
if content == "" {
content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title})
}
msg := email.EmailMessage{
Subject: i18n.GetMsgByKey("PanelAlertTitle"),
@@ -209,7 +208,7 @@ func CheckSMSSendLimit(method string) bool {
return false
}
var cfg dto.AlertSmsConfig
err = json.Unmarshal([]byte(config.Config), &cfg)
cfg, err = ParseAlertSmsConfig(config.Config)
if err != nil {
return false
}
@@ -241,7 +240,6 @@ type Category struct {
Type []string `json:"type"`
}
// CheckSendTimeRange 是否在时间范围内
func CheckSendTimeRange(alertType string) bool {
alertRepo := repo.NewIAlertRepo()
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.CommonConfig))
@@ -302,42 +300,42 @@ func isWithinTimeRange(savedTimeString string) bool {
return now.After(skipTime) && now.Before(endSkipTime)
}
func GetEmailContent(alertType string, params []dto.Param) string {
func GetEmailContent(alertType string, params []dto.Param, agentInfo *dto.AgentInfo) string {
switch GetCronJobType(alertType) {
case "ssl":
return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2")})
return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "siteEndTime":
return i18n.GetMsgWithMap("WebSiteAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2")})
return i18n.GetMsgWithMap("WebSiteAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "panelPwdEndTime":
return i18n.GetMsgWithMap("PanelPwdExpirationAlert", map[string]interface{}{"day": getValueByIndex(params, "1")})
return i18n.GetMsgWithMap("PanelPwdExpirationAlert", map[string]interface{}{"day": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "licenseTime":
return i18n.GetMsgWithMap("LicenseExpirationAlert", map[string]interface{}{"day": getValueByIndex(params, "1")})
return i18n.GetMsgWithMap("LicenseExpirationAlert", map[string]interface{}{"day": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "panelUpdate":
return i18n.GetMsgByKey("PanelVersionAlert")
return i18n.GetMsgWithMap("PanelVersionAlert", map[string]interface{}{"node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "cpu":
return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3")})
return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "memory":
return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3")})
return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "load":
return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3")})
return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "disk":
return i18n.GetMsgWithMap("DiskUsedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "used": getValueByIndex(params, "2")})
return i18n.GetMsgWithMap("DiskUsedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "used": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "cronJob":
return i18n.GetMsgWithMap("CronJobFailedAlert", map[string]interface{}{"name": getValueByIndex(params, "1")})
return i18n.GetMsgWithMap("CronJobFailedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "clams":
return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1")})
return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "panelLogin":
return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "ip": getValueByIndex(params, "2")})
return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "loginIp": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "sshLogin":
return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "ip": getValueByIndex(params, "2")})
return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "loginIp": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "panelIpLogin":
return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "ip": getValueByIndex(params, "2")})
return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "loginIp": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "sshIpLogin":
return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "ip": getValueByIndex(params, "2")})
return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "loginIp": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "nodeException":
return i18n.GetMsgWithMap("NodeExceptionAlert", map[string]interface{}{"num": getValueByIndex(params, "1")})
return i18n.GetMsgWithMap("NodeExceptionAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
case "licenseException":
return i18n.GetMsgWithMap("LicenseExceptionAlert", map[string]interface{}{"num": getValueByIndex(params, "1")})
return i18n.GetMsgWithMap("LicenseExceptionAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)})
default:
return ""
}
@@ -517,3 +515,84 @@ func parseLogTime(line string) (time.Time, error) {
}
return parsedTime.AddDate(time.Now().Year(), 0, 0), nil
}
func getNodeName(agentInfo *dto.AgentInfo) string {
var nodeName string
if agentInfo != nil && agentInfo.NodeName != "" {
nodeName = agentInfo.NodeName
}
return formatWithFallback(nodeName, getFallbackHostname)
}
func getNodeIp(agentInfo *dto.AgentInfo) string {
var nodeIP string
if agentInfo != nil && agentInfo.NodeAddr != "" && agentInfo.NodeAddr != "127.0.0.1" {
nodeIP = agentInfo.NodeAddr
}
return formatWithFallback(nodeIP, getFallbackIP)
}
func formatWithFallback(value string, fallback func() string) string {
value = strings.TrimSpace(value)
if value == "" {
value = strings.TrimSpace(fallback())
}
if value == "" {
return ""
}
return fmt.Sprintf("「%s」", value)
}
func getFallbackHostname() string {
hostInfo, err := psutil.HOST.GetHostInfo(false)
if err != nil {
return ""
}
return hostInfo.Hostname
}
func getFallbackIP() string {
if systemIP, err := repo.NewISettingRepo().GetValueByKey("SystemIP"); err == nil && systemIP != "" {
return systemIP
}
return loadOutboundIP()
}
func loadOutboundIP() string {
conn, err := network.Dial("udp", "8.8.8.8:80")
if err != nil {
return ""
}
defer conn.Close()
localAddr := conn.LocalAddr().(*network.UDPAddr)
return localAddr.IP.String()
}
func ParseAlertSmsConfig(configJSON string) (dto.AlertSmsConfig, error) {
var tempMap map[string]interface{}
err := json.Unmarshal([]byte(configJSON), &tempMap)
if err != nil {
return dto.AlertSmsConfig{}, err
}
var cfg dto.AlertSmsConfig
if phone, ok := tempMap["phone"].(string); ok {
cfg.Phone = phone
}
switch v := tempMap["alertDailyNum"].(type) {
case float64:
cfg.AlertDailyNum = strconv.FormatFloat(v, 'f', 0, 64)
case string:
cfg.AlertDailyNum = v
default:
cfg.AlertDailyNum = "50"
}
return cfg, nil
}
+4 -3
View File
@@ -25,7 +25,6 @@ func PushAlert(pushAlert dto.PushAlert) error {
var alert dto.AlertDTO
_ = copier.Copy(&alert, &alertInfo)
// 根据发送方式推送不同的日志记录
methods := strings.Split(alert.Method, ",")
for _, m := range methods {
m = strings.TrimSpace(m)
@@ -45,6 +44,7 @@ func PushAlert(pushAlert dto.PushAlert) error {
}
_ = xpack.CreateTaskScanSMSAlertLog(alert, alert.Type, create, pushAlert, constant.SMS)
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.SMS)
global.LOG.Infof("%s %s alert push successful", alert.Type, constant.SMS)
case constant.Email:
todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.Email)
if err != nil || alert.SendCount <= todayCount {
@@ -56,14 +56,15 @@ func PushAlert(pushAlert dto.PushAlert) error {
Count: todayCount + 1,
}
transport := xpack.LoadRequestTransport()
err = alertUtil.CreateTaskScanEmailAlertLog(alert, create, pushAlert, constant.Email, transport)
agentInfo, _ := xpack.GetAgentInfo()
err = alertUtil.CreateTaskScanEmailAlertLog(alert, create, pushAlert, constant.Email, transport, agentInfo)
if err != nil {
return err
}
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.Email)
global.LOG.Infof("%s %s alert push successful", alert.Type, constant.Email)
default:
}
}
global.LOG.Infof("%s alert push successful", alert.Type)
return nil
}
+4 -8
View File
@@ -148,10 +148,8 @@ func (i *Iptables) Port(port FireInfo, operation string) error {
if port.Chain == iptables.Chain1PanelBasicBefore {
name = iptables.BasicBeforeFileName
}
if port.Chain == iptables.Chain1PanelBasic {
if err := iptables.SaveRulesToFile(iptables.FilterTab, port.Chain, name); err != nil {
global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelBasic, err)
}
if err := iptables.SaveRulesToFile(iptables.FilterTab, port.Chain, name); err != nil {
global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelBasic, err)
}
return nil
}
@@ -215,10 +213,8 @@ func (i *Iptables) RichRules(rule FireInfo, operation string) error {
if rule.Chain == iptables.Chain1PanelBasicBefore {
name = iptables.BasicBeforeFileName
}
if rule.Chain == iptables.Chain1PanelBasic {
if err := iptables.SaveRulesToFile(iptables.FilterTab, rule.Chain, name); err != nil {
global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelBasic, err)
}
if err := iptables.SaveRulesToFile(iptables.FilterTab, rule.Chain, name); err != nil {
global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelBasic, err)
}
return nil
}
+7 -6
View File
@@ -72,12 +72,13 @@ 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"`
Name string `json:"name"`
Type string `json:"type"`
Version string `json:"version"`
Format string `json:"format"`
TargetDir string `json:"targetDir"`
FileName string `json:"fileName"`
Args []string `json:"args"`
Timeout uint `json:"timeout"` // second
}
+13 -2
View File
@@ -16,6 +16,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/files"
)
@@ -237,9 +238,19 @@ func (r *Local) Backup(info BackupInfo) error {
if r.Type == constant.AppMariaDB {
dumpCmd = "mariadb-dump"
}
global.LOG.Infof("start to %s | gzip > %s.gzip", dumpCmd, info.TargetDir+"/"+info.FileName)
global.LOG.Infof("start to %s | gzip > %s.gzip, args: %v", dumpCmd, info.TargetDir+"/"+info.FileName, info.Args)
cmd := exec.Command("docker", "exec", r.ContainerName, dumpCmd, "--routines", "-uroot", "-p"+r.Password, "--default-character-set="+info.Format, info.Name)
info.Args = append(info.Args, "--routines")
itemArgs := common.RemoveRepeatStr(info.Args)
args := []string{"exec", r.ContainerName, dumpCmd, "-uroot", "-p" + r.Password, "--default-character-set=" + info.Format}
for _, arg := range itemArgs {
if len(arg) == 0 {
continue
}
args = append(args, arg)
}
args = append(args, info.Name)
cmd := exec.Command("docker", args...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
+13 -2
View File
@@ -17,6 +17,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/client"
@@ -253,8 +254,18 @@ func (r *Remote) Backup(info BackupInfo) error {
if err != nil {
return err
}
backupCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c '%s --routines -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'",
image, dumpCmd, r.Address, r.Port, r.User, r.Password, sslSkip(info.Version, r.Type), info.Format, info.Name)
info.Args = append(info.Args, "--routines")
itemArgs := common.RemoveRepeatStr(info.Args)
var args []string
for _, arg := range itemArgs {
if len(arg) == 0 {
continue
}
args = append(args, arg)
}
backupCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c '%s %s -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'",
image, dumpCmd, strings.Join(args, " "), r.Address, r.Port, r.User, r.Password, sslSkip(info.Version, r.Type), info.Format, info.Name)
global.LOG.Debug(strings.ReplaceAll(backupCmd, r.Password, "******"))
cmd := exec.Command("bash", "-c", backupCmd)
+31 -2
View File
@@ -424,12 +424,41 @@ func (s *Server) UpdatePHPProxy(proxy []string, localPath string) {
},
)
if localPath == "" {
block.AppendDirectives(&Directive{
Name: "set",
Parameters: []string{"$real_script_name", "$fastcgi_script_name"},
})
ifDir := &Directive{
Name: "if",
Parameters: []string{"($fastcgi_script_name ~ \"^(.+?\\.php)(/.+)$\")"},
}
ifDir.Block = &Block{
Directives: []IDirective{
&Directive{
Name: "set",
Parameters: []string{"$real_script_name", "$1"},
},
&Directive{
Name: "set",
Parameters: []string{"$path_info", "$2"},
},
},
}
block.AppendDirectives(
ifDir,
&Directive{
Name: "fastcgi_param",
Parameters: []string{"SCRIPT_FILENAME", "$document_root$fastcgi_script_name"},
Parameters: []string{"SCRIPT_FILENAME", "$document_root$real_script_name"},
},
)
&Directive{
Name: "fastcgi_param",
Parameters: []string{"SCRIPT_NAME", "$real_script_name"},
},
&Directive{
Name: "fastcgi_param",
Parameters: []string{"PATH_INFO", "$path_info"},
})
} else {
block.AppendDirectives(&Directive{
Name: "fastcgi_param",
+10
View File
@@ -19,6 +19,7 @@ import (
"github.com/go-acme/lego/v4/providers/dns/namedotcom"
"github.com/go-acme/lego/v4/providers/dns/namesilo"
"github.com/go-acme/lego/v4/providers/dns/ovh"
"github.com/go-acme/lego/v4/providers/dns/porkbun"
"github.com/go-acme/lego/v4/providers/dns/rainyun"
"github.com/go-acme/lego/v4/providers/dns/regru"
"github.com/go-acme/lego/v4/providers/dns/route53"
@@ -57,6 +58,7 @@ const (
BaiduCloud DnsType = "BaiduCloud"
Ovh DnsType = "Ovh"
AcmeDNS DnsType = "AcmeDNS"
PorkBun DnsType = "PorkBun"
)
type DNSParam struct {
@@ -291,6 +293,14 @@ func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, e
config.APIBase = param.Endpoint
config.StorageBaseURL = param.BaseURL
p, err = acmedns.NewDNSProviderConfig(config)
case PorkBun:
config := porkbun.NewDefaultConfig()
config.APIKey = param.APIkey
config.SecretAPIKey = param.SecretKey
config.PropagationTimeout = propagationTimeout
config.PollingInterval = pollingInterval
config.TTL = ttl
p, err = porkbun.NewDNSProviderConfig(config)
}
if err != nil {
+62 -15
View File
@@ -71,12 +71,39 @@ type Resolve struct {
}
func (c *ManualClient) GetDNSResolve(ctx context.Context, websiteSSL *model.WebsiteSSL) (map[string]Resolve, error) {
order, err := c.client.AuthorizeOrder(ctx, acme.DomainIDs(getWebsiteSSLDomains(websiteSSL)...))
var order *acme.Order
var err error
// Check if we have an existing valid order for this SSL
existingOrder, exists := Orders[websiteSSL.ID]
if exists && existingOrder != nil {
// Verify the order is still valid (not expired and still pending)
// If Expires is zero, order is still valid (ACME doesn't always set expiry immediately)
isNotExpired := existingOrder.Expires.IsZero() || existingOrder.Expires.After(time.Now())
if isNotExpired && (existingOrder.Status == acme.StatusPending || existingOrder.Status == acme.StatusReady) {
// Try to reuse the existing order
records, err := c.extractDNSChallenges(ctx, existingOrder)
if err == nil && len(records) > 0 {
return records, nil
}
// If extraction failed, fall through to create a new order
}
// Existing order is expired or invalid, remove it
delete(Orders, websiteSSL.ID)
}
// Create a new order
order, err = c.client.AuthorizeOrder(ctx, acme.DomainIDs(getWebsiteSSLDomains(websiteSSL)...))
if err != nil {
return nil, err
}
Orders[websiteSSL.ID] = order
return c.extractDNSChallenges(ctx, order)
}
// extractDNSChallenges extracts DNS-01 challenge values from an ACME order
func (c *ManualClient) extractDNSChallenges(ctx context.Context, order *acme.Order) (map[string]Resolve, error) {
records := make(map[string]Resolve)
for _, authzURL := range order.AuthzURLs {
@@ -103,7 +130,14 @@ func (c *ManualClient) GetDNSResolve(ctx context.Context, websiteSSL *model.Webs
return nil, err
}
records[domain] = Resolve{
// Use different map key for wildcard vs non-wildcard to avoid overwriting
// Both use the same DNS record name (_acme-challenge.domain) but different values
mapKey := domain
if authz.Wildcard {
mapKey = "*." + domain
}
records[mapKey] = Resolve{
Key: fmt.Sprintf("_acme-challenge.%s", domain),
Value: txtValue,
}
@@ -111,15 +145,15 @@ func (c *ManualClient) GetDNSResolve(ctx context.Context, websiteSSL *model.Webs
return records, nil
}
func queryDNSRecords(domain string) (map[string]string, error) {
func queryDNSRecords(domain string) (map[string][]string, error) {
recordName := fmt.Sprintf("_acme-challenge.%s", domain)
txts, err := net.LookupTXT(recordName)
if err != nil {
return nil, err
}
records := make(map[string]string)
records := make(map[string][]string)
if len(txts) > 0 {
records[recordName] = txts[0]
records[recordName] = txts
}
return records, nil
}
@@ -159,7 +193,7 @@ func (c *ManualClient) handleAuthorization(ctx context.Context, authzURL string,
for {
c.logger.Printf("[INFO] [%s] acme: Checking DNS record propagation.", domain)
var currentRecords map[string]string
var currentRecords map[string][]string
var queryErr error
if len(nameservers) == 0 {
currentRecords, queryErr = queryDNSRecords(domain)
@@ -177,16 +211,26 @@ func (c *ManualClient) handleAuthorization(ctx context.Context, authzURL string,
return fmt.Errorf("failed to query DNS records: %v", queryErr)
}
recordName := fmt.Sprintf("_acme-challenge.%s", domain)
providedRecord, exists := currentRecords[recordName]
if exists && providedRecord == expectedRecord {
providedRecords, exists := currentRecords[recordName]
// Check if expected record is in any of the TXT values
found := false
if exists {
for _, record := range providedRecords {
if record == expectedRecord {
found = true
break
}
}
}
if found {
break
}
if time.Now().After(deadline) {
if !exists {
if !exists || len(providedRecords) == 0 {
return fmt.Errorf("TXT record not provided for domain %s after retrying", domain)
}
c.logger.Printf("[INFO] [%s] TXT record mismatch for %s: expected %s, got %s\"", domain, domain, expectedRecord, providedRecord)
return fmt.Errorf("TXT record mismatch for %s: expected %s, got %s", domain, expectedRecord, providedRecord)
c.logger.Printf("[INFO] [%s] TXT record mismatch for %s: expected %s, got %v", domain, domain, expectedRecord, providedRecords)
return fmt.Errorf("TXT record mismatch for %s: expected %s, got %v", domain, expectedRecord, providedRecords)
}
time.Sleep(pollingInterval)
}
@@ -339,7 +383,7 @@ func handleNameserver(nameserver string) string {
return fmt.Sprintf("%s:53", nameserver)
}
func queryDNSRecordsWithResolver(ctx context.Context, logger *log.Logger, domain string, dnsServer string) (map[string]string, error) {
func queryDNSRecordsWithResolver(ctx context.Context, logger *log.Logger, domain string, dnsServer string) (map[string][]string, error) {
recordName := fmt.Sprintf("_acme-challenge.%s", domain)
c := new(dns.Client)
c.Timeout = 10 * time.Second
@@ -367,16 +411,19 @@ func queryDNSRecordsWithResolver(ctx context.Context, logger *log.Logger, domain
return nil, fmt.Errorf("DNS query failed with code: %s", dns.RcodeToString[r.Rcode])
}
records := make(map[string]string)
records := make(map[string][]string)
var txtValues []string
for _, answer := range r.Answer {
if txt, ok := answer.(*dns.TXT); ok {
if len(txt.Txt) > 0 {
records[recordName] = txt.Txt[0]
break
txtValues = append(txtValues, txt.Txt[0])
}
}
}
if len(txtValues) > 0 {
records[recordName] = txtValues
}
return records, nil
}
+4
View File
@@ -89,3 +89,7 @@ func ValidateCertificate(c *gin.Context) bool {
func PushSSLToNode(websiteSSL *model.WebsiteSSL) error {
return nil
}
func GetAgentInfo() (*dto.AgentInfo, error) {
return nil, nil
}
+29 -3
View File
@@ -3,15 +3,16 @@ package v2
import (
"encoding/base64"
"errors"
"fmt"
"net/http"
"os"
"path"
"regexp"
"strings"
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/buserr"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/utils/mfa"
@@ -93,7 +94,7 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
}
if req.Key == "SecurityEntrance" {
if !checkEntrancePattern(req.Value) {
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", fmt.Errorf("the format of the security entrance %s is incorrect.", req.Value))
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", buserr.WithName("ErrEntranceFormat", req.Value))
return
}
}
@@ -522,5 +523,30 @@ func checkEntrancePattern(val string) bool {
return true
}
result, _ := regexp.MatchString("^[a-zA-Z0-9]{5,116}$", val)
return result
if !result {
return false
}
lowerVal := strings.ToLower(val)
for key := range constant.WebUrlMap {
if key == "/" || !strings.HasPrefix(key, "/") {
continue
}
if strings.Count(key, "/") != 1 {
continue
}
segment := strings.ToLower(strings.TrimPrefix(key, "/"))
if len(segment) < 5 {
continue
}
if lowerVal == segment {
return false
}
}
assetsList := [2]string{"public", "assets"}
for _, item := range assetsList {
if lowerVal == item {
return false
}
}
return true
}
+1
View File
@@ -60,6 +60,7 @@ var WebUrlMap = map[string]struct{}{
"/ai/model": {},
"/ai/gpu": {},
"/ai/gpu/current": {},
"/ai/gpu/history": {},
"/ai/mcp": {},
"/ai/model/tensorrt": {},
"/ai/model/ollama": {},
+1
View File
@@ -22,6 +22,7 @@ ErrHttpReqFailed: "Request failed {{ .err }}"
ErrHttpReqTimeOut: "Request timed out {{ .err }}"
ErrCreateHttpClient: "Failed to create request {{ .err }}"
ErrProxySetting: "Proxy server information unavailable {{ .err }}, please check and try again!"
ErrEntranceFormat: "Security entrance {{ .name }} is not currently supported. Please check and try again!"
# common
ErrDemoEnvironment: "Demo server, this operation is prohibited!"
+1
View File
@@ -27,6 +27,7 @@ ErrHttpReqFailed: "Solicitud fallida {{ .err }}"
ErrHttpReqTimeOut: "La solicitud ha expirado {{ .err }}"
ErrCreateHttpClient: "Error al crear la solicitud {{ .err }}"
ErrProxySetting: "Información del servidor proxy no disponible {{ .err }}, ¡compruebe e inténtelo de nuevo!"
ErrEntranceFormat: "La entrada de seguridad {{ .name }} no está actualmente soportada. ¡Por favor verifique e inténtelo de nuevo!"
# common
ErrDemoEnvironment: "Servidor de demostración, ¡esta operación está prohibida!"
+1
View File
@@ -22,6 +22,7 @@ ErrHttpReqFailed: "リクエスト失敗 {{ .err }}"
ErrHttpReqTimeOut: "リクエストタイムアウト {{ .err }}"
ErrCreateHttpClient: "リクエストの作成に失敗しました {{ .err }}"
ErrProxySetting: "プロキシサーバー情報が利用できません {{ .err }}、確認して再試行してください!"
ErrEntranceFormat: "セキュリティエントランス {{ .name }} は現在サポートされていません。確認して再試行してください!"
# common
ErrDemoEnvironment: "デモサーバーではこの操作は許可されていません!"
+1
View File
@@ -22,6 +22,7 @@ ErrHttpReqFailed: "요청 실패 {{ .err }}"
ErrHttpReqTimeOut: "요청 시간이 초과되었습니다 {{ .err }}"
ErrCreateHttpClient: "요청 생성 실패 {{ .err }}"
ErrProxySetting: "프록시 서버 정보를 사용할 수 없음 {{ .err }}, 확인 후 다시 시도하세요!"
ErrEntranceFormat: "보안 입구 {{ .name }}은(는) 현재 지원되지 않습니다. 확인 후 다시 시도해 주세요!"
# common
ErrDemoEnvironment: "데모 서버에서는 이 작업이 금지되어 있습니다!"
+1
View File
@@ -22,6 +22,7 @@ ErrHttpReqFailed: "Permintaan gagal {{ .err }}"
ErrHttpReqTimeOut: "Permintaan telah tamat masa {{ .err }}"
ErrCreateHttpClient: "Gagal mencipta permintaan {{ .err }}"
ErrProxySetting: "Maklumat pelayan proksi tidak tersedia {{ .err }}, sila periksa dan cuba lagi!"
ErrEntranceFormat: "Pintu masuk keselamatan {{ .name }} tidak disokong buat masa ini. Sila periksa dan cuba lagi!"
# common
ErrDemoEnvironment: "Pelayan demo, operasi ini dilarang!"
+1
View File
@@ -22,6 +22,7 @@ ErrHttpReqFailed: "Falha na requisição {{ .err }}"
ErrHttpReqTimeOut: "Tempo de requisição esgotado {{ .err }}"
ErrCreateHttpClient: "Falha ao criar a requisição {{ .err }}"
ErrProxySetting: "Informação do servidor proxy indisponível {{ .err }}, verifique e tente novamente!"
ErrEntranceFormat: "A entrada de segurança {{ .name }} não é suportada atualmente. Por favor verifique e tente novamente!"
# common
ErrDemoEnvironment: "Servidor de demonstração, essa operação é proibida!"
+1
View File
@@ -22,6 +22,7 @@ ErrHttpReqFailed: "Ошибка запроса {{ .err }}"
ErrHttpReqTimeOut: "Время ожидания запроса истекло {{ .err }}"
ErrCreateHttpClient: "Ошибка создания запроса {{ .err }}"
ErrProxySetting: "Информация о прокси-сервере недоступна {{ .err }}, проверьте и повторите попытку!"
ErrEntranceFormat: "Защищенный вход {{ .name }} в настоящее время не поддерживается. Пожалуйста, проверьте и повторите попытку!"
# common
ErrDemoEnvironment: "Демонстрационный сервер, эта операция запрещена!"
+1
View File
@@ -22,6 +22,7 @@ ErrHttpReqFailed: "İstek başarısız {{ .err }}"
ErrHttpReqTimeOut: "İstek zaman aşımına uğradı {{ .err }}"
ErrCreateHttpClient: "İstek oluşturma başarısız {{ .err }}"
ErrProxySetting: "Proxy sunucu bilgisi kullanılamıyor {{ .err }}, lütfen kontrol edip tekrar deneyin!"
ErrEntranceFormat: "{{ .name }} güvenlik girişi şu anda desteklenmiyor. Lütfen kontrol edip tekrar deneyin!"
# common
ErrDemoEnvironment: "Demo sunucu, bu işlem yasak!"
+1
View File
@@ -22,6 +22,7 @@ ErrHttpReqFailed: "請求失敗 {{ .err }}"
ErrHttpReqTimeOut: "請求逾時 {{ .err }}"
ErrCreateHttpClient: "建立請求失敗 {{ .err }}"
ErrProxySetting: "代理伺服器資訊不可用 {{ .err }},請檢查後重試!"
ErrEntranceFormat: "暫不支援安全入口 {{ .name }} ,請檢查後重試!"
#common
ErrDemoEnvironment: "示範伺服器,禁止此操作!"
+1
View File
@@ -22,6 +22,7 @@ ErrHttpReqFailed: "请求失败 {{ .err }}"
ErrHttpReqTimeOut: "请求超时 {{ .err }}"
ErrCreateHttpClient: "创建请求失败 {{ .err }}"
ErrProxySetting: "代理服务器信息不可用 {{ .err }},请检查后重试!"
ErrEntranceFormat: "暂不支持安全入口 {{ .name }} ,请检查后重试!"
#common
ErrDemoEnvironment: "演示服务器,禁止此操作!"
+3 -2
View File
@@ -3,10 +3,11 @@ package helper
import (
"encoding/json"
"fmt"
"strings"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/app/model"
"gorm.io/gorm"
"strings"
)
func LoadMenus() string {
@@ -43,7 +44,7 @@ func LoadMenus() string {
Children: []dto.ShowMenu{
{ID: "118", Disabled: false, Title: "xpack.app.app", IsShow: true, Label: "XApp", Path: "/xpack/app", Sort: 100},
{ID: "112", Disabled: false, Title: "xpack.waf.name", IsShow: true, Label: "Dashboard", Path: "/xpack/waf/dashboard", Sort: 200},
{ID: "111", Disabled: false, Title: "xpack.node.nodeManagement", IsShow: true, Label: "Node", Path: "/xpack/node", Sort: 300},
{ID: "111", Disabled: false, Title: "xpack.node.nodeManagement", IsShow: true, Label: "NodeDashboard", Path: "/xpack/node/dashboard", Sort: 300},
{ID: "119", Disabled: false, Title: "xpack.upage", IsShow: true, Label: "Upage", Path: "/xpack/upage", Sort: 400},
{ID: "113", Disabled: false, Title: "xpack.monitor.name", IsShow: true, Label: "MonitorDashboard", Path: "/xpack/monitor/dashboard", Sort: 500},
{ID: "114", Disabled: false, Title: "xpack.tamper.tamper", IsShow: true, Label: "Tamper", Path: "/xpack/tamper", Sort: 600},
+1
View File
@@ -26,6 +26,7 @@ func Init() {
migrations.AddUpgradeBackupCopies,
migrations.AddScriptSync,
migrations.UpdateXpackHideMenuSort,
migrations.AdjustXpackNode,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+41
View File
@@ -602,3 +602,44 @@ var UpdateXpackHideMenuSort = &gormigrate.Migration{
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
},
}
var AdjustXpackNode = &gormigrate.Migration{
ID: "20260108-adjust-xpack-node",
Migrate: func(tx *gorm.DB) error {
var menuJSON string
if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil {
return err
}
if menuJSON == "" {
menuJSON = helper.LoadMenus()
}
var menus []dto.ShowMenu
if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
for i := 0; i < len(menus); i++ {
if menus[i].Label != "Xpack-Menu" {
continue
}
for j := 0; j < len(menus[i].Children); j++ {
if menus[i].Children[j].Label == "Node" {
menus[i].Children[j].Label = "NodeDashboard"
menus[i].Children[j].Path = "/xpack/node/dashboard"
break
}
}
}
updatedJSON, err := json.Marshal(menus)
if err != nil {
return tx.Model(&model.Setting{}).
Where("key = ?", "HideMenu").
Update("value", helper.LoadMenus()).Error
}
return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error
},
}
+97 -43
View File
@@ -1,6 +1,7 @@
package server
import (
"bufio"
"crypto/tls"
"encoding/gob"
"fmt"
@@ -8,6 +9,8 @@ import (
"net/http"
"os"
"path"
"strings"
"time"
"github.com/1Panel-dev/1Panel/core/init/auth"
"github.com/1Panel-dev/1Panel/core/init/db"
@@ -64,8 +67,12 @@ func Start() {
global.CONF.Conn.BindAddress = fmt.Sprintf("[%s]", global.CONF.Conn.BindAddress)
}
server := &http.Server{
Addr: global.CONF.Conn.BindAddress + ":" + global.CONF.Conn.Port,
Handler: rootRouter,
Addr: global.CONF.Conn.BindAddress + ":" + global.CONF.Conn.Port,
Handler: rootRouter,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 600 * time.Second,
WriteTimeout: 600 * time.Second,
IdleTimeout: 240 * time.Second,
}
ln, err := net.Listen(tcpItem, server.Addr)
if err != nil {
@@ -75,21 +82,7 @@ func Start() {
*net.TCPListener
}
if global.CONF.Conn.SSL == constant.StatusEnable {
certPath := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.crt")
keyPath := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.key")
certificate, err := os.ReadFile(certPath)
if err != nil {
panic(err)
}
key, err := os.ReadFile(keyPath)
if err != nil {
panic(err)
}
cert, err := tls.X509KeyPair(certificate, key)
if err != nil {
panic(err)
}
constant.CertStore.Store(&cert)
constant.CertStore.Store(loadCert())
server.TLSConfig = &tls.Config{
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
@@ -103,34 +96,20 @@ func Start() {
}
return
} else if global.CONF.Conn.SSL == constant.StatusMux {
certPath := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.crt")
keyPath := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.key")
certificate, err := os.ReadFile(certPath)
if err != nil {
panic(err)
}
key, err := os.ReadFile(keyPath)
if err != nil {
panic(err)
}
cert, err := tls.X509KeyPair(certificate, key)
if err != nil {
panic(err)
}
constant.CertStore.Store(&cert)
constant.CertStore.Store(loadCert())
server.TLSConfig = &tls.Config{
NextProtos: []string{"h2", "http/1.1"},
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return constant.CertStore.Load().(*tls.Certificate), nil
},
}
global.LOG.Infof("listen at mux (http/https)://%s:%s [%s]", global.CONF.Conn.BindAddress, global.CONF.Conn.Port, tcpItem)
m := cmux.New(ln)
httpsL := m.Match(cmux.TLS())
httpL := m.Match(cmux.Any())
httpL := m.Match(cmux.HTTP1Fast())
anyL := m.Match(cmux.Any())
go func() {
if err := server.Serve(tls.NewListener(httpsL, server.TLSConfig)); err != nil {
@@ -139,17 +118,27 @@ func Start() {
}()
go func() {
redirectServer := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
target := "https://" + r.Host + r.RequestURI
http.Redirect(w, r, target, http.StatusTemporaryRedirect)
}),
}
if err := redirectServer.Serve(httpL); err != nil {
global.LOG.Errorf("HTTP Redirect Serve Error: %v", err)
for {
conn, err := httpL.Accept()
if err != nil {
return
}
go handleMuxHttpConn(conn)
}
}()
go func() {
for {
conn, err := anyL.Accept()
if err != nil {
return
}
conn.Close()
}
}()
global.LOG.Infof("listen at mux (http/https)://%s:%s [%s]", global.CONF.Conn.BindAddress, global.CONF.Conn.Port, tcpItem)
if err := m.Serve(); err != nil {
panic(err)
}
@@ -162,3 +151,68 @@ func Start() {
return
}
}
func loadCert() *tls.Certificate {
certPath := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.crt")
keyPath := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.key")
certificate, err := os.ReadFile(certPath)
if err != nil {
panic(err)
}
key, err := os.ReadFile(keyPath)
if err != nil {
panic(err)
}
cert, err := tls.X509KeyPair(certificate, key)
if err != nil {
panic(err)
}
return &cert
}
func handleMuxHttpConn(conn net.Conn) {
defer conn.Close()
req, err := http.ReadRequest(bufio.NewReader(conn))
if err != nil {
return
}
if req.Host == "" {
return
}
ua := req.Header.Get("User-Agent")
if ua == "" {
return
}
switch req.Method {
case http.MethodGet, http.MethodHead, http.MethodPost:
default:
return
}
if len(req.RequestURI) > 4096 {
return
}
if !strings.HasPrefix(req.URL.Path, "/") {
return
}
target := "https://" + req.Host + req.URL.RequestURI()
resp := &http.Response{
StatusCode: http.StatusTemporaryRedirect,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
}
resp.Header.Set("Location", target)
resp.Header.Set("Connection", "close")
_ = resp.Write(conn)
return
}
+1 -1
View File
@@ -66,7 +66,7 @@ func CopyItem(isDir, withName bool, src, dst string) error {
if !isDir {
cmdStr = fmt.Sprintf(`cp -f %s %s`, src, dst+"/")
}
stdout, err := cmd.RunDefaultWithStdoutBashC(cmdStr)
stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(60 * time.Second)).RunWithStdoutBashC(cmdStr)
if err != nil {
return fmt.Errorf("handle %s failed, stdout: %s, err: %v", cmdStr, stdout, err)
}
+22 -5
View File
@@ -3,6 +3,11 @@ package security
import (
"encoding/base64"
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
"github.com/1Panel-dev/1Panel/core/app/repo"
"github.com/1Panel-dev/1Panel/core/app/service"
"github.com/1Panel-dev/1Panel/core/cmd/server/res"
@@ -11,10 +16,6 @@ import (
"github.com/1Panel-dev/1Panel/core/global"
"github.com/1Panel-dev/1Panel/core/utils/common"
"github.com/gin-gonic/gin"
"net/http"
"regexp"
"strconv"
"strings"
)
func HandleNotRoute(c *gin.Context) bool {
@@ -98,7 +99,7 @@ func HandleNotSecurity(c *gin.Context, resType string) {
return
}
if resPage == "444" {
c.String(444, "")
CloseDirectly(c)
return
}
@@ -183,3 +184,19 @@ func checkSession(c *gin.Context) bool {
_, err := global.SESSION.Get(c)
return err == nil
}
func CloseDirectly(c *gin.Context) {
hijacker, ok := c.Writer.(http.Hijacker)
if !ok {
c.AbortWithStatus(http.StatusForbidden)
return
}
conn, _, err := hijacker.Hijack()
if err != nil {
c.AbortWithStatus(http.StatusForbidden)
return
}
conn.Close()
c.Abort()
}
+1 -1
View File
@@ -39,7 +39,7 @@
"dompurify": "^3.3.1",
"echarts": "^5.5.0",
"element-plus": "2.11.9",
"fit2cloud-ui-plus": "^1.2.3",
"fit2cloud-ui-plus": "^1.2.4",
"highlight.js": "^11.9.0",
"js-base64": "^3.7.7",
"jsencrypt": "^3.3.2",
+6
View File
@@ -354,4 +354,10 @@ export namespace App {
status: string;
imagePrefix: string;
}
export interface InstallAppToNodes extends AppInstall {
nodes: string[];
appKey: string;
version: string;
}
}
+3
View File
@@ -67,6 +67,8 @@ export namespace Cronjob {
alertMethodItems: Array<string>;
scopes: string[];
args: string;
argItems: Array<string>;
}
export interface Item {
val: string;
@@ -113,6 +115,7 @@ export namespace Cronjob {
alertMethod: string;
scopes?: string[];
args: string;
}
export interface CronjobTrans {
name: string;
+6 -2
View File
@@ -131,8 +131,12 @@ export const getCurrentNodeCustomAppConfig = () => {
return http.get<App.CustomAppStoreConfig>(`/custom/app/config`);
};
export function getAppIconUrl(appId: number, node?: string): string {
export function getAppIconUrl(appKey: string, node?: string): string {
const baseURL = import.meta.env.VITE_API_URL as string;
const params = node ? `?operateNode=${node}` : '';
return `${baseURL}/apps/icon/${appId}${params}`;
return `${baseURL}/apps/icon/${appKey}${params}`;
}
export const installAppToNodes = (param: App.InstallAppToNodes) => {
return http.post(`/core/xpack/sync/app/install`, param);
};
+1 -1
View File
@@ -56,7 +56,7 @@ export const getLicenseSmsInfo = () => {
};
export const listAppNodes = () => {
return http.get<Array<Setting.NodeAppItem>>(`/core/xpack/nodes/apps/update`);
return http.get<Array<Setting.NodeAppItem>>(`/core/xpack/nodes/apps/update`, {}, { timeout: TimeoutEnum.T_60S });
};
// agent
+16 -2
View File
@@ -100,7 +100,6 @@
<DialogPro
v-model="open"
:title="isBackup ? $t('commons.button.backup') : $t('commons.button.recover') + ' - ' + name"
size="small"
@close="handleBackupClose"
>
<el-alert :closable="false">
@@ -110,6 +109,17 @@
<el-form-item :label="$t('setting.compressPassword')">
<el-input v-model="secret" :placeholder="$t('setting.backupRecoverMessage')" />
</el-form-item>
<el-form-item v-if="type === 'mysql' || type === 'mysql-cluster'" :label="$t('cronjob.backupArgs')">
<el-select v-model="args" filterable allow-create multiple>
<el-option v-for="item in mysqlArgs" :key="item.arg" :value="item.arg" :label="item.arg">
{{ item.arg }}
<span class="ml-2">{{ item.description }}</span>
</el-option>
</el-select>
<span class="input-help">
{{ $t('cronjob.backupArgsHelper') }}
</span>
</el-form-item>
<el-form-item
v-if="!isBackup && type !== 'app' && type !== 'website'"
:label="$t('cronjob.timeout')"
@@ -166,6 +176,7 @@ import { MsgSuccess } from '@/utils/message';
import TaskLog from '@/components/log/task/index.vue';
import { routerToFileWithPath } from '@/utils/router';
import { useGlobalStore } from '@/composables/useGlobalStore';
import { mysqlArgs } from '@/views/cronjob/cronjob/helper';
const { currentNode } = useGlobalStore();
const PushApp = defineAsyncComponent(async () => {
@@ -178,6 +189,7 @@ const PushApp = defineAsyncComponent(async () => {
});
const selects = ref<any>([]);
const args = ref([]);
const loading = ref();
const opRef = ref();
const taskLogRef = ref();
@@ -295,7 +307,7 @@ const loadSize = async (params: any) => {
};
const openTaskLog = (taskID: string) => {
taskLogRef.value.openWithTaskID(taskID);
taskLogRef.value.openWithTaskID(taskID, true, node.value);
};
function selectable(row) {
@@ -311,6 +323,7 @@ const backup = async () => {
secret: secret.value,
taskID: taskID,
description: description.value,
args: args.value,
};
loading.value = true;
await handleBackup(params, node.value)
@@ -352,6 +365,7 @@ const recover = async (row?: any) => {
const onBackup = async () => {
description.value = '';
secret.value = '';
args.value = [];
isBackup.value = true;
open.value = true;
};
@@ -41,27 +41,36 @@ const nginxRules: TokenRule[] = [
pattern: /\[(crit|error)\]/g,
color: '#E74C3C',
},
{
type: 'path',
pattern: /(?<=[\s"])\/[^"\s]+(?:\.\w+)?(?:\?\w+=\w+)?/g,
pattern: /(?<=[\s"])\/[^\s"]*/g,
color: '#B87A2B',
},
{
type: 'http-method',
pattern: /(?<=)(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)(?=\s)/g,
pattern: /\b(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\b/g,
color: '#27AE60',
},
{
type: 'status-success',
pattern: /\s(2\d{2})\s/g,
pattern: /(?<=")\s(2\d{2})\b/g,
color: '#2ECC71',
},
{
type: 'status-error',
pattern: /\s([45]\d{2})\s/g,
pattern: /(?<=")\s([45]\d{2})\b/g,
color: '#E74C3C',
},
{
type: 'status-redirect',
pattern: /(?<=")\s(3\d{2})\b/g,
color: '#F39C12',
},
{
type: 'status-info',
pattern: /(?<=")\s(1\d{2})\b/g,
color: '#268785',
},
{
type: 'process-info',
pattern: /\d+#\d+/g,
+2 -7
View File
@@ -151,7 +151,6 @@ const containerHeight = ref(500);
const scrollTop = ref(0);
const lastScrollTop = ref(0);
const totalLines = ref(0);
const stopReading = ref(false);
const totalPages = ref(0);
let resizeObserver: ResizeObserver | null = null;
const isEndOfFile = ref(false);
@@ -227,7 +226,7 @@ const changeLoading = () => {
const onDownload = async () => {
changeLoading();
downloadFile(logPath.value, globalStore.currentNode);
downloadFile(logPath.value, props.config.operateNode || globalStore.currentNode);
changeLoading();
};
@@ -263,7 +262,7 @@ const getContent = async (pre: boolean) => {
let res;
try {
res = await readByLine(readReq, props.config.operateNode || '');
res = await readByLine(readReq, props.config.operateNode || globalStore.currentNode);
} catch (error) {
isLoading.value = false;
firstLoading.value = false;
@@ -387,10 +386,6 @@ const getContent = async (pre: boolean) => {
};
const onCloseLog = async () => {
if (stopReading.value) {
return;
}
stopReading.value = true;
tailLog.value = false;
if (timer) {
clearInterval(Number(timer));
@@ -5,6 +5,7 @@
class="p-w-200"
:placeholder="$t('setting.selectNode')"
>
<template #prefix>{{ $t('xpack.node.node') }}</template>
<el-option
v-for="item in nodes"
:key="item.id"
+1 -1
View File
@@ -97,7 +97,7 @@ const search = async () => {
};
const openTaskLog = (row: Log.Task) => {
taskLogRef.value.openWithTaskID(row.id, row.status == 'Executing');
taskLogRef.value.openWithTaskID(row.id, row.status == 'Executing', targeNode.value);
};
const acceptParams = () => {
+5 -3
View File
@@ -168,9 +168,11 @@ const initWebSocket = (endpoint_: string, args: string = '') => {
const protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss';
const host = href.split('//')[1].split('/')[0];
const endpoint = endpoint_.replace(/^\/+/, '');
terminalSocket.value = new WebSocket(
`${protocol}://${host}/${endpoint}?cols=${term.value.cols}&rows=${term.value.rows}&${args}&operateNode=${globalStore.currentNode}`,
);
let conn = `${protocol}://${host}/${endpoint}?cols=${term.value.cols}&rows=${term.value.rows}&${args}&operateNode=${globalStore.currentNode}`;
if (args.indexOf('&operateNode=') !== -1) {
conn = `${protocol}://${host}/${endpoint}?cols=${term.value.cols}&rows=${term.value.rows}&${args}`;
}
terminalSocket.value = new WebSocket(conn);
terminalSocket.value.onopen = runRealTerminal;
terminalSocket.value.onmessage = onWSReceive;
terminalSocket.value.onclose = closeRealTerminal;
+4
View File
@@ -264,6 +264,10 @@ export const DNSTypes = [
label: i18n.global.t('website.volcengine'),
value: 'Volcengine',
},
{
label: 'PorkBun',
value: 'PorkBun',
},
{
label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')',
value: 'DnsPod',
+32 -3
View File
@@ -1133,6 +1133,12 @@ const message = {
failedRecord: 'Failure records',
successRecord: 'Successful records',
database: 'Backup database',
backupArgs: 'Backup Arguments',
backupArgsHelper:
'Unlisted backup arguments can be manually entered and selected. For example: Enter --no-data and select the first option from the dropdown list.',
singleTransaction: 'Backup InnoDB tables using a single transaction, suitable for large-volume data backups',
quick: 'Read data row by row instead of loading the entire table into memory, suitable for large-volume data and low-memory machine backups',
skipLockTables: 'Backup without locking all tables, suitable for highly concurrent databases',
missBackupAccount: 'The backup account could not be found',
syncDate: 'Synchronization time ',
clean: 'Cache clean',
@@ -1996,7 +2002,10 @@ const message = {
error444: 'Connection Closed',
error500: 'Internal Server Error',
https: 'Setting up HTTPS for the panel improves access security.\nStrict mode blocks non-HTTPS traffic from reaching the panel.\nMux mode redirects HTTP to HTTPS, but may reduce security.',
https: 'Setting HTTPS for the panel improves access security',
strictHelper: 'Non-HTTPS traffic cannot connect to the panel',
muxHelper:
'The panel will listen on both HTTP and HTTPS ports and redirect HTTP to HTTPS, but this may reduce security',
certType: 'Certificate type',
selfSigned: 'Self signed',
selfSignedHelper: `Browsers may not trust self-signed certificates and may display security warnings.`,
@@ -2172,7 +2181,11 @@ const message = {
monitor:
'Upgrade to the professional version to view the real-time status of the website, visitor trends, visitor sources, request logs and other information. ',
alert: 'Upgrade to the professional version to receive alarm information via SMS and view alarm logs, fully control various key events, and ensure worry-free system operation',
node: 'Upgrading to the professional version allows you to manage multiple Linux servers with 1Panel.',
node: 'Upgrading to the Professional Edition allows you to manage multiple Linux servers with 1Panel.',
nodeApp:
'Upgrading to the Professional Edition allows unified upgrading of multi-node application versions without manually switching nodes.',
nodeDashboard:
'Upgrading to the Professional Edition enables centralized management of multi-node applications, websites, databases, and scheduled tasks.',
fileExchange: 'Upgrade to the Professional Edition to quickly transfer files between multiple servers.',
app: 'Upgrade to the professional version to view service information, abnormal monitoring, etc. through the mobile APP. ',
cluster:
@@ -2183,7 +2196,7 @@ const message = {
scanHelper: 'Easily clean up junk files produced during 1Panel runtime',
clean: 'Clean now',
reScan: 'Rescan',
cleanHelper: `This will clean up the selected system junk files and can't be undone. Do you want to continue?`,
cleanHelper: 'Selected files and directories cannot be rolled back after cleanup. Continue?',
statusSuggest: '(Recommended Cleaning)',
statusClean: '(Very clean)',
statusEmpty: 'Very clean, no cleaning needed!',
@@ -3703,6 +3716,19 @@ const message = {
alertContent3:
'Provide users with custom phone numbers, daily push frequency, and daily push time configurations, making it easier for users to set up more reasonable push alerts.',
nodeDashTitle1: 'Application Management',
nodeDashContent1:
'Unified management of multi-node applications, supports status monitoring, quick start/stop, terminal connection, and backup',
nodeDashTitle2: 'Website Management',
nodeDashContent2:
'Unified management of multi-node websites, real-time status monitoring, supports batch start/stop and quick backup',
nodeDashTitle3: 'Database Management',
nodeDashContent3:
'Unified management of multi-node databases, key status at a glance, supports one-click backup',
nodeDashTitle4: 'Scheduled Task Management',
nodeDashContent4:
'Unified management of multi-node scheduled tasks, supports status monitoring, quick start/stop, and manual trigger execution',
nodeTitle1: 'One-Click Node Addition',
nodeContent1: 'Quickly integrate multiple server nodes',
nodeTitle2: 'Batch Upgrade',
@@ -3721,6 +3747,9 @@ const message = {
fileExchangeContent3:
'Easily transfer project files between different nodes, flexible management of multiple servers.',
nodeAppTitle1: 'Application Upgrade Management',
nodeAppContent1: 'Unified monitoring of multi-node application updates, supports one-click upgrade',
appTitle1: 'Flexible Panel Management',
appContent1: 'Easily manage your 1Panel server anytime, anywhere.',
appTitle2: 'Comprehensive Service Information',
+38 -3
View File
@@ -1137,6 +1137,14 @@ const message = {
failedRecord: 'Registros fallidos',
successRecord: 'Registros completados correctamente',
database: 'Respaldo de base de datos',
backupArgs: 'Argumentos de Copia de Seguridad',
backupArgsHelper:
'Los argumentos de copia de seguridad no listados pueden ser ingresados y seleccionados manualmente. Por ejemplo: Ingrese --no-data y seleccione la primera opción de la lista desplegable.',
singleTransaction:
'Copia de seguridad de tablas InnoDB utilizando una única transacción, adecuada para copias de seguridad de datos de gran volumen',
quick: 'Lee datos fila por fila en lugar de cargar la tabla completa en memoria, adecuado para copias de seguridad de datos de gran volumen y máquinas con poca memoria',
skipLockTables:
'Copia de seguridad sin bloquear todas las tablas, adecuada para bases de datos altamente concurrentes',
missBackupAccount: 'No se pudo encontrar la cuenta de respaldo',
syncDate: 'Hora de sincronización',
clean: 'Limpiar caché',
@@ -2010,7 +2018,10 @@ const message = {
error416: 'Rango no satisfactorio',
error444: 'Conexión cerrada',
error500: 'Error interno del servidor',
https: 'Configurar HTTPS en el panel mejora la seguridad de acceso.\nEn modo Strict, el tráfico sin HTTPS no puede conectarse al panel.\nEl modo Mux redirige HTTP a HTTPS, pero puede reducir la seguridad.',
https: 'Configurar HTTPS para el panel mejora la seguridad de acceso',
strictHelper: 'El tráfico no HTTPS no puede conectarse al panel',
muxHelper:
'El panel escuchará en los puertos HTTP y HTTPS y redirigirá HTTP a HTTPS, pero esto puede reducir la seguridad',
certType: 'Tipo de certificado',
selfSigned: 'Autofirmado',
selfSignedHelper:
@@ -2186,7 +2197,11 @@ const message = {
setting: 'Actualizar a Pro permite personalizar logo, mensaje de bienvenida y más.',
monitor: 'Pro permite ver estado web en tiempo real, tendencias, logs, etc.',
alert: 'Pro permite recibir alertas SMS y ver logs, control total de eventos clave.',
node: 'Pro permite gestionar múltiples servidores Linux con 1Panel.',
node: 'Actualizar a la Edición Profesional le permite administrar múltiples servidores Linux con 1Panel.',
nodeApp:
'Actualizar a la Edición Profesional permite actualizar de forma unificada las versiones de aplicaciones multi-nodo sin cambiar manualmente entre nodos.',
nodeDashboard:
'Actualizar a la Edición Profesional permite la gestión centralizada de aplicaciones, sitios web, bases de datos y tareas programadas multi-nodo.',
fileExchange: 'Pro permite transferir archivos entre varios servidores.',
app: 'Pro permite ver información de servicio, monitoreo, etc. vía app móvil.',
cluster:
@@ -2197,7 +2212,8 @@ const message = {
scanHelper: 'Limpie fácilmente archivos basura generados por 1Panel',
clean: 'Limpiar ahora',
reScan: 'Volver a escanear',
cleanHelper: 'Esto eliminará los archivos seleccionados y no se puede deshacer. ¿Desea continuar?',
cleanHelper:
'Los archivos y directorios seleccionados no se pueden revertir después de la limpieza. ¿Continuar?',
statusSuggest: '(Limpieza recomendada)',
statusClean: '(Muy limpio)',
statusEmpty: 'Muy limpio, ¡no necesita limpieza!',
@@ -3673,6 +3689,20 @@ const message = {
alertTitle3: 'Configuración de Alertas',
alertContent3:
'Permite configurar números de teléfono, frecuencia y horarios de notificación para alertas más personalizadas.',
nodeDashTitle1: 'Gestión de Aplicaciones',
nodeDashContent1:
'Gestión unificada de aplicaciones multi-nodo, admite monitoreo de estado, inicio/parada rápida, conexión de terminal y copia de seguridad',
nodeDashTitle2: 'Gestión de Sitios Web',
nodeDashContent2:
'Gestión unificada de sitios web multi-nodo, monitoreo de estado en tiempo real, admite inicio/parada por lotes y copia de seguridad rápida',
nodeDashTitle3: 'Gestión de Bases de Datos',
nodeDashContent3:
'Gestión unificada de bases de datos multi-nodo, estado clave de un vistazo, admite copia de seguridad con un clic',
nodeDashTitle4: 'Gestión de Tareas Programadas',
nodeDashContent4:
'Gestión unificada de tareas programadas multi-nodo, admite monitoreo de estado, inicio/parada rápida y ejecución manual por activación',
nodeTitle1: 'Adición de Nodo en un Clic',
nodeContent1: 'Integra rápidamente múltiples nodos de servidor',
nodeTitle2: 'Actualización en Lote',
@@ -3689,6 +3719,11 @@ const message = {
fileExchangeTitle3: 'Intercomunicación Multi-Nodo',
fileExchangeContent3:
'Permite transferir fácilmente archivos entre diferentes nodos y gestionar múltiples servidores de forma flexible.',
nodeAppTitle1: 'Gestión de Actualización de Aplicaciones',
nodeAppContent1:
'Monitoreo unificado de actualizaciones de aplicaciones multi-nodo, admite actualización con un clic',
appTitle1: 'Gestión Flexible del Panel',
appContent1: 'Gestiona tu servidor 1Panel en cualquier momento y lugar.',
appTitle2: 'Información Completa de Servicios',
+33 -2
View File
@@ -1105,6 +1105,13 @@ const message = {
failedRecord: '失敗記録',
successRecord: '成功した記録',
database: 'バックアップデータベース',
backupArgs: 'バックアップ引数',
backupArgsHelper:
'リストにないバックアップ引数は手動で入力して選択できます。例:--no-data を入力し、ドロップダウンリストの最初のオプションを選択してください。',
singleTransaction:
'単一トランザクションを使用して InnoDB テーブルをバックアップします。大容量データのバックアップに適しています',
quick: 'テーブル全体をメモリにロードする代わりに、データを行単位で読み取ります。大容量データや低メモリマシンのバックアップに適しています',
skipLockTables: 'すべてのテーブルをロックせずにバックアップします。高並列データベースに適しています',
missBackupAccount: 'バックアップアカウントは見つかりませんでした',
syncDate: '同期時間',
clean: 'キャッシュクリーン',
@@ -1921,7 +1928,10 @@ const message = {
error444: '接続が閉じた',
error500: 'サーバーエラー',
https: 'パネルに HTTPS を設定するとアクセスの安全性が向上します。\nStrict モードでは HTTPS 以外の通信はパネルに接続できません。\nMux モードは HTTP を HTTPS にリダイレクトしますが、安全性が低下する可能性があります。',
https: 'パネルにHTTPSを設定するとアクセスセキュリティが向上します',
strictHelper: 'HTTPS以外のトラフィックはパネルに接続できません',
muxHelper:
'パネルはHTTPポートとHTTPSポートの両方を監視し、HTTPをHTTPSにリダイレクトしますが、セキュリティが低下する可能性があります',
certType: '証明書の種類',
selfSigned: '自己署名',
selfSignedHelper: `ブラウザは、自己署名の証明書を信頼していない場合があり、セキュリティ警告を表示する場合があります。`,
@@ -2094,6 +2104,11 @@ const message = {
'プロのバージョンにアップグレードして、Webサイトのリアルタイムステータス、訪問者の傾向、訪問者ソース、リクエストログ、その他の情報を表示します。',
alert: 'プロのバージョンにアップグレードして、SMSを介してアラーム情報を受信し、アラームログを表示し、さまざまなキーイベントを完全に制御し、心配のないシステム操作を確実にする',
app: 'モバイルアプリでサービス情報、異常監視などを表示するには、プロフェッショナル版にアップグレードしてください。',
node: 'プロフェッショナル版にアップグレードすると、1Panel で複数の Linux サーバーを管理できます。',
nodeApp:
'プロフェッショナル版にアップグレードすると、ノードを手動で切り替えることなく、マルチノードアプリケーションのバージョンを一括アップグレードできます。',
nodeDashboard:
'プロフェッショナル版にアップグレードすると、マルチノードのアプリケーション、ウェブサイト、データベース、スケジュールタスクを集中管理できます。',
fileExchange: 'プロフェッショナル版にアップグレードすると、複数のサーバー間でファイルを迅速に転送できます。',
cluster:
'プロフェッショナル版にアップグレードすると、MySQL/Postgres/Redisマスタースレーブクラスタを管理できます。',
@@ -2103,7 +2118,7 @@ const message = {
scanHelper: '1パネルのランタイム中に作成されたジャンクファイルを簡単にクリーンアップできます',
clean: '今すぐきれいにします',
reScan: 'レスカン',
cleanHelper: `これにより、選択したシステムジャンクファイルがクリーンアップされ、元に戻すことはできません。続けたいですか?`,
cleanHelper: '選択されたファイルおよびディレクトリは、クリーンアップ後にロールバックできません。続行しますか?',
statusSuggest: '(推奨クリーニング)',
statusClean: '(とてもきれい)',
statusEmpty: 'とてもきれいで、掃除は必要ありません!',
@@ -3595,6 +3610,19 @@ const message = {
alertContent3:
'カスタム電話番号、日々のプッシュ頻度、日々のプッシュ時間設定を提供し、ユーザーがより合理的なプッシュアラートを設定しやすく。',
nodeDashTitle1: 'アプリケーション管理',
nodeDashContent1:
'マルチノードアプリケーションの統一管理、ステータス監視、迅速な起動/停止、端末接続、バックアップをサポート',
nodeDashTitle2: 'Webサイト管理',
nodeDashContent2:
'マルチノードWebサイトの統一管理、リアルタイムステータス監視、一括起動/停止と迅速なバックアップをサポート',
nodeDashTitle3: 'データベース管理',
nodeDashContent3:
'マルチノードデータベースの統一管理、主要ステータスが一目で分かり、ワンクリックバックアップをサポート',
nodeDashTitle4: 'スケジュールタスク管理',
nodeDashContent4:
'マルチノードスケジュールタスクの統一管理、ステータス監視、迅速な起動/停止、手動トリガー実行をサポート',
nodeTitle1: 'ワンクリックノード追加',
nodeContent1: '複数のサーバーノードを迅速に統合',
nodeTitle2: 'バッチアップグレード',
@@ -3612,6 +3640,9 @@ const message = {
fileExchangeContent3:
'異なるノード間でプロジェクトファイルを簡単に転送し、複数のサーバーを柔軟に管理します。',
nodeAppTitle1: 'アプリケーションアップグレード管理',
nodeAppContent1: 'マルチノードアプリケーションの更新状況を統一監視、ワンクリックアップグレードをサポート',
appTitle1: '柔軟なパネル管理',
appContent1: 'いつでもどこでも1Panelサーバーを簡単に管理できます。',
appTitle2: '包括的なサービス情報',
+28 -2
View File
@@ -1091,6 +1091,12 @@ const message = {
failedRecord: '실패한 레코드',
successRecord: '성공한 레코드',
database: '백업 데이터베이스',
backupArgs: '백업 인수',
backupArgsHelper:
'목록에 없는 백업 인수는 수동으로 입력하여 선택할 수 있습니다. 예: --no-data를 입력하고 드롭다운 목록의 첫 번째 옵션을 선택하세요.',
singleTransaction: '단일 트랜잭션을 사용하여 InnoDB 테이블을 백업하며, 대용량 데이터 백업에 적합합니다',
quick: '전체 테이블을 메모리에 로드하는 대신 데이터를 행별로 읽습니다. 대용량 데이터 및 저메모리 시스템 백업에 적합합니다',
skipLockTables: '모든 테이블을 잠그지 않고 백업합니다. 높은 동시성 데이터베이스에 적합합니다',
missBackupAccount: '백업 계정을 찾을 수 없습니다',
syncDate: '동기화 시간',
clean: '캐시 정리',
@@ -1891,7 +1897,9 @@ const message = {
error444: '연결 닫힘',
error500: '서버 오류',
https: '패널에 HTTPS를 설정하면 접근 보안이 향상됩니다.\nStrict 모드에서는 HTTPS가 아닌 트래픽이 패널에 연결할 수 없습니다.\nMux 모드는 HTTP를 HTTPS로 리다이렉트하지만 보안이 다소 낮아질 수 있습니다.',
https: '패널에 HTTPS를 설정하면 액세스 보안이 향상됩니다',
strictHelper: '비 HTTPS 트래픽은 패널에 연결할 수 없습니다',
muxHelper: '패널은 HTTP 및 HTTPS 포트 모두를 수신하고 HTTP를 HTTPS로 리디렉션하지만 보안이 저하될 수 있습니다',
certType: '인증서 유형',
selfSigned: '자가 서명',
selfSignedHelper: '자가 서명 인증서는 브라우저에서 신뢰하지 않을 수 있으며 보안 경고가 표시될 수 있습니다.',
@@ -2058,6 +2066,11 @@ const message = {
monitor:
'전문 버전으로 업그레이드하면 웹사이트의 실시간 상태, 방문자 트렌드, 방문자 출처, 요청 로그 등 정보를 확인할 수 있습니다.',
alert: '전문 버전으로 업그레이드하면 SMS 를 통해 알림 정보를 받고 알림 로그를 볼 수 있으며, 다양한 주요 이벤트를 완벽하게 제어하여 시스템 운영을 걱정 없이 유지할 수 있습니다.',
node: '프로페셔널 에디션으로 업그레이드하면 1Panel로 여러 Linux 서버를 관리할 수 있습니다.',
nodeApp:
'프로페셔널 에디션으로 업그레이드하면 노드를 수동으로 전환하지 않고도 다중 노드 애플리케이션 버전을 통합 업그레이드할 수 있습니다.',
nodeDashboard:
'프로페셔널 에디션으로 업그레이드하면 다중 노드 애플리케이션, 웹사이트, 데이터베이스 및 예약 작업을 중앙 집중식으로 관리할 수 있습니다.',
fileExchange: '프로페셔널 에디션으로 업그레이드하여 여러 서버 간에 파일을 빠르게 전송할 수 있습니다.',
app: '프로페셔널 버전으로 업그레이드하면 모바일 APP을 통해 서비스 정보, 이상 모니터링 등을 확인할 수 있습니다.',
cluster:
@@ -2068,7 +2081,7 @@ const message = {
scanHelper: '1Panel 실행 중에 생성된 불필요한 파일을 쉽게 정리합니다.',
clean: '지금 정리',
reScan: '다시 스캔',
cleanHelper: '선택한 시스템 불필요 파일을 정리합니다. 이 작업은 되돌릴 수 없습니다. 계속 하시겠습니까?',
cleanHelper: '선택된 파일 및 디렉터리는 정리 후 롤백할 수 없습니다. 계속하시겠습니까?',
statusSuggest: '(권장 정리)',
statusClean: '(매우 깨끗함)',
statusEmpty: '매우 깨끗합니다. 정리가 필요하지 않습니다!',
@@ -3529,6 +3542,16 @@ const message = {
alertContent3:
'사용자에게 전화번호, 일일 푸시 빈도, 일일 푸시 시간 등을 사용자 정의할 수 있는 설정을 제공하여 보다 합리적인 푸시 알림을 설정할 수 있도록 합니다.',
nodeDashTitle1: '애플리케이션 관리',
nodeDashContent1:
'다중 노드 애플리케이션의 통합 관리, 상태 모니터링, 빠른 시작/중지, 터미널 연결 및 백업 지원',
nodeDashTitle2: '웹사이트 관리',
nodeDashContent2: '다중 노드 웹사이트의 통합 관리, 실시간 상태 모니터링, 일괄 시작/중지 및 빠른 백업 지원',
nodeDashTitle3: '데이터베이스 관리',
nodeDashContent3: '다중 노드 데이터베이스의 통합 관리, 주요 상태 한눈에 확인, 원클릭 백업 지원',
nodeDashTitle4: '예약 작업 관리',
nodeDashContent4: '다중 노드 예약 작업의 통합 관리, 상태 모니터링, 빠른 시작/중지 및 수동 트리거 실행 지원',
nodeTitle1: '원클릭 노드 추가',
nodeContent1: '여러 서버 노드를 빠르게 통합',
nodeTitle2: '일괄 업그레이드',
@@ -3545,6 +3568,9 @@ const message = {
fileExchangeTitle3: '다중 노드 상호 통신 지원',
fileExchangeContent3: '다른 노드 간에 프로젝트 파일을 쉽게 전송하고, 여러 서버를 유연하게 관리합니다.',
nodeAppTitle1: '애플리케이션 업그레이드 관리',
nodeAppContent1: '다중 노드 애플리케이션 업데이트 통합 모니터링, 원클릭 업그레이드 지원',
appTitle1: '유연한 패널 관리',
appContent1: '언제 어디서나 1Panel 서버를 쉽게 관리하세요.',
appTitle2: '종합적인 서비스 정보',
+33 -3
View File
@@ -1125,6 +1125,13 @@ const message = {
failedRecord: 'Rekod kegagalan',
successRecord: 'Rekod berjaya',
database: 'Pangkalan data sandaran',
backupArgs: 'Argumen Sandaran',
backupArgsHelper:
'Argumen sandaran yang tidak disenaraikan boleh dimasukkan dan dipilih secara manual. Contoh: Masukkan --no-data dan pilih pilihan pertama dari senarai juntai bawah.',
singleTransaction:
'Sandaran jadual InnoDB menggunakan transaksi tunggal, sesuai untuk sandaran data isipadu besar',
quick: 'Baca data baris demi baris daripada memuatkan keseluruhan jadual ke dalam ingatan, sesuai untuk sandaran data isipadu besar dan mesin ingatan rendah',
skipLockTables: 'Sandaran tanpa mengunci semua jadual, sesuai untuk pangkalan data konkuren tinggi',
missBackupAccount: 'Akaun sandaran tidak dijumpai',
syncDate: 'Waktu penyelarasan',
clean: 'Bersihkan cache',
@@ -1978,7 +1985,10 @@ const message = {
error444: 'Sambungan ditutup',
error500: 'Ralat Pelayan',
https: 'Menetapkan HTTPS untuk panel meningkatkan keselamatan akses.\nDalam mod Strict, trafik bukan HTTPS tidak boleh menyambung ke panel.\nMod Mux mengalih hala HTTP ke HTTPS, tetapi mungkin mengurangkan keselamatan.',
https: 'Mengatur HTTPS untuk panel meningkatkan keselamatan akses',
strictHelper: 'Trafik bukan HTTPS tidak boleh menyambung ke panel',
muxHelper:
'Panel akan mendengar pada kedua-dua port HTTP dan HTTPS dan mengarahkan HTTP ke HTTPS, tetapi ini mungkin mengurangkan keselamatan',
certType: 'Jenis sijil',
selfSigned: 'Diterbitkan sendiri',
selfSignedHelper:
@@ -2152,6 +2162,11 @@ const message = {
monitor:
'Tingkatkan ke versi profesional untuk melihat status masa nyata laman web, tren pelawat, sumber pelawat, log permintaan dan maklumat lain.',
alert: 'Tingkatkan ke versi profesional untuk menerima maklumat amaran melalui SMS dan melihat log amaran, mengawal sepenuhnya pelbagai acara utama, dan memastikan operasi sistem bebas kerisauan.',
node: 'Menaik taraf ke Edisi Profesional membolehkan anda menguruskan berbilang pelayan Linux dengan 1Panel.',
nodeApp:
'Menaik taraf ke Edisi Profesional membolehkan peningkatan bersatu versi aplikasi berbilang nod tanpa menukar nod secara manual.',
nodeDashboard:
'Menaik taraf ke Edisi Profesional membolehkan pengurusan berpusat aplikasi, laman web, pangkalan data dan tugas berjadual berbilang nod.',
fileExchange: 'Naik taraf ke Edisi Professional untuk menghantar fail dengan cepat antara pelbagai pelayan.',
app: 'Tingkatkan ke versi profesional untuk melihat maklumat perkhidmatan, pemantauan abnormal, dll melalui aplikasi mudah alih.',
cluster:
@@ -2162,8 +2177,7 @@ const message = {
scanHelper: 'Bersihkan fail sampah dengan mudah yang dihasilkan semasa operasi 1Panel',
clean: 'Bersihkan sekarang',
reScan: 'Imbas semula',
cleanHelper:
'Ini akan membersihkan fail sampah sistem yang dipilih dan tidak boleh dipulihkan. Adakah anda mahu meneruskan?',
cleanHelper: 'Fail dan direktori terpilih tidak boleh dipulihkan selepas pembersihan. Teruskan?',
statusSuggest: '(Disyorkan untuk Pembersihan)',
statusClean: '(Sangat bersih)',
statusEmpty: 'Sangat bersih, tiada pembersihan diperlukan!',
@@ -3662,6 +3676,19 @@ const message = {
alertContent3:
'Memberikan pengguna konfigurasi nombor telefon tersuai, kekerapan push harian, dan masa push harian, memudahkan pengguna untuk menetapkan amaran push yang lebih munasabah.',
nodeDashTitle1: 'Pengurusan Aplikasi',
nodeDashContent1:
'Pengurusan bersatu aplikasi berbilang nod, menyokong pemantauan status, mula/henti pantas, sambungan terminal dan sandaran',
nodeDashTitle2: 'Pengurusan Laman Web',
nodeDashContent2:
'Pengurusan bersatu laman web berbilang nod, pemantauan status masa nyata, menyokong mula/henti kelompok dan sandaran pantas',
nodeDashTitle3: 'Pengurusan Pangkalan Data',
nodeDashContent3:
'Pengurusan bersatu pangkalan data berbilang nod, status utama sekilas, menyokong sandaran satu klik',
nodeDashTitle4: 'Pengurusan Tugas Berjadual',
nodeDashContent4:
'Pengurusan bersatu tugas berjadual berbilang nod, menyokong pemantauan status, mula/henti pantas dan pelaksanaan pencetus manual',
nodeTitle1: 'Satu Klik Tambah Node',
nodeContent1: 'Mengintegrasikan pelbagai nod pelayan dengan cepat',
nodeTitle2: 'Kelompok Naik Taraf',
@@ -3680,6 +3707,9 @@ const message = {
fileExchangeContent3:
'Mudah memindahkan fail projek antara nod yang berbeza, mengurus pelbagai pelayan dengan fleksibiliti.',
nodeAppTitle1: 'Pengurusan Naik Taraf Aplikasi',
nodeAppContent1: 'Pemantauan bersatu kemas kini aplikasi berbilang nod, menyokong naik taraf satu klik',
appTitle1: 'Pengurusan Panel yang Fleksibel',
appContent1: 'Uruskan pelayan 1Panel anda dengan mudah pada bila-bila masa dan di mana sahaja.',
appTitle2: 'Maklumat Perkhidmatan Komprehensif',
+34 -2
View File
@@ -1122,6 +1122,13 @@ const message = {
failedRecord: 'Registros de falha',
successRecord: 'Registros de sucesso',
database: 'Backup de banco de dados',
backupArgs: 'Argumentos de Backup',
backupArgsHelper:
'Argumentos de backup não listados podem ser inseridos e selecionados manualmente. Por exemplo: Digite --no-data e selecione a primeira opção da lista suspensa.',
singleTransaction:
'Faz backup de tabelas InnoDB usando uma única transação, adequado para backups de dados de grande volume',
quick: 'Lê dados linha por linha em vez de carregar toda a tabela na memória, adequado para backups de dados de grande volume e máquinas com pouca memória',
skipLockTables: 'Backup sem bloquear todas as tabelas, adequado para bancos de dados altamente concorrentes',
missBackupAccount: 'A conta de backup não foi encontrada',
syncDate: 'Data de sincronização',
clean: 'Limpeza de cache',
@@ -1966,7 +1973,10 @@ const message = {
error444: 'Conexão fechada',
error500: 'Erro no servidor',
https: 'Configurar HTTPS no painel melhora a segurança de acesso.\nNo modo Strict, tráfego sem HTTPS não consegue se conectar ao painel.\nO modo Mux redireciona HTTP para HTTPS, mas pode reduzir a segurança.',
https: 'Configurar HTTPS para o painel melhora a segurança de acesso',
strictHelper: 'Tráfego não HTTPS não pode se conectar ao painel',
muxHelper:
'O painel ouvirá nas portas HTTP e HTTPS e redirecionará HTTP para HTTPS, mas isso pode reduzir a segurança',
certType: 'Tipo de certificado',
selfSigned: 'Autoassinado',
selfSignedHelper:
@@ -2146,6 +2156,11 @@ const message = {
monitor:
'Upgrade para a versão profissional para visualizar o status em tempo real do site, tendências de visitantes, fontes de visitantes, logs de solicitações e outras informações.',
alert: 'Upgrade para a versão profissional para receber informações de alarme via SMS e visualizar logs de alarmes, controlar completamente vários eventos chave e garantir a operação sem preocupações do sistema',
node: 'Atualizar para a Edição Profissional permite gerenciar vários servidores Linux com o 1Panel.',
nodeApp:
'Atualizar para a Edição Profissional permite a atualização unificada de versões de aplicativos multi-nó sem a necessidade de alternar manualmente entre nós.',
nodeDashboard:
'Atualizar para a Edição Profissional permite o gerenciamento centralizado de aplicativos, sites, bancos de dados e tarefas agendadas multi-nó.',
fileExchange:
'Atualize para a Edição Profissional para transferir arquivos rapidamente entre vários servidores.',
app: 'Upgrade para a versão profissional para visualizar informações do serviço, monitoramento anômalo, etc., através do aplicativo móvel.',
@@ -2157,7 +2172,7 @@ const message = {
scanHelper: 'Limpeza fácil de arquivos inúteis gerados durante a execução do 1Panel',
clean: 'Limpar agora',
reScan: 'Repetir escaneamento',
cleanHelper: 'Isso limpará os arquivos inúteis selecionados e não poderá ser desfeito. Você deseja continuar?',
cleanHelper: 'Arquivos e diretórios selecionados não podem ser revertidos após a limpeza. Continuar?',
statusSuggest: '(Limpeza recomendada)',
statusClean: '(Muito limpo)',
statusEmpty: 'Muito limpo, nenhuma limpeza necessária!',
@@ -3680,6 +3695,19 @@ const message = {
alertContent3:
'Permite que os usuários configurem números de telefone personalizados, frequência de envio diário e horários de envio, facilitando a criação de alertas mais adequados.',
nodeDashTitle1: 'Gerenciamento de Aplicativos',
nodeDashContent1:
'Gerenciamento unificado de aplicativos multi-nó, suporta monitoramento de status, início/parada rápida, conexão de terminal e backup',
nodeDashTitle2: 'Gerenciamento de Sites',
nodeDashContent2:
'Gerenciamento unificado de sites multi-nó, monitoramento de status em tempo real, suporta início/parada em lote e backup rápido',
nodeDashTitle3: 'Gerenciamento de Bancos de Dados',
nodeDashContent3:
'Gerenciamento unificado de bancos de dados multi-nó, status chave de relance, suporta backup com um clique',
nodeDashTitle4: 'Gerenciamento de Tarefas Agendadas',
nodeDashContent4:
'Gerenciamento unificado de tarefas agendadas multi-nó, suporta monitoramento de status, início/parada rápida e execução manual por acionamento',
nodeTitle1: 'Adição de Nó com Um Clique',
nodeContent1: 'Integre rapidamente vários nós de servidor',
nodeTitle2: 'Atualização em Lote',
@@ -3698,6 +3726,10 @@ const message = {
fileExchangeContent3:
'Transfira facilmente arquivos de projeto entre diferentes nós, gerencie vários servidores de forma flexível.',
nodeAppTitle1: 'Gerenciamento de Atualização de Aplicativos',
nodeAppContent1:
'Monitoramento unificado de atualizações de aplicativos multi-nó, suporta atualização com um clique',
appTitle1: 'Gerenciamento Flexível do Painel',
appContent1: 'Gerencie facilmente seu servidor 1Panel a qualquer hora e em qualquer lugar.',
appTitle2: 'Informações Completas do Serviço',
+34 -2
View File
@@ -1118,6 +1118,13 @@ const message = {
failedRecord: 'Неудачные записи',
successRecord: 'Успешные записи',
database: 'Резервное копирование базы данных',
backupArgs: 'Аргументы Резервного Копирования',
backupArgsHelper:
'Неперечисленные аргументы резервного копирования могут быть введены и выбраны вручную. Например: Введите --no-data и выберите первый вариант из выпадающего списка.',
singleTransaction:
'Резервное копирование таблиц InnoDB с использованием одной транзакции, подходит для резервного копирования данных большого объема',
quick: 'Чтение данных построчно вместо загрузки всей таблицы в память, подходит для резервного копирования данных большого объема и машин с низкой памятью',
skipLockTables: 'Резервное копирование без блокировки всех таблиц, подходит для высококонкурентных баз данных',
missBackupAccount: 'Не удалось найти учетную запись резервного копирования',
syncDate: 'Время синхронизации',
clean: 'Очистка кэша',
@@ -1964,7 +1971,10 @@ const message = {
error444: 'Соединение закрыто',
error500: 'Ошибка сервера',
https: 'Настройка HTTPS для панели повышает безопасность доступа.\nВ режиме Strict трафик без HTTPS не может подключиться к панели.\nРежим Mux перенаправляет HTTP на HTTPS, но может снизить безопасность.',
https: 'Настройка HTTPS для панели повышает безопасность доступа',
strictHelper: 'Трафик без HTTPS не может подключиться к панели',
muxHelper:
'Панель будет прослушивать порты HTTP и HTTPS и перенаправлять HTTP на HTTPS, но это может снизить безопасность',
certType: 'Тип сертификата',
selfSigned: 'Самоподписанный',
selfSignedHelper:
@@ -2142,6 +2152,11 @@ const message = {
monitor:
'Обновление до профессиональной версии позволяет просматривать статус веб-сайта в реальном времени, тенденции посещений, источники посетителей, логи запросов и другую информацию.',
alert: 'Обновление до профессиональной версии позволяет получать информацию о тревогах через SMS и просматривать логи тревог, полностью контролировать различные ключевые события и обеспечивать беспроблемную работу системы',
node: 'Обновление до профессиональной редакции позволяет управлять несколькими серверами Linux с помощью 1Panel.',
nodeApp:
'Обновление до профессиональной редакции позволяет унифицированно обновлять версии приложений на нескольких узлах без необходимости ручного переключения узлов.',
nodeDashboard:
'Обновление до профессиональной редакции позволяет централизованно управлять приложениями, веб-сайтами, базами данных и запланированными задачами на нескольких узлах.',
fileExchange: 'Обновите до Профессиональной версии, чтобы быстро передавать файлы между несколькими серверами.',
app: 'Обновите до профессиональной версии, чтобы просматривать информацию о сервисах, мониторинг аномалий и т.д. через мобильное приложение.',
cluster:
@@ -2152,7 +2167,7 @@ const message = {
scanHelper: 'Легко очищайте мусорные файлы, созданные во время работы 1Panel',
clean: 'Очистить сейчас',
reScan: 'Пересканировать',
cleanHelper: 'Это очистит выбранные системные мусорные файлы, и действие нельзя отменить. Хотите продолжить?',
cleanHelper: 'Выбранные файлы и каталоги нельзя откатить после очистки. Продолжить?',
statusSuggest: '(Рекомендуется очистка)',
statusClean: '(Очень чисто)',
statusEmpty: 'Очень чисто, очистка не требуется!',
@@ -3665,6 +3680,19 @@ const message = {
alertContent3:
'Предоставляет пользователям возможность настройки номеров телефонов, частоты и времени отправки уведомлений в день, что позволяет настраивать более удобные уведомления.',
nodeDashTitle1: 'Управление Приложениями',
nodeDashContent1:
'Единое управление приложениями на нескольких узлах, поддерживает мониторинг состояния, быстрое запуск/остановку, подключение терминала и резервное копирование',
nodeDashTitle2: 'Управление Веб-сайтами',
nodeDashContent2:
'Единое управление веб-сайтами на нескольких узлах, мониторинг состояния в реальном времени, поддерживает пакетный запуск/остановку и быстрое резервное копирование',
nodeDashTitle3: 'Управление Базами Данных',
nodeDashContent3:
'Единое управление базами данных на нескольких узлах, ключевые состояния с первого взгляда, поддерживает резервное копирование в один клик',
nodeDashTitle4: 'Управление Запланированными Задачами',
nodeDashContent4:
'Единое управление запланированными задачами на нескольких узлах, поддерживает мониторинг состояния, быстрый запуск/остановку и выполнение по ручному триггеру',
nodeTitle1: 'Добавление узла одним кликом',
nodeContent1: 'Быстро интегрируйте несколько серверных узлов',
nodeTitle2: 'Пакетное обновление',
@@ -3683,6 +3711,10 @@ const message = {
fileExchangeContent3:
'Легко передавайте проектные файлы между разными узлами, гибко управляйте несколькими серверами.',
nodeAppTitle1: 'Управление Обновлениями Приложений',
nodeAppContent1:
'Единый мониторинг обновлений приложений на нескольких узлах, поддерживает обновление в один клик',
appTitle1: 'Гибкое управление панелью',
appContent1: 'Легко управляйте сервером 1Panel в любое время и в любом месте.',
appTitle2: 'Полная информация о сервисе',
+34 -4
View File
@@ -1146,6 +1146,13 @@ const message = {
failedRecord: 'Başarısız kayıtlar',
successRecord: 'Başarılı kayıtlar',
database: 'Veritabanını yedekle',
backupArgs: 'Yedekleme Argümanları',
backupArgsHelper:
'Listelenmemiş yedekleme argümanları manuel olarak girilip seçilebilir. Örneğin: --no-data yazın ve açılır listeden ilk seçeneği seçin.',
singleTransaction:
'InnoDB tablolarını tek bir işlem kullanarak yedekler, büyük hacimli veri yedeklemeleri için uygundur',
quick: 'Tüm tabloyu belleğe yüklemek yerine verileri satır satır okur, büyük hacimli veri ve düşük bellekli makine yedeklemeleri için uygundur',
skipLockTables: 'Tüm tabloları kilitlemeden yedekleme, yüksek eşzamanlılığa sahip veritabanları için uygundur',
missBackupAccount: 'Yedekleme hesabı bulunamadı',
syncDate: 'Senkronizasyon zamanı ',
clean: 'Önbellek temizleme',
@@ -2019,7 +2026,10 @@ const message = {
error416: 'Aralık Karşılanamadı',
error444: 'Bağlantı Kapalı',
error500: 'Dahili Sunucu Hatası',
https: "Panel için HTTPS ayarlamak erişim güvenliğini artırır.\nStrict modda HTTPS olmayan trafik panele bağlanamaz.\nMux modu HTTP'yi HTTPS'e yönlendirir, ancak güvenliği biraz azaltabilir.",
https: 'Panel için HTTPS ayarlamak erişim güvenliğini artırır',
strictHelper: 'HTTPS olmayan trafik panele bağlanamaz',
muxHelper:
"Panel hem HTTP hem de HTTPS portlarını dinleyecek ve HTTP'yi HTTPS'ye yönlendirecektir, ancak bu güvenliği azaltabilir",
certType: 'Sertifika türü',
selfSigned: 'Kendi kendine imzalı',
selfSignedHelper:
@@ -2197,7 +2207,11 @@ const message = {
monitor:
'Profesyonel sürüme yükseltme, web sitesinin gerçek zamanlı durumunu, ziyaretçi trendlerini, ziyaretçi kaynaklarını, istek günlüklerini ve diğer bilgileri görüntülemenize olanak tanır. ',
alert: 'Profesyonel sürüme yükseltme, SMS yoluyla alarm bilgisi almanızı, alarm günlüklerini görüntülemenizi, çeşitli önemli olayları tamamen kontrol etmenizi ve sistemin sorunsuz çalışmasını sağlar.',
node: 'Profesyonel sürüme yükseltme, birden fazla Linux sunucusunu 1Panel ile yönetmenize olanak tanır.',
node: 'Profesyonel Sürüme yükseltme, 1Panel ile birden fazla Linux sunucusunu yönetmenize olanak tanır.',
nodeApp:
'Profesyonel Sürüme yükseltme, düğümleri manuel olarak değiştirmeye gerek kalmadan çoklu düğüm uygulama sürümlerini birleşik olarak yükseltmenize olanak tanır.',
nodeDashboard:
'Profesyonel Sürüme yükseltme, çoklu düğüm uygulamaları, web siteleri, veritabanları ve zamanlanmış görevleri merkezi olarak yönetmenizi sağlar.',
fileExchange: 'Profesyonel Sürüme yükseltme, birden fazla sunucu arasında hızlı dosya aktarımı sağlar.',
app: 'Profesyonel sürüme yükseltme, mobil uygulama üzerinden hizmet bilgilerini, anormal izlemeyi vb. görüntülemenize olanak tanır. ',
cluster: 'Profesyonel Sürüme Yükseltme, MySQL/Postgers/Redis ana-çalışan kümelerini yönetmenizi sağlar.',
@@ -2207,8 +2221,7 @@ const message = {
scanHelper: '1Panel çalışma zamanında üretilen gereksiz dosyaları kolayca temizleyin',
clean: 'Şimdi temizle',
reScan: 'Yeniden tara',
cleanHelper:
'Bu, seçilen sistem gereksiz dosyalarını temizleyecek ve geri alınamaz. Devam etmek istiyor musunuz?',
cleanHelper: 'Seçilen dosya ve dizinler temizlendikten sonra geri alınamaz. Devam etmek istiyor musunuz?',
statusStatusSuggest: '(Temizlik Önerilir)',
statusClean: '(Çok temiz)',
statusEmpty: 'Çok temiz, temizlik gerekmiyor!',
@@ -3743,6 +3756,19 @@ const message = {
alertContent3:
'Kullanıcılara özel telefon numaraları, günlük gönderim sıklığı ve günlük gönderim zamanı yapılandırmaları sağlar, kullanıcıların daha makul gönderim uyarıları ayarlamasını kolaylaştırır.',
nodeDashTitle1: 'Uygulama Yönetimi',
nodeDashContent1:
'Çoklu düğüm uygulamalarının birleşik yönetimi, durum izleme, hızlı başlatma/durdurma, terminal bağlantısı ve yedekleme desteği',
nodeDashTitle2: 'Web Sitesi Yönetimi',
nodeDashContent2:
'Çoklu düğüm web sitelerinin birleşik yönetimi, gerçek zamanlı durum izleme, toplu başlatma/durdurma ve hızlı yedekleme desteği',
nodeDashTitle3: 'Veritabanı Yönetimi',
nodeDashContent3:
'Çoklu düğüm veritabanlarının birleşik yönetimi, önemli durumlar tek bakışta, tek tıklamayla yedekleme desteği',
nodeDashTitle4: 'Zamanlanmış Görev Yönetimi',
nodeDashContent4:
'Çoklu düğüm zamanlanmış görevlerinin birleşik yönetimi, durum izleme, hızlı başlatma/durdurma ve manuel tetikleme çalıştırma desteği',
nodeTitle1: 'Tek Tıkla Düğüm Ekleme',
nodeContent1: 'Birden fazla sunucu düğümünü hızlıca entegre eder',
nodeTitle2: 'Toplu Yükseltme',
@@ -3761,6 +3787,10 @@ const message = {
fileExchangeContent3:
'Farklı düğümler arasında proje dosyalarını kolayca aktarır, birden fazla sunucuyu esnek bir şekilde yönetir.',
nodeAppTitle1: 'Uygulama Yükseltme Yönetimi',
nodeAppContent1:
'Çoklu düğüm uygulama güncellemelerinin birleşik izlenmesi, tek tıklamayla yükseltme desteği',
appTitle1: 'Esnek Panel Yönetimi',
appContent1: '1Panel sunucunuzu her zaman, her yerde kolayca yönetin.',
appTitle2: 'Kapsamlı Hizmet Bilgisi',
+24 -3
View File
@@ -1072,6 +1072,11 @@ const message = {
rulesHelper: '支援多個排除規則,使用英文逗號 , 分隔,例如:*.log,*.sql',
lastRecordTime: '上次執行情況',
database: '備份資料庫',
backupArgs: '備份參數',
backupArgsHelper: '未列出的備份參數可手動輸入並選擇,例如:輸入 --no-data 後選擇下拉列表中的第一個選項',
singleTransaction: '使用單一事務備份 InnoDB 表,適用於大數據量的備份',
quick: '逐行讀取數據,而不是將整個表加載到內存中,適用於大數據量和低內存機器的備份',
skipLockTables: '不鎖定所有表進行備份,適用於高併發的資料庫',
missBackupAccount: '未能找到備份帳號',
syncDate: '同步時間 ',
clean: '快取清理',
@@ -1945,7 +1950,9 @@ const message = {
error444: '連線已關閉',
error500: '內部伺服器錯誤',
https: '為面板設定 HTTPS 可提升訪問安全性 \nStrict 模式下非 HTTPS 流量無法連接面板\nMux 模式會將 HTTP 重新導向到 HTTPS,但可能會降低安全性',
https: '為面板設置 HTTPS 可提升訪問安全性',
strictHelper: '非 HTTPS 流量無法連接面板',
muxHelper: '面板將同時監聽 HTTP 和 HTTPS 端口,並將 HTTP 重定向到 HTTPS,但可能會降低安全性',
certType: '證書類型',
selfSigned: '自簽名',
selfSignedHelper: '自簽證書,不被瀏覽器信任,顯示不安全是正常現象',
@@ -2022,7 +2029,9 @@ const message = {
setting: '升級專業版可以自訂面板 Logo、歡迎簡介等資訊。',
monitor: '升級專業版可以查看網站的即時狀態、訪客趨勢、訪客來源、請求日誌等資訊。',
alert: '升級專業版可透過簡訊接收告警資訊,並查看告警日誌,全面掌控各類關鍵事件,確保系統執行無憂。',
node: '升級專業版可以使用 1Panel 管理多台 linux 伺服器。',
node: '升級專業版可以使用 1Panel 管理多台 Linux 伺服器。',
nodeApp: '升級專業版可以統一升級多節點應用版本,無需手動切換節點。',
nodeDashboard: '升級專業版可以集中管理多節點應用、網站、資料庫和計劃任務。',
fileExchange: '升級專業版可以在多台伺服器之間快速傳輸文件。',
app: '升級專業版可透過手機APP,查看服務資訊、異常監控等。',
cluster: '升級專業版可以管理 MySQL/Postgres/Redis 主從叢集。',
@@ -2032,7 +2041,7 @@ const message = {
scanHelper: '輕鬆梳理 1Panel 執行期間積累的垃圾文件',
clean: '立即清理',
reScan: '重新掃描',
cleanHelper: '已勾選文件及目錄清理後無法回滾(系統快取文件清理需要重啟服務),是否繼續?',
cleanHelper: '已勾選文件及目錄清理後無法回滾,是否繼續?',
statusSuggest: '( 建議清理 )',
statusClean: '( 很乾淨 )',
statusEmpty: '非常乾淨,無需清理!',
@@ -3428,6 +3437,15 @@ const message = {
alertContent3:
'為使用者提供自訂手機號碼、每日推送次數、每日推送時間的配寘,方便使用者的設定來更加合理的進行推送告警。',
nodeDashTitle1: '應用管理',
nodeDashContent1: '統一管理多節點應用,支援狀態監控、快速起停、終端連線及備份',
nodeDashTitle2: '網站管理',
nodeDashContent2: '統一管理多節點網站,即時監控狀態,支援批次起停與快速備份',
nodeDashTitle3: '資料庫管理',
nodeDashContent3: '統一管理多節點資料庫,關鍵狀態一目了然,支援一鍵備份',
nodeDashTitle4: '計劃任務管理',
nodeDashContent4: '統一管理多節點計劃任務,支援狀態監控、快速啟停與手動觸發執行',
nodeTitle1: '一鍵新增節點',
nodeContent1: '快速接入多台伺服器節點',
nodeTitle2: '批次升級',
@@ -3444,6 +3462,9 @@ const message = {
fileExchangeTitle3: '支援多節點互傳',
fileExchangeContent3: '可在不同節點間便捷傳送項目文件,靈活管理多台伺服器。',
nodeAppTitle1: '應用升級管理',
nodeAppContent1: '統一監控多節點應用更新情況,支援一鍵升級',
appTitle1: '靈活管理面板',
appContent1: '隨時隨地輕鬆管理你的 1Panel 伺服器。',
appTitle2: '全面服務資訊',
+23 -2
View File
@@ -1075,6 +1075,11 @@ const message = {
rulesHelper: '支持多个排除规则,使用英文逗号 , 分隔,例如:*.log,*.sql',
lastRecordTime: '上次执行情况',
database: '备份数据库',
backupArgs: '备份参数',
backupArgsHelper: '未列出的备份参数可手动输入并选择,如: 输入 --no-data 后选择下拉列表中的第一个',
singleTransaction: '使用单一事务备份 InnoDB 表,适用于大数据量的备份',
quick: '逐行读取数据,而不是将整个表加载到内存中,适用于大数据量和低内存机器的备份',
skipLockTables: '不锁定所有表进行备份,适用于高并发的数据库',
missBackupAccount: '未能找到备份账号',
syncDate: '同步时间 ',
clean: '缓存清理',
@@ -1944,7 +1949,9 @@ const message = {
error444: '连接被关闭',
error500: '内部错误',
https: '为面板设置HTTPS可提升访问安全性 \n Strict模式下非HTTPS流量无法连接面板\nMux模式会将 HTTP 重定向到 HTTPS ,但可能会降低安全性',
https: '为面板设置HTTPS可提升访问安全性',
strictHelper: '非 HTTPS 流量无法连接面板',
muxHelper: '面板将同时监听 HTTP 和 HTTPS 端口,并将 HTTP 重定向到 HTTPS,但可能会降低安全性',
certType: '证书类型',
selfSigned: '自签名',
selfSignedHelper: '自签证书,不被浏览器信任,显示不安全是正常现象',
@@ -2021,6 +2028,8 @@ const message = {
monitor: '升级专业版可以查看网站的实时状态、访客趋势、访客来源、请求日志等信息。',
alert: '升级专业版可通过短信接收告警信息,并查看告警日志,全面掌控各类关键事件,确保系统运行无忧。',
node: '升级专业版可以使用 1Panel 管理多台 linux 服务器。',
nodeApp: '升级专业版可以统一升级多节点应用版本,无需手动切换节点。',
nodeDashboard: '升级专业版可以集中管理多节点应用、网站、数据库和计划任务。',
fileExchange: '升级专业版可以在多台服务器之间快速传输文件。',
app: '升级专业版可通过手机 APP,查看服务信息、异常监控等。',
cluster: '升级专业版可以管理 MySQL/Postgres/Redis 主从集群。',
@@ -2030,7 +2039,7 @@ const message = {
scanHelper: '轻松梳理 1Panel 运行期间积累的垃圾文件',
clean: '立即清理',
reScan: '重新扫描',
cleanHelper: '已勾选文件及目录清理后无法回滚(系统缓存文件清理需要重启服务),是否继续?',
cleanHelper: '已勾选文件及目录清理后无法回滚,是否继续?',
statusSuggest: '( 建议清理 )',
statusClean: '( 很干净 )',
statusEmpty: '非常干净,无需清理!',
@@ -3421,6 +3430,15 @@ const message = {
alertContent3:
'为用户提供自定义手机号、每日推送次数、每日推送时间的配置,方便用户的设置来更加合理的进行推送告警。',
nodeDashTitle1: '应用管理',
nodeDashContent1: '统一管理多节点应用,支持状态监控、快速起停、终端连接及备份',
nodeDashTitle2: '网站管理',
nodeDashContent2: '统一管理多节点网站,实时监控状态,支持批量起停与快速备份',
nodeDashTitle3: '数据库管理',
nodeDashContent3: '统一管理多节点数据库,关键状态一目了然,支持一键备份',
nodeDashTitle4: '计划任务管理',
nodeDashContent4: '统一管理多节点计划任务,支持状态监控、快速启停与手动触发执行',
nodeTitle1: '一键添加节点',
nodeContent1: '快速接入多台服务器节点',
nodeTitle2: '批量升级',
@@ -3437,6 +3455,9 @@ const message = {
fileExchangeTitle3: '支持多节点互传',
fileExchangeContent3: '可在不同节点间便捷传送项目文件,灵活管理多台服务器。',
nodeAppTitle1: '应用升级管理',
nodeAppContent1: '统一监控多节点应用更新情况,支持一键升级',
appTitle1: '灵活管理面板',
appContent1: '随时随地轻松管理你的 1Panel 服务器。',
appTitle2: '全面服务信息',
@@ -3,7 +3,7 @@
<el-card>
<div class="app-wrapper" @click="openDetail(app.key)">
<div class="app-image">
<el-avatar shape="square" :size="60" :src="getAppIconUrl(app.id, currentNode)" />
<el-avatar shape="square" :size="60" :src="getAppIconUrl(app.key, currentNode)" />
</div>
<div class="app-content">
<div class="content-top">
@@ -222,6 +222,7 @@ const formRules = ref<FormRules>({
specifyIP: [Rules.ipv4orV6],
restartPolicy: [Rules.requiredSelect],
format: [Rules.requiredInput],
nodes: [Rules.requiredSelect],
});
const initFormData = () => ({
@@ -5,7 +5,7 @@
<div class="detail flex">
<div class="w-12 h-12 rounded p-1 shadow-md icon">
<img
:src="getAppIconUrl(app.id, currentNode)"
:src="getAppIconUrl(app.key, currentNode)"
alt="App Icon"
class="w-full h-full rounded"
style="object-fit: contain"
@@ -29,7 +29,7 @@ import { newUUID } from '@/utils/util';
import { routerToName } from '@/utils/router';
import TaskLog from '@/components/log/task/index.vue';
import i18n from '@/lang';
import { installAppToNodes } from '@/xpack/api/modules/appstore';
import { installAppToNodes } from '@/api/modules/app';
const router = useRouter();
const open = ref(false);
@@ -5,7 +5,7 @@
<el-col :xs="3" :sm="3" :md="3" :lg="4" :xl="3">
<AppIcon
@open-detail="$emit('openDetail')"
:appID="installed.appID"
:appKey="installed.appKey"
:currentNode="currentNode"
></AppIcon>
</el-col>
@@ -1,6 +1,6 @@
<template>
<div class="icon">
<el-avatar @click="$emit('openDetail')" shape="square" :size="77" :src="getAppIconUrl(appID, currentNode)" />
<el-avatar @click="$emit('openDetail')" shape="square" :size="77" :src="getAppIconUrl(appKey, currentNode)" />
</div>
</template>
@@ -9,7 +9,7 @@ import { getAppIconUrl } from '@/api/modules/app';
interface Props {
currentNode: string;
appID: number;
appKey: string;
}
defineProps<Props>();
@@ -108,9 +108,13 @@ const initTerm = (formEl: FormInstance | undefined) => {
if (!valid) return;
terminalOpen.value = true;
await nextTick();
let args = `source=container&containerid=${form.containerID}&user=${form.user}&command=${form.command}`;
if (form.node) {
args += `&operateNode=${form.node}`;
}
terminalRef.value!.acceptParams({
endpoint: '/api/v2/containers/exec',
args: `source=container&containerid=${form.containerID}&user=${form.user}&command=${form.command}&node=${form.node}`,
args: args,
error: '',
initCmd: '',
});
@@ -78,6 +78,9 @@ const acceptParams = async (): Promise<void> => {
unUsedList.value.push(item);
}
}
checkedLists.value = [];
checkAll.value = false;
isIndeterminate.value = false;
dialogVisible.value = true;
scope.value = 'untag';
changeScope();
@@ -89,14 +92,14 @@ const changeScope = () => {
if (scope.value === 'untag') {
data.value = unTagList.value || [];
showMsg.value =
data.value.length === 0
data.value.length !== 0
? i18n.global.t('container.imagePruneSomeHelper')
: i18n.global.t('container.imagePruneSomeEmpty');
return;
}
data.value = unUsedList.value || [];
showMsg.value =
data.value.length === 0
data.value.length !== 0
? i18n.global.t('container.imagePruneAllHelper')
: i18n.global.t('container.imagePruneAllEmpty');
return;
@@ -12,9 +12,10 @@
</el-select>
</el-form-item>
<el-form-item :label="$t('container.imageName')" prop="imageName">
<el-input-tag v-model="form.imageName">
<template v-if="form.fromRepo" #prefix>{{ loadDetailInfo(form.repoID) }}/</template>
<el-input-tag v-if="form.fromRepo" v-model="form.imageName">
<template #prefix>{{ loadDetailInfo(form.repoID) }}/</template>
</el-input-tag>
<el-input-tag v-else v-model="form.imageName" />
<span class="input-help">{{ $t('container.imagePullHelper') }}</span>
</el-form-item>
</el-form>
@@ -21,6 +21,11 @@ export const weekOptions = [
{ label: i18n.global.t('cronjob.saturday'), value: 6 },
{ label: i18n.global.t('cronjob.sunday'), value: 0 },
];
export const mysqlArgs = [
{ arg: '--single-transaction', description: i18n.global.t('cronjob.singleTransaction') },
{ arg: '--quick', description: i18n.global.t('cronjob.quick') },
{ arg: '--skip-lock-tables', description: i18n.global.t('cronjob.skipLockTables') },
];
function loadWeek(i: number) {
for (const week of weekOptions) {
if (week.value === i) {
@@ -255,10 +255,20 @@
:value="item.id + ''"
:label="item.primaryDomain"
>
<span>{{ item.primaryDomain }}</span>
<el-tag class="tagClass">
{{ item.alias }}
</el-tag>
<div class="option-content">
<el-tooltip
:content="item.primaryDomain"
placement="top"
:open-delay="500"
effect="dark"
>
<span class="domain-text">{{ item.primaryDomain }}</span>
</el-tooltip>
<el-tag class="tagClass">
{{ item.alias }}
</el-tag>
</div>
</el-option>
</el-select>
<span class="input-help" v-if="form.type === 'cutWebsiteLog'">
@@ -335,6 +345,30 @@
</el-select>
</el-form-item>
</LayoutCol>
<LayoutCol
:span="20"
v-if="
form.type === 'database' &&
(form.dbType === 'mysql' || form.dbType === 'mysql-cluster')
"
>
<el-form-item :label="$t('cronjob.backupArgs')">
<el-select v-model="form.argItems" filterable allow-create multiple>
<el-option
v-for="item in mysqlArgs"
:key="item.arg"
:value="item.arg"
:label="item.arg"
>
{{ item.arg }}
<span class="ml-2">{{ item.description }}</span>
</el-option>
</el-select>
<span class="input-help">
{{ $t('cronjob.backupArgsHelper') }}
</span>
</el-form-item>
</LayoutCol>
<LayoutCol v-if="form.type === 'directory'">
<el-form-item :label="$t('commons.button.backup')">
<el-radio-group v-model="form.isDir" class="w-full">
@@ -774,7 +808,6 @@ import CodemirrorPro from '@/components/codemirror-pro/index.vue';
import InputTag from '@/components/input-tag/index.vue';
import LayoutCol from '@/components/layout-col/form.vue';
import CleanLogConfig from '@/views/cronjob/cronjob/config/clean-log.vue';
import { reactive, ref } from 'vue';
import { Rules } from '@/global/form-rules';
import { listBackupOptions } from '@/api/modules/backup';
@@ -797,6 +830,7 @@ import {
transSpecToObj,
weekOptions,
cronjobTypes,
mysqlArgs,
} from '../helper';
import { loadUsers } from '@/api/modules/toolbox';
import { loadContainerUsers } from '@/api/modules/container';
@@ -883,6 +917,8 @@ const form = reactive<Cronjob.CronjobInfo>({
alertMethodItems: [],
scopes: [],
args: '',
argItems: [],
});
const search = async () => {
@@ -982,6 +1018,7 @@ const search = async () => {
form.alertMethodItems = [];
}
form.scopes = res.data.scopes;
form.argItems = res.data.args ? res.data.args.split(',') : [];
})
.catch(() => {
loading.value = false;
@@ -1481,6 +1518,7 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
form.url = form.urlItems.join(',');
form.sourceAccountIDs = form.sourceAccountItems.join(',');
form.spec = specs.join('&&');
form.args = form.argItems.join(',');
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
@@ -1596,4 +1634,22 @@ onMounted(() => {
margin: 3px 0;
border-top: 1px var(--el-border-color) var(--el-border-style);
}
.option-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.domain-text {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
vertical-align: middle;
}
</style>
@@ -1,5 +1,5 @@
<template>
<div class="h-full" @dragover="handleDragover" @drop="handleDrop" @dragleave="handleDragleave">
<div class="h-full" ref="fileTableRef" @dragover="handleDragover" @drop="handleDrop" @dragleave="handleDragleave">
<el-tabs
type="card"
class="file-tabs"
@@ -574,6 +574,7 @@
:sortable="'custom'"
></el-table-column>
<fu-table-operations
:max-height="dropdownMaxHeight"
:ellipsis="mobile ? 0 : 2"
:buttons="tableMoreButtons"
:label="$t('commons.table.operate')"
@@ -711,6 +712,14 @@ const router = useRouter();
const data = ref();
const tableRefs = ref<Record<string, any>>({});
const heightDiff = ref(365);
const fileTableRef = ref<HTMLElement | null>(null);
const dropdownMaxHeight = ref(450);
const baseDir = ref();
const editableTabsKey = ref('');
const editableTabs = ref([
{ id: '1', name: getLastPath(baseDir.value), path: baseDir.value },
{ id: '2', name: 'home', path: '/home' },
]);
const setTableRef = (key: string, el: any) => {
if (el) {
@@ -734,7 +743,6 @@ const initData = () => ({
});
let req = reactive(initData());
let loading = ref(false);
const baseDir = ref();
const paths = ref<FilePaths[]>([]);
const hidePaths = ref<FilePaths[]>([]);
let pathWidth = ref(0);
@@ -997,6 +1005,7 @@ const observeResize = () => {
if (isElChanged) resizeHandler();
if (isEleChanged) btnResizeHandler();
updateHeight();
});
observe.observe(el);
@@ -1013,10 +1022,6 @@ function watchTitleHeight() {
}
}
watchTitleHeight();
window.addEventListener('resize', watchTitleHeight);
const resetPaths = () => {
paths.value = [...hidePaths.value, ...paths.value];
hidePaths.value = [];
@@ -1089,24 +1094,23 @@ const backForwardJump = async (url: string) => {
});
};
const getPaths = (reqPath: string) => {
const pathArray = reqPath.split('/');
paths.value = [];
hidePaths.value = [];
let base = '/';
const getPaths = (reqPath: string | undefined | null) => {
const pathString = reqPath || '';
const pathArray = pathString.split('/').filter((p) => p !== '');
const breadcrumbs = [];
let base = '';
for (const p of pathArray) {
if (p != '') {
if (base.endsWith('/')) {
base = base + p;
} else {
base = base + '/' + p;
}
paths.value.push({
url: base,
name: p,
});
}
base = `${base}/${p}`;
breadcrumbs.push({
url: base,
name: p,
});
}
paths.value = breadcrumbs;
hidePaths.value = [];
};
const handleCreate = (command: string) => {
@@ -1775,12 +1779,19 @@ function initTabsAndPaths() {
}
function buildPaths(path: string) {
const segments = path.split('/').filter(Boolean);
let url = '';
return segments.map((segment) => {
url += '/' + segment;
return { url, name: segment };
});
return path
.split('/')
.filter(Boolean)
.reduce((accumulator, segment) => {
const lastPath = accumulator[accumulator.length - 1];
const currentUrl = lastPath ? `${lastPath.url}/${segment}` : `/${segment}`;
accumulator.push({
url: currentUrl,
name: segment,
});
return accumulator;
}, []);
}
function initHistory() {
@@ -1791,31 +1802,26 @@ function initHistory() {
function getInitialPath(): string {
const routePath = router.currentRoute.value.query.path;
if (routePath != undefined) {
const p = String(routePath);
globalStore.setLastFilePath(p);
return p;
} else if (
typeof globalStore.lastFilePath === 'string' &&
globalStore.lastFilePath.trim() !== '' &&
globalStore.lastFilePath !== 'undefined'
) {
return globalStore.lastFilePath;
if (routePath && typeof routePath === 'string') {
const p = routePath.trim();
if (p !== '') {
globalStore.setLastFilePath(p);
return p;
}
}
const tab = editableTabs.value.find((t) => t.id === editableTabsKey.value);
if (tab) {
globalStore.setLastFilePath(tab.path);
return tab.path;
if (tab && typeof tab.path === 'string' && tab.path.trim() !== '') {
const p = tab.path.trim();
globalStore.setLastFilePath(p);
return p;
}
if (typeof globalStore.lastFilePath === 'string' && globalStore.lastFilePath.trim() !== '') {
return globalStore.lastFilePath;
}
return '/';
}
const editableTabsKey = ref('');
const editableTabs = ref([
{ id: '1', name: getLastPath(baseDir.value), path: baseDir.value },
{ id: '2', name: 'home', path: '/home' },
]);
function initTabs() {
const savedTabs = localStorage.getItem('editableTabs');
if (savedTabs) {
@@ -1931,14 +1937,27 @@ const checkFFmpeg = () => {
});
};
onMounted(() => {
loadPath();
const updateHeight = () => {
const el = fileTableRef.value;
if (!el) return;
let tabHeight = globalStore.openMenuTabs ? 40 : -4;
const half = (el.offsetHeight + tabHeight) / 2;
dropdownMaxHeight.value = Math.max(half, 300);
};
onMounted(async () => {
await loadPath();
await nextTick();
watchTitleHeight();
window.addEventListener('resize', watchTitleHeight);
updateHeight();
window.addEventListener('resize', updateHeight);
initShowHidden();
initTabsAndPaths();
getHostMount();
await getHostMount();
initHistory();
checkFFmpeg();
nextTick(function () {
await nextTick(function () {
handlePath();
observeResize();
});
@@ -1947,6 +1966,7 @@ onMounted(() => {
onBeforeUnmount(() => {
if (resizeObserver) resizeObserver.disconnect();
window.removeEventListener('resize', watchTitleHeight);
window.removeEventListener('resize', updateHeight);
});
</script>
+10 -6
View File
@@ -104,11 +104,12 @@
</el-form-item>
<el-form-item :label="$t('setting.panelSSL')" prop="ssl">
<el-radio-group v-model="form.ssl" @change="handleSSL">
<el-radio value="Disable">{{ $t('setting.sslDisable') }}</el-radio>
<el-radio value="Enable">{{ $t('commons.button.enable') }} (Strict)</el-radio>
<el-radio value="Mux">{{ $t('commons.button.enable') }} (Mux)</el-radio>
</el-radio-group>
<el-switch
@change="handleSSL"
v-model="form.sslItem"
active-value="Enable"
inactive-value="Disable"
/>
<span class="input-help">{{ $t('setting.https') }}</span>
<div v-if="form.ssl !== 'Disable' && sslInfo">
<el-tag>{{ $t('setting.domainOrIP') }} {{ sslInfo.domain }}</el-tag>
@@ -222,6 +223,7 @@ const form = reactive({
ipv6: 'Disable',
bindAddress: '',
ssl: 'Disable',
sslItem: 'Disable',
sslType: 'self',
securityEntrance: '',
expirationDays: 0,
@@ -243,6 +245,7 @@ const search = async () => {
form.ipv6 = res.data.ipv6;
form.bindAddress = res.data.bindAddress;
form.ssl = res.data.ssl;
form.sslItem = res.data.ssl === 'Disable' ? 'Disable' : 'Enable';
lastSSL.value = form.ssl;
form.sslType = res.data.sslType;
if (form.ssl !== 'Disable') {
@@ -327,7 +330,7 @@ const onChangeAllowIPs = () => {
allowIPsRef.value.acceptParams({ allowIPs: form.allowIPs });
};
const handleSSL = async () => {
if (form.ssl !== 'Disable') {
if (form.sslItem !== 'Disable') {
let params = {
ssl: form.ssl,
sslType: form.sslType,
@@ -359,6 +362,7 @@ const handleSSL = async () => {
})
.catch(() => {
form.ssl = lastSSL.value;
form.sslItem = lastSSL.value === 'Disable' ? 'Disable' : 'Enable';
});
};
@@ -8,6 +8,14 @@
</template>
</el-alert>
<el-form ref="formRef" label-position="top" :model="form" :rules="rules" v-loading="loading">
<el-form-item :label="$t('setting.mode')" prop="ssl">
<el-radio-group v-model="form.ssl">
<el-radio-button value="Enable">Strict</el-radio-button>
<el-radio-button value="Mux">Mux</el-radio-button>
</el-radio-group>
<span v-if="form.ssl === 'Enable'" class="input-help">{{ $t('setting.strictHelper') }}</span>
<span v-if="form.ssl === 'Mux'" class="input-help">{{ $t('setting.muxHelper') }}</span>
</el-form-item>
<el-form-item :label="$t('setting.certType')">
<el-radio-group v-model="form.sslType">
<el-radio value="self">{{ $t('setting.selfSigned') }}</el-radio>
@@ -163,7 +171,7 @@ interface DialogProps {
sslInfo?: Setting.SSLInfo;
}
const acceptParams = async (params: DialogProps): Promise<void> => {
form.ssl = params.ssl;
form.ssl = params.ssl === 'Disable' ? 'Enable' : params.ssl;
if (params.sslType.indexOf('-') !== -1) {
form.sslType = 'import';
form.itemSSLType = params.sslType.split('-')[1];
+17 -21
View File
@@ -101,7 +101,7 @@
:default-checked-keys="systemDefaultCheck"
show-checkbox
:props="defaultProps"
@check-change="onChange"
@check="onChange"
class="responsive-tree"
:empty-text="$t('clean.statusEmpty')"
>
@@ -127,7 +127,7 @@
:default-checked-keys="backupDefaultCheck"
show-checkbox
:props="defaultProps"
@check-change="onChange"
@check="onChange"
class="responsive-tree"
:empty-text="$t('clean.statusEmpty')"
>
@@ -153,7 +153,7 @@
:default-checked-keys="containerDefaultCheck"
show-checkbox
:props="defaultProps"
@check-change="onChange"
@check="onChange"
class="responsive-tree"
:empty-text="$t('clean.statusEmpty')"
>
@@ -178,7 +178,7 @@
:default-checked-keys="uploadDefaultCheck"
show-checkbox
:props="defaultProps"
@check-change="onChange"
@check="onChange"
class="responsive-tree"
:empty-text="$t('clean.statusEmpty')"
>
@@ -203,7 +203,7 @@
:default-checked-keys="downloadDefaultCheck"
show-checkbox
:props="defaultProps"
@check-change="onChange"
@check="onChange"
class="responsive-tree"
:empty-text="$t('clean.statusEmpty')"
>
@@ -228,7 +228,7 @@
:default-checked-keys="systemLogDefaultCheck"
show-checkbox
:props="defaultProps"
@check-change="onChange"
@check="onChange"
class="responsive-tree"
:empty-text="$t('clean.statusEmpty')"
>
@@ -401,17 +401,12 @@ const loadSubmitCheck = (data: any) => {
}
return;
}
for (const item of data) {
if (
item.isCheck &&
item.label !== 'unknown_app' &&
item.label !== 'unknown_database' &&
item.label !== 'unknown_website' &&
item.label !== 'unknown_snapshot' &&
item.label !== 'unknown_website_log' &&
item.type !== 'app_tmp_download'
) {
if (item.type === 'unknown_backup' && item.isCheck && item.children) {
loadSubmitCheck(item.children);
continue;
}
if (item.isCheck && item.type !== 'app_tmp_download') {
submitCleans.value.push({ treeType: item.type, name: item.name, size: item.size });
continue;
}
@@ -421,17 +416,18 @@ const loadSubmitCheck = (data: any) => {
}
};
const changeCheckStatus = (data: any, isCheck: boolean) => {
data.isCheck = isCheck;
const changeCheckStatus = (data: any, checked: Array<string>) => {
data.isCheck = checked.indexOf(data.id) !== -1;
if (data.children) {
for (const item of data.children) {
changeCheckStatus(item, isCheck);
changeCheckStatus(item, checked);
}
}
};
function onChange(data: any, isCheck: boolean) {
changeCheckStatus(data, isCheck);
function onChange(data: any, checked: any) {
let keys = checked.checkedKeys || [];
changeCheckStatus(data, keys);
selectSize.value = 0;
let systemSelects = systemRef.value.getCheckedNodes(false, true);
for (const item of systemSelects) {
@@ -175,6 +175,14 @@
<el-input v-model.trim="account.authorization['endpoint']"></el-input>
</el-form-item>
</div>
<div v-if="account.type === 'PorkBun'">
<el-form-item label="API Key" prop="authorization.apiKey">
<el-input v-model.trim="account.authorization['apiKey']"></el-input>
</el-form-item>
<el-form-item label="Secret Key" prop="authorization.secretKey">
<el-input v-model.trim="account.authorization['secretKey']"></el-input>
</el-form-item>
</div>
</el-form>
</el-col>
</el-row>

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