Compare commits

..
78 Commits
Author SHA1 Message Date
蘭 0cfdf0f335 feat: alert count limit (#9621) 2025-07-23 21:34:20 +08:00
蘭 cca1863b63 feat: Adjust alarm settings (#9616) 2025-07-23 08:58:24 +00:00
CityFun 033f08a6d2 fix: Fix the issue where an error occurs on the runtime environment page when Docker is not installed. (#9615) 2025-07-23 15:39:48 +08:00
CityFun a83a6690ff fix: Fix the issue where application labels are displayed incorrectly… (#9611) 2025-07-23 04:04:23 +00:00
ssongliu 83df457379 fix: Add backup master missing alert (#9610) 2025-07-23 04:02:23 +00:00
蘭 657e55cde3 fix: Fix offline alert issue (#9608) 2025-07-22 18:39:26 +08:00
ssongliu 90e76e3a0f fix: Fix Redis performance tuning abnormalities (#9607) 2025-07-22 10:00:07 +00:00
CityFun 2af1993383 fix: Fix the issue of stale data caused by node deletion (#9606) 2025-07-22 16:12:00 +08:00
蘭 6e650d977e fix: Fix some issues with the alert (#9605) 2025-07-22 07:10:06 +00:00
ssongliu 8a76171dbe fix: Resolve backup to server disk failure for cronjob (#9603) 2025-07-22 14:56:13 +08:00
ssongliu 949b6fe944 fix: Fix node state synchronization issues (#9599) 2025-07-22 03:48:06 +00:00
CityFun 3f90333090 feat: Application installation supports using existing remote databases (#9598) 2025-07-22 03:20:05 +00:00
ssongliu aeaca0c5cd fix: Change container creation to asynchronous operation (#9597) 2025-07-22 11:00:16 +08:00
ssongliu fb7f638a54 fix: Optimize container filter component for cronjob (#9595)
<img width="633" height="304" alt="image" src="https://github.com/user-attachments/assets/b5077719-ddf8-438c-9ce4-ca6bb76d7217" />
2025-07-22 02:34:05 +00:00
CityFun 7994193a39 feat: Added Docker detection to the runtime environment (#9596) 2025-07-22 10:32:14 +08:00
蘭 3334bf594d fix: Fix some issues with the alert (#9594) 2025-07-22 02:11:59 +00:00
蘭 71193cc912 ref: Optimize IPv4 forwarding and preset chain existence check (#9592) 2025-07-21 11:04:42 +00:00
蘭 9fde8c447d fix: Improve alert notifications (#9591) 2025-07-21 11:02:42 +00:00
ssongliu 117faa952e fix: Fix container edit task log abnormalities (#9590) 2025-07-21 10:14:41 +00:00
CityFun 6427337728 feat: Adapt to Redis Cluster (#9588) 2025-07-21 09:56:42 +00:00
ssongliu 81f97f6f88 fix: Fix overview page load metrics localization issues (#9587) 2025-07-21 09:54:42 +00:00
ssongliu b3b472d711 fix: Optimize container creation page exit behavior (#9586)
Refs #9424
2025-07-21 09:52:43 +00:00
CityFun 1c22e1cf99 fix: Fix the issue where remote databases cannot be selected during application installation (#9589) 2025-07-21 17:52:35 +08:00
ssongliu 3110d91bf6 fix: Fix app upload/restore failure issues (#9583)
Refs #9572
2025-07-21 09:50:43 +00:00
ssongliu 624a11407a fix: Fix data synchronization during backup account modification (#9585) 2025-07-21 17:49:56 +08:00
ssongliu ead6d1268f feat: Optimize node addition notification messages (#9582) 2025-07-21 15:53:23 +08:00
CityFun 80c00c5a8c fix: fix some bugs with app-cluster (#9581) 2025-07-21 15:53:11 +08:00
ssongliu da0f6acd01 fix: Optimize snapshot synchronization options (#9579) 2025-07-21 02:36:41 +00:00
ssongliu 485aa99dea fix: Fix DNS configuration editing issue for containers (#9578)
Refs #9570
2025-07-21 02:34:41 +00:00
蘭 5f68d7ce90 feat: Dealing with compatibility issues with alert history data (#9571) 2025-07-18 22:43:19 +08:00
ssongliu 685fca6c8c fix: Add 'Deleted' Node State (#9569) 2025-07-18 22:43:07 +08:00
蘭 9c609da332 refactor: Improve alarm prompt information (#9564) 2025-07-18 07:43:22 +00:00
蘭 84608b7d42 feat: Alert notification supports email service (#9554) 2025-07-18 11:44:20 +08:00
CityFun 02cf2a80cb fix: Fixed issue with install Mailserver failed (#9562)
Refs https://github.com/1Panel-dev/1Panel/issues/9539
2025-07-18 03:43:23 +00:00
CityFun a74c6dd522 feat: Add installation support for Redis Cluster (#9561) 2025-07-18 03:41:23 +00:00
ssongliu 5d4ab23d95 style: Optimize node switching transition effects (#9560)
Refs #9556
2025-07-18 03:39:23 +00:00
ssongliu a8faba3206 style: Optimize drawer closing mechanism (#9559)
Refs #9371
2025-07-18 03:37:22 +00:00
ssongliu c603495b12 fix: Fix container log filtering anddownload issues (#9553)
Refs #9269 #9457
2025-07-17 10:13:21 +00:00
ssongliu adce6103bd fix: Fix log printing issues caused by container log tracking switches (#9552) 2025-07-17 17:51:23 +08:00
ssongliu 6b673017c5 fix: Fix the problem of abnormal request forwarding (#9548) 2025-07-17 07:43:20 +00:00
ssongliu e66bd43fc6 feat: Add customizable snapshot timeout support (#9537)
Refs #9502
2025-07-16 10:43:52 +00:00
CityFun ff5cdb9a02 feat: The container-compose module is adapted for Docker Compose. (#9536) 2025-07-16 10:41:52 +00:00
CityFun 1aec5e443d fix: Fix the issue where some applications incorrectly displayed a redirect button on error (#9535)
Refs https://github.com/1Panel-dev/1Panel/issues/9477
2025-07-16 10:17:53 +00:00
ssongliu 0b1f7e9322 fix: Change backup temporary file directory (#9533)
Refs #9428
2025-07-16 10:15:52 +00:00
CityFun 3ec2dec49b feat: Remove the required field prompt for HSTS in the website HTTPS … (#9532)
Refs https://github.com/1Panel-dev/1Panel/issues/9465
2025-07-16 09:59:52 +00:00
ssongliu 7cbebc8236 feat: Support backup for master (#9498) 2025-07-16 17:53:04 +08:00
CityFun 700076f278 feat: Application installation supports existing remote databases (#9531) 2025-07-16 17:47:27 +08:00
ssongliu 7bf1bf61a7 fix: Fix memory usage display issue on dashboard (#9530)
Refs #9497
2025-07-16 09:45:53 +00:00
CityFun ef2cb1b315 feat: Adapt to PostgreSQL cluster (#9529) 2025-07-16 17:44:20 +08:00
CityFun 75bfc3a114 feat: Scheduled tasks support backing up MySQL cluster databases (#9526) 2025-07-16 15:52:04 +08:00
CityFun 70f63090fe fix: Resolve the issue of failed slave installation (#9519) 2025-07-15 07:39:56 +00:00
CityFun ab2cf0d0c3 feat: add cluster status page (#9514) 2025-07-14 19:08:57 +08:00
CityFun ea1a34f57a feat: Optimize node management logic (#9510) 2025-07-14 18:59:26 +08:00
CityFun 765add6184 fix: Fix the issue where some applications fail to execute scripts (#9508) 2025-07-14 04:21:51 +00:00
CityFun e5263d6c05 fix: Modify the header of the application logs page (#9507)
Refs https://github.com/1Panel-dev/1Panel/issues/9501
2025-07-14 04:19:51 +00:00
CityFun c391177d34 feat: Add MySQL master-slave installation feature (#9505) 2025-07-13 22:36:21 +08:00
CityFun 7d5f0aa906 fix: Fix the issue of failing to save IPv6 self-signed certificate (#9495) 2025-07-11 17:35:39 +08:00
CityFun 2694a725d6 feat: Add quick installation for the sodium extension (#9481) 2025-07-10 06:27:19 +00:00
蘭 54ca40bbcd fix: Fix container log download ANSI control code garbled issue (#9479) 2025-07-10 11:21:13 +08:00
蘭 b20411f8f1 fix: Fix the issue of calculating size incorrectly during sorting (#9474) 2025-07-09 11:02:55 +00:00
ssongliu 355e656caa fix: Fix the issue where the demo configuration takes effect (#9468) 2025-07-09 02:10:49 +00:00
ssongliu 383bdd5f15 fix: Change container image building to asynchronous operation (#9462)
Refs #8463
2025-07-08 11:06:47 +00:00
CityFun 96208c786d feat: The runtime terminal supports specifying a user (#9461) 2025-07-08 10:34:14 +00:00
蘭 4c7b04cf27 feat: Perfect Türkiye language configuration (#9458) 2025-07-08 10:20:51 +00:00
蘭 a067a97e21 fix: Fix the issue of garbled characters when opening UTF-8 encoded files (#9454)
refs: #9411
2025-07-08 06:09:54 +00:00
CityFun 4a07603a7c Fix: change loadBalanceHelper translate (#9453) 2025-07-08 06:07:54 +00:00
ssongliu 7196c5b656 fix: Fix https image registry deletion failure (#9451) 2025-07-08 06:05:55 +00:00
CityFun b5de2df119 Fix: change loadBalance translate (#9448) 2025-07-08 02:47:54 +00:00
Sinan Eldem 2f72eb37ee Turkish Language Files for v.dev-v2 (#9390) 2025-07-08 10:45:19 +08:00
CityFun 0128cd3138 Fix: fix issue with element-plus tab style wrong (#9441) 2025-07-07 18:34:00 +08:00
ssongliu 157d3093f8 fix: Update license validation error messages (#9438) 2025-07-07 08:17:50 +00:00
ssongliu 4ca188973e fix: Add rule validation for FTP account creation (#9437) 2025-07-07 06:25:49 +00:00
wanghe-fit2cloud f387d89a45 docs: Optimize README 2025-07-07 12:09:52 +08:00
CityFun 54567db22f Fix: Resolved issue where domain binding failed in MCP due to OpenResty not using 80 port (#9435)
Refs https://github.com/1Panel-dev/1Panel/issues/9431
2025-07-07 04:07:50 +00:00
ssongliu 2bee68dd40 fix: Fix timezone modification reboot exception (#9432)
Refs #9422
2025-07-07 04:05:50 +00:00
CityFun e2b6ea5253 Fix: Resolved unInstall app failed due to run compose down timeout (#9436) 2025-07-07 04:03:49 +00:00
CityFun 9974c5fa20 feat: add DownloadCallBack for install php runtime (#9430) 2025-07-07 03:05:50 +00:00
ssongliu 997b706d8a fix: Fix dashboard refresh exception issue (#9429) 2025-07-07 03:03:49 +00:00
224 changed files with 12687 additions and 760 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
{
"ansible.python.interpreterPath": "/opt/homebrew/bin/python3"
}
}
+1 -1
View File
@@ -125,4 +125,4 @@ enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
https://www.contributor-covenant.org/translations.
+200
View File
@@ -0,0 +1,200 @@
package v2
import (
"errors"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/gin-gonic/gin"
)
func (b *BaseApi) PageAlert(c *gin.Context) {
var req dto.AlertSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, alerts, err := alertService.PageAlert(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Total: total,
Items: alerts,
})
}
func (b *BaseApi) GetAlerts(c *gin.Context) {
alerts, err := alertService.GetAlerts()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, alerts)
}
func (b *BaseApi) CreateAlert(c *gin.Context) {
var req dto.AlertCreate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := alertService.CreateAlert(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) DeleteAlert(c *gin.Context) {
var req dto.DeleteRequest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := alertService.DeleteAlert(req.ID)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) UpdateAlert(c *gin.Context) {
var req dto.AlertUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := alertService.UpdateAlert(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) GetAlert(c *gin.Context) {
id, err := helper.GetParamID(c)
if err != nil {
helper.BadRequest(c, errors.New("no such id in request param"))
return
}
alert, err := alertService.GetAlert(id)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, alert)
}
func (b *BaseApi) UpdateAlertStatus(c *gin.Context) {
var req dto.AlertUpdateStatus
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := alertService.UpdateStatus(req.ID, req.Status); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) GetDisks(c *gin.Context) {
alerts, err := alertService.GetDisks()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, alerts)
}
func (b *BaseApi) PageAlertLogs(c *gin.Context) {
var req dto.AlertLogSearch
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
total, alertLogs, err := alertService.PageAlertLogs(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Total: total,
Items: alertLogs,
})
}
func (b *BaseApi) CleanAlertLogs(c *gin.Context) {
if err := alertService.CleanAlertLogs(); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) GetClams(c *gin.Context) {
clams, err := alertService.GetClams()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, clams)
}
func (b *BaseApi) GetCronJobs(c *gin.Context) {
var req dto.CronJobReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
cronJobs, err := alertService.GetCronJobs(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, cronJobs)
}
func (b *BaseApi) GetAlertConfig(c *gin.Context) {
config, err := alertService.GetAlertConfig()
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, config)
}
func (b *BaseApi) UpdateAlertConfig(c *gin.Context) {
var req dto.AlertConfigUpdate
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := alertService.UpdateAlertConfig(req); err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) DeleteAlertConfig(c *gin.Context) {
var req dto.DeleteRequest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
err := alertService.DeleteAlertConfig(req.ID)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.Success(c)
}
func (b *BaseApi) TestAlertConfig(c *gin.Context) {
var req dto.AlertConfigTest
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
flag, err := alertService.TestAlertConfig(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, flag)
}
+22
View File
@@ -327,3 +327,25 @@ func (b *BaseApi) UpdateAppConfig(c *gin.Context) {
}
helper.Success(c)
}
// @Tags App
// @Summary Get app install info
// @Accept json
// @Param appInstallId path integer true "App install id"
// @Success 200 {object} dto.AppInstallInfo
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /apps/installed/info/:appInstallId [get]
func (b *BaseApi) GetAppInstallInfo(c *gin.Context) {
appInstallId, err := helper.GetIntParamByKey(c, "appInstallId")
if err != nil {
helper.BadRequest(c, err)
return
}
info, err := appInstallService.GetAppInstallInfo(appInstallId)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, info)
}
+6 -6
View File
@@ -371,12 +371,12 @@ func (b *BaseApi) Backup(c *gin.Context) {
helper.InternalServer(c, err)
return
}
case "mysql", "mariadb":
case "mysql", "mariadb", constant.AppMysqlCluster:
if err := backupService.MysqlBackup(req); err != nil {
helper.InternalServer(c, err)
return
}
case constant.AppPostgresql:
case constant.AppPostgresql, constant.AppPostgresqlCluster:
if err := backupService.PostgresqlBackup(req); err != nil {
helper.InternalServer(c, err)
return
@@ -386,7 +386,7 @@ func (b *BaseApi) Backup(c *gin.Context) {
helper.InternalServer(c, err)
return
}
case "redis":
case "redis", constant.AppRedisCluster:
if err := backupService.RedisBackup(req); err != nil {
helper.InternalServer(c, err)
return
@@ -421,12 +421,12 @@ func (b *BaseApi) Recover(c *gin.Context) {
}
req.File = downloadPath
switch req.Type {
case "mysql", "mariadb":
case "mysql", "mariadb", constant.AppMysqlCluster:
if err := backupService.MysqlRecover(req); err != nil {
helper.InternalServer(c, err)
return
}
case constant.AppPostgresql:
case constant.AppPostgresql, constant.AppPostgresqlCluster:
if err := backupService.PostgresqlRecover(req); err != nil {
helper.InternalServer(c, err)
return
@@ -436,7 +436,7 @@ func (b *BaseApi) Recover(c *gin.Context) {
helper.InternalServer(c, err)
return
}
case "redis":
case "redis", constant.AppRedisCluster:
if err := backupService.RedisRecover(req); err != nil {
helper.InternalServer(c, err)
return
+2 -7
View File
@@ -50,12 +50,7 @@ func (b *BaseApi) LoadContainerUsers(c *gin.Context) {
return
}
list, err := containerService.LoadUsers(req)
if err != nil {
helper.InternalServer(c, err)
return
}
helper.SuccessWithData(c, list)
helper.SuccessWithData(c, containerService.LoadUsers(req))
}
// @Tags Container
@@ -266,7 +261,7 @@ func (b *BaseApi) ContainerCreate(c *gin.Context) {
return
}
if err := containerService.ContainerCreate(req); err != nil {
if err := containerService.ContainerCreate(req, true); err != nil {
helper.InternalServer(c, err)
return
}
+6 -6
View File
@@ -11,13 +11,13 @@ import (
// @Tags Database Redis
// @Summary Load redis status info
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Param request body dto.LoadRedisStatus true "request"
// @Success 200 {object} dto.RedisStatus
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/redis/status [post]
func (b *BaseApi) LoadRedisStatus(c *gin.Context) {
var req dto.OperationWithName
var req dto.LoadRedisStatus
if err := helper.CheckBind(&req, c); err != nil {
return
}
@@ -33,13 +33,13 @@ func (b *BaseApi) LoadRedisStatus(c *gin.Context) {
// @Tags Database Redis
// @Summary Load redis conf
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Param request body dto.LoadRedisStatus true "request"
// @Success 200 {object} dto.RedisConf
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/redis/conf [post]
func (b *BaseApi) LoadRedisConf(c *gin.Context) {
var req dto.OperationWithName
var req dto.LoadRedisStatus
if err := helper.CheckBind(&req, c); err != nil {
return
}
@@ -55,13 +55,13 @@ func (b *BaseApi) LoadRedisConf(c *gin.Context) {
// @Tags Database Redis
// @Summary Load redis persistence conf
// @Accept json
// @Param request body dto.OperationWithName true "request"
// @Param request body dto.LoadRedisStatus true "request"
// @Success 200 {object} dto.RedisPersistence
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /databases/redis/persistence/conf [post]
func (b *BaseApi) LoadPersistenceConf(c *gin.Context) {
var req dto.OperationWithName
var req dto.LoadRedisStatus
if err := helper.CheckBind(&req, c); err != nil {
return
}
+1
View File
@@ -70,4 +70,5 @@ var (
websiteCAService = service.NewIWebsiteCAService()
taskService = service.NewITaskService()
groupService = service.NewIGroupService()
alertService = service.NewIAlertService()
)
+4 -4
View File
@@ -89,8 +89,8 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
var containerID string
var initCmd []string
switch source {
case "redis":
containerID, initCmd, err = loadRedisInitCmd(c)
case "redis", "redis-cluster":
containerID, initCmd, err = loadRedisInitCmd(c, source)
case "ollama":
containerID, initCmd, err = loadOllamaInitCmd(c)
case "container":
@@ -127,7 +127,7 @@ func (b *BaseApi) ContainerWsSSH(c *gin.Context) {
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
}
func loadRedisInitCmd(c *gin.Context) (string, []string, error) {
func loadRedisInitCmd(c *gin.Context, redisType string) (string, []string, error) {
name := c.Query("name")
from := c.Query("from")
commands := []string{"exec", "-it"}
@@ -136,7 +136,7 @@ func loadRedisInitCmd(c *gin.Context) (string, []string, error) {
return "", nil, fmt.Errorf("no such database in db, err: %v", err)
}
if from == "local" {
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: "redis"})
redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: redisType})
if err != nil {
return "", nil, fmt.Errorf("no such app in db, err: %v", err)
}
+311 -4
View File
@@ -1,10 +1,17 @@
package dto
import (
"encoding/json"
"github.com/1Panel-dev/1Panel/agent/app/model"
"time"
)
type CreateOrUpdateAlert struct {
AlertTitle string `json:"alertTitle"`
AlertType string `json:"alertType"`
AlertCount uint `json:"alertCount"`
EntryID uint `json:"entryID"`
AlertTitle string `json:"alertTitle"`
AlertType string `json:"alertType"`
AlertMethod string `json:"alertMethod"`
AlertCount uint `json:"alertCount"`
EntryID uint `json:"entryID"`
}
type AlertBase struct {
@@ -18,3 +25,303 @@ type PushAlert struct {
EntryID uint `json:"entryID"`
Param string `json:"param"`
}
type AlertSearch struct {
PageInfo
OrderBy string `json:"orderBy" validate:"required,oneof=created_at"`
Order string `json:"order" validate:"required,oneof=null ascending descending"`
Type string `json:"type"`
Status string `json:"status"`
Method string `json:"method"`
}
type AlertDTO struct {
ID uint `json:"id"`
Type string `json:"type"`
Cycle uint `json:"cycle"`
Count uint `json:"count"`
Method string `json:"method"`
Title string `json:"title"`
Project string `json:"project"`
Status string `json:"status"`
SendCount uint `json:"sendCount"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type AlertCreate struct {
Type string `json:"type" validate:"required"`
Cycle uint `json:"cycle"`
Count uint `json:"count"`
Method string `json:"method" validate:"required"`
Title string `json:"title"`
Project string `json:"project"`
Status string `json:"status"`
SendCount uint `json:"sendCount"`
}
type AlertUpdate struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type"`
Cycle uint `json:"cycle"`
Count uint `json:"count"`
Method string `json:"method"`
Title string `json:"title"`
Project string `json:"project"`
Status string `json:"status"`
SendCount uint `json:"sendCount"`
}
type DeleteRequest struct {
ID uint `json:"id" validate:"required"`
}
type AlertUpdateStatus struct {
ID uint `json:"id" validate:"required"`
Status string `json:"status" validate:"required"`
}
type DiskDTO struct {
Path string `json:"path"`
Type string `json:"type"`
Device string `json:"device"`
Total uint64 `json:"total"`
Free uint64 `json:"free"`
Used uint64 `json:"used"`
UsedPercent float64 `json:"usedPercent"`
InodesTotal uint64 `json:"inodesTotal"`
InodesUsed uint64 `json:"inodesUsed"`
InodesFree uint64 `json:"inodesFree"`
InodesUsedPercent float64 `json:"inodesUsedPercent"`
}
type AlertLogSearch struct {
PageInfo
Count uint `json:"count"`
Status string `json:"status"`
}
type AlertLogDTO struct {
ID uint `json:"id"`
Type string `json:"type"`
Count uint `json:"count"`
AlertId uint `json:"alertId"`
AlertDetail AlertDetail `json:"alertDetail"`
AlertRule AlertRule `json:"alertRule"`
Status string `json:"status"`
Method string `json:"method"`
Message string `json:"message"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type AlertLogCreate struct {
Type string `json:"type" validate:"required"`
Count uint `json:"count" validate:"required"`
AlertId uint `json:"alertId" validate:"required"`
AlertDetail string `json:"alertDetail" validate:"required"`
AlertRule string `json:"alertRule" validate:"required"`
Status string `json:"status" validate:"required"`
Method string `json:"method" validate:"required"`
Message string `json:"message"`
RecordId uint `json:"recordId"`
LicenseId string `json:"licenseId" validate:"required"`
}
type AlertLog struct {
ID uint `json:"id" validate:"required"`
}
type AlertDetail struct {
LicenseId string `json:"licenseId"`
Type string `json:"type"`
SubType string `json:"subType"`
Title string `json:"title"`
Method string `json:"method"`
LicenseCode string `json:"licenseCode"`
DeviceId string `json:"deviceId"`
Project string `json:"project"`
Params []Param `json:"params"`
Phone string `json:"phone"`
}
type AlertRule struct {
ID uint `json:"id"`
Type string `json:"type"`
Cycle uint `json:"cycle"`
Count uint `json:"count"`
Method string `json:"method"`
Title string `json:"title"`
Project string `json:"project"`
Status string `json:"status"`
SendCount uint `json:"sendCount"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type Param struct {
Index string `json:"index"`
Key string `json:"key"`
Value string `json:"value"`
}
type ClamDTO struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Name string `json:"name"`
Status string `json:"status"`
Path string `json:"path"`
}
type CronJobDTO struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Name string `json:"name"`
Status string `json:"status"`
Type string `json:"type"`
}
type CronJobReq struct {
Name string `json:"name"`
Status string `json:"status"`
Type string `json:"type"`
}
type UpgradeInfo struct {
TestVersion string `json:"testVersion"`
NewVersion string `json:"newVersion"`
LatestVersion string `json:"latestVersion"`
ReleaseNote string `json:"releaseNote"`
}
type AlertDiskInfo struct {
Type string
Mount string
Device string
}
type SyncResult struct {
ID uint `json:"id"`
LicenseID string `json:"licenseId"`
SendStatus string `json:"sendStatus"`
CreateTime string `json:"createTime"`
UpdateTime string `json:"updateTime"`
Remarks string `json:"remarks"`
MsgCount int `json:"msgCount"`
MsgCountMax int `json:"msgCountMax"`
}
type QueryRequest struct {
QueryIds []uint `json:"queryIds"`
LicenseId string `json:"licenseId"`
}
type AlertResponse struct {
Result bool `json:"result"`
Data json.RawMessage `json:"data"`
Message string `json:"message"`
}
type PushResult struct {
RecordId uint `json:"recordId"`
LicenseId string `json:"licenseId"`
}
type UpdateOfflineAlertLog struct {
ID uint `json:"id"`
Status string `json:"status"`
Message string `json:"message"`
RecordId uint `json:"recordId"`
LicenseId string `json:"licenseId"`
}
type SyncOfflineAlertLogDTO struct {
QueryRequest string `json:"queryRequest"`
Ids []uint `json:"ids"`
AlertLogs []model.AlertLog `json:"alertLogs"`
LicenseId string `json:"licenseId"`
}
type OfflineAlertResponse struct {
ID uint `json:"id"`
Type string `json:"type"`
RemoteErr error `json:"remoteErr"`
ResponseStruct AlertResponse `json:"responseStruct"`
AlertLogs []model.AlertLog `json:"alertLogs"`
Ids []uint `json:"ids"`
LicenseId string `json:"licenseId"`
}
type OfflineAlertLogDTO struct {
ID uint `json:"id"`
Type string `json:"type"`
Count uint `json:"count"`
AlertId uint `json:"alertId"`
AlertDetail string `json:"alertDetail"`
AlertRule string `json:"alertRule"`
Status string `json:"status"`
Method string `json:"method"`
Message string `json:"message"`
RecordId uint `json:"recordId"`
LicenseId string `json:"licenseId"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type OfflineQueryRequest struct {
Ids []uint `json:"ids"`
QueryIds []uint `json:"queryIds"`
LicenseId string `json:"licenseId"`
}
type AlertConfigUpdate struct {
ID uint `json:"id"`
Type string `json:"type"`
Title string `json:"title"`
Status string `json:"status"`
Config string `json:"config"`
}
type AlertConfigTest struct {
Host string `json:"host"`
Port int `json:"port"`
Sender string `json:"sender"`
Password string `json:"password"`
DisplayName string `json:"displayName"`
Encryption string `json:"encryption"` // "ssl" / "tls" / "none"
Recipient string `json:"recipient"`
}
type AlertSendTimeRange struct {
NoticeAlert struct {
SendTimeRange string `json:"sendTimeRange"`
Type []string `json:"type"`
} `json:"noticeAlert"`
ResourceAlert struct {
SendTimeRange string `json:"sendTimeRange"`
Type []string `json:"type"`
} `json:"resourceAlert"`
}
type AlertCommonConfig struct {
IsOffline string `json:"isOffline"`
AlertSendTimeRange AlertSendTimeRange `json:"alertSendTimeRange"`
}
type AlertSmsConfig struct {
Phone string `json:"phone"`
AlertDailyNum uint `json:"alertDailyNum"`
}
type AlertEmailConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Sender string `json:"sender"`
Password string `json:"password"`
DisplayName string `json:"displayName"`
Encryption string `json:"encryption"` // "ssl" / "tls" / "none"
Recipient string `json:"recipient"`
}
+6 -4
View File
@@ -7,10 +7,11 @@ import (
)
type AppDatabase struct {
ServiceName string `json:"PANEL_DB_HOST"`
DbName string `json:"PANEL_DB_NAME"`
DbUser string `json:"PANEL_DB_USER"`
Password string `json:"PANEL_DB_USER_PASSWORD"`
ServiceName string `json:"PANEL_DB_HOST"`
DbName string `json:"PANEL_DB_NAME"`
DbUser string `json:"PANEL_DB_USER"`
Password string `json:"PANEL_DB_USER_PASSWORD"`
DatabaseName string `json:"DATABASE_NAME"`
}
type AuthParam struct {
@@ -126,6 +127,7 @@ type Locale struct {
ZhHant string `json:"zh-hant" yaml:"zh-hant"`
Zh string `json:"zh"`
Ko string `json:"ko"`
Tr string `json:"tr"`
}
type AppForm struct {
+2 -2
View File
@@ -54,7 +54,7 @@ type BackupOption struct {
}
type CommonBackup struct {
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql"`
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"`
@@ -65,7 +65,7 @@ type CommonBackup struct {
}
type CommonRecover struct {
DownloadAccountID uint `json:"downloadAccountID" validate:"required"`
Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql"`
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"`
File string `json:"file"`
+3
View File
@@ -34,6 +34,7 @@ type ClamInfo struct {
Spec string `json:"spec"`
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertMethod string `json:"alertMethod"`
}
type ClamLogSearch struct {
@@ -74,6 +75,7 @@ type ClamCreate struct {
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
AlertMethod string `json:"alertMethod"`
}
type ClamUpdate struct {
@@ -87,6 +89,7 @@ type ClamUpdate struct {
Description string `json:"description"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
AlertMethod string `json:"alertMethod"`
}
type ClamUpdateStatus struct {
+7
View File
@@ -39,6 +39,11 @@ type ContainerInfo struct {
Websites []string `json:"websites"`
}
type ContainerOptions struct {
Name string `json:"name"`
State string `json:"state"`
}
type ContainerStatus struct {
All uint `json:"all"`
Created uint `json:"created"`
@@ -164,6 +169,8 @@ type ContainerCommit struct {
Comment string `json:"comment"`
Author string `json:"author"`
Pause bool `json:"pause"`
TaskID string `json:"taskID"`
}
type ContainerPrune struct {
+3 -2
View File
@@ -48,8 +48,9 @@ type CronjobOperate struct {
IgnoreErr bool `json:"ignoreErr"`
Secret string `json:"secret"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
AlertCount uint `json:"alertCount"`
AlertTitle string `json:"alertTitle"`
AlertMethod string `json:"alertMethod"`
}
type SnapshotRule struct {
+4 -1
View File
@@ -74,8 +74,11 @@ type DashboardCurrent struct {
CPUTotal int `json:"cpuTotal"`
MemoryTotal uint64 `json:"memoryTotal"`
MemoryAvailable uint64 `json:"memoryAvailable"`
MemoryUsed uint64 `json:"memoryUsed"`
MemoryFree uint64 `json:"memoryFree"`
MemoryShard uint64 `json:"memoryShard"`
MemoryCache uint64 `json:"memoryCache"`
MemoryAvailable uint64 `json:"memoryAvailable"`
MemoryUsedPercent float64 `json:"memoryUsedPercent"`
SwapMemoryTotal uint64 `json:"swapMemoryTotal"`
+13 -6
View File
@@ -4,14 +4,14 @@ import "time"
// common
type DBConfUpdateByFile struct {
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql redis"`
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql redis mysql-cluster postgresql-cluster"`
Database string `json:"database" validate:"required"`
File string `json:"file"`
}
type ChangeDBInfo struct {
ID uint `json:"id"`
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql"`
Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql mysql-cluster postgresql-cluster"`
Database string `json:"database" validate:"required"`
Value string `json:"value" validate:"required"`
}
@@ -74,19 +74,19 @@ type BindUser struct {
type MysqlLoadDB struct {
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"`
Database string `json:"database" validate:"required"`
}
type MysqlDBDeleteCheck struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"`
Database string `json:"database" validate:"required"`
}
type MysqlDBDelete struct {
ID uint `json:"id" validate:"required"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"`
Database string `json:"database" validate:"required"`
ForceDelete bool `json:"forceDelete"`
DeleteBackup bool `json:"deleteBackup"`
@@ -153,7 +153,7 @@ type MysqlVariables struct {
}
type MysqlVariablesUpdate struct {
Type string `json:"type" validate:"required,oneof=mysql mariadb"`
Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"`
Database string `json:"database" validate:"required"`
Variables []MysqlVariablesUpdateHelper `json:"variables"`
}
@@ -174,6 +174,7 @@ type RedisConfUpdate struct {
Timeout string `json:"timeout"`
Maxclients string `json:"maxclients"`
Maxmemory string `json:"maxmemory"`
DBType string `json:"dbType" validate:"required,oneof=redis redis-cluster"`
}
type RedisConfPersistenceUpdate struct {
Database string `json:"database" validate:"required"`
@@ -181,6 +182,7 @@ type RedisConfPersistenceUpdate struct {
Appendonly string `json:"appendonly"`
Appendfsync string `json:"appendfsync"`
Save string `json:"save"`
DBType string `json:"dbType" validate:"required,oneof=redis redis-cluster"`
}
type RedisConf struct {
@@ -319,3 +321,8 @@ type DatabaseDelete struct {
ForceDelete bool `json:"forceDelete"`
DeleteBackup bool `json:"deleteBackup"`
}
type LoadRedisStatus struct {
Name string `json:"name" validate:"required"`
Type string `json:"type" validate:"required"`
}
+14
View File
@@ -129,6 +129,19 @@ type AppInstallDTO struct {
Container string `json:"container"`
}
type AppInstallInfo struct {
ID uint `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Status string `json:"status"`
Message string `json:"message"`
HttpPort int `json:"HttpPort"`
Container string `json:"container"`
ComposePath string `json:"composePath"`
Env map[string]interface{} `json:"env"`
}
type DatabaseConn struct {
Status string `json:"status"`
Username string `json:"username"`
@@ -158,6 +171,7 @@ type AppParam struct {
ShowValue string `json:"showValue"`
Required bool `json:"required"`
Multiple bool `json:"multiple"`
Label dto.Locale `json:"label"`
}
type AppConfig struct {
+1
View File
@@ -18,6 +18,7 @@ type SnapshotCreate struct {
Description string `json:"description" validate:"max=256"`
Secret string `json:"secret"`
InterruptStep string `json:"interruptStep"`
Timeout uint `json:"timeout"`
AppData []DataTree `json:"appData"`
BackupData []DataTree `json:"backupData"`
+45
View File
@@ -0,0 +1,45 @@
package model
type Alert struct {
BaseModel
Title string `gorm:"type:varchar(256);not null" json:"title"`
Type string `gorm:"type:varchar(64);not null" json:"type"`
Cycle uint `gorm:"type:integer;not null" json:"cycle"`
Count uint `gorm:"type:integer;not null" json:"count"`
Project string `gorm:"type:varchar(64)" json:"project"`
Status string `gorm:"type:varchar(64);not null" json:"status"`
Method string `gorm:"type:varchar(64);not null" json:"method"`
SendCount uint `gorm:"type:integer" json:"sendCount"`
}
type AlertTask struct {
BaseModel
Type string `gorm:"type:varchar(64);not null" json:"type"`
Quota string `gorm:"type:varchar(64)" json:"quota"`
QuotaType string `gorm:"type:varchar(64)" json:"quotaType"`
Method string `gorm:"type:varchar(64);not null;default:'sms" json:"method"`
}
type AlertLog struct {
BaseModel
Type string `gorm:"type:varchar(64);not null" json:"type"`
Status string `gorm:"type:varchar(64);not null" json:"status"`
AlertId uint `gorm:"type:integer;not null" json:"alertId"`
AlertDetail string `gorm:"type:varchar(256);not null" json:"alertDetail"`
AlertRule string `gorm:"type:varchar(256);not null" json:"alertRule"`
Count uint `gorm:"type:integer;not null" json:"count"`
Message string `gorm:"type:varchar(256);" json:"message"`
RecordId uint `gorm:"type:integer;" json:"recordId"`
LicenseId string `gorm:"type:varchar(256);not null;" json:"licenseId" `
Method string `gorm:"type:varchar(64);not null;default:'sms" json:"method"`
}
type AlertConfig struct {
BaseModel
Type string `gorm:"type:varchar(64);not null" json:"type"`
Title string `gorm:"type:varchar(64);not null" json:"title"`
Status string `gorm:"type:varchar(64);not null" json:"status"`
Config string `gorm:"type:varchar(256);not null" json:"config"`
}
+1
View File
@@ -10,6 +10,7 @@ type Snapshot struct {
Status string `json:"status"`
Message string `json:"message"`
Version string `json:"version"`
Timeout uint `json:"timeout"`
TaskID string `json:"taskID"`
TaskRecoverID string `json:"taskRecoverID"`
+337
View File
@@ -0,0 +1,337 @@
package repo
import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"google.golang.org/genproto/googleapis/type/date"
"gorm.io/gorm"
"time"
)
type AlertRepo struct{}
type IAlertRepo interface {
WithByType(alertType string) DBOption
WithByStatusIn(status []string) DBOption
WithByProject(project string) DBOption
WithByCount(count uint) DBOption
WithByAlertId(alertId uint) DBOption
WithByCreateAt(date *date.Date) DBOption
WithByLicenseId(licenseId string) DBOption
WithByRecordId(recordId uint) DBOption
WithByMethod(method string) DBOption
Create(alert *model.Alert) error
Get(opts ...DBOption) (model.Alert, error)
Page(page, size int, opts ...DBOption) (int64, []model.Alert, error)
List(opts ...DBOption) ([]model.Alert, error)
Delete(opts ...DBOption) error
Save(alert *model.Alert) error
Update(maps map[string]interface{}, opts ...DBOption) error
GetLog(opts ...DBOption) (model.AlertLog, error)
CreateLog(alertLog *model.AlertLog) error
PageLog(limit, offset int, opts ...DBOption) (int64, []model.AlertLog, error)
ListLog(opts ...DBOption) ([]model.AlertLog, error)
UpdateLog(id uint, maps map[string]interface{}) error
BatchUpdateLogBy(maps map[string]interface{}, opts ...DBOption) error
DeleteLog(opts ...DBOption) error
CleanAlertLogs() error
CreateAlertTask(alertTaskBase *model.AlertTask) error
DeleteAlertTask(opts ...DBOption) error
GetAlertTask(opts ...DBOption) (model.AlertTask, error)
LoadTaskCount(alertType string, project string, method string) (uint, uint, error)
GetTaskLog(alertType string, alertId uint) (time.Time, error)
GetLicensePushCount(method string) (uint, error)
GetConfig(opts ...DBOption) (model.AlertConfig, error)
AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error)
UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error
CreateAlertConfig(config *model.AlertConfig) error
DeleteAlertConfig(opts ...DBOption) error
SyncAll(data []model.AlertConfig) error
}
func NewIAlertRepo() IAlertRepo {
return &AlertRepo{}
}
func (a *AlertRepo) WithByType(alertType string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("`type` = ?", alertType)
}
}
func (a *AlertRepo) WithByStatusIn(status []string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("status in (?)", status)
}
}
func (a *AlertRepo) WithByCount(count uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("count = ?", count)
}
}
func (a *AlertRepo) WithByProject(project string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("project = ?", project)
}
}
func (a *AlertRepo) WithByAlertId(alertId uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("alert_id = ?", alertId)
}
}
func (a *AlertRepo) WithByLicenseId(licenseId string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("license_id = ?", licenseId)
}
}
func (a *AlertRepo) WithByRecordId(recordId uint) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("record_id = ?", recordId)
}
}
func (a *AlertRepo) WithByMethod(method string) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("method = ?", method)
}
}
func (a *AlertRepo) WithByCreateAt(createAt *date.Date) DBOption {
return func(g *gorm.DB) *gorm.DB {
return g.Where("DATE(created_at) = DATE(?)", createAt)
}
}
func (a *AlertRepo) Create(alert *model.Alert) error {
return global.AlertDB.Model(&model.Alert{}).Create(alert).Error
}
func (a *AlertRepo) Save(alert *model.Alert) error {
return global.AlertDB.Save(alert).Error
}
func (a *AlertRepo) Get(opts ...DBOption) (model.Alert, error) {
var alert model.Alert
db, _ := getAlertDB(opts...)
err := db.First(&alert).Error
return alert, err
}
func (a *AlertRepo) Page(page, size int, opts ...DBOption) (int64, []model.Alert, error) {
var alerts []model.Alert
alertDb, _ := getAlertDB(opts...)
db := alertDb.Model(&model.Alert{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&alerts).Error
return count, alerts, err
}
func (a *AlertRepo) List(opts ...DBOption) ([]model.Alert, error) {
var alert []model.Alert
db, _ := getAlertDB(opts...)
err := db.Find(&alert).Error
return alert, err
}
func (a *AlertRepo) Update(maps map[string]interface{}, opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Model(&model.Alert{}).Updates(maps).Error
}
func (a *AlertRepo) Delete(opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Delete(&model.Alert{}).Error
}
func (a *AlertRepo) GetLog(opts ...DBOption) (model.AlertLog, error) {
var alertLog model.AlertLog
db, _ := getAlertDB(opts...)
err := db.First(&alertLog).Error
return alertLog, err
}
func (a *AlertRepo) CreateLog(log *model.AlertLog) error {
return global.AlertDB.Model(&model.AlertLog{}).Create(&log).Error
}
func (a *AlertRepo) UpdateLog(id uint, maps map[string]interface{}) error {
return global.AlertDB.Model(&model.AlertLog{}).Where("id = ?", id).Updates(maps).Error
}
func (a *AlertRepo) BatchUpdateLogBy(maps map[string]interface{}, opts ...DBOption) error {
db, _ := getAlertDB(opts...)
if len(opts) == 0 {
db = db.Where("1=1")
}
return db.Model(&model.AlertLog{}).Updates(&maps).Error
}
func (a *AlertRepo) PageLog(page, size int, opts ...DBOption) (int64, []model.AlertLog, error) {
var alerts []model.AlertLog
db := global.AlertDB.Model(&model.AlertLog{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Order("created_at desc").Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&alerts).Error
return count, alerts, err
}
func (a *AlertRepo) ListLog(opts ...DBOption) ([]model.AlertLog, error) {
var alertLog []model.AlertLog
db, _ := getAlertDB(opts...)
err := db.Find(&alertLog).Error
return alertLog, err
}
func (a *AlertRepo) DeleteLog(opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Delete(&model.AlertLog{}).Error
}
func (a *AlertRepo) CleanAlertLogs() error {
return global.AlertDB.Where("1 = 1").Delete(&model.AlertLog{}).Error
}
func (a *AlertRepo) CreateAlertTask(alertTaskBase *model.AlertTask) error {
return global.AlertDB.Model(&model.AlertTask{}).Create(&alertTaskBase).Error
}
func (a *AlertRepo) DeleteAlertTask(opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Delete(&model.AlertTask{}).Error
}
func (a *AlertRepo) GetAlertTask(opts ...DBOption) (model.AlertTask, error) {
var data model.AlertTask
db, _ := getAlertDB(opts...)
err := db.First(&data).Error
return data, err
}
func (a *AlertRepo) LoadTaskCount(alertType string, project string, method string) (uint, uint, error) {
var (
todayCount int64
totalCount int64
)
_ = global.AlertDB.Model(&model.AlertTask{}).Where("type = ? AND quota_type = ? AND method = ?", alertType, project, method).Count(&totalCount).Error
now := time.Now()
todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
tomorrowMidnight := todayMidnight.Add(24 * time.Hour)
err := global.AlertDB.Model(&model.AlertTask{}).Where("type = ? AND quota_type = ? AND method = ? AND created_at > ? AND created_at < ?", alertType, project, method, todayMidnight, tomorrowMidnight).Count(&todayCount).Error
return uint(todayCount), uint(totalCount), err
}
func (a *AlertRepo) GetTaskLog(alertType string, alertId uint) (time.Time, error) {
var newDate time.Time
status := []string{constant.AlertSuccess, constant.AlertPushSuccess, constant.AlertSyncError, constant.AlertPushing}
now := time.Now()
todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
tomorrowMidnight := todayMidnight.Add(24 * time.Hour)
err := global.AlertDB.Model(&model.AlertLog{}).
Where("type = ? AND alert_id = ? AND status in ? AND created_at > ? AND created_at < ?", alertType, alertId, status, todayMidnight, tomorrowMidnight).
Order("created_at DESC").
Limit(1).
Pluck("created_at", &newDate).Error
if err != nil {
return time.Time{}, err
}
if newDate.IsZero() {
return time.Time{}, nil
}
return newDate, nil
}
func getAlertDB(opts ...DBOption) (*gorm.DB, error) {
var db *gorm.DB
db = global.AlertDB
for _, opt := range opts {
db = opt(db)
}
return db, nil
}
func (a *AlertRepo) GetLicensePushCount(method string) (uint, error) {
var (
todayCount int64
)
now := time.Now()
todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
tomorrowMidnight := todayMidnight.Add(24 * time.Hour)
err := global.AlertDB.Model(&model.AlertTask{}).Where("created_at > ? AND created_at < ? AND method = ?", todayMidnight, tomorrowMidnight, method).Count(&todayCount).Error
return uint(todayCount), err
}
func (a *AlertRepo) AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error) {
var config []model.AlertConfig
db, _ := getAlertDB(opts...)
err := db.Find(&config).Error
return config, err
}
func (a *AlertRepo) UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Model(&model.AlertConfig{}).Updates(maps).Error
}
func (a *AlertRepo) CreateAlertConfig(config *model.AlertConfig) error {
return global.AlertDB.Model(&model.AlertConfig{}).Create(config).Error
}
func (a *AlertRepo) DeleteAlertConfig(opts ...DBOption) error {
db, _ := getAlertDB(opts...)
return db.Delete(&model.AlertConfig{}).Error
}
func (a *AlertRepo) GetConfig(opts ...DBOption) (model.AlertConfig, error) {
var alertConfig model.AlertConfig
db, _ := getAlertDB(opts...)
err := db.First(&alertConfig).Error
return alertConfig, err
}
func (a *AlertRepo) SyncAll(data []model.AlertConfig) error {
tx := global.AlertDB.Begin()
var oldConfigs []model.AlertConfig
_ = tx.Find(&oldConfigs).Error
oldConfigMap := make(map[string]uint)
for _, item := range oldConfigs {
oldConfigMap[item.Type] = item.ID
}
for _, item := range data {
if val, ok := oldConfigMap[item.Type]; ok {
item.ID = val
delete(oldConfigMap, item.Type)
} else {
item.ID = 0
}
if err := tx.Model(model.AlertConfig{}).Where("id = ?", item.ID).Save(&item).Error; err != nil {
tx.Rollback()
return err
}
}
for _, val := range oldConfigMap {
if err := tx.Where("id = ?", val).Delete(&model.AlertConfig{}).Error; err != nil {
tx.Rollback()
return err
}
}
tx.Commit()
return nil
}
+3 -3
View File
@@ -206,17 +206,17 @@ func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error
return nil, err
}
switch app.Key {
case "mysql", "mariadb":
case "mysql", "mariadb", constant.AppMysqlCluster:
password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string)
if ok {
info.Password = password
}
case "redis":
case "redis", constant.AppRedisCluster:
password, ok := envMap["PANEL_REDIS_ROOT_PASSWORD"].(string)
if ok {
info.Password = password
}
case "mongodb", constant.AppPostgresql:
case "mongodb", constant.AppPostgresql, constant.AppPostgresqlCluster:
user, ok := envMap["PANEL_DB_ROOT_USER"].(string)
if ok {
info.UserName = user
+507
View File
@@ -0,0 +1,507 @@
package service
import (
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"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/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/copier"
"github.com/1Panel-dev/1Panel/agent/utils/email"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"github.com/shirou/gopsutil/v3/disk"
"sort"
"strings"
"sync"
"time"
)
type AlertService struct{}
type IAlertService interface {
PageAlert(req dto.AlertSearch) (int64, []dto.AlertDTO, error)
GetAlerts() ([]dto.AlertDTO, error)
CreateAlert(create dto.AlertCreate) error
UpdateAlert(req dto.AlertUpdate) error
DeleteAlert(id uint) error
GetAlert(id uint) (dto.AlertDTO, error)
UpdateStatus(id uint, status string) error
ExternalUpdateAlert(req dto.AlertCreate) error
GetDisks() ([]dto.DiskDTO, error)
PageAlertLogs(req dto.AlertLogSearch) (int64, []dto.AlertLogDTO, error)
CleanAlertLogs() error
GetClams() ([]dto.ClamDTO, error)
GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error)
GetAlertConfig() ([]model.AlertConfig, error)
UpdateAlertConfig(req dto.AlertConfigUpdate) error
DeleteAlertConfig(id uint) error
TestAlertConfig(req dto.AlertConfigTest) (bool, error)
}
func NewIAlertService() IAlertService {
return &AlertService{}
}
func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO, error) {
var (
opts []repo.DBOption
result []dto.AlertDTO
)
if search.Status != "" {
opts = append(opts, repo.WithByStatus(search.Status))
}
if search.Type != "" {
opts = append(opts, alertRepo.WithByType(search.Type))
}
opts = append(opts, repo.WithOrderBy("created_at desc"))
total, alerts, err := alertRepo.Page(search.Page, search.PageSize, opts...)
if err != nil {
return 0, nil, err
}
for _, item := range alerts {
result = append(result, dto.AlertDTO{
ID: item.ID,
Type: item.Type,
Cycle: item.Cycle,
Count: item.Count,
Method: item.Method,
Title: item.Title,
Project: item.Project,
Status: item.Status,
SendCount: item.SendCount,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
})
}
return total, result, err
}
func (a AlertService) GetAlerts() ([]dto.AlertDTO, error) {
var (
opts []repo.DBOption
result []dto.AlertDTO
)
opts = append(opts, repo.WithByStatus(constant.AlertEnable))
alerts, err := alertRepo.List(opts...)
if err != nil {
return nil, err
}
for _, item := range alerts {
result = append(result, dto.AlertDTO{
ID: item.ID,
Type: item.Type,
Cycle: item.Cycle,
Count: item.Count,
Method: item.Method,
Title: item.Title,
Project: item.Project,
Status: item.Status,
SendCount: item.SendCount,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
})
}
return result, err
}
func (a AlertService) CreateAlert(create dto.AlertCreate) error {
var alertID uint
var alertInfo model.Alert
if create.Project != "" {
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(create.Type), alertRepo.WithByProject(create.Project))
alertID = alertInfo.ID
} else {
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(create.Type))
alertID = alertInfo.ID
}
if alertID != 0 {
var upAlert dto.AlertUpdate
if err := copier.Copy(&upAlert, &create); err != nil {
return buserr.WithErr("ErrStructTransform", err)
}
upAlert.ID = alertID
err := a.UpdateAlert(upAlert)
if err != nil {
return err
}
} else {
alertInfo.Status = constant.AlertEnable
if err := copier.Copy(&alertInfo, &create); err != nil {
return buserr.WithErr("ErrStructTransform", err)
}
if err := alertRepo.Create(&alertInfo); err != nil {
return err
}
NewIAlertTaskHelper().InitTask(alertInfo.Type)
}
return nil
}
func (a AlertService) UpdateAlert(req dto.AlertUpdate) error {
upMap := make(map[string]interface{})
upMap["id"] = req.ID
upMap["type"] = req.Type
upMap["cycle"] = req.Cycle
upMap["count"] = req.Count
upMap["method"] = req.Method
upMap["title"] = req.Title
upMap["project"] = req.Project
upMap["status"] = req.Status
upMap["send_count"] = req.SendCount
if err := alertRepo.Update(upMap, repo.WithByID(req.ID)); err != nil {
return err
}
NewIAlertTaskHelper().InitTask(req.Type)
return nil
}
func (a AlertService) DeleteAlert(id uint) error {
return alertRepo.Delete(repo.WithByID(id))
}
func (a AlertService) GetAlert(id uint) (dto.AlertDTO, error) {
var res dto.AlertDTO
alertInfo, err := alertRepo.Get(repo.WithByID(id))
if err != nil {
return res, err
}
_ = copier.Copy(&res, &alertInfo)
return res, nil
}
func (a AlertService) UpdateStatus(id uint, status string) error {
alertInfo, _ := alertRepo.Get(repo.WithByID(id))
if alertInfo.ID == 0 {
return buserr.New("ErrRecordNotFound")
}
err := alertRepo.Update(map[string]interface{}{"status": status}, repo.WithByID(alertInfo.ID))
if err != nil {
return err
}
alerts, err := a.GetAlerts()
if err != nil {
return err
}
if len(alerts) > 0 {
NewIAlertTaskHelper().InitTask(alertInfo.Type)
} else {
NewIAlertTaskHelper().StopTask()
}
return nil
}
func (a AlertService) GetDisks() ([]dto.DiskDTO, error) {
var disks []dto.DiskDTO
excludes := map[string]struct{}{
"/mnt/cdrom": {}, "/boot": {}, "/boot/efi": {}, "/dev": {}, "/dev/shm": {},
"/run/lock": {}, "/run": {}, "/run/shm": {}, "/run/user": {},
}
stdout, err := executeDiskCommand()
if err != nil {
return disks, nil
}
lines := strings.Split(stdout, "\n")
var mounts []dto.AlertDiskInfo
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 7 {
continue
}
mountPoint := strings.Join(fields[6:], " ")
if shouldExclude(fields, mountPoint, excludes) {
continue
}
mounts = append(mounts, dto.AlertDiskInfo{Type: fields[1], Device: fields[0], Mount: mountPoint})
}
var (
wg sync.WaitGroup
mu sync.Mutex
)
wg.Add(len(mounts))
for i := 0; i < len(mounts); i++ {
go func(timeoutCh <-chan time.Time, mount dto.AlertDiskInfo) {
defer wg.Done()
var itemData dto.DiskDTO
itemData.Path = mount.Mount
itemData.Type = mount.Type
itemData.Device = mount.Device
select {
case <-timeoutCh:
mu.Lock()
disks = append(disks, itemData)
mu.Unlock()
global.LOG.Errorf("load disk info from %s failed, err: timeout", mount.Mount)
default:
state, err := disk.Usage(mount.Mount)
if err != nil {
mu.Lock()
disks = append(disks, itemData)
mu.Unlock()
global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, err)
return
}
itemData.Total = state.Total
itemData.Free = state.Free
itemData.Used = state.Used
itemData.UsedPercent = state.UsedPercent
itemData.InodesTotal = state.InodesTotal
itemData.InodesUsed = state.InodesUsed
itemData.InodesFree = state.InodesFree
itemData.InodesUsedPercent = state.InodesUsedPercent
mu.Lock()
disks = append(disks, itemData)
mu.Unlock()
}
}(time.After(5*time.Second), mounts[i])
}
wg.Wait()
sort.Slice(disks, func(i, j int) bool {
return disks[i].Path < disks[j].Path
})
return disks, nil
}
func executeDiskCommand() (string, error) {
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second))
stdout, err := cmdMgr.RunWithStdoutBashC("df -hT -P | grep '/' | grep -v tmpfs | grep -v 'snap/core' | grep -v udev")
if err != nil {
cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second))
stdout, err = cmdMgr2.RunWithStdoutBashC("df -lhT -P | grep '/' | grep -v tmpfs | grep -v 'snap/core' | grep -v udev")
}
return stdout, err
}
func shouldExclude(fields []string, mountPoint string, excludes map[string]struct{}) bool {
if strings.HasPrefix(mountPoint, "/snap") || len(strings.Split(mountPoint, "/")) > 10 {
return true
}
if strings.TrimSpace(fields[1]) == "tmpfs" {
return true
}
if strings.Contains(fields[2], "K") {
return true
}
if strings.Contains(mountPoint, "docker") {
return true
}
_, excluded := excludes[mountPoint]
return excluded
}
func (a AlertService) PageAlertLogs(search dto.AlertLogSearch) (int64, []dto.AlertLogDTO, error) {
var (
opts []repo.DBOption
result []dto.AlertLogDTO
)
if search.Status != "" {
opts = append(opts, repo.WithByStatus(search.Status))
}
if search.Count != 0 {
opts = append(opts, alertRepo.WithByCount(search.Count))
}
opts = append(opts, repo.WithOrderBy("created_at desc"))
total, alerts, err := alertRepo.PageLog(search.Page, search.PageSize, opts...)
if err != nil {
return 0, nil, err
}
for _, item := range alerts {
alertLogDTO, err := a.parseAlertLog(item)
if err != nil {
return 0, nil, err
}
result = append(result, alertLogDTO)
}
return total, result, err
}
func (a AlertService) parseAlertLog(item model.AlertLog) (dto.AlertLogDTO, error) {
var alertDetail dto.AlertDetail
var alertRule dto.AlertRule
if err := unmarshalAlertInfo(item.AlertDetail, &alertDetail); err != nil {
return dto.AlertLogDTO{}, err
}
if err := unmarshalAlertInfo(item.AlertRule, &alertRule); err != nil {
return dto.AlertLogDTO{}, err
}
return dto.AlertLogDTO{
ID: item.ID,
Count: item.Count,
Type: item.Type,
Status: item.Status,
Method: item.Method,
Message: item.Message,
AlertId: item.AlertId,
AlertDetail: alertDetail,
AlertRule: alertRule,
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
}, nil
}
func unmarshalAlertInfo(data string, v interface{}) error {
if err := json.Unmarshal([]byte(data), v); err != nil {
return fmt.Errorf("unmarshal alert info vars failed, err: %v", err)
}
return nil
}
func (a AlertService) CleanAlertLogs() error {
return alertRepo.CleanAlertLogs()
}
func (a AlertService) GetClams() ([]dto.ClamDTO, error) {
var clams []dto.ClamDTO
clamList, err := clamRepo.List()
for _, clam := range clamList {
var clamDTO dto.ClamDTO
clamDTO.ID = clam.ID
clamDTO.Name = clam.Name
clamDTO.Path = clam.Path
clamDTO.Status = clam.Status
clamDTO.UpdatedAt = clam.UpdatedAt
clamDTO.CreatedAt = clam.CreatedAt
clams = append(clams, clamDTO)
}
return clams, err
}
func (a AlertService) GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error) {
var cronJobs []dto.CronJobDTO
var (
opts []repo.DBOption
)
if req.Status != "" {
opts = append(opts, repo.WithByStatus(req.Status))
}
if req.Type != "" {
opts = append(opts, repo.WithByType(req.Type))
}
cronjobList, err := cronjobRepo.List(opts...)
for _, cronJob := range cronjobList {
var cronJobDTO dto.CronJobDTO
cronJobDTO.ID = cronJob.ID
cronJobDTO.Name = cronJob.Name
cronJobDTO.Status = cronJob.Status
cronJobDTO.Type = cronJob.Type
cronJobDTO.UpdatedAt = cronJob.UpdatedAt
cronJobDTO.CreatedAt = cronJob.CreatedAt
cronJobs = append(cronJobs, cronJobDTO)
}
return cronJobs, err
}
func (a AlertService) GetAlertConfig() ([]model.AlertConfig, error) {
var (
opts []repo.DBOption
configs []model.AlertConfig
)
opts = append(opts, repo.WithByStatus(constant.AlertEnable))
configs, err := alertRepo.AlertConfigList(opts...)
return configs, err
}
func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate) error {
if req.ID != 0 {
upMap := make(map[string]interface{})
upMap["id"] = req.ID
upMap["type"] = req.Type
upMap["title"] = req.Title
upMap["status"] = req.Status
upMap["config"] = req.Config
if err := alertRepo.UpdateAlertConfig(upMap, repo.WithByID(req.ID)); err != nil {
return err
}
} else {
var alertConfig model.AlertConfig
if err := copier.Copy(&alertConfig, &req); err != nil {
return buserr.WithErr("ErrStructTransform", err)
}
if err := alertRepo.CreateAlertConfig(&alertConfig); err != nil {
return err
}
}
return nil
}
func (a AlertService) DeleteAlertConfig(id uint) error {
return alertRepo.DeleteAlertConfig(repo.WithByID(id))
}
func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) {
cfg := email.SMTPConfig{
Host: req.Host,
Port: req.Port,
Username: req.Sender,
Password: req.Password,
From: fmt.Sprintf("%s <%s>", req.DisplayName, req.Sender),
Encryption: req.Encryption,
Recipient: req.Recipient,
}
msg := email.EmailMessage{
Subject: i18n.GetMsgByKey("TestAlertTitle"),
Body: i18n.GetMsgByKey("TestAlert"),
IsHTML: false,
}
transport := xpack.LoadRequestTransport()
if err := email.SendMail(cfg, msg, transport); err != nil {
return false, err
}
return true, nil
}
func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate) error {
upMap := make(map[string]interface{})
if updateAlert.SendCount == 0 {
upMap["status"] = constant.AlertDisable
} else {
upMap["status"] = constant.AlertEnable
upMap["send_count"] = updateAlert.SendCount
}
upMap["method"] = updateAlert.Method
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(updateAlert.Type), alertRepo.WithByProject(updateAlert.Project))
if alertInfo.ID > 0 {
if err := alertRepo.Update(upMap, alertRepo.WithByProject(updateAlert.Project), alertRepo.WithByType(updateAlert.Type)); err != nil {
return err
}
} else {
updateAlert.Status = constant.AlertEnable
err := a.CreateAlert(updateAlert)
if err != nil {
return err
}
}
return nil
}
+894
View File
@@ -0,0 +1,894 @@
package service
import (
"encoding/json"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
versionUtil "github.com/1Panel-dev/1Panel/agent/utils/version"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"math"
"sort"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
)
type AlertTaskHelper struct {
DiskIO chan []disk.IOCountersStat
NetIO chan []net.IOCountersStat
}
type IAlertTaskHelper interface {
StopTask()
StartTask()
ResetTask()
InitTask(alertType string)
}
var cpuLoad1, cpuLoad5, cpuLoad15 []float64
var memoryLoad1, memoryLoad5, memoryLoad15 []float64
const ResourceAlertInterval = 30
var baseTypes = map[string]bool{"ssl": true, "siteEndTime": true, "panelPwdEndTime": true, "panelUpdate": true}
var resourceTypes = map[string]bool{"cpu": true, "memory": true, "disk": true, "load": true}
func NewIAlertTaskHelper() IAlertTaskHelper {
return &AlertTaskHelper{}
}
func (m *AlertTaskHelper) StartTask() {
baseAlert, resourceAlert := handleTask()
if len(baseAlert) == 0 && len(resourceAlert) == 0 {
return
}
handleBaseAlerts(baseAlert)
handleResourceAlerts(resourceAlert)
}
func (m *AlertTaskHelper) StopTask() {
stopBaseJob()
stopResourceJob()
}
func (m *AlertTaskHelper) ResetTask() {
m.StopTask()
m.StartTask()
}
func (m *AlertTaskHelper) InitTask(alertType string) {
if alertType == "cpu" {
cpuLoad1 = []float64{}
cpuLoad5 = []float64{}
cpuLoad15 = []float64{}
}
if alertType == "memory" {
memoryLoad1 = []float64{}
memoryLoad5 = []float64{}
memoryLoad15 = []float64{}
}
if baseTypes[alertType] {
stopBaseJob()
} else if resourceTypes[alertType] {
stopResourceJob()
}
m.StartTask()
}
func resourceTask(resourceAlert []dto.AlertDTO) {
for _, alert := range resourceAlert {
if !alertUtil.CheckSendTimeRange(alert.Type) {
continue
}
switch alert.Type {
case "cpu":
loadCPUUsage(alert)
case "memory":
loadMemUsage(alert)
case "load":
loadLoadInfo(alert)
case "disk":
loadDiskUsage(alert)
default:
}
}
}
func baseTask(baseAlert []dto.AlertDTO) {
for _, alert := range baseAlert {
if !alertUtil.CheckSendTimeRange(alert.Type) {
continue
}
switch alert.Type {
case "ssl":
loadSSLInfo(alert)
case "siteEndTime":
loadWebsiteInfo(alert)
case "panelPwdEndTime":
if global.IsMaster {
loadPanelPwd(alert)
}
case "panelUpdate":
if global.IsMaster {
loadPanelUpdate(alert)
}
default:
}
}
}
func handleTask() (baseAlert []dto.AlertDTO, resourceAlert []dto.AlertDTO) {
alertList, _ := NewIAlertService().GetAlerts()
baseAlert, resourceAlert = classifyAlerts(alertList)
return baseAlert, resourceAlert
}
func classifyAlerts(alertList []dto.AlertDTO) (baseAlert, resourceAlert []dto.AlertDTO) {
for _, alert := range alertList {
if baseTypes[alert.Type] {
baseAlert = append(baseAlert, alert)
} else if resourceTypes[alert.Type] {
resourceAlert = append(resourceAlert, alert)
}
}
return
}
func handleBaseAlerts(baseAlert []dto.AlertDTO) {
if len(baseAlert) > 0 {
if global.AlertBaseJobID == 0 {
baseTask(baseAlert)
jobID, err := global.Cron.AddFunc("*/30 * * * *", func() {
baseTask(baseAlert)
})
if err != nil {
global.LOG.Errorf("alert base job start failed: %v", err)
return
}
global.AlertBaseJobID = jobID
global.LOG.Info("start alert base job")
}
} else {
stopBaseJob()
}
}
func handleResourceAlerts(resourceAlert []dto.AlertDTO) {
if len(resourceAlert) > 0 {
if global.AlertResourceJobID == 0 {
jobID, err := global.Cron.AddFunc("*/1 * * * *", func() {
resourceTask(resourceAlert)
})
if err != nil {
global.LOG.Errorf("alert resource job start failed: %v", err)
return
}
global.AlertResourceJobID = jobID
global.LOG.Info("start alert resource job")
}
} else {
stopResourceJob()
}
}
func stopBaseJob() {
if global.AlertBaseJobID != 0 {
global.Cron.Remove(global.AlertBaseJobID)
global.AlertBaseJobID = 0
global.LOG.Info("stop alert base job")
}
}
func stopResourceJob() {
if global.AlertResourceJobID != 0 {
global.Cron.Remove(global.AlertResourceJobID)
global.AlertResourceJobID = 0
global.LOG.Info("stop alert resource job")
}
}
func loadSSLInfo(alert dto.AlertDTO) {
var opts []repo.DBOption
if alert.Project != "all" {
itemID, _ := strconv.Atoi(alert.Project)
opts = append(opts, repo.WithByID(uint(itemID)))
}
sslList, _ := repo.NewISSLRepo().List(opts...)
currentDate := time.Now()
daysDifferenceMap := make(map[int][]string)
projectMap := make(map[uint][]time.Time)
for _, ssl := range sslList {
daysDifference := int(ssl.ExpireDate.Sub(currentDate).Hours() / 24)
if daysDifference > 0 && int(alert.Cycle) >= daysDifference {
daysDifferenceMap[daysDifference] = append(daysDifferenceMap[daysDifference], ssl.PrimaryDomain)
projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate)
}
}
projectJSON := serializeAndSortProjects(projectMap)
if projectJSON == "" {
return
}
if len(daysDifferenceMap) > 0 {
for daysDifference, ssl := range daysDifferenceMap {
primaryDomain := strings.Join(ssl, ",")
var params []dto.Param
params = createAlertBaseParams(strconv.Itoa(len(ssl)), strconv.Itoa(daysDifference))
methods := strings.Split(alert.Method, ",")
for _, m := range methods {
m = strings.TrimSpace(m)
switch m {
case constant.SMS:
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, projectJSON, constant.SMS)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: totalCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
if !alertUtil.CheckTaskFrequency(constant.SMS) {
continue
}
_ = xpack.CreateSMSAlertLog(alert, create, primaryDomain, params, constant.SMS)
alertUtil.CreateNewAlertTask(alert.Project, alert.Type, projectJSON, constant.SMS)
case constant.Email:
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, projectJSON, constant.Email)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: totalCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
alertDetail := alertUtil.ProcessAlertDetail(alert, primaryDomain, params, constant.Email)
alertRule := alertUtil.ProcessAlertRule(alert)
create.AlertRule = alertRule
create.AlertDetail = alertDetail
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
alertUtil.CreateNewAlertTask(alert.Project, alert.Type, projectJSON, constant.Email)
default:
}
}
}
global.LOG.Info("SSL alert push successful")
}
}
func loadWebsiteInfo(alert dto.AlertDTO) {
var opts []repo.DBOption
if alert.Project != "all" {
itemID, _ := strconv.Atoi(alert.Project)
opts = append(opts, repo.WithByID(uint(itemID)))
}
websiteList, _ := websiteRepo.List(opts...)
currentDate := time.Now()
daysDifferenceMap := make(map[int][]string)
projectMap := make(map[uint][]time.Time)
for _, website := range websiteList {
daysDifference := int(website.ExpireDate.Sub(currentDate).Hours() / 24)
if daysDifference > 0 && int(alert.Cycle) >= daysDifference {
daysDifferenceMap[daysDifference] = append(daysDifferenceMap[daysDifference], website.PrimaryDomain)
projectMap[website.ID] = append(projectMap[website.ID], website.ExpireDate)
}
}
projectJSON := serializeAndSortProjects(projectMap)
if projectJSON == "" {
return
}
if len(daysDifferenceMap) > 0 {
methods := strings.Split(alert.Method, ",")
for daysDifference, websites := range daysDifferenceMap {
primaryDomain := strings.Join(websites, ",")
var params []dto.Param
params = createAlertBaseParams(strconv.Itoa(len(websites)), strconv.Itoa(daysDifference))
for _, m := range methods {
m = strings.TrimSpace(m)
switch m {
case constant.SMS:
if !alertUtil.CheckTaskFrequency(constant.SMS) {
continue
}
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, projectJSON, constant.SMS)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: totalCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
_ = xpack.CreateSMSAlertLog(alert, create, primaryDomain, params, constant.SMS)
alertUtil.CreateNewAlertTask(alert.Project, alert.Type, projectJSON, constant.SMS)
case constant.Email:
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, projectJSON, constant.Email)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: totalCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
alertDetail := alertUtil.ProcessAlertDetail(alert, primaryDomain, params, constant.Email)
alertRule := alertUtil.ProcessAlertRule(alert)
create.AlertDetail = alertDetail
create.AlertRule = alertRule
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
alertUtil.CreateNewAlertTask(alert.Project, alert.Type, projectJSON, constant.Email)
default:
}
}
}
global.LOG.Info("website expiration alert push successful")
}
}
func loadPanelPwd(alert dto.AlertDTO) {
// only master alert
var expirationDays model.Setting
if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", "ExpirationDays").First(&expirationDays).Error; err != nil {
global.LOG.Errorf("load %s from db setting failed, err: %v", "ExpirationDays", err)
return
}
if expirationDays.Value == "0" {
global.LOG.Info("panel password expiration setting not enabled, skip")
return
}
var expirationTime model.Setting
if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", "ExpirationTime").First(&expirationTime).Error; err != nil {
global.LOG.Errorf("load %s from db setting failed, err: %v", "ExpirationTime", err)
return
}
var params []dto.Param
defaultDate, _ := time.Parse(constant.DateTimeLayout, expirationTime.Value)
daysDifference := calculateDaysDifference(defaultDate)
if daysDifference >= 0 && int(alert.Cycle) >= daysDifference {
params = createAlertPwdParams(strconv.Itoa(daysDifference))
methods := strings.Split(alert.Method, ",")
for _, m := range methods {
m = strings.TrimSpace(m)
switch m {
case constant.SMS:
if !alertUtil.CheckTaskFrequency(constant.SMS) {
continue
}
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, expirationTime.Value, constant.SMS)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
create := dto.AlertLogCreate{
Count: totalCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
_ = xpack.CreateSMSAlertLog(alert, create, strconv.Itoa(daysDifference), params, constant.SMS)
alertUtil.CreateNewAlertTask(expirationTime.Value, alert.Type, expirationTime.Value, constant.SMS)
case constant.Email:
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, expirationTime.Value, constant.Email)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
create := dto.AlertLogCreate{
Count: totalCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
alertDetail := alertUtil.ProcessAlertDetail(alert, strconv.Itoa(daysDifference), params, constant.Email)
alertRule := alertUtil.ProcessAlertRule(alert)
create.AlertRule = alertRule
create.AlertDetail = alertDetail
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
alertUtil.CreateNewAlertTask(expirationTime.Value, alert.Type, expirationTime.Value, constant.Email)
default:
}
}
global.LOG.Info("panel password expiration alert push successful")
}
}
func loadPanelUpdate(alert dto.AlertDTO) {
// only master alert
info, err := versionUtil.GetUpgradeVersionInfo()
if err != nil {
global.LOG.Errorf("error getting version, err: %s", err)
return
}
// 获取版本信息
var version string
// 检查哪个版本字段不为空,并赋值
if info.NewVersion != "" {
version = info.NewVersion
} else if info.TestVersion != "" {
version = info.TestVersion
} else if info.LatestVersion != "" {
version = info.LatestVersion
}
if version == "" {
return
}
var params []dto.Param
methods := strings.Split(alert.Method, ",")
for _, m := range methods {
m = strings.TrimSpace(m)
switch m {
case constant.SMS:
if !alertUtil.CheckTaskFrequency(constant.SMS) {
continue
}
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, version, constant.SMS)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
var create = dto.AlertLogCreate{
Type: alert.Type,
AlertId: alert.ID,
Count: totalCount + 1,
}
_ = xpack.CreateSMSAlertLog(alert, create, version, params, constant.SMS)
alertUtil.CreateNewAlertTask(version, alert.Type, version, constant.SMS)
case constant.Email:
todayCount, totalCount, err := alertRepo.LoadTaskCount(alert.Type, version, constant.Email)
if err != nil || todayCount >= 1 || alert.SendCount <= totalCount {
continue
}
var create = dto.AlertLogCreate{
Type: alert.Type,
AlertId: alert.ID,
Count: totalCount + 1,
}
alertDetail := alertUtil.ProcessAlertDetail(alert, version, params, constant.Email)
alertRule := alertUtil.ProcessAlertRule(alert)
create.AlertRule = alertRule
create.AlertDetail = alertDetail
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
alertUtil.CreateNewAlertTask(version, alert.Type, version, constant.Email)
default:
}
}
global.LOG.Info("panel update alert push successful")
}
// 获取 CPU 使用率数据并发送到通道
func loadCPUUsage(alert dto.AlertDTO) {
percent, err := cpu.Percent(3*time.Second, false)
if err != nil {
global.LOG.Errorf("error getting cpu usage, err: %v", err)
return
}
if len(percent) > 0 {
var cpuLoad *[]float64
var threshold int
switch alert.Cycle {
case 1:
cpuLoad = &cpuLoad1
threshold = 1
case 5:
cpuLoad = &cpuLoad5
threshold = 5
case 15:
cpuLoad = &cpuLoad15
threshold = 15
default:
return
}
if checkAndSendAlert(alert, percent[0], cpuLoad, threshold) {
global.LOG.Info("cpu alert push successful")
}
}
}
// 获取内存使用情况数据并发送到通道
func loadMemUsage(alert dto.AlertDTO) {
memStat, err := mem.VirtualMemory()
if err != nil {
global.LOG.Errorf("error getting memory usage, err: %v", err)
return
}
percent := memStat.UsedPercent
var memoryLoad *[]float64
var threshold int
switch alert.Cycle {
case 1:
memoryLoad = &memoryLoad1
threshold = 1
case 5:
memoryLoad = &memoryLoad5
threshold = 5
case 15:
memoryLoad = &memoryLoad15
threshold = 15
default:
return
}
if checkAndSendAlert(alert, percent, memoryLoad, threshold) {
global.LOG.Info("memory alert push successful")
}
}
// 获取系统负载数据并发送到通道
func loadLoadInfo(alert dto.AlertDTO) {
avgStat, err := load.Avg()
if err != nil {
global.LOG.Errorf("error getting load usage, err: %v", err)
return
}
var loadValue float64
CPUTotal, _ := cpu.Counts(true)
switch alert.Cycle {
case 1:
loadValue = avgStat.Load1 / (float64(CPUTotal*2) * 0.75) * 100
case 5:
loadValue = avgStat.Load5 / (float64(CPUTotal*2) * 0.75) * 100
case 15:
loadValue = avgStat.Load15 / (float64(CPUTotal*2) * 0.75) * 100
default:
return
}
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
if err != nil {
global.LOG.Errorf("task log record not found, err: %v", err)
}
if newDate.IsZero() || calculateMinutesDifference(newDate) > ResourceAlertInterval {
if loadValue >= float64(alert.Count) {
global.LOG.Infof("%d minute load: %f,detail: %v", alert.Cycle, loadValue, avgStat)
createAndLogAlert(alert, loadValue)
global.LOG.Info("load alert task push successful")
}
}
}
// 内存/cpu检查是否需要发送告警并处理相关逻辑
func checkAndSendAlert(alert dto.AlertDTO, currentUsage float64, usageLoad *[]float64, threshold int) bool {
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
if err != nil {
global.LOG.Errorf("record not found, err: %v", err)
return false
}
*usageLoad = append(*usageLoad, currentUsage)
if len(*usageLoad) > threshold {
*usageLoad = (*usageLoad)[1:]
}
if newDate.IsZero() || calculateMinutesDifference(newDate) > ResourceAlertInterval {
if len(*usageLoad) == threshold {
avgUsage := average(*usageLoad)
if avgUsage >= float64(alert.Count) {
global.LOG.Infof("%d minute %s: %f , usage: %v", threshold, alert.Type, avgUsage, usageLoad)
createAndLogAlert(alert, avgUsage)
return true
}
}
}
return false
}
// 检查是否超过今日发送次数限制
func checkTaskFrequency(alertType, quotaType string, sendCount uint, method string) (uint, bool) {
todayCount, _, err := alertRepo.LoadTaskCount(alertType, quotaType, method)
if err != nil {
global.LOG.Errorf("error getting task info, err: %v", err)
return todayCount, false
}
if todayCount >= sendCount {
return todayCount, false
}
return todayCount, true
}
// 创建告警日志和详情
func createAndLogAlert(alert dto.AlertDTO, avgUsage float64) {
avgUsagePercent := common.FormatPercent(avgUsage)
params := createAlertAvgParams(strconv.Itoa(int(alert.Cycle)), getModule(alert.Type), avgUsagePercent)
methods := strings.Split(alert.Method, ",")
for _, m := range methods {
m = strings.TrimSpace(m)
switch m {
case constant.SMS:
if !alertUtil.CheckTaskFrequency(constant.SMS) {
continue
}
todayCount, isValid := checkTaskFrequency(alert.Type, strconv.Itoa(int(alert.Cycle)), alert.SendCount, constant.SMS)
if !isValid {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: todayCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
_ = xpack.CreateSMSAlertLog(alert, create, avgUsagePercent, params, constant.SMS)
alertUtil.CreateNewAlertTask(avgUsagePercent, alert.Type, strconv.Itoa(int(alert.Cycle)), constant.SMS)
case constant.Email:
todayCount, isValid := checkTaskFrequency(alert.Type, strconv.Itoa(int(alert.Cycle)), alert.SendCount, constant.Email)
if !isValid {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: todayCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
alertDetail := alertUtil.ProcessAlertDetail(alert, avgUsagePercent, params, constant.Email)
alertRule := alertUtil.ProcessAlertRule(alert)
create.AlertRule = alertRule
create.AlertDetail = alertDetail
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
alertUtil.CreateNewAlertTask(avgUsagePercent, alert.Type, strconv.Itoa(int(alert.Cycle)), constant.Email)
default:
}
}
}
func getModule(alertType string) string {
var module string
switch alertType {
case "cpu":
module = " CPU "
case "memory":
module = "内存"
case "load":
module = "负载"
default:
}
return module
}
func loadDiskUsage(alert dto.AlertDTO) {
newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID)
if err != nil {
global.LOG.Errorf("record not found, err: %v", err)
}
if newDate.IsZero() || calculateMinutesDifference(newDate) > ResourceAlertInterval {
if strings.Contains(alert.Project, "all") {
err = processAllDisks(alert)
} else {
err = processSingleDisk(alert)
}
if err != nil {
global.LOG.Errorf("error processing disk usage, err: %v", err)
}
}
}
func processAllDisks(alert dto.AlertDTO) error {
diskList, err := NewIAlertService().GetDisks()
if err != nil {
global.LOG.Errorf("error getting disk list, err: %v", err)
return err
}
var flag bool
for _, item := range diskList {
if success, err := checkAndCreateDiskAlert(alert, item.Path); err == nil && success {
flag = true
}
}
if flag {
global.LOG.Info("all disk alert push successful")
}
return nil
}
func processSingleDisk(alert dto.AlertDTO) error {
success, err := checkAndCreateDiskAlert(alert, alert.Project)
if err != nil {
return err
}
if success {
global.LOG.Info("disk alert push successful")
}
return nil
}
func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) (bool, error) {
usageStat, err := disk.Usage(path)
if err != nil {
global.LOG.Errorf("error getting disk usage for %s, err: %v", path, err)
return false, err
}
usedTotal, usedStr := calculateUsedTotal(alert.Cycle, usageStat)
commonTotal := float64(alert.Count)
if alert.Cycle == 1 {
commonTotal *= 1024 * 1024 * 1024
}
if usedTotal < commonTotal {
return false, nil
}
global.LOG.Infof("disk「 %s 」usage: %s", path, usedStr)
var params []dto.Param
params = createAlertDiskParams(path, usedStr)
methods := strings.Split(alert.Method, ",")
for _, m := range methods {
m = strings.TrimSpace(m)
switch m {
case constant.SMS:
if !alertUtil.CheckTaskFrequency(constant.SMS) {
continue
}
todayCount, isValid := checkTaskFrequency(alert.Type, alert.Project, alert.SendCount, constant.SMS)
if !isValid {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: todayCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
_ = xpack.CreateSMSAlertLog(alert, create, path, params, constant.SMS)
alertUtil.CreateNewAlertTask(strconv.Itoa(int(alert.Cycle)), alert.Type, alert.Project, constant.SMS)
case constant.Email:
todayCount, isValid := checkTaskFrequency(alert.Type, alert.Project, alert.SendCount, constant.Email)
if !isValid {
continue
}
create := dto.AlertLogCreate{
Status: constant.AlertSuccess,
Count: todayCount + 1,
AlertId: alert.ID,
Type: alert.Type,
}
alertDetail := alertUtil.ProcessAlertDetail(alert, path, params, constant.Email)
alertRule := alertUtil.ProcessAlertRule(alert)
create.AlertRule = alertRule
create.AlertDetail = alertDetail
transport := xpack.LoadRequestTransport()
_ = alertUtil.CreateEmailAlertLog(create, alert, params, transport)
alertUtil.CreateNewAlertTask(strconv.Itoa(int(alert.Cycle)), alert.Type, alert.Project, constant.Email)
default:
}
}
return true, nil
}
func calculateUsedTotal(cycle uint, usageStat *disk.UsageStat) (float64, string) {
if cycle == 1 {
return float64(usageStat.Used), common.FormatBytes(usageStat.Used)
}
return usageStat.UsedPercent, common.FormatPercent(usageStat.UsedPercent)
}
func calculateDaysDifference(expirationTime time.Time) int {
currentDate := time.Now()
formattedTime := currentDate.Format(constant.DateTimeLayout)
parsedTime, _ := time.Parse(constant.DateTimeLayout, formattedTime)
timeGap := expirationTime.Sub(parsedTime).Milliseconds()
if timeGap < 0 {
return -1
}
daysDifference := int(math.Floor(float64(timeGap) / (3600 * 1000 * 24)))
return daysDifference
}
func calculateMinutesDifference(newDate time.Time) int {
now := time.Now()
if newDate.After(now) {
return -1
}
minutesDifference := int(now.Sub(newDate).Minutes())
return minutesDifference
}
func average(arr []float64) float64 {
total := 0.0
for _, v := range arr {
total += v
}
return total / float64(len(arr))
}
func createAlertBaseParams(project, cycle string) []dto.Param {
return []dto.Param{
{
Index: "1",
Key: "project",
Value: project,
},
{
Index: "2",
Key: "cycle",
Value: cycle,
},
}
}
func createAlertPwdParams(cycle string) []dto.Param {
return []dto.Param{
{
Index: "1",
Key: "cycle",
Value: cycle,
},
}
}
func createAlertAvgParams(cycle, module, count string) []dto.Param {
return []dto.Param{
{
Index: "1",
Key: "cycle",
Value: cycle,
},
{
Index: "2",
Key: "module",
Value: module,
},
{
Index: "3",
Key: "count",
Value: count,
},
}
}
func createAlertDiskParams(project, count string) []dto.Param {
return []dto.Param{
{
Index: "1",
Key: "project",
Value: project,
},
{
Index: "2",
Key: "count",
Value: count,
},
}
}
func serializeAndSortProjects(projectMap map[uint][]time.Time) string {
keys := make([]int, 0, len(projectMap))
for k := range projectMap {
keys = append(keys, int(k))
}
sort.Ints(keys)
projectJSON, err := json.Marshal(projectMap)
if err != nil {
global.LOG.Errorf("Failed to serialize projectMap: %v", err)
return ""
}
return string(projectJSON)
}
+1
View File
@@ -483,6 +483,7 @@ func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.App
req.Params["PANEL_DB_HOST"] = database.Address
req.Params["PANEL_DB_PORT"] = database.Port
req.Params["PANEL_DB_HOST_NAME"] = hostName
req.Params["DATABASE_NAME"] = database.Name
}
}
paramByte, err = json.Marshal(req.Params)
+43 -2
View File
@@ -56,6 +56,7 @@ type IAppInstallService interface {
UpdateAppConfig(req request.AppConfigUpdate) error
GetInstallList() ([]dto.AppInstallInfo, error)
GetAppInstallInfo(appInstallID uint) (*response.AppInstallInfo, error)
}
func NewIAppInstalledService() IAppInstallService {
@@ -472,10 +473,20 @@ func (a *AppInstallService) SyncAll(systemInit bool) error {
func (a *AppInstallService) GetServices(key string) ([]response.AppService, error) {
var res []response.AppService
if DatabaseKeys[key] > 0 {
types := []string{key}
if key == constant.AppPostgres {
key = constant.AppPostgresql
}
dbs, _ := databaseRepo.GetList(repo.WithByType(key))
switch key {
case constant.AppMysql:
types = []string{constant.AppMysql, constant.AppMysqlCluster}
case constant.AppPostgresql:
types = []string{constant.AppPostgresql, constant.AppPostgresqlCluster}
case constant.AppRedis:
types = []string{constant.AppRedis, constant.AppRedisCluster}
}
dbs, _ := databaseRepo.GetList(repo.WithTypes(types))
if len(dbs) == 0 {
return res, nil
}
@@ -658,7 +669,7 @@ func (a *AppInstallService) GetDefaultConfigByKey(key, name string) (string, err
return "", buserr.New("ErrPathNotFound")
}
if key == constant.AppMysql || key == constant.AppMariaDB {
if key == constant.AppMysql || key == constant.AppMariaDB || key == constant.AppMysqlCluster {
filePath = path.Join(filePath, "my.cnf")
}
if key == constant.AppRedis {
@@ -710,6 +721,7 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
}
appParam.LabelZh = form.LabelZh
appParam.LabelEn = form.LabelEn
appParam.Label = form.Label
appParam.Value = v
if form.Type == "service" {
appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(v.(string)))
@@ -763,6 +775,7 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
Values: form.Values,
Multiple: form.Multiple,
Required: form.Required,
Label: form.Label,
})
}
}
@@ -898,3 +911,31 @@ func updateInstallInfoInDB(appKey, appName, param string, value interface{}) err
}
return nil
}
func (a *AppInstallService) GetAppInstallInfo(installID uint) (*response.AppInstallInfo, error) {
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(installID))
if appInstall.ID == 0 {
return &response.AppInstallInfo{
Status: constant.StatusDeleted,
}, nil
}
_ = syncAppInstallStatus(&appInstall, false)
appInstall, _ = appInstallRepo.GetFirst(repo.WithByID(installID))
var envMap map[string]interface{}
err := json.Unmarshal([]byte(appInstall.Env), &envMap)
if err != nil {
return nil, err
}
res := &response.AppInstallInfo{
ID: appInstall.ID,
Name: appInstall.Name,
Version: appInstall.Version,
Container: appInstall.ContainerName,
HttpPort: appInstall.HttpPort,
Status: appInstall.Status,
Message: appInstall.Message,
Env: envMap,
ComposePath: appInstall.GetComposePath(),
}
return res, nil
}
+37 -38
View File
@@ -116,13 +116,16 @@ func checkPortExist(port int) error {
}
var DatabaseKeys = map[string]uint{
constant.AppMysql: 3306,
constant.AppMariaDB: 3306,
constant.AppPostgresql: 5432,
constant.AppPostgres: 5432,
constant.AppMongodb: 27017,
constant.AppRedis: 6379,
constant.AppMemcached: 11211,
constant.AppMysql: 3306,
constant.AppMariaDB: 3306,
constant.AppPostgresql: 5432,
constant.AppPostgres: 5432,
constant.AppMongodb: 27017,
constant.AppRedis: 6379,
constant.AppMemcached: 11211,
constant.AppMysqlCluster: 3306,
constant.AppPostgresqlCluster: 5432,
constant.AppRedisCluster: 6379,
}
var ToolKeys = map[string]uint{
@@ -138,7 +141,6 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
}
_ = deleteLink(del)
}
var dbConfig dto.AppDatabase
if DatabaseKeys[app.Key] > 0 {
handleDataBaseApp := func(task *task.Task) error {
database := &model.Database{
@@ -170,11 +172,11 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
}
switch app.Key {
case constant.AppMysql, constant.AppMariaDB, constant.AppPostgresql, constant.AppMongodb:
case constant.AppMysql, constant.AppMariaDB, constant.AppPostgresql, constant.AppMongodb, constant.AppMysqlCluster, constant.AppPostgresqlCluster:
if password, ok := params["PANEL_DB_ROOT_PASSWORD"]; ok {
if password != "" {
database.Password = password.(string)
if app.Key == "mysql" || app.Key == "mariadb" {
if app.Key == "mysql" || app.Key == "mariadb" || app.Key == constant.AppMysqlCluster {
database.Username = "root"
}
if rootUser, ok := params["PANEL_DB_ROOT_USER"]; ok {
@@ -192,7 +194,7 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
}
}
case constant.AppRedis:
case constant.AppRedis, constant.AppRedisCluster:
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
authParam := dto.RedisAuthParam{
RootPassword: "",
@@ -229,6 +231,7 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
}
}
var dbConfig dto.AppDatabase
if app.Type == "website" || app.Type == "tool" {
paramByte, err := json.Marshal(params)
if err != nil {
@@ -252,8 +255,11 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
var resourceId uint
if dbConfig.DbName != "" && dbConfig.DbUser != "" && dbConfig.Password != "" {
switch database.Type {
case constant.AppPostgresql, constant.AppPostgres:
case constant.AppPostgresql, constant.AppPostgres, constant.AppPostgresqlCluster:
oldPostgresqlDb, _ := postgresqlRepo.Get(repo.WithByName(dbConfig.DbName), repo.WithByFrom(constant.ResourceLocal))
if oldPostgresqlDb.ID == 0 {
oldPostgresqlDb, _ = postgresqlRepo.Get(repo.WithByName(dbConfig.DbName), postgresqlRepo.WithByPostgresqlName(dbConfig.DatabaseName), repo.WithByFrom(constant.AppResourceRemote))
}
resourceId = oldPostgresqlDb.ID
if oldPostgresqlDb.ID > 0 {
if oldPostgresqlDb.Username != dbConfig.DbUser || oldPostgresqlDb.Password != dbConfig.Password {
@@ -274,8 +280,11 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
}
resourceId = pgdb.ID
}
case constant.AppMysql, constant.AppMariaDB:
case constant.AppMysql, constant.AppMariaDB, constant.AppMysqlCluster:
oldMysqlDb, _ := mysqlRepo.Get(repo.WithByName(dbConfig.DbName), repo.WithByFrom(constant.ResourceLocal))
if oldMysqlDb.ID == 0 {
oldMysqlDb, _ = mysqlRepo.Get(repo.WithByName(dbConfig.DbName), mysqlRepo.WithByMysqlName(dbConfig.DatabaseName), repo.WithByFrom(constant.AppResourceRemote))
}
resourceId = oldMysqlDb.ID
if oldMysqlDb.ID > 0 {
if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password {
@@ -405,9 +414,9 @@ func deleteAppInstall(deleteReq request.AppInstallDelete) error {
}
switch install.App.Key {
case constant.AppMysql, constant.AppMariaDB:
case constant.AppMysql, constant.AppMariaDB, constant.AppMysqlCluster:
_ = mysqlRepo.Delete(ctx, mysqlRepo.WithByMysqlName(install.Name))
case constant.AppPostgresql:
case constant.AppPostgresql, constant.AppPostgresqlCluster:
_ = postgresqlRepo.Delete(ctx, postgresqlRepo.WithByPostgresqlName(install.Name))
}
@@ -656,10 +665,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
}
command := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rn %s/* %s || true", detailDir, install.GetPath()))
stdout, _ := command.CombinedOutput()
if stdout != nil {
t.Logger.Printf("upgrade app [%s] [%s] cp file log : %s ", install.App.Key, install.Name, string(stdout))
}
_, _ = command.CombinedOutput()
sourceScripts := path.Join(detailDir, "scripts")
if fileOp.Stat(sourceScripts) {
dstScripts := path.Join(install.GetPath(), "scripts")
@@ -968,14 +974,16 @@ func runScript(task *task.Task, appInstall *model.AppInstall, operate string) er
case "uninstall":
scriptPath = path.Join(workDir, "scripts", "uninstall.sh")
}
if !files.NewFileOp().Stat(scriptPath) {
fileOp := files.NewFileOp()
if !fileOp.Stat(scriptPath) {
return nil
}
_ = fileOp.ChmodR(scriptPath, constant.DirPerm, false)
logStr := i18n.GetWithName("ExecShell", operate)
task.LogStart(logStr)
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute), cmd.WithScriptPath(scriptPath), cmd.WithWorkDir(workDir))
out, err := cmdMgr.RunWithStdout("bash")
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute), cmd.WithWorkDir(workDir))
out, err := cmdMgr.RunWithStdoutBashCf(scriptPath)
if err != nil {
if out != "" {
err = errors.New(out)
@@ -1020,12 +1028,12 @@ func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) error
errMsg string
)
if pullImages && appInstall.App.Type != "php" {
projectName := strings.ToLower(appInstall.Name)
//projectName := strings.ToLower(appInstall.Name)
envByte, err := files.NewFileOp().GetContent(appInstall.GetEnvPath())
if err != nil {
return err
}
images, err := composeV2.GetDockerComposeImages(projectName, envByte, []byte(appInstall.DockerCompose))
images, err := composeV2.GetImagesFromDockerCompose(envByte, []byte(appInstall.DockerCompose))
if err != nil {
return err
}
@@ -1490,7 +1498,7 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
continue
}
lastVersion := versions[0]
if app.Key == constant.AppMysql {
if app.Key == constant.AppMysql || app.Key == constant.AppMysqlCluster {
for _, version := range versions {
majorVersion := getMajorVersion(installed.Version)
if !strings.HasPrefix(version, majorVersion) {
@@ -1792,24 +1800,15 @@ func getAppTags(appID uint, lang string) ([]response.TagDTO, error) {
}
var res []response.TagDTO
for _, t := range tags {
if t.Name != "" {
var translations = make(map[string]string)
_ = json.Unmarshal([]byte(t.Translations), &translations)
if name, ok := translations[lang]; ok {
tagDTO := response.TagDTO{
ID: t.ID,
Key: t.Key,
Name: t.Name,
Name: name,
}
res = append(res, tagDTO)
} else {
var translations = make(map[string]string)
_ = json.Unmarshal([]byte(t.Translations), &translations)
if name, ok := translations[lang]; ok {
tagDTO := response.TagDTO{
ID: t.ID,
Key: t.Key,
Name: name,
}
res = append(res, tagDTO)
}
}
}
return res, nil
+6
View File
@@ -541,11 +541,17 @@ func changeLocalBackup(oldPath, newPath string) error {
return err
}
}
if fileOp.Stat(path.Join(oldPath, "master")) {
if err := fileOp.CopyDir(path.Join(oldPath, "master"), newPath); err != nil {
return err
}
}
_ = fileOp.RmRf(path.Join(oldPath, "app"))
_ = fileOp.RmRf(path.Join(oldPath, "database"))
_ = fileOp.RmRf(path.Join(oldPath, "directory"))
_ = fileOp.RmRf(path.Join(oldPath, "system_snapshot"))
_ = fileOp.RmRf(path.Join(oldPath, "website"))
_ = fileOp.RmRf(path.Join(oldPath, "log"))
_ = fileOp.RmRf(path.Join(oldPath, "master"))
return nil
}
+8 -7
View File
@@ -23,7 +23,7 @@ import (
)
func (u *BackupService) RedisBackup(req dto.CommonBackup) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return err
}
@@ -64,7 +64,7 @@ func (u *BackupService) RedisBackup(req dto.CommonBackup) error {
}
func (u *BackupService) RedisRecover(req dto.CommonRecover) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return err
}
@@ -76,6 +76,7 @@ func (u *BackupService) RedisRecover(req dto.CommonRecover) error {
}
func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, backupDir, fileName, secret, taskID string) error {
var (
err error
itemTask *task.Task
@@ -102,7 +103,7 @@ func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, backupDi
}
if strings.HasSuffix(fileName, ".tar.gz") {
redisDataDir := fmt.Sprintf("%s/%s/%s/data/appendonlydir", global.Dir.AppInstallDir, "redis", redisInfo.Name)
redisDataDir := fmt.Sprintf("%s/%s/%s/data/appendonlydir", global.Dir.AppInstallDir, redisInfo.Key, redisInfo.Name)
if err := fileOp.TarGzCompressPro(true, redisDataDir, path.Join(backupDir, fileName), secret, ""); err != nil {
return err
}
@@ -111,14 +112,14 @@ func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, backupDi
if strings.HasSuffix(fileName, ".aof") {
stdout1, err := cmd.RunDefaultWithStdoutBashCf("docker cp %s:/data/appendonly.aof %s/%s", redisInfo.ContainerName, backupDir, fileName)
if err != nil {
return errors.New(string(stdout1))
return errors.New(stdout1)
}
return nil
}
stdout1, err1 := cmd.RunDefaultWithStdoutBashCf("docker cp %s:/data/dump.rdb %s/%s", redisInfo.ContainerName, backupDir, fileName)
if err1 != nil {
return errors.New(string(stdout1))
return errors.New(stdout1)
}
return nil
}
@@ -196,12 +197,12 @@ func handleRedisRecover(redisInfo *repo.RootInfo, parentTask *task.Task, recover
}
}()
}
composeDir := fmt.Sprintf("%s/redis/%s", global.Dir.AppInstallDir, redisInfo.Name)
composeDir := fmt.Sprintf("%s/%s/%s", global.Dir.AppInstallDir, redisInfo.Key, redisInfo.Name)
if _, err := compose.Down(composeDir + "/docker-compose.yml"); err != nil {
return err
}
if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "7.") {
redisDataDir := fmt.Sprintf("%s/%s/%s/data", global.Dir.AppInstallDir, "redis", redisInfo.Name)
redisDataDir := fmt.Sprintf("%s/%s/%s/data", global.Dir.AppInstallDir, redisInfo.Key, redisInfo.Name)
if err := fileOp.TarGzExtractPro(recoverFile, redisDataDir, secret); err != nil {
return err
}
+22 -21
View File
@@ -3,6 +3,7 @@ package service
import (
"bufio"
"fmt"
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
"os"
"os/exec"
"path"
@@ -158,9 +159,10 @@ func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interfa
AlertType: "clams",
EntryID: datas[i].ID,
}
alertCount := xpack.GetAlert(alertBase)
if alertCount != 0 {
datas[i].AlertCount = alertCount
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
datas[i].AlertMethod = alertInfo.Method
if alertInfo.SendCount != 0 {
datas[i].AlertCount = alertInfo.SendCount
} else {
datas[i].AlertCount = 0
}
@@ -191,13 +193,15 @@ func (c *ClamService) Create(req dto.ClamCreate) error {
return err
}
if req.AlertCount != 0 {
createAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertCount: req.AlertCount,
AlertType: "clams",
EntryID: clam.ID,
createAlert := dto.AlertCreate{
Title: req.AlertTitle,
SendCount: req.AlertCount,
Method: req.AlertMethod,
Type: "clams",
Project: strconv.Itoa(int(clam.ID)),
Status: constant.AlertEnable,
}
err := xpack.CreateAlert(createAlert)
err := NewIAlertService().CreateAlert(createAlert)
if err != nil {
return err
}
@@ -247,13 +251,14 @@ func (c *ClamService) Update(req dto.ClamUpdate) error {
if err := clamRepo.Update(req.ID, upMap); err != nil {
return err
}
updateAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertType: "clams",
AlertCount: req.AlertCount,
EntryID: clam.ID,
updateAlert := dto.AlertCreate{
Title: req.AlertTitle,
SendCount: req.AlertCount,
Method: req.AlertMethod,
Type: "clams",
Project: strconv.Itoa(int(clam.ID)),
}
err := xpack.UpdateAlert(updateAlert)
err := NewIAlertService().ExternalUpdateAlert(updateAlert)
if err != nil {
return err
}
@@ -297,11 +302,7 @@ func (c *ClamService) Delete(req dto.ClamDelete) error {
if err := clamRepo.Delete(repo.WithByID(id)); err != nil {
return err
}
alertBase := dto.AlertBase{
AlertType: "clams",
EntryID: clam.ID,
}
err := xpack.DeleteAlert(alertBase)
err := alertRepo.Delete(alertRepo.WithByProject(strconv.Itoa(int(clam.ID))), alertRepo.WithByType("clams"))
if err != nil {
return err
}
@@ -639,7 +640,7 @@ func handleAlert(stdout, clamName string, clamId uint) {
EntryID: clamId,
Param: strconv.Itoa(infectedFiles),
}
_ = xpack.PushAlert(pushAlert)
_ = alert_push.PushAlert(pushAlert)
break
}
}
+105 -80
View File
@@ -12,6 +12,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
@@ -52,7 +53,7 @@ type ContainerService struct{}
type IContainerService interface {
Page(req dto.PageContainer) (int64, interface{}, error)
List() []string
List() []dto.ContainerOptions
LoadStatus() (dto.ContainerStatus, error)
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error)
ListNetwork() ([]dto.Options, error)
@@ -61,7 +62,7 @@ type IContainerService interface {
PageCompose(req dto.SearchWithPage) (int64, interface{}, error)
CreateCompose(req dto.ComposeCreate) error
ComposeOperation(req dto.ComposeOperation) error
ContainerCreate(req dto.ContainerOperate) error
ContainerCreate(req dto.ContainerOperate, inThread bool) error
ContainerCreateByCommand(req dto.ContainerCreateByCommand) error
ContainerUpdate(req dto.ContainerOperate) error
ContainerUpgrade(req dto.ContainerUpgrade) error
@@ -83,7 +84,7 @@ type IContainerService interface {
ComposeUpdate(req dto.ComposeUpdate) error
Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error)
LoadUsers(req dto.OperationWithName) ([]string, error)
LoadUsers(req dto.OperationWithName) []string
StreamLogs(ctx *gin.Context, params dto.StreamLog)
}
@@ -221,7 +222,8 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
return int64(total), backDatas, nil
}
func (u *ContainerService) List() []string {
func (u *ContainerService) List() []dto.ContainerOptions {
var options []dto.ContainerOptions
client, err := docker.NewDockerClient()
if err != nil {
global.LOG.Errorf("load docker client for contianer list failed, err: %v", err)
@@ -230,19 +232,18 @@ func (u *ContainerService) List() []string {
defer client.Close()
containers, err := client.ContainerList(context.Background(), container.ListOptions{All: true})
if err != nil {
global.LOG.Errorf("load contianer list failed, err: %v", err)
global.LOG.Errorf("load container list failed, err: %v", err)
return nil
}
var datas []string
for _, container := range containers {
for _, name := range container.Names {
if len(name) != 0 {
datas = append(datas, strings.TrimPrefix(name, "/"))
options = append(options, dto.ContainerOptions{Name: strings.TrimPrefix(name, "/"), State: container.State})
}
}
}
return datas
return options
}
func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) {
@@ -495,7 +496,7 @@ func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) {
return &data, nil
}
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error {
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bool) error {
client, err := docker.NewDockerClient()
if err != nil {
return err
@@ -513,63 +514,62 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error {
return err
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error {
if !checkImageExist(client, req.Image) || req.ForcePull {
if err := pullImages(taskItem, client, req.Image); err != nil {
if !req.ForcePull {
return err
}
taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error {
if !checkImageExist(client, req.Image) || req.ForcePull {
if err := pullImages(taskItem, client, req.Image); err != nil {
if !req.ForcePull {
return err
}
}
return nil
}, nil)
taskItem.AddSubTask(i18n.GetMsgByKey("ContainerImageCheck"), func(t *task.Task) error {
imageInfo, _, err := client.ImageInspectWithRaw(ctx, req.Image)
if err != nil {
return err
}
if len(req.Entrypoint) == 0 {
req.Entrypoint = imageInfo.Config.Entrypoint
}
if len(req.Cmd) == 0 {
req.Cmd = imageInfo.Config.Cmd
}
return nil
}, nil)
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", req.Name), func(t *task.Task) error {
config, hostConf, networkConf, err := loadConfigInfo(true, req, nil)
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerLoadInfo"), err)
if err != nil {
return err
}
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
if err != nil {
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return err
}
err = client.ContainerStart(ctx, con.ID, container.StartOptions{})
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err)
if err != nil {
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return fmt.Errorf("create successful but start failed, err: %v", err)
}
return nil
}, nil)
if err := taskItem.Execute(); err != nil {
global.LOG.Error(err.Error())
}
}()
wg.Wait()
return nil
return nil
}, nil)
taskItem.AddSubTask(i18n.GetMsgByKey("ContainerImageCheck"), func(t *task.Task) error {
imageInfo, _, err := client.ImageInspectWithRaw(ctx, req.Image)
if err != nil {
return err
}
if len(req.Entrypoint) == 0 {
req.Entrypoint = imageInfo.Config.Entrypoint
}
if len(req.Cmd) == 0 {
req.Cmd = imageInfo.Config.Cmd
}
return nil
}, nil)
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", req.Name), func(t *task.Task) error {
config, hostConf, networkConf, err := loadConfigInfo(true, req, nil)
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerLoadInfo"), err)
if err != nil {
return err
}
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name)
if err != nil {
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return err
}
err = client.ContainerStart(ctx, con.ID, container.StartOptions{})
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err)
if err != nil {
taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed"))
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true})
return fmt.Errorf("create successful but start failed, err: %v", err)
}
return nil
}, nil)
if inThread {
go func() {
if err := taskItem.Execute(); err != nil {
global.LOG.Error(err.Error())
}
}()
return nil
}
return taskItem.Execute()
}
func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) {
@@ -676,7 +676,7 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", req.Name), func(t *task.Task) error {
err := client.ContainerRemove(ctx, req.ContainerID, container.RemoveOptions{Force: true})
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerRemoveOld"), err)
taskItem.LogWithStatus(i18n.GetWithName("ContainerRemoveOld", req.Name), err)
if err != nil {
return err
}
@@ -752,7 +752,7 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
}
}
err := client.ContainerRemove(ctx, req.Name, container.RemoveOptions{Force: true})
taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerRemoveOld"), err)
taskItem.LogWithStatus(i18n.GetWithName("ContainerRemoveOld", req.Name), err)
if err != nil {
return err
}
@@ -809,10 +809,23 @@ func (u *ContainerService) ContainerCommit(req dto.ContainerCommit) error {
Pause: req.Pause,
Config: nil,
}
_, err = client.ContainerCommit(ctx, req.ContainerId, options)
taskItem, err := task.NewTaskWithOps(req.NewImageName, task.TaskCommit, task.TaskScopeContainer, req.TaskID, 1)
if err != nil {
return fmt.Errorf("failed to commit container, err: %v", err)
return fmt.Errorf("new task for container commit failed, err: %v", err)
}
go func() {
taskItem.AddSubTask(i18n.GetWithName("TaskCommit", req.NewImageName), func(t *task.Task) error {
res, err := client.ContainerCommit(ctx, req.ContainerId, options)
if err != nil {
return fmt.Errorf("failed to commit container, err: %v", err)
}
taskItem.Log(res.ID)
return nil
}, nil)
_ = taskItem.Execute()
}()
return nil
}
@@ -1014,9 +1027,15 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
return buserr.New("ErrCmdIllegal")
}
commandArg := []string{"logs", container}
dockerCommand := global.CONF.DockerConfig.Command
if containerType == "compose" {
commandArg = []string{"compose", "-f", container, "logs"}
if dockerCommand == "docker-compose" {
commandArg = []string{"-f", container, "logs"}
} else {
commandArg = []string{"compose", "-f", container, "logs"}
}
}
if tail != "0" {
commandArg = append(commandArg, "--tail")
commandArg = append(commandArg, tail)
@@ -1025,16 +1044,20 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
commandArg = append(commandArg, "--since")
commandArg = append(commandArg, since)
}
cmd := exec.Command("docker", commandArg...)
stdout, err := cmd.StdoutPipe()
var dockerCmd *exec.Cmd
if containerType == "compose" && dockerCommand == "docker-compose" {
dockerCmd = exec.Command("docker-compose", commandArg...)
} else {
dockerCmd = exec.Command("docker", commandArg...)
}
stdout, err := dockerCmd.StdoutPipe()
if err != nil {
_ = cmd.Process.Signal(syscall.SIGTERM)
_ = dockerCmd.Process.Signal(syscall.SIGTERM)
return err
}
cmd.Stderr = cmd.Stdout
if err := cmd.Start(); err != nil {
_ = cmd.Process.Signal(syscall.SIGTERM)
dockerCmd.Stderr = dockerCmd.Stdout
if err := dockerCmd.Start(); err != nil {
_ = dockerCmd.Process.Signal(syscall.SIGTERM)
return err
}
@@ -1051,9 +1074,11 @@ func (u *ContainerService) DownloadContainerLogs(containerType, container, since
errCh := make(chan error)
go func() {
scanner := bufio.NewScanner(stdout)
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>`)
for scanner.Scan() {
line := scanner.Text()
if _, err := tempFile.WriteString(line + "\n"); err != nil {
cleanLine := ansiRegex.ReplaceAllString(line, "")
if _, err := tempFile.WriteString(cleanLine + "\n"); err != nil {
errCh <- err
return
}
@@ -1114,19 +1139,19 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error
return &data, nil
}
func (u *ContainerService) LoadUsers(req dto.OperationWithName) ([]string, error) {
func (u *ContainerService) LoadUsers(req dto.OperationWithName) []string {
var users []string
std, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s cat /etc/passwd", req.Name)
if err != nil {
return nil, err
return users
}
var users []string
lines := strings.Split(string(std), "\n")
for _, line := range lines {
if strings.Contains(line, ":") {
users = append(users, strings.Split(line, ":")[0])
}
}
return users, nil
return users
}
func stringsToMap(list []string) map[string]string {
+2 -3
View File
@@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path"
"sort"
"strings"
@@ -174,7 +173,7 @@ func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
if err := newComposeEnv(req.Path, req.Env); err != nil {
return false, err
}
cmd := exec.Command("docker", "compose", "-f", req.Path, "config")
cmd := getComposeCmd(req.Path, "config")
stdout, err := cmd.CombinedOutput()
if err != nil {
return false, errors.New(string(stdout))
@@ -201,7 +200,7 @@ func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
}
go func() {
taskItem.AddSubTask(i18n.GetMsgByKey("ComposeCreate"), func(t *task.Task) error {
cmd := exec.Command("docker", "compose", "-f", req.Path, "up", "-d")
cmd := getComposeCmd(req.Path, "up")
out, err := cmd.CombinedOutput()
taskItem.Log(i18n.GetWithName("ComposeCreateRes", string(out)))
if err != nil {
+25 -27
View File
@@ -9,12 +9,10 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/jinzhu/copier"
@@ -67,9 +65,9 @@ func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interfac
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
alertCount := xpack.GetAlert(alertBase)
if alertCount != 0 {
item.AlertCount = alertCount
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
if alertInfo.SendCount != 0 {
item.AlertCount = alertInfo.SendCount
} else {
item.AlertCount = 0
}
@@ -91,9 +89,10 @@ func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, err
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
alertCount := xpack.GetAlert(alertBase)
if alertCount != 0 {
item.AlertCount = alertCount
alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable))
item.AlertMethod = alertInfo.Method
if alertInfo.SendCount != 0 {
item.AlertCount = alertInfo.SendCount
} else {
item.AlertCount = 0
}
@@ -313,13 +312,15 @@ func (u *CronjobService) Create(req dto.CronjobOperate) error {
return err
}
if req.AlertCount != 0 {
createAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertCount: req.AlertCount,
AlertType: cronjob.Type,
EntryID: cronjob.ID,
createAlert := dto.AlertCreate{
Title: req.AlertTitle,
SendCount: req.AlertCount,
Method: req.AlertMethod,
Type: cronjob.Type,
Project: strconv.Itoa(int(cronjob.ID)),
Status: constant.AlertEnable,
}
err := xpack.CreateAlert(createAlert)
err := NewIAlertService().CreateAlert(createAlert)
if err != nil {
return err
}
@@ -367,11 +368,7 @@ func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error {
if err := cronjobRepo.Delete(repo.WithByID(id)); err != nil {
return err
}
alertBase := dto.AlertBase{
AlertType: cronjob.Type,
EntryID: cronjob.ID,
}
err := xpack.DeleteAlert(alertBase)
err := alertRepo.Delete(alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))), alertRepo.WithByType(cronjob.Type))
if err != nil {
return err
}
@@ -445,13 +442,14 @@ func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error {
if err != nil {
return err
}
updateAlert := dto.CreateOrUpdateAlert{
AlertTitle: req.AlertTitle,
AlertType: cronModel.Type,
AlertCount: req.AlertCount,
EntryID: cronModel.ID,
updateAlert := dto.AlertCreate{
Title: req.AlertTitle,
SendCount: req.AlertCount,
Method: req.AlertMethod,
Type: cronjob.Type,
Project: strconv.Itoa(int(cronModel.ID)),
}
err = xpack.UpdateAlert(updateAlert)
err = NewIAlertService().ExternalUpdateAlert(updateAlert)
if err != nil {
return err
}
+9 -9
View File
@@ -57,7 +57,7 @@ func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, t
record.Name = app.App.Key
record.DetailName = app.Name
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
backupDir := path.Join(global.Dir.TmpDir, fmt.Sprintf("app/%s/%s", app.App.Key, app.Name))
backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/app/%s/%s", app.App.Key, app.Name))
record.FileName = fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
if err := doAppBackup(&app, task, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil {
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
@@ -108,7 +108,7 @@ func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Tim
record.Name = web.Alias
record.DetailName = web.Alias
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
backupDir := path.Join(global.Dir.TmpDir, fmt.Sprintf("website/%s", web.Alias))
backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/website/%s", web.Alias))
record.FileName = fmt.Sprintf("website_%s_%s.tar.gz", web.Alias, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
if err := doWebsiteBackup(&web, taskItem, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil {
@@ -163,9 +163,9 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti
record.DetailName = dbInfo.Name
record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs
backupDir := path.Join(global.Dir.TmpDir, fmt.Sprintf("database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name))
backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name))
record.FileName = fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" || cronjob.DBType == "mysql-cluster" {
if err := doMysqlBackup(dbInfo, backupDir, record.FileName); err != nil {
if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr {
retry++
@@ -215,7 +215,7 @@ func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.T
return err
}
fileName := fmt.Sprintf("%s.tar.gz", startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))
backupDir := path.Join(global.Dir.TmpDir, fmt.Sprintf("%s/%s", cronjob.Type, cronjob.Name))
backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/%s/%s", cronjob.Type, cronjob.Name))
fileOp := files.NewFileOp()
if cronjob.IsDir {
@@ -261,7 +261,7 @@ func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.T
}
nameItem := startTime.Format(constant.DateTimeSlimLayout) + common.RandStrAndNum(5)
fileName := fmt.Sprintf("system_log_%s.tar.gz", nameItem)
backupDir := path.Join(global.Dir.TmpDir, "log", nameItem)
backupDir := path.Join(global.Dir.LocalBackupDir, "tmp/log", nameItem)
if err := handleBackupLogs(taskItem, backupDir, fileName, cronjob.Secret); err != nil {
return err
}
@@ -354,7 +354,7 @@ type DatabaseHelper struct {
func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
var dbs []DatabaseHelper
if cronjob.DBName == "all" {
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" || cronjob.DBType == "mysql-cluster" {
databaseService := NewIDatabaseService()
mysqlItems, _ := databaseService.LoadItems(cronjob.DBType)
for _, mysql := range mysqlItems {
@@ -381,7 +381,7 @@ func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper {
dbNames := strings.Split(cronjob.DBName, ",")
for _, name := range dbNames {
itemID, _ := strconv.Atoi(name)
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" {
if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" || cronjob.DBType == "mysql-cluster" {
mysqlItem, _ := mysqlRepo.Get(repo.WithByID(uint(itemID)))
dbs = append(dbs, DatabaseHelper{
ID: mysqlItem.ID,
@@ -443,7 +443,7 @@ func handleBackupLogs(taskItem *task.Task, targetDir, fileName string, secret st
}
}
}
itemDir2 := path.Join(global.Dir.LocalBackupDir, "log/website", website.Alias)
itemDir2 := path.Join(global.Dir.LocalBackupDir, "tmp/log/website", website.Alias)
logFiles2, _ := os.ReadDir(itemDir2)
if len(logFiles2) != 0 {
for i := 0; i < len(logFiles2); i++ {
+5 -3
View File
@@ -9,6 +9,8 @@ import (
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
@@ -19,7 +21,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/1Panel-dev/1Panel/agent/utils/files"
"github.com/1Panel-dev/1Panel/agent/utils/ntp"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
)
func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
@@ -48,6 +49,7 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
global.LOG.Debugf("preper to handle cron job [%s] %s failed, err: %v", cronjob.Type, cronjob.Name, err)
record.TaskID = ""
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records)
handleCronJobAlert(cronjob)
return
}
if err := taskItem.Execute(); err != nil {
@@ -268,7 +270,7 @@ func (u *CronjobService) uploadCronjobBackFile(cronjob model.Cronjob, task *task
}()
var errItem error
accounts := strings.Split(cronjob.SourceAccountIDs, ",")
cloudSrc := strings.TrimPrefix(file, global.Dir.TmpDir+"/")
cloudSrc := strings.TrimPrefix(file, global.Dir.LocalBackupDir+"/tmp/")
for _, account := range accounts {
if len(account) != 0 {
task.LogStart(i18n.GetMsgWithMap("UploadFile", map[string]interface{}{
@@ -359,5 +361,5 @@ func handleCronJobAlert(cronjob *model.Cronjob) {
EntryID: cronjob.ID,
Param: cronjob.Type,
}
_ = xpack.PushAlert(pushAlert)
_ = alert_push.PushAlert(pushAlert)
}
+5 -2
View File
@@ -113,7 +113,7 @@ func (u *DashboardService) LoadCurrentInfoForNode() *dto.NodeCurrent {
memoryInfo, _ := mem.VirtualMemory()
currentInfo.MemoryTotal = memoryInfo.Total
currentInfo.MemoryAvailable = memoryInfo.Available
currentInfo.MemoryUsed = memoryInfo.Used
currentInfo.MemoryUsed = memoryInfo.Used + memoryInfo.Shared
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
swapInfo, _ := mem.SwapMemory()
@@ -210,8 +210,11 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
memoryInfo, _ := mem.VirtualMemory()
currentInfo.MemoryTotal = memoryInfo.Total
currentInfo.MemoryUsed = memoryInfo.Used + memoryInfo.Shared
currentInfo.MemoryFree = memoryInfo.Free
currentInfo.MemoryCache = memoryInfo.Cached + memoryInfo.Buffers
currentInfo.MemoryShard = memoryInfo.Shared
currentInfo.MemoryAvailable = memoryInfo.Available
currentInfo.MemoryUsed = memoryInfo.Used
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
swapInfo, _ := mem.SwapMemory()
+1 -1
View File
@@ -92,7 +92,7 @@ func (u *DatabaseService) LoadItems(dbType string) ([]dto.DatabaseItem, error) {
dbs, err := databaseRepo.GetList(databaseRepo.WithTypeList(dbType))
var datas []dto.DatabaseItem
for _, db := range dbs {
if dbType == "postgresql" {
if dbType == constant.AppPostgresql || dbType == constant.AppPostgresqlCluster {
items, _ := postgresqlRepo.List(postgresqlRepo.WithByPostgresqlName(db.Name))
for _, item := range items {
var dItem dto.DatabaseItem
+7 -1
View File
@@ -34,7 +34,7 @@ func (u *DBCommonService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.D
}
data.ContainerName = app.ContainerName
data.Name = app.Name
data.Port = int64(app.Port)
data.Port = app.Port
return &data, nil
}
@@ -42,6 +42,10 @@ func (u *DBCommonService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.D
func (u *DBCommonService) LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error) {
filePath := ""
switch req.Type {
case "mysql-cluster-conf":
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql-cluster/%s/conf/my.cnf", req.Name))
case "postgresql-cluster-conf":
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/postgresql-cluster/%s/data/postgresql.conf", req.Name))
case "mysql-conf":
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql/%s/conf/my.cnf", req.Name))
case "mariadb-conf":
@@ -50,6 +54,8 @@ func (u *DBCommonService) LoadDatabaseFile(req dto.OperationWithNameAndType) (st
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/postgresql/%s/data/postgresql.conf", req.Name))
case "redis-conf":
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/redis/%s/conf/redis.conf", req.Name))
case "redis-cluster-conf":
filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/redis-cluster/%s/conf/redis.conf", req.Name))
}
if _, err := os.Stat(filePath); err != nil {
return "", buserr.New("ErrHttpReqNotFound")
+9 -3
View File
@@ -534,7 +534,7 @@ func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlS
info.File = "OFF"
info.Position = "OFF"
masterStatus := "show master status;"
if common.CompareAppVersion(app.Version, "8.4.0") && req.Type == constant.AppMysql {
if common.CompareAppVersion(app.Version, "8.4.0") && (req.Type == constant.AppMysql || req.Type == constant.AppMysqlCluster) {
masterStatus = "show binary log status;"
}
rows, err := executeSqlForRows(app.ContainerName, app.Key, app.Password, masterStatus)
@@ -553,10 +553,13 @@ func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlS
}
func executeSqlForMaps(containerName, dbType, password, command string) (map[string]string, error) {
if dbType == "mysql-cluster" {
dbType = "mysql"
}
cmd := exec.Command("docker", "exec", containerName, dbType, "-uroot", "-p"+password, "-e", command)
stdout, err := cmd.CombinedOutput()
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
if err != nil || strings.HasPrefix(stdStr, "ERROR ") {
return nil, errors.New(stdStr)
}
@@ -572,10 +575,13 @@ func executeSqlForMaps(containerName, dbType, password, command string) (map[str
}
func executeSqlForRows(containerName, dbType, password, command string) ([]string, error) {
if dbType == "mysql-cluster" {
dbType = "mysql"
}
cmd := exec.Command("docker", "exec", containerName, dbType, "-uroot", "-p"+password, "-e", command)
stdout, err := cmd.CombinedOutput()
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
if err != nil || strings.HasPrefix(stdStr, "ERROR ") {
return nil, errors.New(stdStr)
}
return strings.Split(stdStr, "\n"), nil
+17 -17
View File
@@ -28,9 +28,9 @@ type IRedisService interface {
UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error
ChangePassword(info dto.ChangeRedisPass) error
LoadStatus(req dto.OperationWithName) (*dto.RedisStatus, error)
LoadConf(req dto.OperationWithName) (*dto.RedisConf, error)
LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error)
LoadStatus(req dto.LoadRedisStatus) (*dto.RedisStatus, error)
LoadConf(req dto.LoadRedisStatus) (*dto.RedisConf, error)
LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error)
CheckHasCli() bool
InstallCli() error
@@ -41,7 +41,7 @@ func NewIRedisService() IRedisService {
}
func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Database)
redisInfo, err := appInstallRepo.LoadBaseInfo(req.DBType, req.Database)
if err != nil {
return err
}
@@ -50,7 +50,7 @@ func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error {
confs = append(confs, redisConfig{key: "timeout", value: req.Timeout})
confs = append(confs, redisConfig{key: "maxclients", value: req.Maxclients})
confs = append(confs, redisConfig{key: "maxmemory", value: req.Maxmemory})
if err := confSet(redisInfo.Name, "", confs); err != nil {
if err := confSet(redisInfo.Name, req.DBType, "", confs); err != nil {
return err
}
if _, err := compose.Restart(fmt.Sprintf("%s/redis/%s/docker-compose.yml", global.Dir.AppInstallDir, redisInfo.Name)); err != nil {
@@ -84,7 +84,7 @@ func (u *RedisService) InstallCli() error {
Image: "redis:7.4.4",
Network: "1panel-network",
}
return NewIContainerService().ContainerCreate(item)
return NewIContainerService().ContainerCreate(item, false)
}
func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
@@ -107,7 +107,7 @@ func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error {
}
func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Database)
redisInfo, err := appInstallRepo.LoadBaseInfo(req.DBType, req.Database)
if err != nil {
return err
}
@@ -119,18 +119,18 @@ func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate)
confs = append(confs, redisConfig{key: "appendonly", value: req.Appendonly})
confs = append(confs, redisConfig{key: "appendfsync", value: req.Appendfsync})
}
if err := confSet(redisInfo.Name, req.Type, confs); err != nil {
if err := confSet(redisInfo.Name, req.DBType, req.Type, confs); err != nil {
return err
}
if _, err := compose.Restart(fmt.Sprintf("%s/redis/%s/docker-compose.yml", global.Dir.AppInstallDir, redisInfo.Name)); err != nil {
if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", global.Dir.AppInstallDir, req.DBType, redisInfo.Name)); err != nil {
return err
}
return nil
}
func (u *RedisService) LoadStatus(req dto.OperationWithName) (*dto.RedisStatus, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
func (u *RedisService) LoadStatus(req dto.LoadRedisStatus) (*dto.RedisStatus, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
@@ -157,8 +157,8 @@ func (u *RedisService) LoadStatus(req dto.OperationWithName) (*dto.RedisStatus,
return &info, nil
}
func (u *RedisService) LoadConf(req dto.OperationWithName) (*dto.RedisConf, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
func (u *RedisService) LoadConf(req dto.LoadRedisStatus) (*dto.RedisConf, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
@@ -174,8 +174,8 @@ func (u *RedisService) LoadConf(req dto.OperationWithName) (*dto.RedisConf, erro
return &item, nil
}
func (u *RedisService) LoadPersistenceConf(req dto.OperationWithName) (*dto.RedisPersistence, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo("redis", req.Name)
func (u *RedisService) LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error) {
redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name)
if err != nil {
return nil, err
}
@@ -214,8 +214,8 @@ type redisConfig struct {
value string
}
func confSet(redisName string, updateType string, changeConf []redisConfig) error {
path := fmt.Sprintf("%s/redis/%s/conf/redis.conf", global.Dir.AppInstallDir, redisName)
func confSet(redisName string, redisType string, updateType string, changeConf []redisConfig) error {
path := fmt.Sprintf("%s/%s/%s/conf/redis.conf", global.Dir.AppInstallDir, redisType, redisName)
lineBytes, err := os.ReadFile(path)
if err != nil {
return err
+1 -1
View File
@@ -120,7 +120,7 @@ func (u *DeviceService) Update(key, value string) error {
if err := ntp.UpdateSystemTimeZone(value); err != nil {
return err
}
go common.RestartService(true, true, false)
go common.RestartService(global.IsMaster, true, false)
case "DNS":
if err := updateDNS(strings.Split(value, ",")); err != nil {
return err
+1
View File
@@ -50,4 +50,5 @@ var (
taskRepo = repo.NewITaskRepo()
groupRepo = repo.NewIGroupRepo()
alertRepo = repo.NewIAlertRepo()
)
+18 -15
View File
@@ -18,6 +18,7 @@ import (
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/1Panel-dev/1Panel/agent/app/repo"
@@ -334,22 +335,24 @@ func (f *FileService) GetContent(op request.FileContentReq) (response.FileInfo,
if len(content) > 1024 {
content = content[:1024]
}
_, decodeName, _ := charset.DetermineEncoding(content, "")
decoder := files.GetDecoderByName(decodeName)
if decoder != nil {
reader := strings.NewReader(info.Content)
var dec *encoding.Decoder
if decodeName == "windows-1252" {
dec = simplifiedchinese.GBK.NewDecoder()
} else {
dec = decoder.NewDecoder()
if !utf8.Valid(content) {
_, decodeName, _ := charset.DetermineEncoding(content, "")
decoder := files.GetDecoderByName(decodeName)
if decoder != nil {
reader := strings.NewReader(info.Content)
var dec *encoding.Decoder
if decodeName == "windows-1252" {
dec = simplifiedchinese.GBK.NewDecoder()
} else {
dec = decoder.NewDecoder()
}
decodedReader := transform.NewReader(reader, dec)
contents, err := io.ReadAll(decodedReader)
if err != nil {
return response.FileInfo{}, err
}
info.Content = string(contents)
}
decodedReader := transform.NewReader(reader, dec)
contents, err := io.ReadAll(decodedReader)
if err != nil {
return response.FileInfo{}, err
}
info.Content = string(contents)
}
return response.FileInfo{FileInfo: *info}, nil
}
+5 -3
View File
@@ -164,10 +164,12 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
needRestart = repo.DownloadUrl == req.DownloadUrl
}
if repo.Protocol == "https" && req.Protocol == "http" {
if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil {
return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err)
if req.DownloadUrl != repo.DownloadUrl {
if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil {
return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err)
}
needRestart = true
}
needRestart = true
}
if needRestart {
if err := stopBeforeUpdateRepo(); err != nil {
+10 -2
View File
@@ -283,11 +283,19 @@ func (m McpServerService) BindDomain(req request.McpBindDomain) error {
}
}
group, _ := groupRepo.Get(groupRepo.WithByWebsiteDefault())
domain, err := ParseDomain(req.Domain)
if err != nil {
return err
}
if domain.Port == 0 {
domain.Port = nginxInstall.HttpPort
}
createWebsiteReq := request.WebsiteCreate{
Domains: []request.WebsiteDomain{
{
Domain: req.Domain,
Port: 80,
Domain: domain.Domain,
Port: domain.Port,
},
},
Alias: strings.ToLower(req.Domain),
+3
View File
@@ -211,6 +211,9 @@ func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.Runt
if err != nil {
return 0, nil, err
}
if len(runtimes) == 0 {
return 0, res, nil
}
if err = SyncRuntimesStatus(runtimes); err != nil {
return 0, nil, err
}
+7
View File
@@ -102,6 +102,13 @@ func handlePHP(create request.RuntimeCreate, runtime *model.Runtime, fileOp file
runtime.Params = string(forms)
runtime.Status = constant.StatusBuilding
go func() {
appDetail, err := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID))
if err == nil {
RequestDownloadCallBack(appDetail.DownloadCallBackUrl)
}
}()
go buildRuntime(runtime, "", "", false)
return
}
+6 -8
View File
@@ -61,6 +61,7 @@ func (u *SnapshotService) SnapshotCreate(parentTask *task.Task, req dto.Snapshot
Version: versionItem.Value,
Status: constant.StatusWaiting,
Timeout: req.Timeout,
}
if err := snapshotRepo.Create(&snap); err != nil {
global.LOG.Errorf("create snapshot record to db failed, err: %v", err)
@@ -79,7 +80,7 @@ func (u *SnapshotService) SnapshotCreate(parentTask *task.Task, req dto.Snapshot
}
if jobID == 0 {
go func() {
_ = handleSnapshot(req, taskItem, jobID, 3, 0)
_ = handleSnapshot(req, taskItem, jobID, 3, snap.Timeout)
}()
return nil
}
@@ -123,15 +124,11 @@ func (u *SnapshotService) SnapshotReCreate(id uint) error {
}
func handleSnapshot(req dto.SnapshotCreate, taskItem *task.Task, jobID, retry, timeout uint) error {
rootDir := path.Join(global.Dir.TmpDir, "system", req.Name)
rootDir := path.Join(global.Dir.LocalBackupDir, "tmp/system", req.Name)
openrestyDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR")
itemHelper := snapHelper{SnapID: req.ID, Task: *taskItem, FileOp: files.NewFileOp(), Ctx: context.Background(), OpenrestyDir: openrestyDir}
baseDir := path.Join(rootDir, "base")
_ = os.MkdirAll(baseDir, os.ModePerm)
if timeout == 0 {
timeout = 1800
}
taskItem.AddSubTaskWithAliasAndOps(
"SnapDBInfo",
func(t *task.Task) error {
@@ -406,6 +403,7 @@ func snapBackupData(snap snapHelper, req dto.SnapshotCreate, targetDir string) e
excludes := loadBackupExcludes(snap, req.BackupData)
excludes = append(excludes, req.IgnoreFiles...)
excludes = append(excludes, "./system_snapshot")
excludes = append(excludes, "./tmp")
for _, item := range req.AppData {
for _, itemApp := range item.Children {
if itemApp.Label == "appBackup" {
@@ -517,7 +515,7 @@ func snapCompress(snap snapHelper, rootDir string, secret string) error {
snap.Task.Log("---------------------- 7 / 8 ----------------------")
snap.Task.LogStart(i18n.GetMsgByKey("SnapCompress"))
tmpDir := path.Join(global.Dir.TmpDir, "system")
tmpDir := path.Join(global.Dir.LocalBackupDir, "tmp/system")
fileName := fmt.Sprintf("%s.tar.gz", path.Base(rootDir))
err := snap.FileOp.TarGzCompressPro(true, rootDir, path.Join(tmpDir, fileName), secret, "")
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapCompressFile"), err)
@@ -541,7 +539,7 @@ func snapUpload(snap snapHelper, accounts string, file string) error {
snap.Task.Log("---------------------- 8 / 8 ----------------------")
snap.Task.LogStart(i18n.GetMsgByKey("SnapUpload"))
source := path.Join(global.Dir.TmpDir, "system", path.Base(file))
source := path.Join(global.Dir.LocalBackupDir, "tmp/system", path.Base(file))
accountMap, err := NewBackupClientMap(strings.Split(accounts, ","))
snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapLoadBackup"), err)
if err != nil {
+2 -1
View File
@@ -78,7 +78,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
global.LOG.Errorf("new task for create snapshot failed, err: %v", err)
return err
}
rootDir := path.Join(global.Dir.TmpDir, "system", snap.Name)
rootDir := path.Join(global.Dir.LocalBackupDir, "tmp/system", snap.Name)
if _, err := os.Stat(rootDir); err != nil && os.IsNotExist(err) {
_ = os.MkdirAll(rootDir, os.ModePerm)
}
@@ -175,6 +175,7 @@ func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error {
itemHelper.Task.Log("---------------------- 9 / 11 ----------------------")
itemHelper.Task.LogStart(i18n.GetWithName("RecoverWebsite", snap.Name))
webFile := path.Join(rootDir, snap.Name, "/website.tar.gz")
_ = itemHelper.FileOp.CreateDir(snapJson.OperestyDir, os.ModePerm)
var err error
if itemHelper.FileOp.Stat(webFile) {
err = itemHelper.FileOp.TarGzExtractPro(webFile, snapJson.OperestyDir, "")
+1 -1
View File
@@ -524,7 +524,7 @@ func (w WebsiteSSLService) Update(update request.WebsiteSSLUpdate) error {
if update.OtherDomains != "" {
otherDomainArray := strings.Split(update.OtherDomains, "\n")
for _, domain := range otherDomainArray {
if !common.IsValidDomain(domain) {
if websiteSSL.Provider != constant.SelfSigned && !common.IsValidDomain(domain) {
return buserr.WithName("ErrDomainFormat", domain)
}
domains = append(domains, domain)
+20
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"log"
"net"
"os"
"path"
"path/filepath"
@@ -1443,3 +1444,22 @@ func hasDefaultServer(params []string) bool {
}
return false
}
func ParseDomain(domain string) (*model.WebsiteDomain, error) {
host, portStr, err := net.SplitHostPort(domain)
if err != nil {
return &model.WebsiteDomain{
Domain: domain,
Port: 0,
}, nil
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("invalid port in domain %s: %v", domain, err)
}
return &model.WebsiteDomain{
Domain: host,
Port: port,
}, nil
}
+1
View File
@@ -63,6 +63,7 @@ const (
TaskSync = "TaskSync"
TaskBuild = "TaskBuild"
TaskPull = "TaskPull"
TaskCommit = "TaskCommit"
TaskPush = "TaskPush"
TaskHandle = "TaskHandle"
)
+1
View File
@@ -1,6 +1,7 @@
base:
install_dir: /opt
mode: dev
is_demo: false
remote_url:
app_repo: https://apps-assets.fit2cloud.com
@@ -376,5 +376,12 @@
"file": "pcntl.so",
"versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84"],
"installed": false
},
{
"name": "sodium",
"check": "sodium",
"file": "sodium.so",
"versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83","84"],
"installed": false
}
]
+15
View File
@@ -10,3 +10,18 @@ const (
AlertPushError = "PushError"
AlertPushSuccess = "PushSuccess"
)
const (
CommonConfig = "common"
SMSConfig = "sms"
EmailConfig = "email"
)
const (
WeChat = "wechat"
SMS = "sms"
Email = "mail"
WeCom = "weCom"
DingTalk = "dingTalk"
FeiShu = "feiShu"
)
+11 -8
View File
@@ -6,14 +6,17 @@ const (
AppNormal = "Normal"
AppTakeDown = "TakeDown"
AppOpenresty = "openresty"
AppMysql = "mysql"
AppMariaDB = "mariadb"
AppPostgresql = "postgresql"
AppRedis = "redis"
AppPostgres = "postgres"
AppMongodb = "mongodb"
AppMemcached = "memcached"
AppOpenresty = "openresty"
AppMysql = "mysql"
AppMariaDB = "mariadb"
AppPostgresql = "postgresql"
AppRedis = "redis"
AppPostgres = "postgres"
AppMongodb = "mongodb"
AppMemcached = "memcached"
AppMysqlCluster = "mysql-cluster"
AppPostgresqlCluster = "postgresql-cluster"
AppRedisCluster = "redis-cluster"
AppResourceLocal = "local"
AppResourceRemote = "remote"
+4
View File
@@ -15,6 +15,7 @@ var (
MonitorDB *gorm.DB
TaskDB *gorm.DB
CoreDB *gorm.DB
AlertDB *gorm.DB
LOG *logrus.Logger
CONF ServerConfig
@@ -30,4 +31,7 @@ var (
IsMaster bool
I18n *i18n.Localizer
AlertBaseJobID cron.EntryID
AlertResourceJobID cron.EntryID
)
+1 -1
View File
@@ -48,7 +48,7 @@ require (
github.com/upyun/go-sdk v2.1.0+incompatible
golang.org/x/crypto v0.39.0
golang.org/x/net v0.41.0
golang.org/x/oauth2 v0.29.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sys v0.33.0
golang.org/x/text v0.26.0
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38
+2
View File
@@ -1092,6 +1092,8 @@ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+1
View File
@@ -133,6 +133,7 @@ func Init() {
_, _ = bundle.LoadMessageFileFS(fs, "lang/ru.yaml")
_, _ = bundle.LoadMessageFileFS(fs, "lang/ms.yaml")
_, _ = bundle.LoadMessageFileFS(fs, "lang/ko.yaml")
_, _ = bundle.LoadMessageFileFS(fs, "lang/tr.yaml")
lang := GetLanguageFromDB()
global.I18n = i18n.NewLocalizer(bundle, lang)
}
+17 -2
View File
@@ -177,7 +177,7 @@ ErrPgImagePull: 'Image pull timed out, please configure image acceleration or ma
#runtime
ErrFileNotExist: '{{ .detail }} file does not exist! Please check the integrity of the source file!'
ErrImageBuildErr: 'Image build failed'
ErrImageExist: 'Image already exists!'
ErrImageExist: "Image already exists! Please modify the image name."
ErrDelWithWebsite: 'The operating environment is already associated with a website and cannot be deleted'
ErrRuntimeStart: 'Startup failed'
ErrPackageJsonNotFound: 'package.json file does not exist'
@@ -287,6 +287,7 @@ TaskBackup: 'Backup'
TaskRecover: 'Recover'
TaskRollback: 'Rollback'
TaskPull: 'Pull'
TaskCommit: 'Commit'
TaskBuild: 'Build'
TaskPush: 'Push'
TaskHandle: 'Execute'
@@ -423,4 +424,18 @@ ErrAlertSync: 'Alarm information synchronization error, please check and try aga
ErrAlertRemote: 'Alarm message remote error, please check and try again!'
#task - runtime
ErrInstallExtension: "An installation task is already in progress, please wait for the task to finish"
ErrInstallExtension: "An installation task is already in progress, please wait for the task to finish"
# alert mail template
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."
+18 -3
View File
@@ -177,7 +177,7 @@ ErrPgImagePull: 'イメージのプルがタイムアウトしました。イメ
#runtime
ErrFileNotExist: '{{ .detail }} ファイルが存在しません。ソース ファイルの整合性を確認してください。'
ErrImageBuildErr: 'イメージのビルドに失敗しました'
ErrImageExist: '画像がすでに存在します!'
ErrImageExist: "イメージはすでに存在します!イメージ名を変更してください。"
ErrDelWithWebsite: 'オペレーティング環境は既に Web サイトに関連付けられているため、削除できません'
ErrRuntimeStart: '起動に失敗しました'
ErrPackageJsonNotFound: 'package.json ファイルが存在しません'
@@ -287,6 +287,7 @@ TaskBackup: 'バックアップ'
TaskRecover: '回復'
TaskRollback: 'ロールバック'
TaskPull: 'プル'
TaskCommit: 'コミット'
TaskBuild: 'ビルド'
TaskPush: 'プッシュ'
TaskHandle: '実行'
@@ -325,7 +326,7 @@ CustomAppstore: 'カスタム アプリケーション ウェアハウス'
OllamaModelPull: 'Ollama モデル {{ .name }} をプルします'
OllamaModelSize: "Ollama モデル {{ .name }} のサイズを取得します"
#task - snapshot
#task - snapshot
Snapshot: 'スナップショット'
SnapDBInfo: '1Panel データベース情報を書き込む'
SnapCopy: 'ファイルとディレクトリ {{ .name }} をコピーします'
@@ -423,4 +424,18 @@ ErrAlertSync: 'アラーム情報の同期エラーです。確認してもう
ErrAlertRemote: 'アラーム メッセージのリモート エラーです。確認してもう一度お試しください。'
#task - runtime
ErrInstallExtension: "インストールタスクが進行中です、タスクが終了するのを待ってください"
ErrInstallExtension: "インストールタスクが進行中です、タスクが終了するのを待ってください"
# alert mail template
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 }} 日後に期限切れとなります。詳細はパネルでご確認ください。"
+17 -2
View File
@@ -177,7 +177,7 @@ ErrPgImagePull: '이미지 풀링 시간이 초과되었습니다. 이미지 가
#실행 시간
ErrFileNotExist: '{{ .detail }} 파일이 존재하지 않습니다! 소스 파일의 무결성을 확인하세요!'
ErrImageBuildErr: '이미지 빌드 실패'
ErrImageExist: '이미지가 이미 존재합니다!'
ErrImageExist: "이미지가 이미 존재합니다! 이미지 이름을 수정하세요."
ErrDelWithWebsite: '운영 환경이 이미 웹사이트와 연결되어 있어 삭제할 수 없습니다'
ErrRuntimeStart: '시작 실패'
ErrPackageJsonNotFound: 'package.json 파일이 존재하지 않습니다'
@@ -287,6 +287,7 @@ TaskBackup: '백업'
TaskRecover: '복구'
TaskRollback: '롤백'
TaskPull: '당기기'
TaskCommit: '커밋'
TaskBuild: '빌드'
TaskPush: '푸시'
TaskHandle: '실행'
@@ -423,4 +424,18 @@ ErrAlertSync: '알람 정보 동기화 오류입니다. 확인하고 다시 시
ErrAlertRemote: '알람 메시지 원격 오류, 확인하고 다시 시도하세요!'
#task - runtime
ErrInstallExtension: "이미 설치 작업이 진행 중입니다. 작업이 완료될 때까지 기다려 주세요."
ErrInstallExtension: "이미 설치 작업이 진행 중입니다. 작업이 완료될 때까지 기다려 주세요."
# alert mail template
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 }}일 후에 만료됩니다. 자세한 내용은 패널에서 확인하세요."
+17 -2
View File
@@ -176,7 +176,7 @@ ErrPgImagePull: 'Tarikh imej tamat masa, sila konfigurasikan pecutan imej atau t
#masa berjalan
ErrFileNotExist: 'Fail {{ .detail }} tidak wujud! Sila semak integriti fail sumber!'
ErrImageBuildErr: 'Pembinaan imej gagal'
ErrImageExist: 'Imej sudah wujud!'
ErrImageExist: "Imej sudah wujud! Sila ubah nama imej."
ErrDelWithWebsite: 'Persekitaran pengendalian sudah dikaitkan dengan tapak web dan tidak boleh dipadamkan'
ErrRuntimeStart: 'Permulaan gagal'
ErrPackageJsonNotFound: 'fail package.json tidak wujud'
@@ -286,6 +286,7 @@ TaskBackup: 'Sandaran'
TaskRecover: 'Pulihkan'
TaskRollback: 'Rollback'
TaskPull: 'Tarik'
TaskCommit: 'Komit'
TaskBuild: 'Bina'
TaskPush: 'Tolak'
TaskHandle: 'Laksanakan'
@@ -422,4 +423,18 @@ ErrAlertSync: 'Ralat penyegerakan maklumat penggera, sila semak dan cuba lagi!'
ErrAlertRemote: 'Ralat jauh mesej penggera, sila semak dan cuba lagi!'
#task - runtime
ErrInstallExtension: "Tugas pemasangan sudah sedang berjalan, silakan tunggu tugas selesai"
ErrInstallExtension: "Tugas pemasangan sudah sedang berjalan, silakan tunggu tugas selesai"
# alert mail template
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."
+17 -2
View File
@@ -177,7 +177,7 @@ ErrPgImagePull: 'Tempo limite para extração de imagem. Configure a aceleraçã
#tempo de execução
ErrFileNotExist: 'O arquivo {{ .detail }} não existe! Verifique a integridade do arquivo de origem!'
ErrImageBuildErr: 'Falha na criação da imagem'
ErrImageExist: 'A imagem já existe!'
ErrImageExist: "A imagem já existe! Por favor, modifique o nome da imagem."
ErrDelWithWebsite: 'O ambiente operacional já está associado a um site e não pode ser excluído'
ErrRuntimeStart: 'Falha na inicialização'
ErrPackageJsonNotFound: 'o arquivo package.json não existe'
@@ -287,6 +287,7 @@ Backup de Tarefa: 'Backup'
TaskRecover: 'Recuperar'
TaskRollback: 'Reverter'
TaskPull: 'Puxar'
TaskCommit: 'Commit'
TaskBuild: 'Construir'
TaskPush: 'Empurrar'
TaskHandle: 'Executar'
@@ -423,4 +424,18 @@ ErrAlertSync: 'Erro de sincronização de informações de alarme, verifique e t
ErrAlertRemote: 'Erro remoto na mensagem de alarme, verifique e tente novamente!'
#task - runtime
ErrInstallExtension: "Já existe uma tarefa de instalação em andamento, aguarde a conclusão da tarefa"
ErrInstallExtension: "Já existe uma tarefa de instalação em andamento, aguarde a conclusão da tarefa"
# alert mail template
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."
+17 -2
View File
@@ -177,7 +177,7 @@ ErrPgImagePull: 'Время извлечения изображения исте
#время выполнения
ErrFileNotExist: 'Файл {{ .detail }} не существует! Проверьте целостность исходного файла!'
ErrImageBuildErr: 'Сборка образа не удалась'
ErrImageExist: 'Изображение уже существует!'
ErrImageExist: "Изображение уже существует! Пожалуйста, измените имя изображения."
ErrDelWithWebsite: 'Операционная среда уже связана с веб-сайтом и не может быть удалена'
ErrRuntimeStart: 'Запуск не удался'
ErrPackageJsonNotFound: 'файл package.json не существует'
@@ -287,6 +287,7 @@ TaskBackup: 'Резервное копирование'
TaskRecover: 'Восстановить'
TaskRollback: 'Откат'
TaskPull: 'Вытянуть'
TaskCommit: 'Kоммит'
ЗадачаСборка: 'Сборка'
TaskPush: 'Push'
TaskHandle: 'Выполнить'
@@ -423,4 +424,18 @@ ErrAlertSync: 'Ошибка синхронизации информации о
ErrAlertRemote: 'Ошибка удаленного сообщения об ошибке, проверьте и повторите попытку!'
#task - runtime
ErrInstallExtension: "Уже выполняется задача установки, подождите, пока задача завершится"
ErrInstallExtension: "Уже выполняется задача установки, подождите, пока задача завершится"
# alert mail template
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 }} дней. Подробности смотрите в панели."
+439
View File
@@ -0,0 +1,439 @@
ErrInvalidParams: 'İstek parametre hatası: {{ .detail }}'
ErrTokenParse: 'Token oluşturma hatası: {{ .detail }}'
ErrInitialPassword: 'Orijinal şifre yanlış'
ErrInternalServer: 'Dahili sunucu hatası: {{ .detail }}'
ErrRecordExist: 'Kayıt zaten mevcut'
ErrRecordNotFound: 'Kayıt bulunamadı'
ErrStructTransform: 'Tür dönüştürme başarısız: {{ .err }}'
ErrNotLogin: 'Kullanıcı giriş yapmamış: {{ .detail }}'
ErrPasswordExpired: 'Mevcut şifrenin süresi dolmuş: {{ .detail }}'
ErrNotSupportType: 'Sistem mevcut türü desteklemiyor: {{ .name }}'
ErrProxy: 'İstek hatası, lütfen düğüm durumunu kontrol edin: {{ .detail }}'
ErrApiConfigStatusInvalid: 'API arayüzüne erişim yasak: {{ .detail }}'
ErrApiConfigKeyInvalid: 'API arayüz anahtarı hatası: {{ .detail }}'
ErrApiConfigIPInvalid: 'API arayüzünü çağırmak için kullanılan IP beyaz listede değil: {{ .detail }}'
ErrApiConfigDisable: 'Bu arayüz API arayüz çağrılarının kullanımını yasaklıyor: {{ .detail }}'
ErrApiConfigKeyTimeInvalid: 'API arayüz zaman damgası hatası: {{ .detail }}'
#common
ErrUsernameIsExist: 'Kullanıcı adı zaten mevcut'
ErrNameIsExist: 'İsim zaten mevcut'
ErrDemoEnvironment: 'Demo sunucu, bu işlem yasak!'
ErrCmdTimeout: 'Komut yürütme zaman aşımına uğradı!'
ErrCmdIllegal: 'Yürütme komutunda yasak karakterler var, lütfen değiştirin ve tekrar deneyin!'
ErrPortExist: '{{ .port }} portu zaten {{ .type }} [{{ .name }}] tarafından kullanılıyor'
TYPE_APP: 'Uygulama'
TYPE_RUNTIME: 'Çalışma ortamı'
TYPE_DOMAIN: 'Alan Adı'
ErrTypePort: 'Port {{ .name }} formatı yanlış'
ErrTypePortRange: 'Port aralığının 1-65535 arasında olması gerekiyor'
Success: 'Başarılı'
Failed: 'Başarısız'
SystemRestart: 'Sistem yeniden başlatması nedeniyle görev kesildi'
ErrGroupIsDefault: 'Varsayılan grup, silinemez'
ErrGroupIsInWebsiteUse: 'Grup başka bir web sitesi tarafından kullanılıyor ve silinemez.'
#backup
ErrBackupInUsed: 'Yedekleme hesabı zamanlanmış görevde kullanıldı ve silinemez.'
ErrBackupCheck: 'Yedekleme hesabı test bağlantısı başarısız {{ .err }}'
ErrBackupLocalDelete: 'Yerel sunucu yedekleme hesabını silme henüz desteklenmiyor'
ErrBackupLocalCreate: 'Yerel sunucu yedekleme hesapları oluşturma henüz desteklenmiyor'
#app
ErrPortInUsed: '{{ .detail }} portu zaten kullanılıyor!'
ErrAppLimit: 'Yüklenen uygulama sayısı sınırı aştı'
ErrNotInstall: 'Uygulama yüklenmedi'
ErrPortInOtherApp: '{{ .port }} portu zaten {{ .apps }} uygulaması tarafından kullanılıyor!'
ErrDbUserNotValid: 'Mevcut veritabanı, kullanıcı adı ve şifre eşleşmiyor!'
ErrUpdateBuWebsite: 'Uygulama başarıyla güncellendi, ancak web sitesi yapılandırma dosyası değiştirme başarısız. Lütfen yapılandırmayı kontrol edin! '
Err1PanelNetworkFailed: 'Varsayılan konteyner ağ oluşturma başarısız! {{ .detail }}'
ErrFileParse: 'Uygulama docker-compose dosya ayrıştırma başarısız!'
ErrInstallDirNotFound: 'Kurulum dizini mevcut değil. Kaldırmanız gerekiyorsa, lütfen Zorla Kaldır seçeneğini seçin'
AppStoreIsUpToDate: 'Uygulama mağazası zaten en son sürüm'
LocalAppVersionNull: '{{ .name }} uygulaması sürüme senkronize edilmedi! Uygulama listesine eklenemez'
LocalAppVersionErr: '{{ .name }} senkronizasyon sürümü {{ .version }} başarısız! {{ .err }}'
ErrFileNotFound: '{{ .name }} dosyası mevcut değil'
ErrFileParseApp: '{{ .name }} dosya ayrıştırma başarısız {{ .err }}'
ErrAppDirNull: 'Sürüm klasörü mevcut değil'
LocalAppErr: 'Uygulama {{ .name }} senkronizasyonu başarısız! {{ .err }}'
ErrContainerName: 'Konteyner adı zaten mevcut'
ErrCreateHttpClient: 'İstek oluşturma başarısız {{ .err }}'
ErrHttpReqTimeOut: 'İstek zaman aşımı {{ .err }}'
ErrHttpReqFailed: 'İstek başarısız {{ .err }}'
ErrNoSuchHost: 'İstenen sunucu bulunamıyor {{ .err }}'
ErrHttpReqNotFound: 'İstenen kaynak {{ .err }} bulunamadı'
ErrImagePullTimeOut: 'Image çekme zaman aşımı'
ErrContainerNotFound: '{{ .name }} konteyneri mevcut değil'
ErrContainerMsg: '{{ .name }} konteyneri anormal. Lütfen ayrıntılar için konteyner sayfasındaki günlüğü kontrol edin'
ErrAppBackup: '{{ .name }} uygulama yedeklemesi başarısız {{ .err }}'
ErrVersionTooLow: 'Mevcut 1Panel sürümü Uygulama Mağazasını güncellemek için çok düşük. Lütfen işlemden önce sürümü yükseltin.'
ErrAppNameExist: 'Uygulama adı zaten mevcut'
AppStoreIsSyncing: 'Uygulama Mağazası senkronize ediliyor, lütfen daha sonra tekrar deneyin'
ErrGetCompose: 'docker-compose.yml dosyası alınamadı! {{ .detail }}'
ErrAppWarn: 'Anormal durum, lütfen günlüğü kontrol edin'
ErrAppParamKey: 'Parametre {{ .name }} alanı anormal'
ErrAppUpgrade: 'Uygulama {{ .name }} yükseltmesi başarısız {{ .err }}'
AppRecover: 'Uygulama {{ .name }} geri alma'
PullImageStart: 'Image {{ .name }} çekmeye başla'
PullImageSuccess: 'Image çekme başarılı'
AppStoreIsLastVersion: 'Uygulama Mağazası zaten en son sürüm'
AppStoreSyncSuccess: 'Uygulama Mağazası senkronizasyonu başarılı'
SyncAppDetail: 'Uygulama yapılandırmasını senkronize et'
AppVersionNotMatch: '{{ .name }} uygulaması daha yüksek 1Panel sürümü gerektiriyor, senkronizasyon atlanıyor'
MoveSiteDir: 'Mevcut yükseltme OpenResty web sitesi dizininin taşınmasını gerektiriyor'
MoveSiteToDir: 'Site dizinini {{ .name }} konumuna taşı'
ErrMoveSiteDir: 'Site dizini taşıma başarısız'
MoveSiteDirSuccess: 'Web sitesi dizini taşıma başarılı'
DeleteRuntimePHP: 'PHP çalışma zamanını sil'
CustomAppStoreFileValid: 'Uygulama mağazası paketlerinin .tar.gz formatında olması gerekiyor'
PullImageTimeout: 'Image çekme zaman aşımı, lütfen image hızlandırmayı artırmayı deneyin veya başka bir image hızlandırmaya geçin'
ErrAppIsDown: '{{ .name }} uygulama durumu anormal, lütfen kontrol edin'
ErrCustomApps: 'Yüklü bir uygulama var, lütfen önce kaldırın'
ErrCustomRuntimes: 'Yüklü bir çalışma ortamı var, lütfen önce silin'
ErrAppVersionDeprecated: "{{ .name }} uygulaması mevcut 1Panel sürümü ile uyumlu değil, atlandı"
ErrDockerFailed: "Docker durumu anormal, lütfen servis durumunu kontrol edin"
ErrDockerComposeCmdNotFound: "Docker Compose komutu mevcut değil, lütfen önce bu komutu host makinesine yükleyin"
#file
ErrFileCanNotRead: 'Bu dosya önizlemeyi desteklemiyor'
ErrFileToLarge: 'Dosya 10M'dan büyük ve açılamıyor'
ErrPathNotFound: 'Dizin mevcut değil'
ErrMovePathFailed: 'Hedef yol orijinal yolu içeremez!'
ErrLinkPathNotFound: 'Hedef yol mevcut değil!'
ErrFileIsExist: 'Dosya veya klasör zaten mevcut!'
ErrFileUpload: '{{ .name }} dosya yükleme başarısız {{ .detail }}'
ErrFileDownloadDir: 'Klasör indirme desteklenmiyor'
ErrCmdNotFound: '{{ .name}} komutu mevcut değil, lütfen önce bu komutu host'a yükleyin'
ErrSourcePathNotFound: 'Kaynak dizin mevcut değil'
ErrFavoriteExist: 'Bu yol zaten favorilere eklendi'
ErrInvalidChar: 'Yasak karakterlere izin verilmiyor'
ErrPathNotDelete: 'Seçilen dizin silinemez'
ErrLogFileToLarge: "Günlük dosyası 500MB'ı aşıyor ve açılamıyor"
#website
ErrAliasIsExist: 'Takma ad zaten mevcut'
ErrBackupMatch: 'Yedekleme dosyası mevcut web sitesi verilerinin bir kısmıyla eşleşmiyor {{ .detail }}'
ErrBackupExist: 'Yedekleme dosyasındaki kaynak verilerin karşılık gelen kısmı mevcut değil {{ .detail }}'
ErrPHPResource: 'Yerel işletim ortamı değiştirmeyi desteklemiyor! '
ErrPathPermission: 'Dizin dizininde 1000:1000 olmayan izinlere sahip bir klasör tespit edildi, bu web sitesinde Erişim reddedildi hatasına neden olabilir. Lütfen yukarıdaki Kaydet düğmesine tıklayın'
ErrDomainIsUsed: 'Alan adı zaten [{{ .name }}] web sitesi tarafından kullanılıyor'
ErrDomainFormat: '{{ .name }} alan adı formatı yanlış'
ErrDefaultAlias: 'default ayrılmış bir kod, lütfen başka bir kod kullanın'
ErrParentWebsite: 'Önce {{ .name }} alt sitesini silmeniz gerekiyor'
ErrBuildDirNotFound: 'Yapı dizini mevcut değil'
ErrImageNotExist: 'İşletim ortamı {{ .name }} image mevcut değil, lütfen işletim ortamını yeniden düzenleyin'
ErrProxyIsUsed: "Yük dengeleme ters proxy tarafından kullanıldı, silinemez"
ErrSSLValid: 'Sertifika dosyası anormal, lütfen sertifika durumunu kontrol edin!'
#ssl
ErrSSLCannotDelete: '{{ .name }} sertifikası bir web sitesi tarafından kullanılıyor ve silinemez'
ErrAccountCannotDelete: 'Hesap bir sertifikayla ilişkili ve silinemez'
ErrSSLApply: 'Sertifika yenileme başarılı, openresty yeniden yükleme başarısız, lütfen yapılandırmayı kontrol edin!'
ErrEmailIsExist: 'Posta kutusu zaten mevcut'
ErrSSLKeyNotFound: 'Özel anahtar dosyası mevcut değil'
ErrSSLCertificateNotFound: 'Sertifika dosyası mevcut değil'
ErrSSLKeyFormat: 'Özel anahtar dosyası doğrulama başarısız'
ErrSSLCertificateFormat: 'Sertifika dosyası formatı yanlış, lütfen pem formatını kullanın'
ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid veya EabHmacKey boş olamaz'
ErrOpenrestyNotFound: 'Http modu önce Openresty yüklenmesini gerektiriyor'
ApplySSLStart: 'Sertifika başvurusu başlatılıyor, alan adı [{{ .domain }}] başvuru yöntemi [{{ .type }}] '
dnsAccount: 'DNS Otomatik'
dnsManual: 'DNS Manuel'
http: 'HTTP'
ApplySSLFailed: '[{{ .domain }}] sertifika başvurusu başarısız, {{ .detail }} '
ApplySSLSuccess: '[{{ .domain }}] sertifika başvurusu başarılı! !'
DNSAccountName: 'DNS hesabı [{{ .name }}] sağlayıcı [{{ .type }}]'
PushDirLog: 'Sertifika [{{ .path }}] dizinine gönderildi {{ .status }}'
ErrDeleteCAWithSSL: 'Mevcut kuruluşun verilmiş bir sertifikası var ve silinemez.'
ErrDeleteWithPanelSSL: 'Panel SSL yapılandırması bu sertifikayı kullanıyor ve silinemez'
ErrDefaultCA: 'Varsayılan otorite silinemez'
ApplyWebSiteSSLLog: '{{ .name }} web sitesi sertifikası yenilemeyi başlatıyor'
ErrUpdateWebsiteSSL: '{{ .name }} web sitesi sertifika güncelleme başarısız: {{ .err }}'
ApplyWebSiteSSLSuccess: 'Web sitesi sertifikası güncelleme başarılı'
ErrExecShell: 'Betik yürütme başarısız {{ .err }}'
ExecShellStart: 'Betik yürütmeyi başlat'
ExecShellSuccess: 'Betik yürütme başarılı'
StartUpdateSystemSSL: 'Sistem sertifikası güncellemeyi başlat'
UpdateSystemSSLSuccess: 'Sistem sertifikası güncelleme başarılı'
ErrWildcardDomain: 'HTTP modunda joker alan adı sertifikası başvurusu yapılamıyor'
#mysql
ErrUserIsExist: 'Mevcut kullanıcı zaten mevcut, lütfen yeniden girin'
ErrDatabaseIsExist: 'Mevcut veritabanı zaten mevcut, lütfen yeniden girin'
ErrExecTimeOut: 'SQL yürütme zaman aşımı, lütfen veritabanını kontrol edin'
ErrRemoteExist: 'Uzak veritabanında bu adla zaten mevcut, lütfen değiştirin ve tekrar deneyin'
ErrLocalExist: 'Yerel veritabanında bu ad zaten mevcut, lütfen değiştirin ve tekrar deneyin'
#redis
ErrTypeOfRedis: 'Kurtarma dosyası türü mevcut kalıcılık yöntemiyle eşleşmiyor, lütfen değiştirin ve tekrar deneyin'
#container
ErrInUsed: '{{ .detail }} kullanımda ve silinemez'
ErrObjectInUsed: 'Nesne kullanımda ve silinemez'
ErrObjectBeDependent: 'Bu image diğer imagelere bağlı 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'
#runtime
ErrFileNotExist: '{{ .detail }} dosyası mevcut değil! Lütfen kaynak dosyanın bütünlüğünü kontrol edin!'
ErrImageBuildErr: 'Image yapı başarısız'
ErrImageExist: "Görüntü zaten var! Lütfen görüntü adını değiştirin."
ErrDelWithWebsite: 'İşletim ortamı zaten bir web sitesiyle ilişkili ve silinemez'
ErrRuntimeStart: 'Başlatma başarısız'
ErrPackageJsonNotFound: 'package.json dosyası mevcut değil'
ErrScriptsNotFound: 'package.json içinde scripts yapılandırma öğesi bulunamadı'
ErrContainerNameNotFound: 'Konteyner adı alınamıyor, lütfen .env dosyasını kontrol edin'
ErrNodeModulesNotFound: 'node_modules klasörü mevcut değil! Lütfen çalışma ortamını düzenleyin veya çalışma ortamının başarıyla başlatılmasını bekleyin'
ErrContainerNameIsNull: 'Konteyner adı mevcut değil'
ErrPHPPortIsDefault: "9000 portu varsayılan port, lütfen değiştirin ve tekrar deneyin"
ErrPHPRuntimePortFailed: "{{ .name }} portu mevcut çalışma ortamı tarafından zaten kullanılıyor, lütfen değiştirin ve tekrar deneyin"
#tool
ErrConfigNotFound: 'Yapılandırma dosyası mevcut değil'
ErrConfigParse: 'Yapılandırma dosyası formatı yanlış'
ErrConfigIsNull: 'Yapılandırma dosyası boş olamaz'
ErrConfigDirNotFound: 'Çalışma dizini mevcut değil'
ErrConfigAlreadyExist: 'Aynı ada sahip bir yapılandırma dosyası zaten mevcut'
ErrUserFindErr: 'Kullanıcı {{ .name }} arama başarısız {{ .err }}'
#cronjob
CutWebsiteLogSuccess: '{{ .name }} web sitesi günlüğü başarıyla kesildi, yedekleme yolu {{ .path }}'
HandleShell: 'Betik {{ .name }} yürüt'
HandleNtpSync: 'Sistem zaman senkronizasyonu'
HandleSystemClean: 'Sistem önbellek temizliği'
SystemLog: 'Sistem Günlüğü'
CutWebsiteLog: 'Web Sitesi Günlüğünü Döndür'
FileOrDir: 'Dizin / Dosya'
UploadFile: 'Yedekleme dosyası {{ .file }} {{ .backup }} konumuna yükleniyor'
#toolbox
ErrNotExistUser: 'Mevcut kullanıcı mevcut değil, lütfen değiştirin ve tekrar deneyin!'
ErrBanAction: 'Ayarlama başarısız. Mevcut {{ .name }} servisi kullanılamıyor. Lütfen kontrol edin ve tekrar deneyin!'
ErrClamdscanNotFound: 'clamdscan komutu tespit edilmedi, lütfen yüklemek için belgeleri inceleyin!'
#waf
ErrScope: 'Bu yapılandırmayı değiştirme desteklenmiyor'
ErrStateChange: 'Durum değiştirme başarısız'
ErrRuleExist: 'Kural zaten mevcut'
ErrRuleNotExist: 'Kural mevcut değil'
ErrParseIP: 'Yanlış IP formatı'
ErrDefaultIP: 'default ayrılmış bir isim, lütfen başka bir isme değiştirin'
ErrGroupInUse: 'IP grubu kara liste/beyaz liste tarafından kullanılıyor ve silinemez'
ErrIPGroupAclUse: "IP grubu {{ .name }} web sitesinin özel kuralları tarafından kullanılıyor, silinemez"
ErrGroupExist: 'IP grup adı zaten mevcut'
ErrIPRange: 'Yanlış IP aralığı'
ErrIPExist: 'IP zaten mevcut'
urlDefense: 'URL kuralları'
urlHelper: 'Yasak URL'
dirFilter: 'Dizin filtresi'
xss: 'XSS'
phpExec: 'PHP betik yürütme'
oneWordTrojan: 'Tek Kelime Truva Atı'
appFilter: 'Tehlikeli dizin filtreleme uygula'
webshell: 'Webshell'
args: 'Parametre kuralları'
protocolFilter: 'Protokol filtreleme'
javaFileter: 'Java tehlikeli dosya filtresi'
scannerFilter: 'Tarayıcı filtresi'
escapeFilter: 'kaçış filtresi'
customRule: 'Özel kural'
httpMethod: 'HTTP yöntem filtreleme'
fileExt: 'Dosya yükleme kısıtlamaları'
defaultIpBlack: 'Kötü niyetli IP grubu'
cookie: 'Cookie Kuralları'
urlBlack: 'URL kara listesi'
uaBlack: 'User-Agent kara listesi'
attackCount: 'Saldırı sıklığı sınırı'
fileExtCheck: 'Dosya yükleme kısıtlamaları'
geoRestrict: 'Bölgesel erişim kısıtlamaları'
unknownWebsite: 'Yetkisiz alan adı erişimi'
notFoundCount: '404 Hız Sınırı'
headerDefense: 'Başlık kuralları'
defaultUaBlack: 'User-Agent kuralları'
methodWhite: 'HTTP kuralları'
captcha: 'insan-makine doğrulaması'
fiveSeconds: '5 saniye doğrulaması'
vulnCheck: 'Tamamlayıcı kurallar'
acl: 'Özel kurallar'
sql: 'SQL enjeksiyonu'
cc: 'Erişim sıklığı sınırı'
defaultUrlBlack: 'URL kuralları'
sqlInject: 'SQL enjeksiyonu'
ErrDBNotExist: 'Veritabanı mevcut değil'
allow: 'izin ver'
deny: 'reddet'
OpenrestyNotFound: 'Openresty yüklü değil'
remoteIpIsNull: "IP listesi boş"
#task
TaskStart: '{{ .name }} görevi başlıyor [BAŞLAT]'
TaskEnd: '{{ .name }} Görev tamamlandı [TAMAMLANDI]'
TaskFailed: '{{ .name }} görev başarısız'
TaskTimeout: '{{ .name }} zaman aşımına uğradı'
TaskSuccess: '{{ .name }} Görev başarılı'
TaskRetry: '{{ .name }}. yeniden denemeyi başlat'
SubTaskSuccess: '{{ .name }} başarılı'
SubTaskFailed: '{{ .name }} başarısız: {{ .err }}'
TaskInstall: 'Yükle'
TaskUninstall: 'Kaldır'
TaskCreate: 'Oluştur'
TaskDelete: 'Sil'
TaskUpgrade: 'Yükselt'
TaskUpdate: 'Güncelle'
TaskRestart: 'Yeniden Başlat'
TaskBackup: 'Yedekle'
TaskRecover: 'Kurtar'
TaskRollback: 'Geri Al'
TaskPull: 'Çek'
TaskCommit: 'işleme'
TaskBuild: 'Yapı'
TaskPush: 'Gönder'
TaskHandle: 'Yürüt'
Website: 'Web Sitesi'
App: 'Uygulama'
Runtime: 'Çalışma ortamı'
Database: 'Veritabanı'
ConfigFTP: 'FTP kullanıcısı {{ .name }} oluştur'
ConfigOpenresty: 'Openresty yapılandırma dosyası oluştur'
InstallAppSuccess: 'Uygulama {{ .name }} başarıyla yüklendi'
ConfigRuntime: 'Çalışma ortamını yapılandır'
ConfigApp: 'Uygulama Yapılandırması'
SuccessStatus: '{{ .name }} başarılı'
FailedStatus: '{{ .name }} başarısız {{ .err }}'
HandleLink: 'Uygulama ilişkisini işle'
HandleDatabaseApp: 'Uygulama parametrelerini işle'
ExecShell: '{{ .name }} betiğini yürüt'
PullImage: 'Image çek'
Start: 'Başlat'
Run: 'Başlat'
Stop: 'Durdur'
Image: 'Ayna'
Compose: 'Düzenleme'
Container: 'Konteyner'
AppLink: 'Bağlantılı Uygulama'
EnableSSL: 'HTTPS Etkinleştir'
AppStore: 'Uygulama Mağazası'
TaskSync: 'Senkronize Et'
LocalApp: 'Yerel Uygulama'
SubTask: 'Alt görev'
RuntimeExtension: 'Çalışma Ortamı Uzantısı'
TaskIsExecuting: 'Görev çalışıyor'
CustomAppstore: 'Özel uygulama deposu'
# task - ai
OllamaModelPull: 'Ollama modeli {{ .name }} çek'
OllamaModelSize: 'Ollama modeli {{ .name }} boyutunu al'
# task-snapshot
Snapshot: 'Anlık Görüntü'
SnapDBInfo: '1Panel veritabanı bilgilerini yaz'
SnapCopy: 'Dosya ve dizinleri {{ .name }} kopyala'
SnapNewDB: 'Veritabanı {{ .name }} bağlantısını başlat'
SnapDeleteOperationLog: 'İşlem günlüğünü sil'
SnapDeleteLoginLog: 'Erişim günlüğünü sil'
SnapDeleteMonitor: 'İzleme verilerini sil'
SnapRemoveSystemIP: 'Sistem IP'sini kaldır'
SnapBaseInfo: '1Panel temel bilgilerini yaz'
SnapInstallAppImageEmpty: 'Uygulama imageı seçilmedi, atlanıyor...'
SnapInstallApp: '1Panel yüklü uygulamaları yedekle'
SnapDockerSave: 'Yüklü uygulamaları sıkıştır'
SnapLocalBackup: '1Panel yerel yedekleme dizinini yedekle'
SnapCompressBackup: 'Yerel yedekleme dizinini sıkıştır'
SnapPanelData: '1Panel veri dizinini yedekle'
SnapCompressPanel: 'Sıkıştırılmış Veri Dizini'
SnapWebsite: '1Panel web sitesi dizinini yedekle'
SnapCloseDBConn: 'Veritabanı bağlantısını kapat'
SnapCompress: 'Anlık görüntü dosyaları oluştur'
SnapCompressFile: 'Anlık görüntü dosyasını sıkıştır'
SnapCheckCompress: 'Anlık görüntü sıkıştırma dosyasını kontrol et'
SnapCompressSize: 'Anlık görüntü dosya boyutu {{ .name }}'
SnapUpload: 'Anlık görüntü dosyasını yükle'
SnapLoadBackup: 'Yedekleme hesabı bilgilerini al'
SnapUploadTo: 'Anlık görüntü dosyasını {{ .name }} konumuna yükle'
SnapUploadRes: 'Anlık görüntü dosyasını {{ .name }} konumuna yükle'
SnapshotRecover: 'Anlık Görüntü Geri Yükleme'
RecoverDownload: 'Anlık görüntü dosyasını indir'
Download: 'İndir'
RecoverDownloadAccount: 'Anlık görüntü indirme yedek hesabı al {{ .name }}'
RecoverDecompress: 'Anlık görüntü sıkıştırılmış dosyalarını açma'
Decompress: 'Açma'
BackupBeforeRecover: 'Anlık görüntü öncesi sistem ilgili verileri yedekleme'
Readjson: 'Anlık görüntüdeki Json dosyasını oku'
ReadjsonPath: 'Anlık görüntüdeki Json dosya yolunu al'
ReadjsonContent: 'Json dosyasını oku'
ReadjsonMarshal: 'Json kaçış işlemesi'
RecoverApp: 'Yüklü uygulamaları geri yükle'
RecoverWebsite: 'Web sitesi dizinini kurtar'
RecoverAppImage: 'Anlık görüntü imaj yedeklemesini geri yükle'
RecoverCompose: 'Diğer besteci içeriğini geri yükle'
RecoverComposeList: 'Geri yüklenecek tüm bestecileri al'
RecoverComposeItem: 'Besteci geri yükle {{ .name }}'
RecoverAppEmpty: 'Anlık görüntü dosyasında uygulama imaj yedeklemesi bulunamadı'
RecoverBaseData: 'Temel veri ve dosyaları kurtar'
RecoverDaemonJsonEmpty: 'Hem anlık görüntü dosyası hem de mevcut makine konteyner yapılandırma daemon.json dosyasına sahip değil'
RecoverDaemonJson: 'Konteyner yapılandırma daemon.json dosyasını geri yükle'
RecoverDBData: 'Veritabanı verilerini kurtar'
RecoverBackups: 'Yerel yedekleme dizinini geri yükle'
RecoverPanelData: 'Veri dizinini kurtar'
# task - container
ContainerNewCliet: 'Docker İstemcisini Başlat'
ContainerImagePull: 'Konteyner imajını çek {{ .name }}'
ContainerRemoveOld: 'Orijinal konteyneri kaldır {{ .name }}'
ContainerImageCheck: 'İmajın normal çekilip çekilmediğini kontrol et'
ContainerLoadInfo: 'Temel konteyner bilgilerini al'
ContainerRecreate: 'Konteyner güncellemesi başarısız, şimdi orijinal konteyneri geri yüklemeye başlıyor'
ContainerCreate: 'Yeni konteyner oluştur {{ .name }}'
ContainerCreateFailed: 'Konteyner oluşturma başarısız, başarısız konteyneri sil'
ContainerStartCheck: 'Konteynerin başlatılıp başlatılmadığını kontrol et'
# task - image
ImageBuild: 'İmaj Oluşturma'
ImageBuildStdoutCheck: 'İmaj çıktı içeriğini ayrıştır'
ImageBuildRes: 'İmaj oluşturma çıktısı: {{ .name }}'
ImagePull: 'İmaj çek'
ImageRepoAuthFromDB: 'Veritabanından depo kimlik doğrulama bilgilerini al'
ImaegPullRes: 'İmaj çekme çıktısı: {{ .name }}'
ImagePush: 'İmaj gönder'
ImageRenameTag: 'İmaj etiketini değiştir'
ImageNewTag: 'Yeni imaj etiketi {{ .name }}'
ImaegPushRes: 'İmaj gönderme çıktısı: {{ .name }}'
ComposeCreate: 'Kompozisyon oluştur'
ComposeCreateRes: 'Compose oluşturma çıktısı: {{ .name }}'
# task - website
BackupNginxConfig: 'Web sitesi OpenResty yapılandırma dosyasını yedekle'
CompressFileSuccess: 'Dizin başarıyla sıkıştırıldı, şuraya sıkıştırıldı {{ .name }}'
CompressDir: 'Sıkıştırma dizini'
DeCompressFile: 'Dosyayı aç {{ .name }}'
ErrCheckValid: 'Yedekleme dosyası doğrulaması başarısız, {{ .name }}'
Rollback: 'Geri alma'
websiteDir: 'Web Sitesi Dizini'
RecoverFailedStartRollBack: 'Kurtarma başarısız, geri almayı başlat'
AppBackupFileIncomplete: 'Yedekleme dosyası eksik ve app.json veya app.tar.gz dosyalarından yoksun'
AppAttributesNotMatch: 'Uygulama türü veya adı eşleşmiyor'
#alert
ErrAlert: 'Uyarı mesajının formatı yanlış, lütfen kontrol edin ve tekrar deneyin!'
ErrAlertPush: 'Uyarı bilgisi gönderiminde hata, lütfen kontrol edin ve tekrar deneyin!'
ErrAlertSave: 'Alarm bilgisini kaydetmede hata, lütfen kontrol edin ve tekrar deneyin!'
ErrAlertSync: 'Alarm bilgisi senkronizasyon hatası, lütfen kontrol edin ve tekrar deneyin!'
ErrAlertRemote: 'Alarm mesajı uzak hata, lütfen kontrol edin ve tekrar deneyin!'
#task - runtime
ErrInstallExtension: "Zaten devam eden bir kurulum görevi var, görevin bitmesini bekleyin"
# alert mail template
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."
+16 -1
View File
@@ -176,7 +176,7 @@ ErrPgImagePull: '鏡像拉取逾時,請配置鏡像加速或手動拉取{{ .na
#runtime
ErrFileNotExist: '{{ .detail }} 檔案不存在!請檢查來源檔案完整性!'
ErrImageBuildErr: '鏡像build 失敗'
ErrImageExist: '鏡像已存在!'
ErrImageExist: "鏡像已存在!請修改鏡像名稱。"
ErrDelWithWebsite: '執行環境已經關聯網站,無法刪除'
ErrRuntimeStart: '啟動失敗'
ErrPackageJsonNotFound: 'package.json 檔案不存在'
@@ -286,6 +286,7 @@ TaskBackup: '備份'
TaskRecover: '復原'
TaskRollback: '回滾'
TaskPull: '拉取'
TaskCommit: '制作'
TaskBuild: '建置'
TaskPush: '推送'
TaskHandle: '執行'
@@ -423,3 +424,17 @@ ErrAlertRemote: '警告訊息遠端錯誤,請檢查後重試!'
#task - runtime
ErrInstallExtension: "已有安裝任務正在進行,請等待任務結束"
# alert mail template
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 }} 天後到期,詳情請登入面板查看。"
+39 -24
View File
@@ -176,7 +176,7 @@ ErrPgImagePull: "镜像拉取超时,请配置镜像加速或手动拉取 {{ .n
#runtime
ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!"
ErrImageBuildErr: "镜像 build 失败"
ErrImageExist: "镜像已存在!"
ErrImageExist: "镜像已存在!请修改镜像名称"
ErrDelWithWebsite: "运行环境已经关联网站,无法删除"
ErrRuntimeStart: "启动失败"
ErrPackageJsonNotFound: "package.json 文件不存在"
@@ -286,6 +286,7 @@ TaskBackup: "备份"
TaskRecover: "恢复"
TaskRollback: "回滚"
TaskPull: "拉取"
TaskCommit: "制作"
TaskBuild: "构建"
TaskPush: "推送"
TaskHandle: "执行"
@@ -298,7 +299,7 @@ ConfigOpenresty: "创建 Openresty 配置文件"
InstallAppSuccess: "应用 {{ .name }} 安装成功"
ConfigRuntime: "配置运行环境"
ConfigApp: "配置应用"
SuccessStatus: "{{ .name }} 成功"
SuccessStatus: "{{ .name }} 成功"
FailedStatus: "{{ .name }} 失败 {{ .err }}"
HandleLink: "处理应用关联"
HandleDatabaseApp: "处理应用参数"
@@ -324,7 +325,7 @@ CustomAppstore: "自定义应用仓库"
OllamaModelPull: "拉取 Ollama 模型 {{ .name }} "
OllamaModelSize: "获取 Ollama 模型 {{ .name }} 大小 "
# task - snapshot
# task - snapshot
Snapshot: "快照"
SnapDBInfo: "写入 1Panel 数据库信息"
SnapCopy: "复制文件&目录 {{ .name }} "
@@ -378,29 +379,29 @@ RecoverBackups: "恢复本地备份目录"
RecoverPanelData: "恢复数据目录"
# task - container
ContainerNewCliet: "初始化 Docker Client"
ContainerImagePull: "拉取容器镜像 {{ .name }}"
ContainerNewCliet: "初始化 Docker Client"
ContainerImagePull: "拉取容器镜像 {{ .name }}"
ContainerRemoveOld: "删除原容器 {{ .name }}"
ContainerImageCheck: "检查镜像是否正常拉取"
ContainerLoadInfo: "获取容器基本信息"
ContainerRecreate: "容器更新失败,现在开始恢复原容器"
ContainerCreate: "创建新容器 {{ .name }}"
ContainerCreateFailed: "容器创建失败,删除失败容器"
ContainerStartCheck: "检查容器是否已启动"
ContainerImageCheck: "检查镜像是否正常拉取"
ContainerLoadInfo: "获取容器基本信息"
ContainerRecreate: "容器更新失败,现在开始恢复原容器"
ContainerCreate: "创建新容器 {{ .name }}"
ContainerCreateFailed: "容器创建失败,删除失败容器"
ContainerStartCheck: "检查容器是否已启动"
# task - image
ImageBuild: "镜像构建"
ImageBuildStdoutCheck: "解析镜像输出内容"
ImageBuildRes: "镜像构建输出:{{ .name }}"
ImagePull: "拉取镜像"
ImageRepoAuthFromDB: "从数据库获取仓库认证信息"
ImaegPullRes: "镜像拉取输出:{{ .name }}"
ImagePush: "推送镜像"
ImageRenameTag: "修改镜像 Tag"
ImageNewTag: "新镜像 Tag {{ .name }}"
ImaegPushRes: "镜像推送输出:{{ .name }}"
ComposeCreate: "创建编排"
ComposeCreateRes: "编排创建输出:{{ .name }}"
ImageBuild: "镜像构建"
ImageBuildStdoutCheck: "解析镜像输出内容"
ImageBuildRes: "镜像构建输出:{{ .name }}"
ImagePull: "拉取镜像"
ImageRepoAuthFromDB: "从数据库获取仓库认证信息"
ImaegPullRes: "镜像拉取输出:{{ .name }}"
ImagePush: "推送镜像"
ImageRenameTag: "修改镜像 Tag"
ImageNewTag: "新镜像 Tag {{ .name }}"
ImaegPushRes: "镜像推送输出:{{ .name }}"
ComposeCreate: "创建编排"
ComposeCreateRes: "编排创建输出:{{ .name }}"
# task - website
BackupNginxConfig: "备份网站 OpenResty 配置文件"
@@ -422,4 +423,18 @@ ErrAlertSync: "告警信息同步错误,请检查后重试!"
ErrAlertRemote: "告警信息远端错误,请检查后重试!"
#task - runtime
ErrInstallExtension: "已有安装任务正在进行,请等待任务结束"
ErrInstallExtension: "已有安装任务正在进行,请等待任务结束"
# alert mail template
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 }} 天后到期,详情请登录面板查看。"
+1
View File
@@ -11,6 +11,7 @@ func Init() {
global.DB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "agent.db"), "agent")
global.TaskDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "task.db"), "task")
global.MonitorDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "monitor.db"), "monitor")
global.AlertDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "alert.db"), "alert")
if global.IsMaster {
global.CoreDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "core.db"), "core")
+2 -1
View File
@@ -1,6 +1,7 @@
package hook
import (
"github.com/1Panel-dev/1Panel/agent/utils/alert_push"
"os"
"strings"
@@ -96,7 +97,7 @@ func handleCronJobAlert(cronjob *model.Cronjob) {
EntryID: cronjob.ID,
Param: cronjob.Type,
}
_ = xpack.PushAlert(pushAlert)
_ = alert_push.PushAlert(pushAlert)
}
func loadLocalDir() {
+4
View File
@@ -29,6 +29,10 @@ func InitAgentDB() {
migrations.UpdatePHPRuntime,
migrations.AddSnapshotIgnore,
migrations.InitAppLauncher,
migrations.AddTableAlert,
migrations.InitAlertConfig,
migrations.AddMethodToAlertLog,
migrations.AddMethodToAlertTask,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)
+60 -1
View File
@@ -337,7 +337,7 @@ var UpdatePHPRuntime = &gormigrate.Migration{
},
}
var AddSnapshotIgnore = &gormigrate.Migration{
ID: "20250628-add-snapshot-ignore",
ID: "20250716-add-snapshot-ignore",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(
&model.Snapshot{},
@@ -360,3 +360,62 @@ var InitAppLauncher = &gormigrate.Migration{
return nil
},
}
var AddTableAlert = &gormigrate.Migration{
ID: "20250122-add-table-alert",
Migrate: func(tx *gorm.DB) error {
return global.AlertDB.AutoMigrate(&model.Alert{}, &model.AlertTask{}, model.AlertLog{}, model.AlertConfig{})
},
}
var InitAlertConfig = &gormigrate.Migration{
ID: "20250705-init-alert-config",
Migrate: func(tx *gorm.DB) error {
records := []model.AlertConfig{
{
Type: "sms",
Title: "xpack.alert.smsConfig",
Status: "Enable",
Config: `{"alertDailyNum":50}`,
},
{
Type: "common",
Title: "xpack.alert.commonConfig",
Status: "Enable",
Config: `{"isOffline":"Disable","alertSendTimeRange":{"noticeAlert":{"sendTimeRange":"08:00:00 - 23:59:59","type":["ssl","siteEndTime","panelPwdEndTime","panelUpdate"]},"resourceAlert":{"sendTimeRange":"00:00:00 - 23:59:59","type":["clams","cronJob","cpu","memory","load","disk"]}}}`,
},
}
for _, r := range records {
if err := global.AlertDB.Model(&model.AlertConfig{}).Create(&r).Error; err != nil {
return err
}
}
return nil
},
}
var AddMethodToAlertLog = &gormigrate.Migration{
ID: "20250713-add-method-to-alert_log",
Migrate: func(tx *gorm.DB) error {
if err := global.AlertDB.AutoMigrate(&model.AlertLog{}); err != nil {
return err
}
if err := global.AlertDB.Model(&model.AlertLog{}).Where("method IS NULL OR method = ''").Update("method", "sms").Error; err != nil {
return err
}
return nil
},
}
var AddMethodToAlertTask = &gormigrate.Migration{
ID: "20250723-add-method-to-alert_task",
Migrate: func(tx *gorm.DB) error {
if err := global.AlertDB.AutoMigrate(&model.AlertTask{}); err != nil {
return err
}
if err := global.AlertDB.Model(&model.AlertTask{}).Where("method IS NULL OR method = ''").Update("method", "sms").Error; err != nil {
return err
}
return nil
},
}
-1
View File
@@ -50,7 +50,6 @@ func Init() {
}
global.CONF = serverConfig
global.CONF.Base.IsDemo = v.GetBool("system.is_demo")
initBaseInfo()
global.Viper = v
+13
View File
@@ -3,9 +3,11 @@ package middleware
import (
"errors"
"fmt"
"strings"
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/gin-gonic/gin"
)
@@ -24,6 +26,17 @@ func Certificate() gin.HandlerFunc {
helper.InternalServer(c, fmt.Errorf("err certificate"))
return
}
conn := c.Request.Header.Get("Connection")
if conn == "Upgrade" {
c.Next()
return
}
masterProxyID := c.Request.Header.Get("Proxy-Id")
proxyID, err := cmd.RunDefaultWithStdoutBashC("cat /etc/1panel/.nodeProxyID")
if err == nil && len(proxyID) != 0 && strings.TrimSpace(proxyID) != strings.TrimSpace(masterProxyID) {
helper.InternalServer(c, fmt.Errorf("err proxy id"))
return
}
c.Next()
}
}
+1
View File
@@ -23,5 +23,6 @@ func commonGroups() []CommonRouter {
&WebsiteCARouter{},
&AIToolsRouter{},
&GroupRouter{},
&AlertRouter{},
}
}
+31
View File
@@ -0,0 +1,31 @@
package router
import (
v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2"
"github.com/gin-gonic/gin"
)
type AlertRouter struct {
}
func (a *AlertRouter) InitRouter(Router *gin.RouterGroup) {
alertRouter := Router.Group("alert")
baseApi := v2.ApiGroupApp.BaseApi
{
alertRouter.POST("", baseApi.CreateAlert)
alertRouter.POST("/update", baseApi.UpdateAlert)
alertRouter.POST("/search", baseApi.PageAlert)
alertRouter.POST("/status", baseApi.UpdateAlertStatus)
alertRouter.POST("/del", baseApi.DeleteAlert)
alertRouter.GET("/disks/list", baseApi.GetDisks)
alertRouter.POST("/logs/search", baseApi.PageAlertLogs)
alertRouter.POST("/logs/clean", baseApi.CleanAlertLogs)
alertRouter.GET("/clams/list", baseApi.GetClams)
alertRouter.POST("/cronjob/list", baseApi.GetCronJobs)
alertRouter.POST("/config/update", baseApi.UpdateAlertConfig)
alertRouter.POST("/config/info", baseApi.GetAlertConfig)
alertRouter.POST("/config/del", baseApi.DeleteAlertConfig)
alertRouter.POST("/config/test", baseApi.TestAlertConfig)
}
}
+1
View File
@@ -38,6 +38,7 @@ func (a *AppRouter) InitRouter(Router *gin.RouterGroup) {
appRouter.POST("/installed/params/update", baseApi.UpdateInstalled)
appRouter.POST("/installed/update/versions", baseApi.GetUpdateVersions)
appRouter.POST("/installed/config/update", baseApi.UpdateAppConfig)
appRouter.GET("/installed/info/:appInstallId", baseApi.GetAppInstallInfo)
appRouter.POST("/installed/ignore", baseApi.IgnoreAppUpgrade)
appRouter.GET("/ignored/detail", baseApi.ListAppIgnored)
+327
View File
@@ -0,0 +1,327 @@
package alert
import (
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/buserr"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/utils/email"
"github.com/jinzhu/copier"
"net/http"
"strings"
"sync"
"time"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/global"
)
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 {
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)
}
func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, transport *http.Transport) error {
var alertLog model.AlertLog
alertRepo := repo.NewIAlertRepo()
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.CommonConfig))
if err != nil {
return err
}
var cfg dto.AlertCommonConfig
err = json.Unmarshal([]byte(config.Config), &cfg)
if err != nil {
return err
}
create.Method = constant.Email
// 获取远端推送信息
if !global.IsMaster && cfg.IsOffline == constant.StatusEnable {
create.Status = constant.AlertPushing
return SaveAlertLog(create, &alertLog)
} else {
emailConfig, err := alertRepo.GetConfig(alertRepo.WithByType(constant.EmailConfig))
if err != nil {
return err
}
var emailInfo dto.AlertEmailConfig
err = json.Unmarshal([]byte(emailConfig.Config), &emailInfo)
if err != nil {
return err
}
smtpConfig := email.SMTPConfig{
Host: emailInfo.Host,
Port: emailInfo.Port,
Username: emailInfo.Sender,
Password: emailInfo.Password,
From: fmt.Sprintf("%s <%s>", emailInfo.DisplayName, emailInfo.Sender),
Encryption: emailInfo.Encryption,
Recipient: emailInfo.Recipient,
}
content := alert.Title
if GetEmailContent(alert.Type, params) != "" {
content = GetEmailContent(alert.Type, params)
}
msg := email.EmailMessage{
Subject: i18n.GetMsgByKey("PanelAlertTitle"),
Body: content,
IsHTML: true,
}
if err = email.SendMail(smtpConfig, msg, transport); err != nil {
create.Message = err.Error()
create.Status = constant.AlertError
return SaveAlertLog(create, &alertLog)
}
create.Status = constant.AlertSuccess
return SaveAlertLog(create, &alertLog)
}
}
func SaveAlertLog(create dto.AlertLogCreate, alertLog *model.AlertLog) error {
alertRepo := repo.NewIAlertRepo()
if err := copier.Copy(&alertLog, &create); err != nil {
return buserr.WithErr("ErrStructTransform", err)
}
if err := alertRepo.CreateLog(alertLog); err != nil {
global.LOG.Errorf("Error creating alert logs, err: %v", err)
return err
}
return nil
}
func CreateNewAlertTask(quota, alertType, quotaType, method string) {
alertRepo := repo.NewIAlertRepo()
taskBase := model.AlertTask{
Type: alertType,
Quota: quota,
QuotaType: quotaType,
Method: method,
}
err := alertRepo.CreateAlertTask(&taskBase)
if err != nil {
global.LOG.Errorf("error creating alert tasks, err: %v", err)
}
}
func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param, method string) string {
alertDetail := dto.AlertDetail{
Type: GetCronJobType(alert.Type),
SubType: alert.Type,
Title: alert.Title,
Method: method,
Project: project,
Params: params,
}
marshal, err := json.Marshal(alertDetail)
if err != nil {
global.LOG.Errorf("error processing alert detail, err: %v", err)
return ""
}
return string(marshal)
}
func ProcessAlertRule(alert dto.AlertDTO) string {
marshal, err := json.Marshal(alert)
if err != nil {
global.LOG.Errorf("error processing alert rule, err: %v", err)
return ""
}
return string(marshal)
}
func GetCronJobType(alertType string) string {
for _, at := range cronJobAlertTypes {
if at == alertType {
return "cronJob"
}
}
return alertType
}
func GetCronJobTypeName(cronJobType string) string {
module := cronJobType
switch cronJobType {
case "shell":
module = "Shell 脚本"
case "app":
module = "备份应用"
case "website":
module = "备份网站"
case "database":
module = "备份数据库"
case "log":
module = "备份日志"
case "directory":
module = "备份目录"
case "curl":
module = "访问 URL"
case "cutWebsiteLog":
module = "切割网站日志"
case "clean":
module = "缓存清理"
case "snapshot":
module = "系统快照"
case "ntp":
module = "同步服务器时间"
default:
}
return module
}
func CreateAlertParams(param string) []dto.Param {
return []dto.Param{
{
Index: "1",
Key: "param",
Value: param,
},
}
}
var checkTaskMutex sync.Mutex
func CheckTaskFrequency(method string) bool {
alertRepo := repo.NewIAlertRepo()
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.SMSConfig))
if err != nil {
return false
}
var cfg dto.AlertSmsConfig
err = json.Unmarshal([]byte(config.Config), &cfg)
if err != nil {
return false
}
limitCount := cfg.AlertDailyNum
checkTaskMutex.Lock()
todayCount, err := alertRepo.GetLicensePushCount(method)
defer checkTaskMutex.Unlock()
if err != nil {
global.LOG.Errorf("error getting license push count info, err: %v", err)
return false
}
if todayCount >= limitCount {
return false
}
return true
}
type Settings struct {
NoticeAlert Category `json:"noticeAlert"`
ResourceAlert Category `json:"resourceAlert"`
}
type Category struct {
SendTimeRange string `json:"sendTimeRange"`
Type []string `json:"type"`
}
// CheckSendTimeRange 是否在时间范围内
func CheckSendTimeRange(alertType string) bool {
alertRepo := repo.NewIAlertRepo()
config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.CommonConfig))
if err != nil {
return false
}
var cfg dto.AlertCommonConfig
err = json.Unmarshal([]byte(config.Config), &cfg)
if err != nil {
return false
}
var timeRange string
if contains(cfg.AlertSendTimeRange.NoticeAlert.Type, alertType) {
timeRange = cfg.AlertSendTimeRange.NoticeAlert.SendTimeRange
} else if contains(cfg.AlertSendTimeRange.ResourceAlert.Type, alertType) {
timeRange = cfg.AlertSendTimeRange.ResourceAlert.SendTimeRange
} else {
global.LOG.Warnf("Alert type not found in sendTimeRange: %s", alertType)
return false
}
if !isWithinTimeRange(timeRange) {
return false
}
return true
}
func contains(arr []string, target string) bool {
for _, item := range arr {
if item == target {
return true
}
}
return false
}
func isWithinTimeRange(savedTimeString string) bool {
now := time.Now()
timeParts := strings.Split(savedTimeString, " - ")
if len(timeParts) != 2 {
global.LOG.Info("Time range string format error, should be: 'HH:MM:SS - HH:MM:SS'")
return false
}
startTime, err1 := time.Parse("15:04:05", strings.TrimSpace(timeParts[0]))
endTime, err2 := time.Parse("15:04:05", strings.TrimSpace(timeParts[1]))
if err1 != nil || err2 != nil {
global.LOG.Infof("Invalid time format in range: %s, errors: %v, %v", savedTimeString, err1, err2)
return false
}
skipTime := time.Date(now.Year(), now.Month(), now.Day(), startTime.Hour(), startTime.Minute(), startTime.Second(), 0, now.Location())
endSkipTime := time.Date(now.Year(), now.Month(), now.Day(), endTime.Hour(), endTime.Minute(), endTime.Second(), 0, now.Location())
if endSkipTime.Before(skipTime) {
return now.After(skipTime) || now.Before(endSkipTime)
}
return now.After(skipTime) && now.Before(endSkipTime)
}
func GetEmailContent(alertType string, params []dto.Param) string {
switch GetCronJobType(alertType) {
case "ssl":
return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2")})
case "siteEndTime":
return i18n.GetMsgWithMap("WebSiteAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2")})
case "panelPwdEndTime":
return i18n.GetMsgWithMap("PanelPwdExpirationAlert", map[string]interface{}{"day": getValueByIndex(params, "1")})
case "licenseTime":
return i18n.GetMsgWithMap("LicenseExpirationAlert", map[string]interface{}{"day": getValueByIndex(params, "1")})
case "panelUpdate":
return i18n.GetMsgByKey("PanelVersionAlert")
case "cpu":
return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3")})
case "memory":
return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3")})
case "load":
return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3")})
case "disk":
return i18n.GetMsgWithMap("DiskUsedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "used": getValueByIndex(params, "2")})
case "cronJob":
return i18n.GetMsgWithMap("CronJobFailedAlert", map[string]interface{}{"name": getValueByIndex(params, "1")})
case "clams":
return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1")})
default:
return ""
}
}
func getValueByIndex(params []dto.Param, index string) string {
for _, p := range params {
if p.Index == index {
return p.Value
}
}
return ""
}
+69
View File
@@ -0,0 +1,69 @@
package alert_push
import (
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/repo"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert"
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
"github.com/jinzhu/copier"
"strconv"
"strings"
)
func PushAlert(pushAlert dto.PushAlert) error {
if !alertUtil.CheckSendTimeRange(alertUtil.GetCronJobType(pushAlert.AlertType)) {
return nil
}
alertRepo := repo.NewIAlertRepo()
alertInfo, err := alertRepo.Get(alertRepo.WithByType(pushAlert.AlertType), alertRepo.WithByProject(strconv.Itoa(int(pushAlert.EntryID))), repo.WithByStatus(constant.AlertEnable))
if err != nil {
return err
}
var alert dto.AlertDTO
_ = copier.Copy(&alert, &alertInfo)
// 根据发送方式推送不同的日志记录
methods := strings.Split(alert.Method, ",")
for _, m := range methods {
m = strings.TrimSpace(m)
switch m {
case constant.SMS:
if !alertUtil.CheckTaskFrequency(constant.SMS) {
continue
}
todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.SMS)
if err != nil || alert.SendCount <= todayCount {
continue
}
var create = dto.AlertLogCreate{
Type: alertUtil.GetCronJobType(alert.Type),
AlertId: alert.ID,
Count: todayCount + 1,
}
_ = xpack.CreateTaskScanSMSAlertLog(alert, create, pushAlert, constant.SMS)
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), 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 {
continue
}
var create = dto.AlertLogCreate{
Type: alertUtil.GetCronJobType(alert.Type),
AlertId: alert.ID,
Count: todayCount + 1,
}
transport := xpack.LoadRequestTransport()
err = alertUtil.CreateTaskScanEmailAlertLog(alert, create, pushAlert, constant.Email, transport)
if err != nil {
return err
}
alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.Email)
default:
}
}
global.LOG.Infof("%s alert push successful", alert.Type)
return nil
}
+7 -5
View File
@@ -79,13 +79,15 @@ func (s sftpClient) Upload(src, target string) (bool, error) {
defer srcFile.Close()
targetDir, _ := path.Split(target)
if _, err = client.Stat(targetDir); err != nil {
if os.IsNotExist(err) {
if err = client.MkdirAll(targetDir); err != nil {
if len(targetDir) != 0 {
if _, err = client.Stat(targetDir); err != nil {
if os.IsNotExist(err) {
if err = client.MkdirAll(targetDir); err != nil {
return false, err
}
} else {
return false, err
}
} else {
return false, err
}
}
dstFile, err := client.Create(target)
+1 -1
View File
@@ -31,7 +31,7 @@ func Down(filePath string) (string, error) {
if err := checkCmd(); err != nil {
return "", err
}
stdout, err := cmd.RunDefaultWithStdoutBashCf(global.CONF.DockerConfig.Command+" -f %s down --remove-orphans", filePath)
stdout, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut(global.CONF.DockerConfig.Command+" -f %s down --remove-orphans", 20*time.Minute, filePath)
return stdout, err
}
+59
View File
@@ -1,6 +1,8 @@
package docker
import (
"bufio"
"bytes"
"context"
"fmt"
"github.com/compose-spec/compose-go/v2/loader"
@@ -152,3 +154,60 @@ func GetDockerComposeImages(projectName string, env, yml []byte) ([]string, erro
}
return images, nil
}
func GetImagesFromDockerCompose(env, yml []byte) ([]string, error) {
envVars, err := loadEnvFile(env)
if err != nil {
return nil, fmt.Errorf("load env failed: %v", err)
}
var compose ComposeProject
if err := yaml.Unmarshal(yml, &compose); err != nil {
return nil, fmt.Errorf("parse docker-compose file failed: %v", err)
}
var images []string
for _, service := range compose.Services {
if service.Image != "" {
resolvedImage := replaceEnvVars(service.Image, envVars)
images = append(images, resolvedImage)
}
}
return images, nil
}
func loadEnvFile(env []byte) (map[string]string, error) {
envVars := make(map[string]string)
scanner := bufio.NewScanner(bytes.NewReader(env))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
value = strings.Trim(value, `"'`)
envVars[key] = value
}
}
return envVars, scanner.Err()
}
func replaceEnvVars(input string, envVars map[string]string) string {
re := regexp.MustCompile(`\$\{([^}]+)\}`)
return re.ReplaceAllStringFunc(input, func(match string) string {
varName := match[2 : len(match)-1]
if value, exists := envVars[varName]; exists {
return value
}
return match
})
}
+3 -3
View File
@@ -63,11 +63,11 @@ func (c Client) Close() {
_ = c.cli.Close()
}
func (c Client) ListContainersByName(names []string) ([]types.Container, error) {
func (c Client) ListContainersByName(names []string) ([]container.Summary, error) {
var (
options container.ListOptions
namesMap = make(map[string]bool)
res []types.Container
res []container.Summary
)
options.All = true
if len(names) > 0 {
@@ -89,7 +89,7 @@ func (c Client) ListContainersByName(names []string) ([]types.Container, error)
}
return res, nil
}
func (c Client) ListAllContainers() ([]types.Container, error) {
func (c Client) ListAllContainers() ([]container.Summary, error) {
var (
options container.ListOptions
)
+238
View File
@@ -0,0 +1,238 @@
package email
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"net/smtp"
"strings"
)
// SMTPConfig holds SMTP connection info
type SMTPConfig struct {
Host string
Port int // 465, 587, 25
Username string
Password string
From string
Encryption string // "ssl", "starttls", "none"
Recipient string
}
// EmailMessage represents an email
type EmailMessage struct {
Subject string
Body string // HTML or plain text
IsHTML bool
}
// SendMail sends the email using the given config
func SendMail(config SMTPConfig, message EmailMessage, transport *http.Transport) error {
// 验证配置
if err := validateConfig(config); err != nil {
return err
}
addr := fmt.Sprintf("%s:%d", config.Host, config.Port)
toList := parseRecipients(config.Recipient)
// 构建邮件内容
msg, err := buildMessage(config, message, toList)
if err != nil {
return err
}
// 根据加密类型选择发送方式
switch strings.ToLower(config.Encryption) {
case "ssl":
return sendWithSSL(config, addr, toList, msg, transport)
case "starttls", "tls":
return sendWithStartTLS(config, addr, toList, msg, transport)
case "none":
return sendPlaintext(config, addr, toList, msg, transport)
default:
return fmt.Errorf("unsupported encryption type: %s", config.Encryption)
}
}
// 验证配置有效性
func validateConfig(config SMTPConfig) error {
if config.Host == "" {
return fmt.Errorf("SMTP host is required")
}
if config.Port <= 0 {
return fmt.Errorf("invalid SMTP port: %d", config.Port)
}
if config.Username == "" {
return fmt.Errorf("SMTP username is required")
}
if config.Password == "" {
return fmt.Errorf("SMTP password is required")
}
if config.From == "" {
return fmt.Errorf("SMTP from address is required")
}
if config.Recipient == "" {
return fmt.Errorf("SMTP recipient is required")
}
if !isValidEncryption(config.Encryption) {
return fmt.Errorf("invalid encryption type: %s. Allowed: ssl, starttls, none", config.Encryption)
}
return nil
}
// 检查加密类型是否有效
func isValidEncryption(enc string) bool {
enc = strings.ToLower(enc)
return enc == "ssl" || enc == "starttls" || enc == "none" || enc == "tls"
}
func parseRecipients(recipient string) []string {
toList := strings.Split(recipient, ",")
for i := range toList {
toList[i] = strings.TrimSpace(toList[i])
}
return toList
}
// 构建邮件内容
func buildMessage(config SMTPConfig, message EmailMessage, toList []string) (string, error) {
headers := make(map[string]string)
headers["From"] = config.From
headers["To"] = strings.Join(toList, ",")
headers["Subject"] = message.Subject
if message.IsHTML {
headers["MIME-version"] = "1.0"
headers["Content-Type"] = "text/html; charset=\"UTF-8\""
} else {
headers["Content-Type"] = "text/plain; charset=\"UTF-8\""
}
var msg strings.Builder
for k, v := range headers {
if !isValidHeader(k, v) {
return "", fmt.Errorf("invalid header: %s: %s", k, v)
}
msg.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
}
msg.WriteString("\r\n" + message.Body)
return msg.String(), nil
}
// 验证邮件头安全性
func isValidHeader(key, value string) bool {
return !strings.ContainsAny(key, "\r\n") && !strings.ContainsAny(value, "\r\n")
}
// SSL/TLS方式发送邮件
func sendWithSSL(config SMTPConfig, addr string, toList []string, msg string, transport *http.Transport) error {
var err error
var conn net.Conn
if transport != nil {
conn, err = transport.DialContext(context.Background(), "tcp", addr)
} else {
conn, err = net.Dial("tcp", addr)
}
if err != nil {
return fmt.Errorf("failed to connect to SMTP server: %w", err)
}
defer conn.Close()
tlsConfig := &tls.Config{
ServerName: config.Host,
}
tlsConn := tls.Client(conn, tlsConfig)
if err := tlsConn.Handshake(); err != nil {
return fmt.Errorf("TLS handshake failed: %w", err)
}
// 创建SMTP客户端
client, err := smtp.NewClient(tlsConn, config.Host)
if err != nil {
return fmt.Errorf("failed to create SMTP client: %w", err)
}
defer client.Quit()
return sendEmailWithClient(client, config, toList, msg)
}
// STARTTLS方式发送邮件
func sendWithStartTLS(config SMTPConfig, addr string, toList []string, msg string, transport *http.Transport) error {
var err error
var conn net.Conn
if transport != nil {
conn, err = transport.DialContext(context.Background(), "tcp", addr)
} else {
conn, err = net.Dial("tcp", addr)
}
if err != nil {
return fmt.Errorf("failed to connect to SMTP server: %w", err)
}
defer conn.Close()
// 创建SMTP客户端
client, err := smtp.NewClient(conn, config.Host)
if err != nil {
return fmt.Errorf("failed to create SMTP client: %w", err)
}
defer client.Quit()
// 启用TLS
if err = client.StartTLS(&tls.Config{ServerName: config.Host}); err != nil {
return fmt.Errorf("failed to start TLS: %w", err)
}
return sendEmailWithClient(client, config, toList, msg)
}
// 明文方式发送邮件
func sendPlaintext(config SMTPConfig, addr string, toList []string, msg string, transport *http.Transport) error {
var err error
var conn net.Conn
if transport != nil {
conn, err = transport.DialContext(context.Background(), "tcp", addr)
} else {
conn, err = net.Dial("tcp", addr)
}
if err != nil {
return fmt.Errorf("failed to connect to SMTP server: %w", err)
}
defer conn.Close()
client, err := smtp.NewClient(conn, config.Host)
if err != nil {
return fmt.Errorf("failed to create SMTP client: %w", err)
}
return sendEmailWithClient(client, config, toList, msg)
}
// 使用SMTP客户端发送邮件
func sendEmailWithClient(client *smtp.Client, config SMTPConfig, toList []string, msg string) error {
auth := smtp.PlainAuth("", config.Username, config.Password, config.Host)
if err := client.Auth(auth); err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
if err := client.Mail(config.Username); err != nil {
return fmt.Errorf("setting sender failed: %w", err)
}
for _, addr := range toList {
if err := client.Rcpt(addr); err != nil {
return fmt.Errorf("adding recipient %s failed: %w", addr, err)
}
}
w, err := client.Data()
if err != nil {
return fmt.Errorf("preparing data failed: %w", err)
}
defer w.Close()
if _, err := w.Write([]byte(msg)); err != nil {
return fmt.Errorf("writing message failed: %w", err)
}
return nil
}
+3 -3
View File
@@ -827,7 +827,7 @@ func (f FileOp) TarGzCompressPro(withDir bool, src, dst, secret, exclusionRules
if len(secret) != 0 {
commands = fmt.Sprintf("tar %s -zcf - %s | openssl enc -aes-256-cbc -salt -k '%s' -out %s", exStr, srcItem, secret, dst)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** "))
} else {
commands = fmt.Sprintf("tar -zcf %s %s %s", dst, exStr, srcItem)
global.LOG.Debug(commands)
@@ -850,7 +850,7 @@ func (f FileOp) TarGzFilesWithCompressPro(list []string, dst, secret string) err
commands := ""
if len(secret) != 0 {
commands = fmt.Sprintf("tar -zcf - %s | openssl enc -aes-256-cbc -salt -k '%s' -out %s", strings.Join(filelist, " "), secret, dst)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** "))
} else {
commands = fmt.Sprintf("tar -zcf %s %s", dst, strings.Join(filelist, " "))
global.LOG.Debug(commands)
@@ -869,7 +869,7 @@ func (f FileOp) TarGzExtractPro(src, dst string, secret string) error {
commands := ""
if len(secret) != 0 {
commands = fmt.Sprintf("openssl enc -d -aes-256-cbc -salt -k '%s' -in %s | tar -zxf - > /root/log", secret, src)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** "))
} else {
commands = fmt.Sprintf("tar zxvf %s", src)
global.LOG.Debug(commands)
+1 -1
View File
@@ -47,7 +47,7 @@ func (t TarGzArchiver) Compress(sourcePaths []string, dstFile string, secret str
if len(secret) != 0 {
extraCmd := fmt.Sprintf("| openssl enc -aes-256-cbc -salt -k '%s' -out '%s'", secret, dstFile)
commands = fmt.Sprintf("tar -zcf - -C \"%s\" %s %s", aheadDir, itemDir, extraCmd)
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******"))
global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** "))
} else {
commands = fmt.Sprintf("tar -zcf \"%s\" -C \"%s\" %s", dstFile, aheadDir, itemDir)
global.LOG.Debug(commands)
+9 -6
View File
@@ -73,15 +73,18 @@ func (iptables *Iptables) runf(tab, rule string, a ...any) error {
func (iptables *Iptables) Check() error {
stdout, err := cmd.RunDefaultWithStdoutBashC("cat /proc/sys/net/ipv4/ip_forward")
if err != nil {
return fmt.Errorf("%s, %s", err, stdout)
return fmt.Errorf("check ip_forward error: %w, output: %s", err, stdout)
}
if stdout == "0" {
return fmt.Errorf("ipv4 forward disable")
if strings.TrimSpace(stdout) == "0" {
return fmt.Errorf("ipv4 forward disabled")
}
chain, _ := iptables.outf(NatTab, "-L -n | grep 'Chain %s'", PreRoutingChain)
if len(strings.ReplaceAll(chain, "\n", "")) != 0 {
return fmt.Errorf("chain enabled")
chain, err := iptables.outf(NatTab, "-L -n | grep 'Chain %s'", PreRoutingChain)
if err != nil {
return fmt.Errorf("failed to check chain: %w", err)
}
if strings.TrimSpace(chain) != "" {
return fmt.Errorf("chain %s already exists", PreRoutingChain)
}
return nil
+5 -1
View File
@@ -30,7 +30,11 @@ type MysqlClient interface {
func NewMysqlClient(conn client.DBInfo) (MysqlClient, error) {
if conn.From == "local" {
connArgs := []string{"exec", conn.Address, conn.Type, "-u" + conn.Username, "-p" + conn.Password, "-e"}
mysqlCli := conn.Type
if mysqlCli == "mysql-cluster" {
mysqlCli = "mysql"
}
connArgs := []string{"exec", conn.Address, mysqlCli, "-u" + conn.Username, "-p" + conn.Password, "-e"}
return client.NewLocal(connArgs, conn.Type, conn.Address, conn.Password, conn.Database), nil
}
+1 -1
View File
@@ -28,7 +28,7 @@ type PostgresqlClient interface {
func NewPostgresqlClient(conn client.DBInfo) (PostgresqlClient, error) {
if conn.From == "local" {
connArgs := []string{"exec", conn.Address, "psql", "-t", "-U", conn.Username, "-c"}
connArgs := []string{"exec", "-e", fmt.Sprintf("PGPASSWORD=%s", conn.Password), conn.Address, "psql", "-t", "-U", conn.Username, "-c"}
return client.NewLocal(connArgs, conn.Address, conn.Username, conn.Password, conn.Database), nil
}
escapedUsername := url.QueryEscape(conn.Username)
+1
View File
@@ -11,6 +11,7 @@ type DBInfo struct {
Port uint `json:"port"`
Username string `json:"userName"`
Password string `json:"password"`
AppKey string `json:"appKey"`
Timeout uint `json:"timeout"` // second
}
+1 -1
View File
@@ -136,7 +136,7 @@ func (r *Local) Backup(info BackupInfo) error {
}
defer outfile.Close()
global.LOG.Infof("start to pg_dump | gzip > %s.gzip", info.TargetDir+"/"+info.FileName)
cmd := exec.Command("docker", "exec", r.ContainerName, "pg_dump", "-F", "c", "-U", r.Username, "-d", info.Name)
cmd := exec.Command("docker", "exec", "-e", fmt.Sprintf("PGPASSWORD=%s", r.Password), r.ContainerName, "pg_dump", "-F", "c", "-U", r.Username, "-d", info.Name)
var stderr bytes.Buffer
cmd.Stderr = &stderr
+219
View File
@@ -0,0 +1,219 @@
package version
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"io"
"net"
"net/http"
"strconv"
"strings"
"time"
)
func GetUpgradeVersionInfo() (*dto.UpgradeInfo, error) {
var upgrade dto.UpgradeInfo
var currentVersion model.Setting
if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", "SystemVersion").First(&currentVersion).Error; err != nil {
global.LOG.Errorf("load %s from db setting failed, err: %v", "SystemVersion", err)
return nil, err
}
var developerMode model.Setting
if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", "DeveloperMode").First(&developerMode).Error; err != nil {
global.LOG.Errorf("load %s from db setting failed, err: %v", "DeveloperMode", err)
return nil, err
}
upgrade.TestVersion, upgrade.NewVersion, upgrade.LatestVersion = loadVersionByMode(developerMode.Value, currentVersion.Value)
var itemVersion string
if len(upgrade.LatestVersion) != 0 {
itemVersion = upgrade.LatestVersion
}
if len(upgrade.NewVersion) != 0 {
itemVersion = upgrade.NewVersion
}
if (global.CONF.Base.Mode == "dev" || developerMode.Value == constant.StatusEnable) && len(upgrade.TestVersion) != 0 {
itemVersion = upgrade.TestVersion
}
if len(itemVersion) == 0 {
return &upgrade, nil
}
mode := global.CONF.Base.Mode
if strings.Contains(itemVersion, "beta") {
mode = "beta"
}
notes, err := loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.RemoteURL.RepoUrl, mode, itemVersion, itemVersion))
if err != nil {
return nil, fmt.Errorf("load releases-notes of version %s failed, err: %v", itemVersion, err)
}
upgrade.ReleaseNote = notes
return &upgrade, nil
}
func loadVersionByMode(developer, currentVersion string) (string, string, string) {
var current, latest string
if global.CONF.Base.Mode == "dev" {
betaVersionLatest := loadVersion(true, currentVersion, "beta")
devVersionLatest := loadVersion(true, currentVersion, "dev")
if common.ComparePanelVersion(betaVersionLatest, devVersionLatest) {
return betaVersionLatest, "", ""
}
return devVersionLatest, "", ""
}
betaVersionLatest := ""
latest = loadVersion(true, currentVersion, "stable")
current = loadVersion(false, currentVersion, "stable")
if developer == constant.StatusEnable {
betaVersionLatest = loadVersion(true, currentVersion, "beta")
}
if current != latest {
return betaVersionLatest, current, latest
}
versionPart := strings.Split(current, ".")
if len(versionPart) < 3 {
return betaVersionLatest, current, latest
}
num, _ := strconv.Atoi(versionPart[1])
if num == 0 {
return betaVersionLatest, current, latest
}
if num >= 10 {
if current[:6] == currentVersion[:6] {
return betaVersionLatest, current, ""
}
return betaVersionLatest, "", latest
}
if current[:5] == currentVersion[:5] {
return betaVersionLatest, current, ""
}
return betaVersionLatest, "", latest
}
func loadVersion(isLatest bool, currentVersion, mode string) string {
path := fmt.Sprintf("%s/%s/latest", global.CONF.RemoteURL.RepoUrl, mode)
if !isLatest {
path = fmt.Sprintf("%s/%s/latest.current", global.CONF.RemoteURL.RepoUrl, mode)
}
_, latestVersionRes, err := HandleRequest(path, http.MethodGet, constant.TimeOut20s)
if err != nil {
global.LOG.Errorf("load latest version from oss failed, err: %v", err)
return ""
}
version := string(latestVersionRes)
if strings.Contains(version, "<") {
global.LOG.Errorf("load latest version from oss failed, err: %v", version)
return ""
}
if isLatest {
return checkVersion(version, currentVersion)
}
versionMap := make(map[string]string)
if err := json.Unmarshal(latestVersionRes, &versionMap); err != nil {
global.LOG.Errorf("load latest version from oss failed (error unmarshal), err: %v", err)
return ""
}
versionPart := strings.Split(currentVersion, ".")
if len(versionPart) < 3 {
global.LOG.Errorf("current version is error format: %s", currentVersion)
return ""
}
num, _ := strconv.Atoi(versionPart[1])
if num == 0 {
global.LOG.Errorf("current version is error format: %s", currentVersion)
return ""
}
if num >= 10 {
if version, ok := versionMap[currentVersion[0:5]]; ok {
return checkVersion(version, currentVersion)
}
return ""
}
if version, ok := versionMap[currentVersion[0:4]]; ok {
return checkVersion(version, currentVersion)
}
return ""
}
func checkVersion(v2, v1 string) string {
addSuffix := false
if !strings.Contains(v1, "-") {
v1 = v1 + "-lts"
}
if !strings.Contains(v2, "-") {
addSuffix = true
v2 = v2 + "-lts"
}
if common.ComparePanelVersion(v2, v1) {
if addSuffix {
return strings.TrimSuffix(v2, "-lts")
}
return v2
}
return ""
}
func loadReleaseNotes(path string) (string, error) {
_, releaseNotes, err := HandleRequest(path, http.MethodGet, constant.TimeOut20s)
if err != nil {
return "", err
}
return string(releaseNotes), nil
}
func HandleRequest(url, method string, timeout int) (int, []byte, error) {
defer func() {
if r := recover(); r != nil {
global.LOG.Errorf("handle request failed, error message: %v", r)
return
}
}()
transport := loadRequestTransport()
client := http.Client{Timeout: time.Duration(timeout) * time.Second, Transport: transport}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
request, err := http.NewRequestWithContext(ctx, method, url, nil)
if err != nil {
return 0, nil, err
}
request.Header.Set("Content-Type", "application/json")
resp, err := client.Do(request)
if err != nil {
return 0, nil, err
}
if resp.StatusCode != http.StatusOK {
return 0, nil, errors.New(resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return 0, nil, err
}
defer resp.Body.Close()
return resp.StatusCode, body, nil
}
func loadRequestTransport() *http.Transport {
return &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DialContext: (&net.Dialer{
Timeout: 60 * time.Second,
KeepAlive: 60 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
IdleConnTimeout: 15 * time.Second,
}
}
+3 -12
View File
@@ -52,20 +52,11 @@ func IsUseCustomApp() bool {
return false
}
// alert
func CreateAlert(createAlert dto.CreateOrUpdateAlert) error {
func CreateTaskScanSMSAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string) error {
return nil
}
func UpdateAlert(updateAlert dto.CreateOrUpdateAlert) error {
return nil
}
func DeleteAlert(alertBase dto.AlertBase) error {
return nil
}
func GetAlert(alertBase dto.AlertBase) uint {
return 0
}
func PushAlert(pushAlert dto.PushAlert) error {
func CreateSMSAlertLog(info dto.AlertDTO, create dto.AlertLogCreate, project string, params []dto.Param, method string) error {
return nil
}
+11 -5
View File
@@ -1,9 +1,11 @@
package helper
import (
"errors"
"fmt"
"github.com/1Panel-dev/1Panel/core/cmd/server/res"
"net/http"
"strconv"
"github.com/1Panel-dev/1Panel/core/app/dto"
"github.com/1Panel-dev/1Panel/core/global"
@@ -70,11 +72,6 @@ func CheckBindAndValidate(req interface{}, c *gin.Context) error {
return nil
}
func ErrResponse(ctx *gin.Context, code int) {
ctx.JSON(code, nil)
ctx.Abort()
}
func ErrWithHtml(ctx *gin.Context, code int, scope string) {
if code == 444 {
ctx.String(444, "")
@@ -94,3 +91,12 @@ func ErrWithHtml(ctx *gin.Context, code int, scope string) {
ctx.Data(code, "text/html; charset=utf-8", data)
ctx.Abort()
}
func GetParamID(c *gin.Context) (uint, error) {
idParam, ok := c.Params.Get("id")
if !ok {
return 0, errors.New("error id in path")
}
intNum, _ := strconv.Atoi(idParam)
return uint(intNum), nil
}
+6
View File
@@ -276,6 +276,12 @@ func (b *BaseApi) WsSsh(c *gin.Context) {
}
defer wsConn.Close()
if global.CONF.Base.IsDemo {
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
return
}
}
id, err := strconv.Atoi(c.Query("id"))
if wshandleError(wsConn, errors.WithMessage(err, "invalid param id in request")) {
return
+1 -1
View File
@@ -28,7 +28,7 @@ type Login struct {
IgnoreCaptcha bool `json:"ignoreCaptcha"`
Captcha string `json:"captcha"`
CaptchaID string `json:"captchaID"`
Language string `json:"language" validate:"required,oneof=zh en 'zh-Hant' ko ja ru ms 'pt-BR'"`
Language string `json:"language" validate:"required,oneof=zh en 'zh-Hant' ko ja ru ms 'pt-BR' tr"`
}
type MFALogin struct {
+9
View File
@@ -0,0 +1,9 @@
package model
type AlertConfig struct {
BaseModel
Type string `json:"type"`
Title string `json:"title"`
Status string `json:"status"`
Config string `json:"config"`
}
+10 -6
View File
@@ -45,16 +45,20 @@ type SubTask struct {
}
const (
TaskUpgrade = "TaskUpgrade"
TaskAddNode = "TaskAddNode"
TaskSync = "TaskSync"
TaskRsync = "TaskRsync"
TaskUpgrade = "TaskUpgrade"
TaskAddNode = "TaskAddNode"
TaskSync = "TaskSync"
TaskRsync = "TaskRsync"
TaskInstallCluster = "TaskInstallCluster"
TaskCreateCluster = "TaskCreateCluster"
TaskBackup = "TaskBackup"
)
const (
TaskScopeSystem = "System"
TaskScopeScript = "Script"
TaskScopeSystem = "System"
TaskScopeScript = "Script"
TaskScopeNodeFile = "NodeFile"
TaskScopeCluster = "Cluster"
)
func GetTaskName(resourceName, operate, scope string) string {

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