1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-16 02:34:45 +08:00

feat: 同步远程应用增加日志 (#6209)

This commit is contained in:
zhengkunwang 2024-08-22 18:48:55 +08:00 committed by GitHub
parent cfe4fa7a92
commit 5865a958fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 372 additions and 267 deletions

View File

@ -2,6 +2,7 @@ package v2
import ( import (
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
"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/request"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
@ -31,20 +32,22 @@ func (b *BaseApi) SearchApp(c *gin.Context) {
} }
// @Tags App // @Tags App
// @Summary Sync app list // @Summary Sync remote app list
// @Description 同步应用列表 // @Description 同步远程应用列表
// @Success 200 // @Success 200
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Router /apps/sync [post] // @Router /apps/sync/remote [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"应用商店同步","formatEN":"App store synchronization"} // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"应用商店同步","formatEN":"App store synchronization"}
func (b *BaseApi) SyncApp(c *gin.Context) { func (b *BaseApi) SyncApp(c *gin.Context) {
go appService.SyncAppListFromLocal() var req dto.OperateWithTask
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
res, err := appService.GetAppUpdate() res, err := appService.GetAppUpdate()
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return
} }
if !res.CanUpdate { if !res.CanUpdate {
if res.IsSyncing { if res.IsSyncing {
helper.SuccessWithMsg(c, i18n.GetMsgByKey("AppStoreIsSyncing")) helper.SuccessWithMsg(c, i18n.GetMsgByKey("AppStoreIsSyncing"))
@ -54,11 +57,23 @@ func (b *BaseApi) SyncApp(c *gin.Context) {
return return
} }
go func() { go func() {
if err := appService.SyncAppListFromRemote(); err != nil { if err = appService.SyncAppListFromRemote(req.TaskID); err != nil {
global.LOG.Errorf("Synchronization with the App Store failed [%s]", err.Error()) global.LOG.Errorf("Synchronization with the App Store failed [%s]", err.Error())
} }
}() }()
helper.SuccessWithData(c, "") helper.SuccessWithData(c, nil)
}
// @Tags App
// @Summary Sync local app list
// @Description 同步本地应用列表
// @Success 200
// @Security ApiKeyAuth
// @Router /apps/sync/local [post]
// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"应用商店同步","formatEN":"App store synchronization"}
func (b *BaseApi) SyncLocalApp(c *gin.Context) {
go appService.SyncAppListFromLocal()
helper.SuccessWithOutData(c)
} }
// @Tags App // @Tags App

View File

@ -35,10 +35,10 @@ func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) {
case errors.As(err, &buserr.BusinessError{}): case errors.As(err, &buserr.BusinessError{}):
res.Message = err.Error() res.Message = err.Error()
default: default:
res.Message = i18n.GetMsgWithMap(msgKey, map[string]interface{}{"detail": err}) res.Message = i18n.GetMsgWithDetail(msgKey, err.Error())
} }
} else { } else {
res.Message = i18n.GetMsgWithMap(msgKey, map[string]interface{}{"detail": err}) res.Message = i18n.GetMsgWithDetail(msgKey, err.Error())
} }
ctx.JSON(http.StatusOK, res) ctx.JSON(http.StatusOK, res)
ctx.Abort() ctx.Abort()

View File

@ -57,3 +57,7 @@ type UpdateGroup struct {
Group uint `json:"group"` Group uint `json:"group"`
NewGroup uint `json:"newGroup"` NewGroup uint `json:"newGroup"`
} }
type OperateWithTask struct {
TaskID string `json:"taskID"`
}

View File

