mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-24 02:59:16 +08:00
feat: 创建网站、应用任务化
This commit is contained in:
parent
865b6cba3f
commit
0e5d6e825e
1
.gitignore
vendored
1
.gitignore
vendored
@ -48,6 +48,7 @@ agent/utils/xpack/xpack_xpack.go
|
||||
core/xpack
|
||||
core/router/entry_xpack.go
|
||||
core/server/init_xpack.go
|
||||
xpack
|
||||
|
||||
.history/
|
||||
dist/
|
||||
|
@ -161,14 +161,11 @@ func (b *BaseApi) InstallApp(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
tx, ctx := helper.GetTxAndContext()
|
||||
install, err := appService.Install(ctx, req)
|
||||
install, err := appService.Install(req)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
helper.SuccessWithData(c, install)
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
@ -78,14 +76,6 @@ func (b *BaseApi) CreateWebsite(c *gin.Context) {
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if len(req.FtpPassword) != 0 {
|
||||
pass, err := base64.StdEncoding.DecodeString(req.FtpPassword)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
req.FtpPassword = string(pass)
|
||||
}
|
||||
err := websiteService.CreateWebsite(req)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
|
@ -128,6 +128,7 @@ type FileReadByLineReq struct {
|
||||
ID uint `json:"ID"`
|
||||
Name string `json:"name"`
|
||||
Latest bool `json:"latest"`
|
||||
TaskID string `json:"taskID"`
|
||||
}
|
||||
|
||||
type FileExistReq struct {
|
||||
|
1
agent/app/dto/request/task.go
Normal file
1
agent/app/dto/request/task.go
Normal file
@ -0,0 +1 @@
|
||||
package request
|
@ -30,7 +30,8 @@ type WebsiteCreate struct {
|
||||
FtpUser string `json:"ftpUser"`
|
||||
FtpPassword string `json:"ftpPassword"`
|
||||
|
||||
RuntimeID uint `json:"runtimeID"`
|
||||
RuntimeID uint `json:"runtimeID"`
|
||||
TaskID string `json:"taskID"`
|
||||
RuntimeConfig
|
||||
}
|
||||
|
||||
|
1
agent/app/dto/response/task.go
Normal file
1
agent/app/dto/response/task.go
Normal file
@ -0,0 +1 @@
|
||||
package response
|
17
agent/app/model/task.go
Normal file
17
agent/app/model/task.go
Normal file
@ -0,0 +1,17 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Task struct {
|
||||
ID string `gorm:"primarykey;" json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
LogFile string `json:"logFile"`
|
||||
Status string `json:"status"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
OperationLogID uint `json:"operationLogID"`
|
||||
ResourceID uint `json:"resourceID"`
|
||||
CurrentStep string `json:"currentStep"`
|
||||
EndAt time.Time `json:"endAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
55
agent/app/repo/task.go
Normal file
55
agent/app/repo/task.go
Normal file
@ -0,0 +1,55 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type TaskRepo struct {
|
||||
}
|
||||
|
||||
type ITaskRepo interface {
|
||||
Create(ctx context.Context, task *model.Task) error
|
||||
GetFirst(opts ...DBOption) (model.Task, error)
|
||||
Page(page, size int, opts ...DBOption) (int64, []model.Task, error)
|
||||
Update(ctx context.Context, task *model.Task) error
|
||||
|
||||
WithByID(id string) DBOption
|
||||
}
|
||||
|
||||
func NewITaskRepo() ITaskRepo {
|
||||
return &TaskRepo{}
|
||||
}
|
||||
|
||||
func (t TaskRepo) WithByID(id string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("id = ?", id)
|
||||
}
|
||||
}
|
||||
|
||||
func (t TaskRepo) Create(ctx context.Context, task *model.Task) error {
|
||||
return getTx(ctx).Create(&task).Error
|
||||
}
|
||||
|
||||
func (t TaskRepo) GetFirst(opts ...DBOption) (model.Task, error) {
|
||||
var task model.Task
|
||||
db := getDb(opts...).Model(&model.Task{})
|
||||
if err := db.First(&task).Error; err != nil {
|
||||
return task, err
|
||||
}
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func (t TaskRepo) Page(page, size int, opts ...DBOption) (int64, []model.Task, error) {
|
||||
var tasks []model.Task
|
||||
db := getDb(opts...).Model(&model.Task{})
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&tasks).Error
|
||||
return count, tasks, err
|
||||
}
|
||||
|
||||
func (t TaskRepo) Update(ctx context.Context, task *model.Task) error {
|
||||
return getTx(ctx).Save(&task).Error
|
||||
}
|
@ -5,18 +5,12 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/response"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
@ -27,7 +21,14 @@ import (
|
||||
http2 "github.com/1Panel-dev/1Panel/agent/utils/http"
|
||||
httpUtil "github.com/1Panel-dev/1Panel/agent/utils/http"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
||||
"github.com/google/uuid"
|
||||
"gopkg.in/yaml.v3"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AppService struct {
|
||||
@ -38,7 +39,7 @@ type IAppService interface {
|
||||
GetAppTags() ([]response.TagDTO, error)
|
||||
GetApp(key string) (*response.AppDTO, error)
|
||||
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
|
||||
Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error)
|
||||
Install(req request.AppInstallCreate) (*model.AppInstall, error)
|
||||
SyncAppListFromRemote() error
|
||||
GetAppUpdate() (*response.AppUpdateRes, error)
|
||||
GetAppDetailByID(id uint) (*response.AppDetailDTO, error)
|
||||
@ -295,7 +296,7 @@ func (a AppService) GetIgnoredApp() ([]response.IgnoredApp, error) {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (appInstall *model.AppInstall, err error) {
|
||||
func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.AppInstall, err error) {
|
||||
if err = docker.CreateDefaultDockerNetwork(); err != nil {
|
||||
err = buserr.WithDetail(constant.Err1PanelNetworkFailed, err.Error(), nil)
|
||||
return
|
||||
@ -423,14 +424,6 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
|
||||
}
|
||||
appInstall.DockerCompose = string(composeByte)
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
hErr := handleAppInstallErr(ctx, appInstall)
|
||||
if hErr != nil {
|
||||
global.LOG.Errorf("delete app dir error %s", hErr.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
if hostName, ok := req.Params["PANEL_DB_HOST"]; ok {
|
||||
database, _ := databaseRepo.Get(commonRepo.WithByName(hostName.(string)))
|
||||
if !reflect.DeepEqual(database, model.Database{}) {
|
||||
@ -445,29 +438,48 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
|
||||
}
|
||||
appInstall.Env = string(paramByte)
|
||||
|
||||
if err = appInstallRepo.Create(ctx, appInstall); err != nil {
|
||||
if err = appInstallRepo.Create(context.Background(), appInstall); err != nil {
|
||||
return
|
||||
}
|
||||
if err = createLink(ctx, app, appInstall, req.Params); err != nil {
|
||||
|
||||
taskID := uuid.New().String()
|
||||
installTask, err := task.NewTaskWithOps(appInstall.Name, task.TaskCreate, task.TaskScopeApp, taskID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = createLink(context.Background(), installTask, app, appInstall, req.Params); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
installApp := func(t *task.Task) error {
|
||||
if err = copyData(t, app, appDetail, appInstall, req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = runScript(t, appInstall, "init"); err != nil {
|
||||
return err
|
||||
}
|
||||
upApp(t, appInstall, req.PullImage)
|
||||
updateToolApp(appInstall)
|
||||
return nil
|
||||
}
|
||||
|
||||
handleAppStatus := func() {
|
||||
appInstall.Status = constant.UpErr
|
||||
appInstall.Message = installTask.Task.ErrorMsg
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
}
|
||||
|
||||
installTask.AddSubTask(task.GetTaskName(appInstall.Name, task.TaskInstall, task.TaskScopeApp), installApp, handleAppStatus)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
appInstall.Status = constant.UpErr
|
||||
appInstall.Message = err.Error()
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
}
|
||||
}()
|
||||
if err = copyData(app, appDetail, appInstall, req); err != nil {
|
||||
return
|
||||
if taskErr := installTask.Execute(); taskErr != nil {
|
||||
appInstall.Status = constant.InstallErr
|
||||
appInstall.Message = taskErr.Error()
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
}
|
||||
if err = runScript(appInstall, "init"); err != nil {
|
||||
return
|
||||
}
|
||||
upApp(appInstall, req.PullImage)
|
||||
}()
|
||||
go updateToolApp(appInstall)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -130,78 +131,82 @@ var ToolKeys = map[string]uint{
|
||||
"minio": 9001,
|
||||
}
|
||||
|
||||
func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall, params map[string]interface{}) error {
|
||||
func createLink(ctx context.Context, installTask *task.Task, app model.App, appInstall *model.AppInstall, params map[string]interface{}) error {
|
||||
deleteAppLink := func() {
|
||||
_ = deleteLink(ctx, appInstall, true, true, true)
|
||||
}
|
||||
var dbConfig dto.AppDatabase
|
||||
if DatabaseKeys[app.Key] > 0 {
|
||||
database := &model.Database{
|
||||
AppInstallID: appInstall.ID,
|
||||
Name: appInstall.Name,
|
||||
Type: app.Key,
|
||||
Version: appInstall.Version,
|
||||
From: "local",
|
||||
Address: appInstall.ServiceName,
|
||||
Port: DatabaseKeys[app.Key],
|
||||
}
|
||||
detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(appInstall.AppDetailId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
formFields := &dto.AppForm{}
|
||||
if err := json.Unmarshal([]byte(detail.Params), formFields); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, form := range formFields.FormFields {
|
||||
if form.EnvKey == "PANEL_APP_PORT_HTTP" {
|
||||
portFloat, ok := form.Default.(float64)
|
||||
if ok {
|
||||
database.Port = uint(int(portFloat))
|
||||
}
|
||||
break
|
||||
handleDataBaseApp := func(task *task.Task) error {
|
||||
database := &model.Database{
|
||||
AppInstallID: appInstall.ID,
|
||||
Name: appInstall.Name,
|
||||
Type: app.Key,
|
||||
Version: appInstall.Version,
|
||||
From: "local",
|
||||
Address: appInstall.ServiceName,
|
||||
Port: DatabaseKeys[app.Key],
|
||||
}
|
||||
detail, err := appDetailRepo.GetFirst(commonRepo.WithByID(appInstall.AppDetailId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch app.Key {
|
||||
case constant.AppMysql, constant.AppMariaDB, constant.AppPostgresql, constant.AppMongodb:
|
||||
if password, ok := params["PANEL_DB_ROOT_PASSWORD"]; ok {
|
||||
if password != "" {
|
||||
formFields := &dto.AppForm{}
|
||||
if err := json.Unmarshal([]byte(detail.Params), formFields); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, form := range formFields.FormFields {
|
||||
if form.EnvKey == "PANEL_APP_PORT_HTTP" {
|
||||
portFloat, ok := form.Default.(float64)
|
||||
if ok {
|
||||
database.Port = uint(int(portFloat))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch app.Key {
|
||||
case constant.AppMysql, constant.AppMariaDB, constant.AppPostgresql, constant.AppMongodb:
|
||||
if password, ok := params["PANEL_DB_ROOT_PASSWORD"]; ok {
|
||||
if password != "" {
|
||||
database.Password = password.(string)
|
||||
if app.Key == "mysql" || app.Key == "mariadb" {
|
||||
database.Username = "root"
|
||||
}
|
||||
if rootUser, ok := params["PANEL_DB_ROOT_USER"]; ok {
|
||||
database.Username = rootUser.(string)
|
||||
}
|
||||
authParam := dto.AuthParam{
|
||||
RootPassword: password.(string),
|
||||
RootUser: database.Username,
|
||||
}
|
||||
authByte, err := json.Marshal(authParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appInstall.Param = string(authByte)
|
||||
|
||||
}
|
||||
}
|
||||
case constant.AppRedis:
|
||||
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
|
||||
if password != "" {
|
||||
authParam := dto.RedisAuthParam{
|
||||
RootPassword: password.(string),
|
||||
}
|
||||
authByte, err := json.Marshal(authParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appInstall.Param = string(authByte)
|
||||
}
|
||||
database.Password = password.(string)
|
||||
if app.Key == "mysql" || app.Key == "mariadb" {
|
||||
database.Username = "root"
|
||||
}
|
||||
if rootUser, ok := params["PANEL_DB_ROOT_USER"]; ok {
|
||||
database.Username = rootUser.(string)
|
||||
}
|
||||
authParam := dto.AuthParam{
|
||||
RootPassword: password.(string),
|
||||
RootUser: database.Username,
|
||||
}
|
||||
authByte, err := json.Marshal(authParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appInstall.Param = string(authByte)
|
||||
|
||||
}
|
||||
}
|
||||
case constant.AppRedis:
|
||||
if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok {
|
||||
if password != "" {
|
||||
authParam := dto.RedisAuthParam{
|
||||
RootPassword: password.(string),
|
||||
}
|
||||
authByte, err := json.Marshal(authParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appInstall.Param = string(authByte)
|
||||
}
|
||||
database.Password = password.(string)
|
||||
}
|
||||
}
|
||||
if err := databaseRepo.Create(ctx, database); err != nil {
|
||||
return err
|
||||
return databaseRepo.Create(ctx, database)
|
||||
}
|
||||
installTask.AddSubTask(i18n.GetMsgByKey("HandleDatabaseApp"), handleDataBaseApp, deleteAppLink)
|
||||
}
|
||||
if ToolKeys[app.Key] > 0 {
|
||||
if app.Key == "minio" {
|
||||
@ -231,95 +236,79 @@ func createLink(ctx context.Context, app model.App, appInstall *model.AppInstall
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(dbConfig, dto.AppDatabase{}) && dbConfig.ServiceName != "" {
|
||||
hostName := params["PANEL_DB_HOST_NAME"]
|
||||
if hostName == nil || hostName.(string) == "" {
|
||||
return nil
|
||||
}
|
||||
database, _ := databaseRepo.Get(commonRepo.WithByName(hostName.(string)))
|
||||
if database.ID == 0 {
|
||||
return nil
|
||||
}
|
||||
var resourceId uint
|
||||
if dbConfig.DbName != "" && dbConfig.DbUser != "" && dbConfig.Password != "" {
|
||||
switch database.Type {
|
||||
case constant.AppPostgresql, constant.AppPostgres:
|
||||
iPostgresqlRepo := repo.NewIPostgresqlRepo()
|
||||
oldPostgresqlDb, _ := iPostgresqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iPostgresqlRepo.WithByFrom(constant.ResourceLocal))
|
||||
resourceId = oldPostgresqlDb.ID
|
||||
if oldPostgresqlDb.ID > 0 {
|
||||
if oldPostgresqlDb.Username != dbConfig.DbUser || oldPostgresqlDb.Password != dbConfig.Password {
|
||||
return buserr.New(constant.ErrDbUserNotValid)
|
||||
createAppDataBase := func(rootTask *task.Task) error {
|
||||
hostName := params["PANEL_DB_HOST_NAME"]
|
||||
if hostName == nil || hostName.(string) == "" {
|
||||
return nil
|
||||
}
|
||||
database, _ := databaseRepo.Get(commonRepo.WithByName(hostName.(string)))
|
||||
if database.ID == 0 {
|
||||
return nil
|
||||
}
|
||||
var resourceId uint
|
||||
if dbConfig.DbName != "" && dbConfig.DbUser != "" && dbConfig.Password != "" {
|
||||
switch database.Type {
|
||||
case constant.AppPostgresql, constant.AppPostgres:
|
||||
iPostgresqlRepo := repo.NewIPostgresqlRepo()
|
||||
oldPostgresqlDb, _ := iPostgresqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iPostgresqlRepo.WithByFrom(constant.ResourceLocal))
|
||||
resourceId = oldPostgresqlDb.ID
|
||||
if oldPostgresqlDb.ID > 0 {
|
||||
if oldPostgresqlDb.Username != dbConfig.DbUser || oldPostgresqlDb.Password != dbConfig.Password {
|
||||
return buserr.New(constant.ErrDbUserNotValid)
|
||||
}
|
||||
} else {
|
||||
var createPostgresql dto.PostgresqlDBCreate
|
||||
createPostgresql.Name = dbConfig.DbName
|
||||
createPostgresql.Username = dbConfig.DbUser
|
||||
createPostgresql.Database = database.Name
|
||||
createPostgresql.Format = "UTF8"
|
||||
createPostgresql.Password = dbConfig.Password
|
||||
createPostgresql.From = database.From
|
||||
createPostgresql.SuperUser = true
|
||||
pgdb, err := NewIPostgresqlService().Create(ctx, createPostgresql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resourceId = pgdb.ID
|
||||
}
|
||||
} else {
|
||||
var createPostgresql dto.PostgresqlDBCreate
|
||||
createPostgresql.Name = dbConfig.DbName
|
||||
createPostgresql.Username = dbConfig.DbUser
|
||||
createPostgresql.Database = database.Name
|
||||
createPostgresql.Format = "UTF8"
|
||||
createPostgresql.Password = dbConfig.Password
|
||||
createPostgresql.From = database.From
|
||||
createPostgresql.SuperUser = true
|
||||
pgdb, err := NewIPostgresqlService().Create(ctx, createPostgresql)
|
||||
if err != nil {
|
||||
return err
|
||||
case constant.AppMysql, constant.AppMariaDB:
|
||||
iMysqlRepo := repo.NewIMysqlRepo()
|
||||
oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iMysqlRepo.WithByFrom(constant.ResourceLocal))
|
||||
resourceId = oldMysqlDb.ID
|
||||
if oldMysqlDb.ID > 0 {
|
||||
if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password {
|
||||
return buserr.New(constant.ErrDbUserNotValid)
|
||||
}
|
||||
} else {
|
||||
var createMysql dto.MysqlDBCreate
|
||||
createMysql.Name = dbConfig.DbName
|
||||
createMysql.Username = dbConfig.DbUser
|
||||
createMysql.Database = database.Name
|
||||
createMysql.Format = "utf8mb4"
|
||||
createMysql.Permission = "%"
|
||||
createMysql.Password = dbConfig.Password
|
||||
createMysql.From = database.From
|
||||
mysqldb, err := NewIMysqlService().Create(ctx, createMysql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resourceId = mysqldb.ID
|
||||
}
|
||||
resourceId = pgdb.ID
|
||||
}
|
||||
case constant.AppMysql, constant.AppMariaDB:
|
||||
iMysqlRepo := repo.NewIMysqlRepo()
|
||||
oldMysqlDb, _ := iMysqlRepo.Get(commonRepo.WithByName(dbConfig.DbName), iMysqlRepo.WithByFrom(constant.ResourceLocal))
|
||||
resourceId = oldMysqlDb.ID
|
||||
if oldMysqlDb.ID > 0 {
|
||||
if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password {
|
||||
return buserr.New(constant.ErrDbUserNotValid)
|
||||
}
|
||||
} else {
|
||||
var createMysql dto.MysqlDBCreate
|
||||
createMysql.Name = dbConfig.DbName
|
||||
createMysql.Username = dbConfig.DbUser
|
||||
createMysql.Database = database.Name
|
||||
createMysql.Format = "utf8mb4"
|
||||
createMysql.Permission = "%"
|
||||
createMysql.Password = dbConfig.Password
|
||||
createMysql.From = database.From
|
||||
mysqldb, err := NewIMysqlService().Create(ctx, createMysql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resourceId = mysqldb.ID
|
||||
}
|
||||
}
|
||||
|
||||
var installResource model.AppInstallResource
|
||||
installResource.ResourceId = resourceId
|
||||
installResource.AppInstallId = appInstall.ID
|
||||
if database.AppInstallID > 0 {
|
||||
installResource.LinkId = database.AppInstallID
|
||||
} else {
|
||||
installResource.LinkId = database.ID
|
||||
}
|
||||
installResource.Key = database.Type
|
||||
installResource.From = database.From
|
||||
return appInstallResourceRepo.Create(ctx, &installResource)
|
||||
}
|
||||
var installResource model.AppInstallResource
|
||||
installResource.ResourceId = resourceId
|
||||
installResource.AppInstallId = appInstall.ID
|
||||
if database.AppInstallID > 0 {
|
||||
installResource.LinkId = database.AppInstallID
|
||||
} else {
|
||||
installResource.LinkId = database.ID
|
||||
}
|
||||
installResource.Key = database.Type
|
||||
installResource.From = database.From
|
||||
if err := appInstallResourceRepo.Create(ctx, &installResource); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleAppInstallErr(ctx context.Context, install *model.AppInstall) error {
|
||||
op := files.NewFileOp()
|
||||
appDir := install.GetPath()
|
||||
dir, _ := os.Stat(appDir)
|
||||
if dir != nil {
|
||||
_, _ = compose.Down(install.GetComposePath())
|
||||
if err := op.DeleteDir(appDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := deleteLink(ctx, install, true, true, true); err != nil {
|
||||
return err
|
||||
installTask.AddSubTask(task.GetTaskName(dbConfig.DbName, task.TaskCreate, task.TaskScopeDatabase), createAppDataBase, deleteAppLink)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -333,7 +322,8 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b
|
||||
if err != nil && !forceDelete {
|
||||
return handleErr(install, err, out)
|
||||
}
|
||||
if err = runScript(&install, "uninstall"); err != nil {
|
||||
//TODO use task
|
||||
if err = runScript(nil, &install, "uninstall"); err != nil {
|
||||
_, _ = compose.Up(install.GetComposePath())
|
||||
return err
|
||||
}
|
||||
@ -652,7 +642,8 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
return
|
||||
}
|
||||
|
||||
if upErr = runScript(&install, "upgrade"); upErr != nil {
|
||||
//TODO use task
|
||||
if upErr = runScript(nil, &install, "upgrade"); upErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@ -800,7 +791,7 @@ func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.App
|
||||
return
|
||||
}
|
||||
|
||||
func copyData(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, req request.AppInstallCreate) (err error) {
|
||||
func copyData(task *task.Task, app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, req request.AppInstallCreate) (err error) {
|
||||
fileOp := files.NewFileOp()
|
||||
appResourceDir := path.Join(constant.AppResourceDir, app.Resource)
|
||||
|
||||
@ -853,7 +844,7 @@ func copyData(app model.App, appDetail model.AppDetail, appInstall *model.AppIns
|
||||
return
|
||||
}
|
||||
|
||||
func runScript(appInstall *model.AppInstall, operate string) error {
|
||||
func runScript(task *task.Task, appInstall *model.AppInstall, operate string) error {
|
||||
workDir := appInstall.GetPath()
|
||||
scriptPath := ""
|
||||
switch operate {
|
||||
@ -867,15 +858,17 @@ func runScript(appInstall *model.AppInstall, operate string) error {
|
||||
if !files.NewFileOp().Stat(scriptPath) {
|
||||
return nil
|
||||
}
|
||||
logStr := i18n.GetWithName("ExecShell", operate)
|
||||
task.LogStart(logStr)
|
||||
out, err := cmd.ExecScript(scriptPath, workDir)
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
errMsg := fmt.Sprintf("run script %s error %s", scriptPath, out)
|
||||
global.LOG.Error(errMsg)
|
||||
return errors.New(errMsg)
|
||||
err = errors.New(out)
|
||||
}
|
||||
task.LogFailedWithErr(logStr, err)
|
||||
return err
|
||||
}
|
||||
task.LogSuccess(logStr)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -905,38 +898,59 @@ func checkContainerNameIsExist(containerName, appDir string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func upApp(appInstall *model.AppInstall, pullImages bool) {
|
||||
func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) {
|
||||
upProject := func(appInstall *model.AppInstall) (err error) {
|
||||
var (
|
||||
out string
|
||||
errMsg string
|
||||
)
|
||||
if pullImages && appInstall.App.Type != "php" {
|
||||
out, err = compose.Pull(appInstall.GetComposePath())
|
||||
projectName := strings.ToLower(appInstall.Name)
|
||||
envByte, err := files.NewFileOp().GetContent(appInstall.GetEnvPath())
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
if strings.Contains(out, "no such host") {
|
||||
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
|
||||
}
|
||||
if strings.Contains(out, "timeout") {
|
||||
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
|
||||
}
|
||||
appInstall.Message = errMsg + out
|
||||
}
|
||||
return err
|
||||
}
|
||||
images, err := composeV2.GetDockerComposeImages(projectName, envByte, []byte(appInstall.DockerCompose))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, image := range images {
|
||||
task.Log(i18n.GetWithName("PullImageStart", image))
|
||||
if out, err = cmd.ExecWithTimeOut("docker pull "+image, 20*time.Minute); err != nil {
|
||||
if out != "" {
|
||||
if strings.Contains(out, "no such host") {
|
||||
errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":"
|
||||
}
|
||||
if strings.Contains(out, "timeout") {
|
||||
errMsg = i18n.GetMsgByKey("ErrImagePullTimeOut") + ":"
|
||||
}
|
||||
}
|
||||
appInstall.Message = errMsg + out
|
||||
task.LogFailedWithErr(i18n.GetMsgByKey("PullImage"), err)
|
||||
return err
|
||||
} else {
|
||||
task.Log(i18n.GetMsgByKey("PullImageSuccess"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("Run"), i18n.GetMsgByKey("App"))
|
||||
task.Log(logStr)
|
||||
out, err = compose.Up(appInstall.GetComposePath())
|
||||
if err != nil {
|
||||
if out != "" {
|
||||
appInstall.Message = errMsg + out
|
||||
err = errors.New(out)
|
||||
}
|
||||
task.LogFailedWithErr(logStr, err)
|
||||
return err
|
||||
}
|
||||
task.LogSuccess(logStr)
|
||||
return
|
||||
}
|
||||
if err := upProject(appInstall); err != nil {
|
||||
if appInstall.Message == "" {
|
||||
appInstall.Message = err.Error()
|
||||
}
|
||||
appInstall.Status = constant.UpErr
|
||||
} else {
|
||||
appInstall.Status = constant.Running
|
||||
@ -944,13 +958,12 @@ func upApp(appInstall *model.AppInstall, pullImages bool) {
|
||||
exist, _ := appInstallRepo.GetFirst(commonRepo.WithByID(appInstall.ID))
|
||||
if exist.ID > 0 {
|
||||
containerNames, err := getContainerNames(*appInstall)
|
||||
if err != nil {
|
||||
return
|
||||
if err == nil {
|
||||
if len(containerNames) > 0 {
|
||||
appInstall.ContainerName = strings.Join(containerNames, ",")
|
||||
}
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
}
|
||||
if len(containerNames) > 0 {
|
||||
appInstall.ContainerName = strings.Join(containerNames, ",")
|
||||
}
|
||||
_ = appInstallRepo.Save(context.Background(), appInstall)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,4 +43,6 @@ var (
|
||||
phpExtensionsRepo = repo.NewIPHPExtensionsRepo()
|
||||
|
||||
favoriteRepo = repo.NewIFavoriteRepo()
|
||||
|
||||
taskRepo = repo.NewITaskRepo()
|
||||
)
|
||||
|
@ -466,7 +466,13 @@ func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.Fi
|
||||
return nil, fmt.Errorf("handle ungzip file %s failed, err: %v", fileGzPath, err)
|
||||
}
|
||||
}
|
||||
case "image-pull", "image-push", "image-build", "compose-create":
|
||||
case constant.TypeTask:
|
||||
task, err := taskRepo.GetFirst(taskRepo.WithByID(req.TaskID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logFilePath = task.LogFile
|
||||
case constant.TypeImagePull, constant.TypeImagePush, constant.TypeImageBuild, constant.TypeComposeCreate:
|
||||
logFilePath = path.Join(global.CONF.System.TmpDir, fmt.Sprintf("docker_logs/%s", req.Name))
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,12 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
@ -206,6 +208,13 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
||||
if exist, _ := websiteRepo.GetBy(websiteRepo.WithAlias(alias)); len(exist) > 0 {
|
||||
return buserr.New(constant.ErrAliasIsExist)
|
||||
}
|
||||
if len(create.FtpPassword) != 0 {
|
||||
pass, err := base64.StdEncoding.DecodeString(create.FtpPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
create.FtpPassword = string(pass)
|
||||
}
|
||||
|
||||
nginxInstall, err := getAppInstallByKey(constant.AppOpenresty)
|
||||
if err != nil {
|
||||
@ -249,23 +258,15 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
||||
runtime *model.Runtime
|
||||
)
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if website.AppInstallID > 0 {
|
||||
req := request.AppInstalledOperate{
|
||||
InstallId: website.AppInstallID,
|
||||
Operate: constant.Delete,
|
||||
ForceDelete: true,
|
||||
}
|
||||
if err := NewIAppInstalledService().Operate(req); err != nil {
|
||||
global.LOG.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
createTask, err := task.NewTaskWithOps(create.PrimaryDomain, task.TaskCreate, task.TaskScopeWebsite, create.TaskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var proxy string
|
||||
|
||||
switch create.Type {
|
||||
|
||||
case constant.Deployment:
|
||||
if create.AppType == constant.NewApp {
|
||||
var (
|
||||
@ -276,13 +277,10 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
||||
req.AppDetailId = create.AppInstall.AppDetailId
|
||||
req.Params = create.AppInstall.Params
|
||||
req.AppContainerConfig = create.AppInstall.AppContainerConfig
|
||||
tx, installCtx := getTxAndContext()
|
||||
install, err = NewIAppService().Install(installCtx, req)
|
||||
install, err = NewIAppService().Install(req)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
tx.Commit()
|
||||
appInstall = install
|
||||
website.AppInstallID = install.ID
|
||||
website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort)
|
||||
@ -292,9 +290,13 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appInstall = &install
|
||||
website.AppInstallID = appInstall.ID
|
||||
website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort)
|
||||
configApp := func(t *task.Task) error {
|
||||
appInstall = &install
|
||||
website.AppInstallID = appInstall.ID
|
||||
website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort)
|
||||
return nil
|
||||
}
|
||||
createTask.AddSubTask(i18n.GetMsgByKey("ConfigApp"), configApp, nil)
|
||||
}
|
||||
case constant.Runtime:
|
||||
runtime, err = runtimeRepo.GetFirst(commonRepo.WithByID(create.RuntimeID))
|
||||
@ -302,75 +304,89 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
||||
return err
|
||||
}
|
||||
website.RuntimeID = runtime.ID
|
||||
switch runtime.Type {
|
||||
case constant.RuntimePHP:
|
||||
if runtime.Resource == constant.ResourceAppstore {
|
||||
var (
|
||||
req request.AppInstallCreate
|
||||
install *model.AppInstall
|
||||
)
|
||||
reg, _ := regexp.Compile(`[^a-z0-9_-]+`)
|
||||
req.Name = reg.ReplaceAllString(strings.ToLower(alias), "")
|
||||
req.AppDetailId = create.AppInstall.AppDetailId
|
||||
req.Params = create.AppInstall.Params
|
||||
req.Params["IMAGE_NAME"] = runtime.Image
|
||||
req.AppContainerConfig = create.AppInstall.AppContainerConfig
|
||||
req.Params["PANEL_WEBSITE_DIR"] = path.Join(nginxInstall.GetPath(), "/www")
|
||||
tx, installCtx := getTxAndContext()
|
||||
install, err = NewIAppService().Install(installCtx, req)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
tx.Commit()
|
||||
website.AppInstallID = install.ID
|
||||
appInstall = install
|
||||
website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort)
|
||||
} else {
|
||||
website.ProxyType = create.ProxyType
|
||||
if website.ProxyType == constant.RuntimeProxyUnix {
|
||||
proxy = fmt.Sprintf("unix:%s", path.Join("/www/sites", website.Alias, "php-pool", "php-fpm.sock"))
|
||||
}
|
||||
if website.ProxyType == constant.RuntimeProxyTcp {
|
||||
proxy = fmt.Sprintf("127.0.0.1:%d", create.Port)
|
||||
}
|
||||
website.Proxy = proxy
|
||||
if runtime.Type == constant.RuntimePHP {
|
||||
var (
|
||||
req request.AppInstallCreate
|
||||
install *model.AppInstall
|
||||
)
|
||||
reg, _ := regexp.Compile(`[^a-z0-9_-]+`)
|
||||
req.Name = reg.ReplaceAllString(strings.ToLower(alias), "")
|
||||
req.AppDetailId = create.AppInstall.AppDetailId
|
||||
req.Params = create.AppInstall.Params
|
||||
req.Params["IMAGE_NAME"] = runtime.Image
|
||||
req.AppContainerConfig = create.AppInstall.AppContainerConfig
|
||||
req.Params["PANEL_WEBSITE_DIR"] = path.Join(nginxInstall.GetPath(), "/www")
|
||||
install, err = NewIAppService().Install(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
|
||||
website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port)
|
||||
website.AppInstallID = install.ID
|
||||
appInstall = install
|
||||
website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort)
|
||||
} else {
|
||||
website.ProxyType = create.ProxyType
|
||||
if website.ProxyType == constant.RuntimeProxyUnix {
|
||||
proxy = fmt.Sprintf("unix:%s", path.Join("/www/sites", website.Alias, "php-pool", "php-fpm.sock"))
|
||||
}
|
||||
if website.ProxyType == constant.RuntimeProxyTcp {
|
||||
proxy = fmt.Sprintf("127.0.0.1:%d", create.Port)
|
||||
}
|
||||
website.Proxy = proxy
|
||||
}
|
||||
}
|
||||
|
||||
if err = configDefaultNginx(website, domains, appInstall, runtime); err != nil {
|
||||
return err
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo:
|
||||
website.Proxy = fmt.Sprintf("127.0.0.1:%d", runtime.Port)
|
||||
}
|
||||
|
||||
if len(create.FtpUser) != 0 && len(create.FtpPassword) != 0 {
|
||||
indexDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "index")
|
||||
itemID, err := NewIFtpService().Create(dto.FtpCreate{User: create.FtpUser, Password: create.FtpPassword, Path: indexDir})
|
||||
if err != nil {
|
||||
global.LOG.Errorf("create ftp for website failed, err: %v", err)
|
||||
createFtpUser := func(t *task.Task) error {
|
||||
indexDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "index")
|
||||
itemID, err := NewIFtpService().Create(dto.FtpCreate{User: create.FtpUser, Password: create.FtpPassword, Path: indexDir})
|
||||
if err != nil {
|
||||
createTask.Log(fmt.Sprintf("create ftp for website failed, err: %v", err))
|
||||
}
|
||||
website.FtpID = itemID
|
||||
return nil
|
||||
}
|
||||
website.FtpID = itemID
|
||||
deleteFtpUser := func() {
|
||||
if website.FtpID > 0 {
|
||||
req := dto.BatchDeleteReq{Ids: []uint{website.FtpID}}
|
||||
if err = NewIFtpService().Delete(req); err != nil {
|
||||
createTask.Log(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
createTask.AddSubTask(i18n.GetWithName("ConfigFTP", create.FtpUser), createFtpUser, deleteFtpUser)
|
||||
}
|
||||
|
||||
if err = createWafConfig(website, domains); err != nil {
|
||||
return err
|
||||
configNginx := func(t *task.Task) error {
|
||||
if err = configDefaultNginx(website, domains, appInstall, runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = createWafConfig(website, domains); err != nil {
|
||||
return err
|
||||
}
|
||||
tx, ctx := helper.GetTxAndContext()
|
||||
defer tx.Rollback()
|
||||
if err = websiteRepo.Create(ctx, website); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range domains {
|
||||
domains[i].WebsiteID = website.ID
|
||||
}
|
||||
if err = websiteDomainRepo.BatchCreate(ctx, domains); err != nil {
|
||||
return err
|
||||
}
|
||||
tx.Commit()
|
||||
return nil
|
||||
}
|
||||
|
||||
tx, ctx := helper.GetTxAndContext()
|
||||
defer tx.Rollback()
|
||||
if err = websiteRepo.Create(ctx, website); err != nil {
|
||||
return err
|
||||
deleteWebsite := func() {
|
||||
_ = deleteWebsiteFolder(nginxInstall, website)
|
||||
}
|
||||
for i := range domains {
|
||||
domains[i].WebsiteID = website.ID
|
||||
}
|
||||
if err = websiteDomainRepo.BatchCreate(ctx, domains); err != nil {
|
||||
return err
|
||||
}
|
||||
tx.Commit()
|
||||
return nil
|
||||
|
||||
createTask.AddSubTask(i18n.GetMsgByKey("ConfigOpenresty"), configNginx, deleteWebsite)
|
||||
|
||||
return createTask.Execute()
|
||||
}
|
||||
|
||||
func (w WebsiteService) OpWebsite(req request.WebsiteOp) error {
|
||||
|
@ -220,10 +220,9 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := createWebsiteFolder(nginxInstall, website, runtime); err != nil {
|
||||
if err = createWebsiteFolder(nginxInstall, website, runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nginxFileName := website.Alias + ".conf"
|
||||
configPath := path.Join(constant.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "conf.d", nginxFileName)
|
||||
nginxContent := string(nginx_conf.WebsiteDefault)
|
||||
@ -284,15 +283,13 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
||||
}
|
||||
|
||||
config.FilePath = configPath
|
||||
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := opNginx(nginxInstall.ContainerName, constant.NginxCheck); err != nil {
|
||||
_ = deleteWebsiteFolder(nginxInstall, website)
|
||||
if err = opNginx(nginxInstall.ContainerName, constant.NginxCheck); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
|
||||
_ = deleteWebsiteFolder(nginxInstall, website)
|
||||
if err = opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -3,28 +3,35 @@ package task
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/google/uuid"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
)
|
||||
|
||||
type ActionFunc func() error
|
||||
type ActionFunc func(*Task) error
|
||||
type RollbackFunc func()
|
||||
|
||||
type Task struct {
|
||||
Name string
|
||||
TaskID string
|
||||
Logger *log.Logger
|
||||
SubTasks []*SubTask
|
||||
Rollbacks []RollbackFunc
|
||||
logFile *os.File
|
||||
taskRepo repo.ITaskRepo
|
||||
Task *model.Task
|
||||
ParentID string
|
||||
}
|
||||
|
||||
type SubTask struct {
|
||||
RootTask *Task
|
||||
Name string
|
||||
Retry int
|
||||
Timeout time.Duration
|
||||
@ -33,51 +40,107 @@ type SubTask struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
func NewTask(name string, taskType string) (*Task, error) {
|
||||
logPath := path.Join(constant.LogDir, taskType)
|
||||
//TODO 增加插入到日志表的逻辑
|
||||
const (
|
||||
TaskInstall = "TaskInstall"
|
||||
TaskUninstall = "TaskUninstall"
|
||||
TaskCreate = "TaskCreate"
|
||||
TaskDelete = "TaskDelete"
|
||||
TaskUpgrade = "TaskUpgrade"
|
||||
TaskUpdate = "TaskUpdate"
|
||||
TaskRestart = "TaskRestart"
|
||||
)
|
||||
|
||||
const (
|
||||
TaskScopeWebsite = "Website"
|
||||
TaskScopeApp = "App"
|
||||
TaskScopeRuntime = "Runtime"
|
||||
TaskScopeDatabase = "Database"
|
||||
)
|
||||
|
||||
const (
|
||||
TaskSuccess = "Success"
|
||||
TaskFailed = "Failed"
|
||||
)
|
||||
|
||||
func GetTaskName(resourceName, operate, scope string) string {
|
||||
return fmt.Sprintf("%s%s [%s]", i18n.GetMsgByKey(operate), i18n.GetMsgByKey(scope), resourceName)
|
||||
}
|
||||
|
||||
func NewTaskWithOps(resourceName, operate, scope, taskID string) (*Task, error) {
|
||||
return NewTask(GetTaskName(resourceName, operate, scope), scope, taskID)
|
||||
}
|
||||
|
||||
func NewChildTask(name, taskType, parentTaskID string) (*Task, error) {
|
||||
task, err := NewTask(name, taskType, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
task.ParentID = parentTaskID
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func NewTask(name, taskType, taskID string) (*Task, error) {
|
||||
if taskID == "" {
|
||||
taskID = uuid.New().String()
|
||||
}
|
||||
logDir := path.Join(constant.LogDir, taskType)
|
||||
if _, err := os.Stat(logDir); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(logDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create log directory: %w", err)
|
||||
}
|
||||
}
|
||||
logPath := path.Join(constant.LogDir, taskType, taskID+".log")
|
||||
file, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open log file: %w", err)
|
||||
}
|
||||
logger := log.New(file, "", log.LstdFlags)
|
||||
return &Task{Name: name, logFile: file, Logger: logger}, nil
|
||||
taskModel := &model.Task{
|
||||
ID: taskID,
|
||||
Name: name,
|
||||
Type: taskType,
|
||||
LogFile: logPath,
|
||||
Status: constant.StatusRunning,
|
||||
}
|
||||
taskRepo := repo.NewITaskRepo()
|
||||
task := &Task{Name: name, logFile: file, Logger: logger, taskRepo: taskRepo, Task: taskModel}
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func (t *Task) AddSubTask(name string, action ActionFunc, rollback RollbackFunc) {
|
||||
subTask := &SubTask{Name: name, Retry: 0, Timeout: 10 * time.Minute, Action: action, Rollback: rollback}
|
||||
subTask := &SubTask{RootTask: t, Name: name, Retry: 0, Timeout: 10 * time.Minute, Action: action, Rollback: rollback}
|
||||
t.SubTasks = append(t.SubTasks, subTask)
|
||||
}
|
||||
|
||||
func (t *Task) AddSubTaskWithOps(name string, action ActionFunc, rollback RollbackFunc, retry int, timeout time.Duration) {
|
||||
subTask := &SubTask{Name: name, Retry: retry, Timeout: timeout, Action: action, Rollback: rollback}
|
||||
subTask := &SubTask{RootTask: t, Name: name, Retry: retry, Timeout: timeout, Action: action, Rollback: rollback}
|
||||
t.SubTasks = append(t.SubTasks, subTask)
|
||||
}
|
||||
|
||||
func (s *SubTask) Execute(logger *log.Logger) bool {
|
||||
logger.Printf(i18n.GetWithName("SubTaskStart", s.Name))
|
||||
func (s *SubTask) Execute() error {
|
||||
s.RootTask.Log(s.Name)
|
||||
var err error
|
||||
for i := 0; i < s.Retry+1; i++ {
|
||||
if i > 0 {
|
||||
logger.Printf(i18n.GetWithName("TaskRetry", strconv.Itoa(i)))
|
||||
s.RootTask.Log(i18n.GetWithName("TaskRetry", strconv.Itoa(i)))
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), s.Timeout)
|
||||
defer cancel()
|
||||
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
done <- s.Action()
|
||||
done <- s.Action(s.RootTask)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.Printf(i18n.GetWithName("TaskTimeout", s.Name))
|
||||
case err := <-done:
|
||||
s.RootTask.Log(i18n.GetWithName("TaskTimeout", s.Name))
|
||||
case err = <-done:
|
||||
if err != nil {
|
||||
s.Error = err
|
||||
logger.Printf(i18n.GetWithNameAndErr("TaskFailed", s.Name, err))
|
||||
s.RootTask.Log(i18n.GetWithNameAndErr("SubTaskFailed", s.Name, err))
|
||||
} else {
|
||||
logger.Printf(i18n.GetWithName("TaskSuccess", s.Name))
|
||||
return true
|
||||
s.RootTask.Log(i18n.GetWithName("SubTaskSuccess", s.Name))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,29 +151,77 @@ func (s *SubTask) Execute(logger *log.Logger) bool {
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
if s.Error != nil {
|
||||
s.Error = fmt.Errorf(i18n.GetWithName("TaskFailed", s.Name))
|
||||
}
|
||||
return false
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Task) updateTask(task *model.Task) {
|
||||
_ = t.taskRepo.Update(context.Background(), task)
|
||||
}
|
||||
|
||||
func (t *Task) Execute() error {
|
||||
t.Logger.Printf(i18n.GetWithName("TaskStart", t.Name))
|
||||
if err := t.taskRepo.Create(context.Background(), t.Task); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
t.Log(i18n.GetWithName("TaskStart", t.Name))
|
||||
for _, subTask := range t.SubTasks {
|
||||
if subTask.Execute(t.Logger) {
|
||||
t.Task.CurrentStep = subTask.Name
|
||||
t.updateTask(t.Task)
|
||||
if err = subTask.Execute(); err == nil {
|
||||
if subTask.Rollback != nil {
|
||||
t.Rollbacks = append(t.Rollbacks, subTask.Rollback)
|
||||
}
|
||||
} else {
|
||||
err = subTask.Error
|
||||
t.Task.ErrorMsg = err.Error()
|
||||
t.Task.Status = constant.StatusFailed
|
||||
for _, rollback := range t.Rollbacks {
|
||||
rollback()
|
||||
}
|
||||
t.updateTask(t.Task)
|
||||
break
|
||||
}
|
||||
}
|
||||
t.Logger.Printf(i18n.GetWithName("TaskEnd", t.Name))
|
||||
if t.Task.Status == constant.Running {
|
||||
t.Task.Status = constant.StatusSuccess
|
||||
t.Log(i18n.GetWithName("TaskSuccess", t.Name))
|
||||
} else {
|
||||
t.Log(i18n.GetWithName("TaskFailed", t.Name))
|
||||
}
|
||||
t.Log("[TASK-END]")
|
||||
t.Task.EndAt = time.Now()
|
||||
t.updateTask(t.Task)
|
||||
_ = t.logFile.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *Task) DeleteLogFile() {
|
||||
_ = os.Remove(t.Task.LogFile)
|
||||
}
|
||||
|
||||
func (t *Task) LogWithStatus(msg string, err error) {
|
||||
if err != nil {
|
||||
t.Logger.Printf(i18n.GetWithNameAndErr("FailedStatus", msg, err))
|
||||
} else {
|
||||
t.Logger.Printf(i18n.GetWithName("SuccessStatus", msg))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Task) Log(msg string) {
|
||||
t.Logger.Printf(msg)
|
||||
}
|
||||
|
||||
func (t *Task) LogFailed(msg string) {
|
||||
t.Logger.Printf(msg + i18n.GetMsgByKey("Failed"))
|
||||
}
|
||||
|
||||
func (t *Task) LogFailedWithErr(msg string, err error) {
|
||||
t.Logger.Printf(fmt.Sprintf("%s %s : %s", msg, i18n.GetMsgByKey("Failed"), err.Error()))
|
||||
}
|
||||
|
||||
func (t *Task) LogSuccess(msg string) {
|
||||
t.Logger.Printf(msg + i18n.GetMsgByKey("Success"))
|
||||
}
|
||||
|
||||
func (t *Task) LogStart(msg string) {
|
||||
t.Logger.Printf(fmt.Sprintf("%s%s", i18n.GetMsgByKey("Start"), msg))
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ const (
|
||||
SyncSuccess = "SyncSuccess"
|
||||
Paused = "Paused"
|
||||
UpErr = "UpErr"
|
||||
InstallErr = "InstallErr"
|
||||
|
||||
ContainerPrefix = "1Panel-"
|
||||
|
||||
|
@ -7,10 +7,15 @@ const (
|
||||
|
||||
SystemRestart = "systemRestart"
|
||||
|
||||
TypeWebsite = "website"
|
||||
TypePhp = "php"
|
||||
TypeSSL = "ssl"
|
||||
TypeSystem = "system"
|
||||
TypeWebsite = "website"
|
||||
TypePhp = "php"
|
||||
TypeSSL = "ssl"
|
||||
TypeSystem = "system"
|
||||
TypeTask = "task"
|
||||
TypeImagePull = "image-pull"
|
||||
TypeImagePush = "image-push"
|
||||
TypeImageBuild = "image-build"
|
||||
TypeComposeCreate = "compose-create"
|
||||
)
|
||||
|
||||
const (
|
||||
|
6
agent/constant/task.go
Normal file
6
agent/constant/task.go
Normal file
@ -0,0 +1,6 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
TaskInstall = "installApp"
|
||||
TaskCreateWebsite = "createWebsite"
|
||||
)
|
@ -198,10 +198,36 @@ ErrXpackNotActive: "This section is a professional edition feature, please synch
|
||||
ErrXpackOutOfDate: "The current license has expired, please re-import the license in Panel Settings-License interface"
|
||||
|
||||
#task
|
||||
TaskStart: "{{.name}} started [START]"
|
||||
TaskEnd: "{{.name}} ended [COMPLETED]"
|
||||
TaskFailed: "{{.name}} failed: {{.err}}"
|
||||
TaskTimeout: "{{.name}} timed out"
|
||||
TaskSuccess: "{{.name}} succeeded"
|
||||
TaskRetry: "Start {{.name}} retry"
|
||||
SubTaskStart: "Start {{.name}}"
|
||||
TaskStart: "{{.name}} Start [START]"
|
||||
TaskEnd: "{{.name}} End [COMPLETED]"
|
||||
TaskFailed: "{{.name}} Failed"
|
||||
TaskTimeout: "{{.name}} Timeout"
|
||||
TaskSuccess: "{{.name}} Success"
|
||||
TaskRetry: "Starting {{.name}} Retry"
|
||||
SubTaskSuccess: "{{ .name }} Success"
|
||||
SubTaskFailed: "{{ .name }} Failed: {{ .err }}"
|
||||
TaskInstall: "Install"
|
||||
TaskUninstall: "Uninstall"
|
||||
TaskCreate: "Create"
|
||||
TaskDelete: "Delete"
|
||||
TaskUpgrade: "Upgrade"
|
||||
TaskUpdate: "Update"
|
||||
TaskRestart: "Restart"
|
||||
Website: "Website"
|
||||
App: "App"
|
||||
Runtime: "Runtime"
|
||||
Database: "Database"
|
||||
ConfigFTP: "Create FTP User {{ .name }}"
|
||||
ConfigOpenresty: "Create Openresty Configuration File"
|
||||
InstallAppSuccess: "App {{ .name }} Installed Successfully"
|
||||
ConfigRuntime: "Configure Runtime"
|
||||
ConfigApp: "Configure App"
|
||||
SuccessStatus: "{{ .name }} Success"
|
||||
FailedStatus: "{{ .name }} Failed {{.err}}"
|
||||
HandleLink: "Handle App Link"
|
||||
HandleDatabaseApp: "Handle App Parameters"
|
||||
ExecShell: "Execute {{ .name }} Script"
|
||||
PullImage: "Pull Image"
|
||||
Start: "Start"
|
||||
Run: "Run"
|
||||
|
||||
|
@ -202,8 +202,33 @@ ErrXpackOutOfDate: "當前許可證已過期,請重新在 面板設置-許可
|
||||
#task
|
||||
TaskStart: "{{.name}} 開始 [START]"
|
||||
TaskEnd: "{{.name}} 結束 [COMPLETED]"
|
||||
TaskFailed: "{{.name}} 失敗: {{.err}}"
|
||||
TaskTimeout: "{{.name}} 逾時"
|
||||
TaskFailed: "{{.name}} 失敗"
|
||||
TaskTimeout: "{{.name}} 超時"
|
||||
TaskSuccess: "{{.name}} 成功"
|
||||
TaskRetry: "開始第 {{.name}} 次重試"
|
||||
SubTaskStart: "開始 {{.name}}"
|
||||
SubTaskSuccess: "{{ .name }} 成功"
|
||||
SubTaskFailed: "{{ .name }} 失敗: {{ .err }}"
|
||||
TaskInstall: "安裝"
|
||||
TaskUninstall: "卸載"
|
||||
TaskCreate: "創建"
|
||||
TaskDelete: "刪除"
|
||||
TaskUpgrade: "升級"
|
||||
TaskUpdate: "更新"
|
||||
TaskRestart: "重啟"
|
||||
Website: "網站"
|
||||
App: "應用"
|
||||
Runtime: "運行環境"
|
||||
Database: "數據庫"
|
||||
ConfigFTP: "創建 FTP 用戶 {{ .name }}"
|
||||
ConfigOpenresty: "創建 Openresty 配置文件"
|
||||
InstallAppSuccess: "應用 {{ .name }} 安裝成功"
|
||||
ConfigRuntime: "配置運行環境"
|
||||
ConfigApp: "配置應用"
|
||||
SuccessStatus: "{{ .name }} 成功"
|
||||
FailedStatus: "{{ .name }} 失敗 {{.err}}"
|
||||
HandleLink: "處理應用關聯"
|
||||
HandleDatabaseApp: "處理應用參數"
|
||||
ExecShell: "執行 {{ .name }} 腳本"
|
||||
PullImage: "拉取鏡像"
|
||||
Start: "開始"
|
||||
Run: "啟動"
|
||||
|
@ -203,8 +203,33 @@ ErrXpackOutOfDate: "当前许可证已过期,请重新在 面板设置-许可
|
||||
#task
|
||||
TaskStart: "{{.name}} 开始 [START]"
|
||||
TaskEnd: "{{.name}} 结束 [COMPLETED]"
|
||||
TaskFailed: "{{.name}} 失败: {{.err}}"
|
||||
TaskFailed: "{{.name}} 失败"
|
||||
TaskTimeout: "{{.name}} 超时"
|
||||
TaskSuccess: "{{.name}} 成功"
|
||||
TaskRetry: "开始第 {{.name}} 次重试"
|
||||
SubTaskStart: "开始 {{.name}}"
|
||||
SubTaskSuccess: "{{ .name }} 成功"
|
||||
SubTaskFailed: "{{ .name }} 失败: {{ .err }}"
|
||||
TaskInstall: "安装"
|
||||
TaskUninstall: "卸载"
|
||||
TaskCreate: "创建"
|
||||
TaskDelete: "删除"
|
||||
TaskUpgrade: "升级"
|
||||
TaskUpdate: "更新"
|
||||
TaskRestart: "重启"
|
||||
Website: "网站"
|
||||
App: "应用"
|
||||
Runtime: "运行环境"
|
||||
Database: "数据库"
|
||||
ConfigFTP: "创建 FTP 用户 {{ .name }}"
|
||||
ConfigOpenresty: "创建 Openresty 配置文件"
|
||||
InstallAppSuccess: "应用 {{ .name }} 安装成功"
|
||||
ConfigRuntime: "配置运行环境"
|
||||
ConfigApp: "配置应用"
|
||||
SuccessStatus: "{{ .name }} 成功"
|
||||
FailedStatus: "{{ .name }} 失败 {{.err}}"
|
||||
HandleLink: "处理应用关联"
|
||||
HandleDatabaseApp: "处理应用参数"
|
||||
ExecShell: "执行 {{ .name }} 脚本"
|
||||
PullImage: "拉取镜像"
|
||||
Start: "开始"
|
||||
Run: "启动"
|
@ -17,6 +17,7 @@ func Init() {
|
||||
migrations.InitDefaultGroup,
|
||||
migrations.InitDefaultCA,
|
||||
migrations.InitPHPExtensions,
|
||||
migrations.AddTask,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
@ -253,3 +253,11 @@ var InitPHPExtensions = &gormigrate.Migration{
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddTask = &gormigrate.Migration{
|
||||
ID: "20240724-add-task",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(
|
||||
&model.Task{})
|
||||
},
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/cron"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/1Panel-dev/1Panel/agent/init/app"
|
||||
"github.com/1Panel-dev/1Panel/agent/init/business"
|
||||
@ -40,25 +39,23 @@ func Start() {
|
||||
server := &http.Server{
|
||||
Handler: rootRouter,
|
||||
}
|
||||
if len(global.CurrentNode) == 0 || global.CurrentNode == "127.0.0.1" {
|
||||
_ = os.Remove("/tmp/agent.sock")
|
||||
listener, err := net.Listen("unix", "/tmp/agent.sock")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_ = server.Serve(listener)
|
||||
} else {
|
||||
server.Addr = "0.0.0.0:9999"
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
ln, err := net.Listen("tcp4", "0.0.0.0:9999")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
global.LOG.Info("listen at http://0.0.0.0:9999")
|
||||
if err := server.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//ln, err := net.Listen("tcp4", "0.0.0.0:9998")
|
||||
//if err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
//type tcpKeepAliveListener struct {
|
||||
// *net.TCPListener
|
||||
//}
|
||||
//
|
||||
//global.LOG.Info("listen at http://0.0.0.0:9998")
|
||||
//if err := server.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}); err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
|
||||
os.Remove("/tmp/agent.sock")
|
||||
listener, err := net.Listen("unix", "/tmp/agent.sock")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
server.Serve(listener)
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ type ILogService interface {
|
||||
CreateLoginLog(operation model.LoginLog) error
|
||||
PageLoginLog(search dto.SearchLgLogWithPage) (int64, interface{}, error)
|
||||
|
||||
CreateOperationLog(operation model.OperationLog) error
|
||||
CreateOperationLog(operation *model.OperationLog) error
|
||||
PageOperationLog(search dto.SearchOpLogWithPage) (int64, interface{}, error)
|
||||
|
||||
CleanLogs(logtype string) error
|
||||
@ -92,8 +92,8 @@ func (u *LogService) PageLoginLog(req dto.SearchLgLogWithPage) (int64, interface
|
||||
return total, dtoOps, err
|
||||
}
|
||||
|
||||
func (u *LogService) CreateOperationLog(operation model.OperationLog) error {
|
||||
return logRepo.CreateOperationLog(&operation)
|
||||
func (u *LogService) CreateOperationLog(operation *model.OperationLog) error {
|
||||
return logRepo.CreateOperationLog(operation)
|
||||
}
|
||||
|
||||
func (u *LogService) PageOperationLog(req dto.SearchOpLogWithPage) (int64, interface{}, error) {
|
||||
|
@ -27,7 +27,7 @@ func OperationLog() gin.HandlerFunc {
|
||||
}
|
||||
|
||||
source := loadLogInfo(c.Request.URL.Path)
|
||||
record := model.OperationLog{
|
||||
record := &model.OperationLog{
|
||||
Source: source,
|
||||
IP: c.ClientIP(),
|
||||
Method: strings.ToLower(c.Request.Method),
|
||||
|
@ -22,7 +22,7 @@ func Proxy() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
currentNode := c.Request.Header.Get("CurrentNode")
|
||||
if currentNode == "127.0.0.1" {
|
||||
if len(currentNode) == 0 || currentNode == "127.0.0.1" {
|
||||
sockPath := "/tmp/agent.sock"
|
||||
if _, err := os.Stat(sockPath); err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrProxy, err)
|
||||
|
@ -49,6 +49,7 @@
|
||||
"qs": "^6.12.1",
|
||||
"screenfull": "^6.0.2",
|
||||
"unplugin-vue-define-options": "^0.7.3",
|
||||
"uuid": "^10.0.0",
|
||||
"vue": "^3.4.27",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-codemirror": "^6.1.1",
|
||||
|
@ -174,6 +174,7 @@ export namespace File {
|
||||
name?: string;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
taskID?: string;
|
||||
}
|
||||
|
||||
export interface Favorite extends CommonModel {
|
||||
|
@ -79,6 +79,7 @@ export namespace Website {
|
||||
proxyType: string;
|
||||
ftpUser: string;
|
||||
ftpPassword: string;
|
||||
taskID: string;
|
||||
}
|
||||
|
||||
export interface WebSiteUpdateReq {
|
||||
|
@ -19,7 +19,7 @@ export const CreateWebsite = (req: Website.WebSiteCreateReq) => {
|
||||
if (request.ftpPassword) {
|
||||
request.ftpPassword = Base64.encode(request.ftpPassword);
|
||||
}
|
||||
return http.post<any>(`/websites`, request);
|
||||
return http.post<any>(`/websites`, request, TimeoutEnum.T_10M);
|
||||
};
|
||||
|
||||
export const OpWebsite = (req: Website.WebSiteOp) => {
|
||||
|
@ -246,7 +246,7 @@ const initCodemirror = () => {
|
||||
}
|
||||
});
|
||||
let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;
|
||||
hljsDom.style['min-height'] = '500px';
|
||||
hljsDom.style['min-height'] = '100px';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
212
frontend/src/components/task-log/index.vue
Normal file
212
frontend/src/components/task-log/index.vue
Normal file
@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="open"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:show-close="showClose"
|
||||
:before-close="handleClose"
|
||||
class="task-log-dialog"
|
||||
>
|
||||
<div>
|
||||
<highlightjs ref="editorRef" language="JavaScript" :autodetect="false" :code="content"></highlightjs>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onUnmounted, reactive, ref } from 'vue';
|
||||
import { ReadByLine } from '@/api/modules/files';
|
||||
|
||||
const editorRef = ref();
|
||||
|
||||
const data = ref({
|
||||
enable: false,
|
||||
content: '',
|
||||
path: '',
|
||||
});
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
const tailLog = ref(false);
|
||||
const content = ref('');
|
||||
const end = ref(false);
|
||||
const lastContent = ref('');
|
||||
const scrollerElement = ref<HTMLElement | null>(null);
|
||||
const minPage = ref(1);
|
||||
const maxPage = ref(1);
|
||||
const open = ref(false);
|
||||
const taskID = ref('');
|
||||
const showClose = ref(false);
|
||||
|
||||
const readReq = reactive({
|
||||
taskID: '',
|
||||
type: 'task',
|
||||
page: 1,
|
||||
pageSize: 500,
|
||||
latest: false,
|
||||
});
|
||||
|
||||
const stopSignals = ['[TASK-END]'];
|
||||
|
||||
const acceptParams = (id: string, closeShow: boolean) => {
|
||||
if (closeShow) {
|
||||
showClose.value = closeShow;
|
||||
}
|
||||
taskID.value = id;
|
||||
open.value = true;
|
||||
initCodemirror();
|
||||
init();
|
||||
};
|
||||
|
||||
const getContent = (pre: boolean) => {
|
||||
readReq.taskID = taskID.value;
|
||||
if (readReq.page < 1) {
|
||||
readReq.page = 1;
|
||||
}
|
||||
ReadByLine(readReq).then((res) => {
|
||||
if (!end.value && res.data.end) {
|
||||
lastContent.value = content.value;
|
||||
}
|
||||
|
||||
res.data.content = res.data.content.replace(/\\u(\w{4})/g, function (match, grp) {
|
||||
return String.fromCharCode(parseInt(grp, 16));
|
||||
});
|
||||
data.value = res.data;
|
||||
if (res.data.content != '') {
|
||||
if (stopSignals.some((signal) => res.data.content.endsWith(signal))) {
|
||||
onCloseLog();
|
||||
}
|
||||
if (end.value) {
|
||||
if (lastContent.value == '') {
|
||||
content.value = res.data.content;
|
||||
} else {
|
||||
content.value = pre
|
||||
? res.data.content + '\n' + lastContent.value
|
||||
: lastContent.value + '\n' + res.data.content;
|
||||
}
|
||||
} else {
|
||||
if (content.value == '') {
|
||||
content.value = res.data.content;
|
||||
} else {
|
||||
content.value = pre
|
||||
? res.data.content + '\n' + content.value
|
||||
: content.value + '\n' + res.data.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
end.value = res.data.end;
|
||||
nextTick(() => {
|
||||
if (pre) {
|
||||
if (scrollerElement.value.scrollHeight > 2000) {
|
||||
scrollerElement.value.scrollTop = 2000;
|
||||
}
|
||||
} else {
|
||||
scrollerElement.value.scrollTop = scrollerElement.value.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
if (readReq.latest) {
|
||||
readReq.page = res.data.total;
|
||||
readReq.latest = false;
|
||||
maxPage.value = res.data.total;
|
||||
minPage.value = res.data.total;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const changeTail = (fromOutSide: boolean) => {
|
||||
if (fromOutSide) {
|
||||
tailLog.value = !tailLog.value;
|
||||
}
|
||||
if (tailLog.value) {
|
||||
timer = setInterval(() => {
|
||||
getContent(false);
|
||||
}, 1000 * 3);
|
||||
} else {
|
||||
onCloseLog();
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
onCloseLog();
|
||||
open.value = false;
|
||||
};
|
||||
|
||||
const onCloseLog = async () => {
|
||||
tailLog.value = false;
|
||||
clearInterval(Number(timer));
|
||||
timer = null;
|
||||
};
|
||||
|
||||
function isScrolledToBottom(element: HTMLElement): boolean {
|
||||
return element.scrollTop + element.clientHeight + 1 >= element.scrollHeight;
|
||||
}
|
||||
|
||||
function isScrolledToTop(element: HTMLElement): boolean {
|
||||
return element.scrollTop === 0;
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
tailLog.value = true;
|
||||
if (tailLog.value) {
|
||||
changeTail(false);
|
||||
}
|
||||
readReq.latest = true;
|
||||
getContent(false);
|
||||
};
|
||||
|
||||
const initCodemirror = () => {
|
||||
nextTick(() => {
|
||||
if (editorRef.value) {
|
||||
scrollerElement.value = editorRef.value.$el as HTMLElement;
|
||||
scrollerElement.value.addEventListener('scroll', function () {
|
||||
if (isScrolledToBottom(scrollerElement.value)) {
|
||||
readReq.page = maxPage.value;
|
||||
getContent(false);
|
||||
}
|
||||
if (isScrolledToTop(scrollerElement.value)) {
|
||||
readReq.page = minPage.value - 1;
|
||||
if (readReq.page < 1) {
|
||||
return;
|
||||
}
|
||||
minPage.value = readReq.page;
|
||||
getContent(true);
|
||||
}
|
||||
});
|
||||
let hljsDom = scrollerElement.value.querySelector('.hljs') as HTMLElement;
|
||||
hljsDom.style['min-height'] = '100px';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
onCloseLog();
|
||||
});
|
||||
|
||||
defineExpose({ acceptParams, handleClose });
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.task-log-dialog {
|
||||
--dialog-max-height: 80vh;
|
||||
--dialog-header-height: 50px;
|
||||
--dialog-padding: 20px;
|
||||
.el-dialog {
|
||||
max-width: 60%;
|
||||
max-height: var(--dialog-max-height);
|
||||
margin-top: 5vh !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.el-dialog__body {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: var(--dialog-padding);
|
||||
}
|
||||
.log-container {
|
||||
height: calc(var(--dialog-max-height) - var(--dialog-header-height) - var(--dialog-padding) * 2);
|
||||
overflow: hidden;
|
||||
}
|
||||
.log-file {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -42,7 +42,7 @@ const GlobalStore = defineStore({
|
||||
|
||||
errStatus: '',
|
||||
|
||||
currentNode: '',
|
||||
currentNode: '127.0.0.1',
|
||||
}),
|
||||
getters: {
|
||||
isDarkTheme: (state) =>
|
||||
|
@ -40,7 +40,6 @@ import { reactive, ref } from 'vue';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import DrawerHeader from '@/components/drawer-header/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { Host } from '@/api/interface/host';
|
||||
import { operateForwardRule } from '@/api/modules/host';
|
||||
|
@ -29,7 +29,7 @@
|
||||
</el-button>
|
||||
<span v-if="errBuckets" class="input-error">{{ $t('commons.rule.requiredSelect') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('cronjob.requestExpirationTime')" prop="varsJson.timeout">
|
||||
<el-form-item :label="$t('cronjob.requestExpirationTime')" prop="varsJson.timeout">
|
||||
<el-input-number
|
||||
style="width: 200px"
|
||||
:min="1"
|
||||
|
@ -163,7 +163,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onBeforeUnmount, reactive, ref, shallowRef } from 'vue';
|
||||
import { onBeforeUnmount, reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
@ -13,10 +13,6 @@
|
||||
<el-button type="primary" @click="openCreate">
|
||||
{{ $t('runtime.create') }}
|
||||
</el-button>
|
||||
|
||||
<el-button type="primary" plain @click="onOpenBuildCache()">
|
||||
{{ $t('container.cleanBuildCache') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()" :heightDiff="350">
|
||||
@ -115,8 +111,6 @@ import { Promotion } from '@element-plus/icons-vue';
|
||||
import PortJumpDialog from '@/components/port-jump/index.vue';
|
||||
import AppResources from '@/views/website/runtime/php/check/index.vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { containerPrune } from '@/api/modules/container';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
const loading = ref(false);
|
||||
@ -221,29 +215,6 @@ const openDelete = async (row: Runtime.Runtime) => {
|
||||
});
|
||||
};
|
||||
|
||||
const onOpenBuildCache = () => {
|
||||
ElMessageBox.confirm(i18n.global.t('container.delBuildCacheHelper'), i18n.global.t('container.cleanBuildCache'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
pruneType: 'buildcache',
|
||||
withTagAll: false,
|
||||
};
|
||||
await containerPrune(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('container.cleanSuccess', [res.data.deletedNumber]));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const openLog = (row: any) => {
|
||||
composeLogRef.value.acceptParams({ compose: row.path + '/docker-compose.yml', resource: row.name });
|
||||
};
|
||||
|
@ -13,10 +13,6 @@
|
||||
<el-button type="primary" @click="openCreate">
|
||||
{{ $t('runtime.create') }}
|
||||
</el-button>
|
||||
|
||||
<el-button type="primary" plain @click="onOpenBuildCache()">
|
||||
{{ $t('container.cleanBuildCache') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()" :heightDiff="350">
|
||||
@ -115,8 +111,6 @@ import { Promotion } from '@element-plus/icons-vue';
|
||||
import PortJumpDialog from '@/components/port-jump/index.vue';
|
||||
import AppResources from '@/views/website/runtime/php/check/index.vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { containerPrune } from '@/api/modules/container';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
const loading = ref(false);
|
||||
@ -221,29 +215,6 @@ const openDelete = (row: Runtime.Runtime) => {
|
||||
});
|
||||
};
|
||||
|
||||
const onOpenBuildCache = () => {
|
||||
ElMessageBox.confirm(i18n.global.t('container.delBuildCacheHelper'), i18n.global.t('container.cleanBuildCache'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
pruneType: 'buildcache',
|
||||
withTagAll: false,
|
||||
};
|
||||
await containerPrune(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('container.cleanSuccess', [res.data.deletedNumber]));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const openLog = (row: any) => {
|
||||
composeLogRef.value.acceptParams({ compose: row.path + '/docker-compose.yml', resource: row.name });
|
||||
};
|
||||
|
@ -13,10 +13,6 @@
|
||||
<el-button type="primary" @click="openCreate">
|
||||
{{ $t('runtime.create') }}
|
||||
</el-button>
|
||||
|
||||
<el-button type="primary" plain @click="onOpenBuildCache()">
|
||||
{{ $t('container.cleanBuildCache') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()" :heightDiff="350">
|
||||
@ -117,8 +113,6 @@ import { Promotion } from '@element-plus/icons-vue';
|
||||
import PortJumpDialog from '@/components/port-jump/index.vue';
|
||||
import AppResources from '@/views/website/runtime/php/check/index.vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { containerPrune } from '@/api/modules/container';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
let timer: NodeJS.Timer | null = null;
|
||||
const loading = ref(false);
|
||||
@ -237,29 +231,6 @@ const openDelete = async (row: Runtime.Runtime) => {
|
||||
});
|
||||
};
|
||||
|
||||
const onOpenBuildCache = () => {
|
||||
ElMessageBox.confirm(i18n.global.t('container.delBuildCacheHelper'), i18n.global.t('container.cleanBuildCache'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
}).then(async () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
pruneType: 'buildcache',
|
||||
withTagAll: false,
|
||||
};
|
||||
await containerPrune(params)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('container.cleanSuccess', [res.data.deletedNumber]));
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const openLog = (row: any) => {
|
||||
composeLogRef.value.acceptParams({ compose: row.path + '/docker-compose.yml', resource: row.name });
|
||||
};
|
||||
|
@ -346,6 +346,7 @@
|
||||
{{ $t('runtime.openrestyWarn') }}
|
||||
</span>
|
||||
</el-card>
|
||||
<TaskLog ref="taskLog" />
|
||||
</DrawerPro>
|
||||
</template>
|
||||
|
||||
@ -365,6 +366,8 @@ import { Group } from '@/api/interface/group';
|
||||
import { SearchRuntimes } from '@/api/modules/runtime';
|
||||
import { Runtime } from '@/api/interface/runtime';
|
||||
import { getRandomStr } from '@/utils/util';
|
||||
import TaskLog from '@/components/task-log/index.vue';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const websiteForm = ref<FormInstance>();
|
||||
const website = ref({
|
||||
@ -402,6 +405,7 @@ const website = ref({
|
||||
proxyProtocol: 'http://',
|
||||
proxyAddress: '',
|
||||
runtimeType: 'php',
|
||||
taskID: '',
|
||||
});
|
||||
const rules = ref<any>({
|
||||
primaryDomain: [Rules.domainWithPort],
|
||||
@ -453,6 +457,7 @@ const runtimeReq = ref<Runtime.RuntimeReq>({
|
||||
const runtimes = ref<Runtime.RuntimeDTO[]>([]);
|
||||
const versionExist = ref(true);
|
||||
const em = defineEmits(['close']);
|
||||
const taskLog = ref();
|
||||
|
||||
const handleClose = () => {
|
||||
open.value = false;
|
||||
@ -612,6 +617,10 @@ function isSubsetOfStrArray(primaryDomain: string, otherDomains: string): boolea
|
||||
return true;
|
||||
}
|
||||
|
||||
const openTaskLog = (taskID: string) => {
|
||||
taskLog.value.acceptParams(taskID);
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid) => {
|
||||
@ -638,6 +647,8 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
website.value.ftpUser = '';
|
||||
website.value.ftpPassword = '';
|
||||
}
|
||||
const taskID = uuidv4();
|
||||
website.value.taskID = taskID;
|
||||
CreateWebsite(website.value)
|
||||
.then(() => {
|
||||
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
|
||||
@ -646,6 +657,7 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
openTaskLog(taskID);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
|
Loading…
Reference in New Issue
Block a user