@ -2,6 +2,8 @@ package repo
import ( import (
"context" "context"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/model"
"gorm.io/gorm" "gorm.io/gorm"
@ -25,6 +27,25 @@ func NewITaskRepo() ITaskRepo {
return &TaskRepo{} return &TaskRepo{}
} }
func getTaskDb(opts ...DBOption) *gorm.DB {
db := global.TaskDB
for _, opt := range opts {
db = opt(db)
}
return db
}
func getTaskTx(ctx context.Context, opts ...DBOption) *gorm.DB {
tx, ok := ctx.Value(constant.DB).(*gorm.DB)
if ok {
for _, opt := range opts {
tx = opt(tx)
}
return tx
}
return getTaskDb(opts...)
}
func (t TaskRepo) WithByID(id string) DBOption { func (t TaskRepo) WithByID(id string) DBOption {
return func(g *gorm.DB) *gorm.DB { return func(g *gorm.DB) *gorm.DB {
return g.Where("id = ?", id) return g.Where("id = ?", id)
@ -44,12 +65,12 @@ func (t TaskRepo) WithResourceID(id uint) DBOption {
} }
func (t TaskRepo) Create(ctx context.Context, task *model.Task) error { func (t TaskRepo) Create(ctx context.Context, task *model.Task) error {
return getTx(ctx).Create(&task).Error return getTaskTx(ctx).Create(&task).Error
} }
func (t TaskRepo) GetFirst(opts ...DBOption) (model.Task, error) { func (t TaskRepo) GetFirst(opts ...DBOption) (model.Task, error) {
var task model.Task var task model.Task
db := getDb(opts...).Model(&model.Task{}) db := getTaskDb(opts...).Model(&model.Task{})
if err := db.First(&task).Error; err != nil { if err := db.First(&task).Error; err != nil {
return task, err return task, err
} }
@ -58,7 +79,7 @@ func (t TaskRepo) GetFirst(opts ...DBOption) (model.Task, error) {
func (t TaskRepo) Page(page, size int, opts ...DBOption) (int64, []model.Task, error) { func (t TaskRepo) Page(page, size int, opts ...DBOption) (int64, []model.Task, error) {
var tasks []model.Task var tasks []model.Task
db := getDb(opts...).Model(&model.Task{}) db := getTaskDb(opts...).Model(&model.Task{})
count := int64(0) count := int64(0)
db = db.Count(&count) db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&tasks).Error err := db.Limit(size).Offset(size * (page - 1)).Find(&tasks).Error
@ -66,5 +87,5 @@ func (t TaskRepo) Page(page, size int, opts ...DBOption) (int64, []model.Task, e
} }
func (t TaskRepo) Update(ctx context.Context, task *model.Task) error { func (t TaskRepo) Update(ctx context.Context, task *model.Task) error {
return getTx(ctx).Save(&task).Error return getTaskTx(ctx).Save(&task).Error
} }

View File

@ -40,7 +40,7 @@ type IAppService interface {
GetApp(key string) (*response.AppDTO, error) GetApp(key string) (*response.AppDTO, error)
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error) GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
Install(req request.AppInstallCreate) (*model.AppInstall, error) Install(req request.AppInstallCreate) (*model.AppInstall, error)
SyncAppListFromRemote() error SyncAppListFromRemote(taskID string) error
GetAppUpdate() (*response.AppUpdateRes, error) GetAppUpdate() (*response.AppUpdateRes, error)
GetAppDetailByID(id uint) (*response.AppDetailDTO, error) GetAppDetailByID(id uint) (*response.AppDetailDTO, error)
SyncAppListFromLocal() SyncAppListFromLocal()
@ -805,242 +805,244 @@ var InitTypes = map[string]struct{}{
"node": {}, "node": {},
} }
func (a AppService) SyncAppListFromRemote() (err error) { func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
global.LOG.Infof("Starting synchronization with App Store...") syncTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("App"), task.TaskSync, task.TaskScopeAppStore, taskID, 0)
updateRes, err := a.GetAppUpdate()
if err != nil { if err != nil {
return err return err
} }
if !updateRes.CanUpdate { syncTask.AddSubTask(task.GetTaskName(i18n.GetMsgByKey("App"), task.TaskSync, task.TaskScopeAppStore), func(t *task.Task) (err error) {
if updateRes.IsSyncing { updateRes, err := a.GetAppUpdate()
global.LOG.Infof("AppStore is Syncing!")
return
}
global.LOG.Infof("The App Store is at the latest version")
return
}
list := &dto.AppList{}
if updateRes.AppList == nil {
list, err = getAppList()
if err != nil {
return
}
} else {
list = updateRes.AppList
}
settingService := NewISettingService()
_ = settingService.Update("AppStoreSyncStatus", constant.Syncing)
var (
tags []*model.Tag
appTags []*model.AppTag
oldAppIds []uint
)
for _, t := range list.Extra.Tags {
tags = append(tags, &model.Tag{
Key: t.Key,
Name: t.Name,
Sort: t.Sort,
})
}
oldApps, err := appRepo.GetBy(appRepo.WithResource(constant.AppResourceRemote))
if err != nil {
return
}
for _, old := range oldApps {
oldAppIds = append(oldAppIds, old.ID)
}
transport := xpack.LoadRequestTransport()
baseRemoteUrl := fmt.Sprintf("%s/%s/1panel", global.CONF.System.AppRepo, global.CONF.System.Mode)
appsMap := getApps(oldApps, list.Apps)
global.LOG.Infof("Starting synchronization of application details...")
for _, l := range list.Apps {
app := appsMap[l.AppProperty.Key]
_, iconRes, err := httpUtil.HandleGetWithTransport(l.Icon, http.MethodGet, transport, constant.TimeOut20s)
if err != nil { if err != nil {
return err return err
} }
iconStr := "" if !updateRes.CanUpdate {
if !strings.Contains(string(iconRes), "<xml>") { if updateRes.IsSyncing {
iconStr = base64.StdEncoding.EncodeToString(iconRes) t.Log(i18n.GetMsgByKey("AppStoreIsSyncing"))
return nil
}
t.Log(i18n.GetMsgByKey("AppStoreIsLastVersion"))
return nil
} }
list := &dto.AppList{}
app.Icon = iconStr if updateRes.AppList == nil {
app.TagsKey = l.AppProperty.Tags list, err = getAppList()
if l.AppProperty.Recommend > 0 { if err != nil {
app.Recommend = l.AppProperty.Recommend return err
}
} else { } else {
app.Recommend = 9999 list = updateRes.AppList
} }
app.ReadMe = l.ReadMe settingService := NewISettingService()
app.LastModified = l.LastModified _ = settingService.Update("AppStoreSyncStatus", constant.Syncing)
versions := l.Versions
detailsMap := getAppDetails(app.Details, versions)
for _, v := range versions {
version := v.Name
detail := detailsMap[version]
versionUrl := fmt.Sprintf("%s/%s/%s", baseRemoteUrl, app.Key, version)
if _, ok := InitTypes[app.Type]; ok { var (
dockerComposeUrl := fmt.Sprintf("%s/%s", versionUrl, "docker-compose.yml") tags []*model.Tag
_, composeRes, err := httpUtil.HandleGetWithTransport(dockerComposeUrl, http.MethodGet, transport, constant.TimeOut20s) appTags []*model.AppTag
if err != nil { oldAppIds []uint
return err )
} for _, t := range list.Extra.Tags {
detail.DockerCompose = string(composeRes) tags = append(tags, &model.Tag{
} else { Key: t.Key,
detail.DockerCompose = "" Name: t.Name,
Sort: t.Sort,
})
}
oldApps, err := appRepo.GetBy(appRepo.WithResource(constant.AppResourceRemote))
if err != nil {
return err
}
for _, old := range oldApps {
oldAppIds = append(oldAppIds, old.ID)
}
transport := xpack.LoadRequestTransport()
baseRemoteUrl := fmt.Sprintf("%s/%s/1panel", global.CONF.System.AppRepo, global.CONF.System.Mode)
appsMap := getApps(oldApps, list.Apps)
t.LogStart(i18n.GetMsgByKey("SyncAppDetail"))
for _, l := range list.Apps {
app := appsMap[l.AppProperty.Key]
_, iconRes, err := httpUtil.HandleGetWithTransport(l.Icon, http.MethodGet, transport, constant.TimeOut20s)
if err != nil {
return err
}
iconStr := ""
if !strings.Contains(string(iconRes), "<xml>") {
iconStr = base64.StdEncoding.EncodeToString(iconRes)
} }
paramByte, _ := json.Marshal(v.AppForm) app.Icon = iconStr
detail.Params = string(paramByte) app.TagsKey = l.AppProperty.Tags
detail.DownloadUrl = fmt.Sprintf("%s/%s", versionUrl, app.Key+"-"+version+".tar.gz") if l.AppProperty.Recommend > 0 {
detail.DownloadCallBackUrl = v.DownloadCallBackUrl app.Recommend = l.AppProperty.Recommend
detail.Update = true
detail.LastModified = v.LastModified
detailsMap[version] = detail
}
var newDetails []model.AppDetail
for _, detail := range detailsMap {
newDetails = append(newDetails, detail)
}
app.Details = newDetails
appsMap[l.AppProperty.Key] = app
}
global.LOG.Infof("Synchronization of application details Success")
var (
addAppArray []model.App
updateAppArray []model.App
deleteAppArray []model.App
deleteIds []uint
tagMap = make(map[string]uint, len(tags))
)
for _, v := range appsMap {
if v.ID == 0 {
addAppArray = append(addAppArray, v)
} else {
if v.Status == constant.AppTakeDown {
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(v.ID))
if len(installs) > 0 {
updateAppArray = append(updateAppArray, v)
continue
}
deleteAppArray = append(deleteAppArray, v)
deleteIds = append(deleteIds, v.ID)
} else { } else {
updateAppArray = append(updateAppArray, v) app.Recommend = 9999
} }
} app.ReadMe = l.ReadMe
} app.LastModified = l.LastModified
tx, ctx := getTxAndContext() versions := l.Versions
defer tx.Rollback() detailsMap := getAppDetails(app.Details, versions)
if len(addAppArray) > 0 { for _, v := range versions {
if err = appRepo.BatchCreate(ctx, addAppArray); err != nil { version := v.Name
return detail := detailsMap[version]
} versionUrl := fmt.Sprintf("%s/%s/%s", baseRemoteUrl, app.Key, version)
}
if len(deleteAppArray) > 0 {
if err = appRepo.BatchDelete(ctx, deleteAppArray); err != nil {
return
}
if err = appDetailRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
return
}
}
if err = tagRepo.DeleteAll(ctx); err != nil {
return
}
if len(tags) > 0 {
if err = tagRepo.BatchCreate(ctx, tags); err != nil {
return
}
for _, t := range tags {
tagMap[t.Key] = t.ID
}
}
for _, update := range updateAppArray {
if err = appRepo.Save(ctx, &update); err != nil {
return
}
}
apps := append(addAppArray, updateAppArray...)
var ( if _, ok := InitTypes[app.Type]; ok {
addDetails []model.AppDetail dockerComposeUrl := fmt.Sprintf("%s/%s", versionUrl, "docker-compose.yml")
updateDetails []model.AppDetail _, composeRes, err := httpUtil.HandleGetWithTransport(dockerComposeUrl, http.MethodGet, transport, constant.TimeOut20s)
deleteDetails []model.AppDetail if err != nil {
) return err
for _, app := range apps {
for _, t := range app.TagsKey {
tagId, ok := tagMap[t]
if ok {
appTags = append(appTags, &model.AppTag{
AppId: app.ID,
TagId: tagId,
})
}
}
for _, d := range app.Details {
d.AppId = app.ID
if d.ID == 0 {
addDetails = append(addDetails, d)
} else {
if d.Status == constant.AppTakeDown {
runtime, _ := runtimeRepo.GetFirst(runtimeRepo.WithDetailId(d.ID))
if runtime != nil {
updateDetails = append(updateDetails, d)
continue
} }
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithDetailIdsIn([]uint{d.ID})) detail.DockerCompose = string(composeRes)
if len(installs) > 0 {
updateDetails = append(updateDetails, d)
continue
}
deleteDetails = append(deleteDetails, d)
} else { } else {
updateDetails = append(updateDetails, d) detail.DockerCompose = ""
}
paramByte, _ := json.Marshal(v.AppForm)
detail.Params = string(paramByte)
detail.DownloadUrl = fmt.Sprintf("%s/%s", versionUrl, app.Key+"-"+version+".tar.gz")
detail.DownloadCallBackUrl = v.DownloadCallBackUrl
detail.Update = true
detail.LastModified = v.LastModified
detailsMap[version] = detail
}
var newDetails []model.AppDetail
for _, detail := range detailsMap {
newDetails = append(newDetails, detail)
}
app.Details = newDetails
appsMap[l.AppProperty.Key] = app
}
t.LogSuccess(i18n.GetMsgByKey("SyncAppDetail"))
var (
addAppArray []model.App
updateAppArray []model.App
deleteAppArray []model.App
deleteIds []uint
tagMap = make(map[string]uint, len(tags))
)
for _, v := range appsMap {
if v.ID == 0 {
addAppArray = append(addAppArray, v)
} else {
if v.Status == constant.AppTakeDown {
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithAppId(v.ID))
if len(installs) > 0 {
updateAppArray = append(updateAppArray, v)
continue
}
deleteAppArray = append(deleteAppArray, v)
deleteIds = append(deleteIds, v.ID)
} else {
updateAppArray = append(updateAppArray, v)
} }
} }
} }
}
if len(addDetails) > 0 {
if err = appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
return
}
}
if len(deleteDetails) > 0 {
if err = appDetailRepo.BatchDelete(ctx, deleteDetails); err != nil {
return
}
}
for _, u := range updateDetails {
if err = appDetailRepo.Update(ctx, u); err != nil {
return
}
}
if len(oldAppIds) > 0 { if len(addAppArray) > 0 {
if err = appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil { if err = appRepo.BatchCreate(context.Background(), addAppArray); err != nil {
return
}
}
if len(deleteAppArray) > 0 {
if err = appRepo.BatchDelete(context.Background(), deleteAppArray); err != nil {
return
}
if err = appDetailRepo.DeleteByAppIds(context.Background(), deleteIds); err != nil {
return
}
}
if err = tagRepo.DeleteAll(context.Background()); err != nil {
return return
} }
} if len(tags) > 0 {
if err = tagRepo.BatchCreate(context.Background(), tags); err != nil {
if len(appTags) > 0 { return
if err = appTagRepo.BatchCreate(ctx, appTags); err != nil { }
return for _, tag := range tags {
tagMap[tag.Key] = tag.ID
}
} }
} for _, update := range updateAppArray {
tx.Commit() if err = appRepo.Save(context.Background(), &update); err != nil {
return
}
}
apps := append(addAppArray, updateAppArray...)
_ = settingService.Update("AppStoreSyncStatus", constant.SyncSuccess) var (
_ = settingService.Update("AppStoreLastModified", strconv.Itoa(list.LastModified)) addDetails []model.AppDetail
updateDetails []model.AppDetail
deleteDetails []model.AppDetail
)
for _, app := range apps {
for _, tag := range app.TagsKey {
tagId, ok := tagMap[tag]
if ok {
appTags = append(appTags, &model.AppTag{
AppId: app.ID,
TagId: tagId,
})
}
}
for _, d := range app.Details {
d.AppId = app.ID
if d.ID == 0 {
addDetails = append(addDetails, d)
} else {
if d.Status == constant.AppTakeDown {
runtime, _ := runtimeRepo.GetFirst(runtimeRepo.WithDetailId(d.ID))
if runtime != nil {
updateDetails = append(updateDetails, d)
continue
}
installs, _ := appInstallRepo.ListBy(appInstallRepo.WithDetailIdsIn([]uint{d.ID}))
if len(installs) > 0 {
updateDetails = append(updateDetails, d)
continue
}
deleteDetails = append(deleteDetails, d)
} else {
updateDetails = append(updateDetails, d)
}
}
}
}
if len(addDetails) > 0 {
if err = appDetailRepo.BatchCreate(context.Background(), addDetails); err != nil {
return
}
}
if len(deleteDetails) > 0 {
if err = appDetailRepo.BatchDelete(context.Background(), deleteDetails); err != nil {
return
}
}
for _, u := range updateDetails {
if err = appDetailRepo.Update(context.Background(), u); err != nil {
return
}
}
global.LOG.Infof("Synchronization with the App Store was successful!") if len(oldAppIds) > 0 {
return if err = appTagRepo.DeleteByAppIds(context.Background(), oldAppIds); err != nil {
return
}
}
if len(appTags) > 0 {
if err = appTagRepo.BatchCreate(context.Background(), appTags); err != nil {
return
}
}
_ = settingService.Update("AppStoreSyncStatus", constant.SyncSuccess)
_ = settingService.Update("AppStoreLastModified", strconv.Itoa(list.LastModified))
t.Log(i18n.GetMsgByKey("AppStoreSyncSuccess"))
return nil
}, nil)
return syncTask.Execute()
} }

View File

@ -50,6 +50,7 @@ const (
TaskUpdate = "TaskUpdate" TaskUpdate = "TaskUpdate"
TaskRestart = "TaskRestart" TaskRestart = "TaskRestart"
TaskBackup = "TaskBackup" TaskBackup = "TaskBackup"
TaskSync = "TaskSync"
) )
const ( const (
@ -57,6 +58,7 @@ const (
TaskScopeApp = "App" TaskScopeApp = "App"
TaskScopeRuntime = "Runtime" TaskScopeRuntime = "Runtime"
TaskScopeDatabase = "Database" TaskScopeDatabase = "Database"
TaskScopeAppStore = "AppStore"
) )
const ( const (

View File

@ -13,7 +13,7 @@ func NewAppStoreJob() *app {
func (a *app) Run() { func (a *app) Run() {
global.LOG.Info("AppStore scheduled task in progress ...") global.LOG.Info("AppStore scheduled task in progress ...")
if err := service.NewIAppService().SyncAppListFromRemote(); err != nil { if err := service.NewIAppService().SyncAppListFromRemote(""); err != nil {
global.LOG.Errorf("AppStore sync failed %s", err.Error()) global.LOG.Errorf("AppStore sync failed %s", err.Error())
} }
global.LOG.Info("AppStore scheduled task has completed") global.LOG.Info("AppStore scheduled task has completed")

View File

@ -15,6 +15,7 @@ import (
var ( var (
DB *gorm.DB DB *gorm.DB
MonitorDB *gorm.DB MonitorDB *gorm.DB
TaskDB *gorm.DB
CoreDB *gorm.DB CoreDB *gorm.DB
LOG *logrus.Logger LOG *logrus.Logger
CONF configs.ServerConfig CONF configs.ServerConfig

View File

@ -32,25 +32,20 @@ func GetMsgWithMap(key string, maps map[string]interface{}) string {
} }
} }
func GetMsgWithName(key string, name string, err error) string { func GetMsgWithDetail(key string, detail string) string {
var ( var (
content string content string
dataMap = make(map[string]interface{}) dataMap = make(map[string]interface{})
) )
dataMap["name"] = name dataMap["detail"] = detail
if err != nil {
dataMap["err"] = err.Error()
}
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{ content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
MessageID: key, MessageID: key,
TemplateData: dataMap, TemplateData: dataMap,
}) })
content = strings.ReplaceAll(content, "<no value>", "") if content != "" {
if content == "" {
return key
} else {
return content return content
} }
return key
} }
func GetErrMsg(key string, maps map[string]interface{}) string { func GetErrMsg(key string, maps map[string]interface{}) string {
@ -87,14 +82,6 @@ func GetWithName(key string, name string) string {
return content return content
} }
func GetWithMap(key string, dataMap map[string]string) string {
content, _ := global.I18n.Localize(&i18n.LocalizeConfig{
MessageID: key,
TemplateData: dataMap,
})
return content
}
func GetWithNameAndErr(key string, name string, err error) string { func GetWithNameAndErr(key string, name string, err error) string {
var ( var (
dataMap = make(map[string]interface{}) dataMap = make(map[string]interface{})

View File

@ -62,6 +62,10 @@ PullImageStart: "Start pulling image {{ .name }}"
PullImageSuccess: "Image pulled successfully" PullImageSuccess: "Image pulled successfully"
UpgradeAppStart: "Start upgrading application {{ .name }}" UpgradeAppStart: "Start upgrading application {{ .name }}"
UpgradeAppSuccess: "App {{ .name }} upgraded successfully" UpgradeAppSuccess: "App {{ .name }} upgraded successfully"
AppStoreIsLastVersion: "The app store is up to date"
AppStoreSyncSuccess: "App store synchronized successfully"
SyncAppDetail: "Synchronize app configuration"
#file #file
ErrFileCanNotRead: "File can not read" ErrFileCanNotRead: "File can not read"
@ -229,4 +233,6 @@ Stop: 'Stop',
Image: 'Image', Image: 'Image',
AppLink: 'Associated Application' AppLink: 'Associated Application'
EnableSSL: "Enable HTTPS" EnableSSL: "Enable HTTPS"
AppStore: "App Store"
TaskSync: "Sync"

View File

@ -63,6 +63,10 @@ PullImageStart: "開始拉取鏡像 {{ .name }}"
PullImageSuccess: "鏡像拉取成功" PullImageSuccess: "鏡像拉取成功"
UpgradeAppStart: "開始升級應用程式 {{ .name }}" UpgradeAppStart: "開始升級應用程式 {{ .name }}"
UpgradeAppSuccess: "應用程式 {{ .name }} 升級成功" UpgradeAppSuccess: "應用程式 {{ .name }} 升級成功"
AppStoreIsLastVersion: "應用商店已經是最新版本"
AppStoreSyncSuccess: "應用商店同步成功"
SyncAppDetail: "同步應用配置"
#file #file
ErrFileCanNotRead: "此文件不支持預覽" ErrFileCanNotRead: "此文件不支持預覽"
@ -231,4 +235,6 @@ Stop: '停止',
Image: '鏡像', Image: '鏡像',
AppLink: '關聯應用' AppLink: '關聯應用'
EnableSSL: "開啟 HTTPS" EnableSSL: "開啟 HTTPS"
AppStore: "應用商店"
TaskSync: "同步"

View File

@ -62,6 +62,9 @@ PullImageStart: "开始拉取镜像 {{ .name }}"
PullImageSuccess: "镜像拉取成功" PullImageSuccess: "镜像拉取成功"
UpgradeAppStart: "开始升级应用 {{ .name }}" UpgradeAppStart: "开始升级应用 {{ .name }}"
UpgradeAppSuccess: "应用 {{ .name }} 升级成功" UpgradeAppSuccess: "应用 {{ .name }} 升级成功"
AppStoreIsLastVersion: "应用商店已经是最新版本"
AppStoreSyncSuccess: "应用商店同步成功"
SyncAppDetail: "同步应用配置"
#file #file
ErrFileCanNotRead: "此文件不支持预览" ErrFileCanNotRead: "此文件不支持预览"
@ -124,7 +127,7 @@ ExecShellSuccess: "脚本执行成功"
ErrUserIsExist: "当前用户已存在,请重新输入" ErrUserIsExist: "当前用户已存在,请重新输入"
ErrDatabaseIsExist: "当前数据库已存在,请重新输入" ErrDatabaseIsExist: "当前数据库已存在,请重新输入"
ErrExecTimeOut: "SQL 执行超时,请检查数据库" ErrExecTimeOut: "SQL 执行超时,请检查数据库"
ErrRemoteExist: "远程数据库已存在该名称,请修改后重试"vv ErrRemoteExist: "远程数据库已存在该名称,请修改后重试"
ErrLocalExist: "本地数据库已存在该名称,请修改后重试" ErrLocalExist: "本地数据库已存在该名称,请修改后重试"
#redis #redis
@ -233,3 +236,5 @@ Stop: "停止"
Image: "镜像" Image: "镜像"
AppLink: "关联应用" AppLink: "关联应用"
EnableSSL: "开启 HTTPS" EnableSSL: "开启 HTTPS"
AppStore: "应用商店"
TaskSync: "同步"

View File

@ -15,7 +15,7 @@ func Init() {
func syncApp() { func syncApp() {
_ = service.NewISettingService().Update("AppStoreSyncStatus", constant.SyncSuccess) _ = service.NewISettingService().Update("AppStoreSyncStatus", constant.SyncSuccess)
if err := service.NewIAppService().SyncAppListFromRemote(); err != nil { if err := service.NewIAppService().SyncAppListFromRemote(""); err != nil {
global.LOG.Errorf("App Store synchronization failed") global.LOG.Errorf("App Store synchronization failed")
return return
} }

View File

@ -13,6 +13,21 @@ import (
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
) )
func GetDBWithPath(dbPath string) (*gorm.DB, error) {
db, _ := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
Logger: newLogger(),
})
sqlDB, dbError := db.DB()
if dbError != nil {
return nil, dbError
}
sqlDB.SetConnMaxIdleTime(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
return db, nil
}
func Init() { func Init() {
if _, err := os.Stat(global.CONF.System.DbPath); err != nil { if _, err := os.Stat(global.CONF.System.DbPath); err != nil {
if err := os.MkdirAll(global.CONF.System.DbPath, os.ModePerm); err != nil { if err := os.MkdirAll(global.CONF.System.DbPath, os.ModePerm); err != nil {
@ -29,27 +44,37 @@ func Init() {
} }
initMonitorDB() initMonitorDB()
initTaskDB()
db, err := gorm.Open(sqlite.Open(fullPath), &gorm.Config{ db, err := GetDBWithPath(fullPath)
DisableForeignKeyConstraintWhenMigrating: true,
Logger: newLogger(),
})
if err != nil { if err != nil {
panic(err) panic(err)
} }
sqlDB, dbError := db.DB()
if dbError != nil {
panic(dbError)
}
sqlDB.SetConnMaxIdleTime(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
global.DB = db global.DB = db
global.LOG.Info("init db successfully") global.LOG.Info("init db successfully")
} }
func initTaskDB() {
fullPath := path.Join(global.CONF.System.DbPath, "task.db")
if _, err := os.Stat(fullPath); err != nil {
f, err := os.Create(fullPath)
if err != nil {
panic(fmt.Errorf("init task db file failed, err: %v", err))
}
_ = f.Close()
}
db, err := GetDBWithPath(fullPath)
if err != nil {
panic(err)
}
global.TaskDB = db
global.LOG.Info("init task db successfully")
}
func initMonitorDB() { func initMonitorDB() {
fullPath := path.Join(global.CONF.System.DbPath, "monitor.db") fullPath := path.Join(global.CONF.System.DbPath, "monitor.db")
if _, err := os.Stat(fullPath); err != nil { if _, err := os.Stat(fullPath); err != nil {

View File

@ -19,6 +19,7 @@ func Init() {
migrations.UpdateWebsite, migrations.UpdateWebsite,
migrations.UpdateWebsiteDomain, migrations.UpdateWebsiteDomain,
migrations.UpdateApp, migrations.UpdateApp,
migrations.AddTaskDB,
}) })
if err := m.Migrate(); err != nil { if err := m.Migrate(); err != nil {
global.LOG.Error(err) global.LOG.Error(err)

View File

@ -242,3 +242,12 @@ var UpdateApp = &gormigrate.Migration{
&model.App{}) &model.App{})
}, },
} }
var AddTaskDB = &gormigrate.Migration{
ID: "20240822-add-task-table",
Migrate: func(tx *gorm.DB) error {
return global.TaskDB.AutoMigrate(
&model.Task{},
)
},
}

View File

@ -3,6 +3,7 @@ package server
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"github.com/1Panel-dev/1Panel/agent/init/business"
"net" "net"
"net/http" "net/http"
"os" "os"
@ -12,7 +13,6 @@ import (
"github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/i18n" "github.com/1Panel-dev/1Panel/agent/i18n"
"github.com/1Panel-dev/1Panel/agent/init/app" "github.com/1Panel-dev/1Panel/agent/init/app"
"github.com/1Panel-dev/1Panel/agent/init/business"
"github.com/1Panel-dev/1Panel/agent/init/db" "github.com/1Panel-dev/1Panel/agent/init/db"
"github.com/1Panel-dev/1Panel/agent/init/hook" "github.com/1Panel-dev/1Panel/agent/init/hook"
"github.com/1Panel-dev/1Panel/agent/init/log" "github.com/1Panel-dev/1Panel/agent/init/log"
@ -36,7 +36,6 @@ func Start() {
gin.SetMode("debug") gin.SetMode("debug")
cron.Run() cron.Run()
InitOthers() InitOthers()
business.Init()
hook.Init() hook.Init()
rootRouter := router.Routers() rootRouter := router.Routers()
@ -75,6 +74,7 @@ func Start() {
ClientAuth: tls.RequireAnyClientCert, ClientAuth: tls.RequireAnyClientCert,
} }
global.LOG.Info("listen at https://0.0.0.0:9999") global.LOG.Info("listen at https://0.0.0.0:9999")
business.Init()
if err := server.ListenAndServeTLS("", ""); err != nil { if err := server.ListenAndServeTLS("", ""); err != nil {
panic(err) panic(err)
} }

View File

@ -251,4 +251,8 @@ export namespace App {
appInstallID: number; appInstallID: number;
updateVersion?: string; updateVersion?: string;
} }
export interface AppStoreSync {
taskID: string;
}
} }

View File

@ -3,8 +3,8 @@ import { ResPage } from '../interface';
import { App } from '../interface/app'; import { App } from '../interface/app';
import { TimeoutEnum } from '@/enums/http-enum'; import { TimeoutEnum } from '@/enums/http-enum';
export const SyncApp = () => { export const SyncApp = (req: App.AppStoreSync) => {
return http.post<any>('apps/sync', {}); return http.post('apps/sync', req);
}; };
export const GetAppListUpdate = () => { export const GetAppListUpdate = () => {

View File

@ -1857,7 +1857,7 @@ const message = {
syncAllAppHelper: 'All applications are about to be synchronized. Do you want to continue? ', syncAllAppHelper: 'All applications are about to be synchronized. Do you want to continue? ',
hostModeHelper: hostModeHelper:
'The current application network mode is host mode. If you need to open the port, please open it manually on the firewall page.', 'The current application network mode is host mode. If you need to open the port, please open it manually on the firewall page.',
showLocal: 'Show Local App', showLocal: 'Local',
reload: 'Reload', reload: 'Reload',
upgradeWarn: upgradeWarn:
'Upgrading the application will replace the docker-compose.yml file. If there are any changes, you can click to view the file comparison', 'Upgrading the application will replace the docker-compose.yml file. If there are any changes, you can click to view the file comparison',
@ -1877,7 +1877,7 @@ const message = {
requireMemory: 'Memory', requireMemory: 'Memory',
supportedArchitectures: 'Architectures', supportedArchitectures: 'Architectures',
link: 'Link', link: 'Link',
showCurrentArch: 'Show current architecture App', showCurrentArch: 'Architecture',
}, },
website: { website: {
website: 'Website', website: 'Website',

View File

@ -3,6 +3,7 @@ import i18n from '@/lang';
import useClipboard from 'vue-clipboard3'; import useClipboard from 'vue-clipboard3';
const { toClipboard } = useClipboard(); const { toClipboard } = useClipboard();
import { MsgError, MsgSuccess } from '@/utils/message'; import { MsgError, MsgSuccess } from '@/utils/message';
import { v4 as uuidv4 } from 'uuid';
export function deepCopy<T>(obj: any): T { export function deepCopy<T>(obj: any): T {
let newObj: any; let newObj: any;
@ -616,3 +617,7 @@ export const getFileType = (extension: string) => {
}); });
return type; return type;
}; };
export const newUUID = () => {
return uuidv4();
};

View File

@ -168,6 +168,7 @@
</LayoutContent> </LayoutContent>
<Install ref="installRef" /> <Install ref="installRef" />
<Detail ref="detailRef" /> <Detail ref="detailRef" />
<TaskLog ref="taskLogRef" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -179,8 +180,9 @@ import Install from '../detail/install/index.vue';
import router from '@/routers'; import router from '@/routers';
import { MsgSuccess } from '@/utils/message'; import { MsgSuccess } from '@/utils/message';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
import { getLanguage } from '@/utils/util'; import { getLanguage, newUUID } from '@/utils/util';
import Detail from '../detail/index.vue'; import Detail from '../detail/index.vue';
import TaskLog from '@/components/task-log/index.vue';
const globalStore = GlobalStore(); const globalStore = GlobalStore();
@ -218,6 +220,7 @@ const installKey = ref('');
const moreTag = ref(''); const moreTag = ref('');
const mainHeight = ref(0); const mainHeight = ref(0);
const detailRef = ref(); const detailRef = ref();
const taskLogRef = ref();
const search = async (req: App.AppReq) => { const search = async (req: App.AppReq) => {
loading.value = true; loading.value = true;
@ -263,14 +266,23 @@ const openDetail = (key: string) => {
detailRef.value.acceptParams(key, 'install'); detailRef.value.acceptParams(key, 'install');
}; };
const openTaskLog = (taskID: string) => {
taskLogRef.value.openWithTaskID(taskID);
};
const sync = () => { const sync = () => {
syncing.value = true; syncing.value = true;
SyncApp() const taskID = newUUID();
const syncReq = {
taskID: taskID,
};
SyncApp(syncReq)
.then((res) => { .then((res) => {
if (res.message != '') { if (res.message != '') {
MsgSuccess(res.message); MsgSuccess(res.message);
} else { } else {
MsgSuccess(i18n.global.t('app.syncStart')); MsgSuccess(i18n.global.t('app.syncStart'));
openTaskLog(taskID);
} }
canUpdate.value = false; canUpdate.value = false;
search(req); search(req);

View File

@ -123,7 +123,7 @@ import { Container } from '@/api/interface/container';
import { loadResourceLimit } from '@/api/modules/container'; import { loadResourceLimit } from '@/api/modules/container';
import CodemirrorPro from '@/components/codemirror-pro/index.vue'; import CodemirrorPro from '@/components/codemirror-pro/index.vue';
import TaskLog from '@/components/task-log/index.vue'; import TaskLog from '@/components/task-log/index.vue';
import { v4 as uuidv4 } from 'uuid'; import { newUUID } from '@/utils/util';
const router = useRouter(); const router = useRouter();
@ -269,7 +269,7 @@ const openTaskLog = (taskID: string) => {
const install = () => { const install = () => {
loading.value = true; loading.value = true;
const taskID = uuidv4(); const taskID = newUUID();
req.taskID = taskID; req.taskID = taskID;
InstallApp(req) InstallApp(req)
.then(() => { .then(() => {