diff --git a/agent/app/api/v2/app.go b/agent/app/api/v2/app.go index 0ac6396e2..34f746690 100644 --- a/agent/app/api/v2/app.go +++ b/agent/app/api/v2/app.go @@ -2,6 +2,7 @@ package v2 import ( "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/constant" "github.com/1Panel-dev/1Panel/agent/global" @@ -31,20 +32,22 @@ func (b *BaseApi) SearchApp(c *gin.Context) { } // @Tags App -// @Summary Sync app list -// @Description 同步应用列表 +// @Summary Sync remote app list +// @Description 同步远程应用列表 // @Success 200 // @Security ApiKeyAuth -// @Router /apps/sync [post] +// @Router /apps/sync/remote [post] // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"应用商店同步","formatEN":"App store synchronization"} 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() if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) return } - if !res.CanUpdate { if res.IsSyncing { helper.SuccessWithMsg(c, i18n.GetMsgByKey("AppStoreIsSyncing")) @@ -54,11 +57,23 @@ func (b *BaseApi) SyncApp(c *gin.Context) { return } 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()) } }() - 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 diff --git a/agent/app/api/v2/helper/helper.go b/agent/app/api/v2/helper/helper.go index 043b74ba0..72e0b9897 100644 --- a/agent/app/api/v2/helper/helper.go +++ b/agent/app/api/v2/helper/helper.go @@ -35,10 +35,10 @@ func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) { case errors.As(err, &buserr.BusinessError{}): res.Message = err.Error() default: - res.Message = i18n.GetMsgWithMap(msgKey, map[string]interface{}{"detail": err}) + res.Message = i18n.GetMsgWithDetail(msgKey, err.Error()) } } else { - res.Message = i18n.GetMsgWithMap(msgKey, map[string]interface{}{"detail": err}) + res.Message = i18n.GetMsgWithDetail(msgKey, err.Error()) } ctx.JSON(http.StatusOK, res) ctx.Abort() diff --git a/agent/app/dto/common_req.go b/agent/app/dto/common_req.go index a3e68ef34..47cd4e6ea 100644 --- a/agent/app/dto/common_req.go +++ b/agent/app/dto/common_req.go @@ -57,3 +57,7 @@ type UpdateGroup struct { Group uint `json:"group"` NewGroup uint `json:"newGroup"` } + +type OperateWithTask struct { + TaskID string `json:"taskID"` +} diff --git a/agent/app/repo/task.go b/agent/app/repo/task.go index 07f38004e..65f07ebcf 100644 --- a/agent/app/repo/task.go +++ b/agent/app/repo/task.go @@ -2,6 +2,8 @@ package repo import ( "context" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/app/model" "gorm.io/gorm" @@ -25,6 +27,25 @@ func NewITaskRepo() ITaskRepo { 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 { return func(g *gorm.DB) *gorm.DB { 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 { - return getTx(ctx).Create(&task).Error + return getTaskTx(ctx).Create(&task).Error } func (t TaskRepo) GetFirst(opts ...DBOption) (model.Task, error) { var task model.Task - db := getDb(opts...).Model(&model.Task{}) + db := getTaskDb(opts...).Model(&model.Task{}) if err := db.First(&task).Error; err != nil { 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) { var tasks []model.Task - db := getDb(opts...).Model(&model.Task{}) + db := getTaskDb(opts...).Model(&model.Task{}) count := int64(0) db = db.Count(&count) 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 { - return getTx(ctx).Save(&task).Error + return getTaskTx(ctx).Save(&task).Error } diff --git a/agent/app/service/app.go b/agent/app/service/app.go index 006e73f8f..144d38c1d 100644 --- a/agent/app/service/app.go +++ b/agent/app/service/app.go @@ -40,7 +40,7 @@ type IAppService interface { GetApp(key string) (*response.AppDTO, error) GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error) Install(req request.AppInstallCreate) (*model.AppInstall, error) - SyncAppListFromRemote() error + SyncAppListFromRemote(taskID string) error GetAppUpdate() (*response.AppUpdateRes, error) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) SyncAppListFromLocal() @@ -805,242 +805,244 @@ var InitTypes = map[string]struct{}{ "node": {}, } -func (a AppService) SyncAppListFromRemote() (err error) { - global.LOG.Infof("Starting synchronization with App Store...") - updateRes, err := a.GetAppUpdate() +func (a AppService) SyncAppListFromRemote(taskID string) (err error) { + syncTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("App"), task.TaskSync, task.TaskScopeAppStore, taskID, 0) if err != nil { return err } - if !updateRes.CanUpdate { - if updateRes.IsSyncing { - 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) + syncTask.AddSubTask(task.GetTaskName(i18n.GetMsgByKey("App"), task.TaskSync, task.TaskScopeAppStore), func(t *task.Task) (err error) { + updateRes, err := a.GetAppUpdate() if err != nil { return err } - iconStr := "" - if !strings.Contains(string(iconRes), "") { - iconStr = base64.StdEncoding.EncodeToString(iconRes) + if !updateRes.CanUpdate { + if updateRes.IsSyncing { + t.Log(i18n.GetMsgByKey("AppStoreIsSyncing")) + return nil + } + t.Log(i18n.GetMsgByKey("AppStoreIsLastVersion")) + return nil } - - app.Icon = iconStr - app.TagsKey = l.AppProperty.Tags - if l.AppProperty.Recommend > 0 { - app.Recommend = l.AppProperty.Recommend + list := &dto.AppList{} + if updateRes.AppList == nil { + list, err = getAppList() + if err != nil { + return err + } } else { - app.Recommend = 9999 + list = updateRes.AppList } - app.ReadMe = l.ReadMe - app.LastModified = l.LastModified - 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) + settingService := NewISettingService() + _ = settingService.Update("AppStoreSyncStatus", constant.Syncing) - if _, ok := InitTypes[app.Type]; ok { - dockerComposeUrl := fmt.Sprintf("%s/%s", versionUrl, "docker-compose.yml") - _, composeRes, err := httpUtil.HandleGetWithTransport(dockerComposeUrl, http.MethodGet, transport, constant.TimeOut20s) - if err != nil { - return err - } - detail.DockerCompose = string(composeRes) - } else { - detail.DockerCompose = "" + 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 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), "") { + iconStr = base64.StdEncoding.EncodeToString(iconRes) } - 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 - } - - 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) + app.Icon = iconStr + app.TagsKey = l.AppProperty.Tags + if l.AppProperty.Recommend > 0 { + app.Recommend = l.AppProperty.Recommend } else { - updateAppArray = append(updateAppArray, v) + app.Recommend = 9999 } - } - } - tx, ctx := getTxAndContext() - defer tx.Rollback() - if len(addAppArray) > 0 { - if err = appRepo.BatchCreate(ctx, addAppArray); err != nil { - return - } - } - 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...) + app.ReadMe = l.ReadMe + app.LastModified = l.LastModified + 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) - var ( - addDetails []model.AppDetail - updateDetails []model.AppDetail - deleteDetails []model.AppDetail - ) - 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 + if _, ok := InitTypes[app.Type]; ok { + dockerComposeUrl := fmt.Sprintf("%s/%s", versionUrl, "docker-compose.yml") + _, composeRes, err := httpUtil.HandleGetWithTransport(dockerComposeUrl, http.MethodGet, transport, constant.TimeOut20s) + if err != nil { + return err } - installs, _ := appInstallRepo.ListBy(appInstallRepo.WithDetailIdsIn([]uint{d.ID})) - if len(installs) > 0 { - updateDetails = append(updateDetails, d) - continue - } - deleteDetails = append(deleteDetails, d) + detail.DockerCompose = string(composeRes) } 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 err = appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil { + if len(addAppArray) > 0 { + 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 } - } - - if len(appTags) > 0 { - if err = appTagRepo.BatchCreate(ctx, appTags); err != nil { - return + if len(tags) > 0 { + if err = tagRepo.BatchCreate(context.Background(), tags); err != nil { + return + } + for _, tag := range tags { + tagMap[tag.Key] = tag.ID + } } - } - tx.Commit() + for _, update := range updateAppArray { + if err = appRepo.Save(context.Background(), &update); err != nil { + return + } + } + apps := append(addAppArray, updateAppArray...) - _ = settingService.Update("AppStoreSyncStatus", constant.SyncSuccess) - _ = settingService.Update("AppStoreLastModified", strconv.Itoa(list.LastModified)) + var ( + 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!") - return + if len(oldAppIds) > 0 { + 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() } diff --git a/agent/app/task/task.go b/agent/app/task/task.go index d5ac3b269..a2bf8f6e9 100644 --- a/agent/app/task/task.go +++ b/agent/app/task/task.go @@ -50,6 +50,7 @@ const ( TaskUpdate = "TaskUpdate" TaskRestart = "TaskRestart" TaskBackup = "TaskBackup" + TaskSync = "TaskSync" ) const ( @@ -57,6 +58,7 @@ const ( TaskScopeApp = "App" TaskScopeRuntime = "Runtime" TaskScopeDatabase = "Database" + TaskScopeAppStore = "AppStore" ) const ( diff --git a/agent/cron/job/app.go b/agent/cron/job/app.go index 868c6f991..11cc39b81 100644 --- a/agent/cron/job/app.go +++ b/agent/cron/job/app.go @@ -13,7 +13,7 @@ func NewAppStoreJob() *app { func (a *app) Run() { 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.Info("AppStore scheduled task has completed") diff --git a/agent/global/global.go b/agent/global/global.go index 1f58f3c5d..4ab371d4a 100644 --- a/agent/global/global.go +++ b/agent/global/global.go @@ -15,6 +15,7 @@ import ( var ( DB *gorm.DB MonitorDB *gorm.DB + TaskDB *gorm.DB CoreDB *gorm.DB LOG *logrus.Logger CONF configs.ServerConfig diff --git a/agent/i18n/i18n.go b/agent/i18n/i18n.go index a8a834e45..cb0153c11 100644 --- a/agent/i18n/i18n.go +++ b/agent/i18n/i18n.go @@ -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 ( content string dataMap = make(map[string]interface{}) ) - dataMap["name"] = name - if err != nil { - dataMap["err"] = err.Error() - } + dataMap["detail"] = detail content, _ = global.I18n.Localize(&i18n.LocalizeConfig{ MessageID: key, TemplateData: dataMap, }) - content = strings.ReplaceAll(content, "", "") - if content == "" { - return key - } else { + if content != "" { return content } + return key } func GetErrMsg(key string, maps map[string]interface{}) string { @@ -87,14 +82,6 @@ func GetWithName(key string, name string) string { 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 { var ( dataMap = make(map[string]interface{}) diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml index cdaebda8b..6e72f9494 100644 --- a/agent/i18n/lang/en.yaml +++ b/agent/i18n/lang/en.yaml @@ -62,6 +62,10 @@ PullImageStart: "Start pulling image {{ .name }}" PullImageSuccess: "Image pulled successfully" UpgradeAppStart: "Start upgrading application {{ .name }}" UpgradeAppSuccess: "App {{ .name }} upgraded successfully" +AppStoreIsLastVersion: "The app store is up to date" +AppStoreSyncSuccess: "App store synchronized successfully" +SyncAppDetail: "Synchronize app configuration" + #file ErrFileCanNotRead: "File can not read" @@ -229,4 +233,6 @@ Stop: 'Stop', Image: 'Image', AppLink: 'Associated Application' EnableSSL: "Enable HTTPS" +AppStore: "App Store" +TaskSync: "Sync" diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml index dcbdff22c..8453928f5 100644 --- a/agent/i18n/lang/zh-Hant.yaml +++ b/agent/i18n/lang/zh-Hant.yaml @@ -63,6 +63,10 @@ PullImageStart: "開始拉取鏡像 {{ .name }}" PullImageSuccess: "鏡像拉取成功" UpgradeAppStart: "開始升級應用程式 {{ .name }}" UpgradeAppSuccess: "應用程式 {{ .name }} 升級成功" +AppStoreIsLastVersion: "應用商店已經是最新版本" +AppStoreSyncSuccess: "應用商店同步成功" +SyncAppDetail: "同步應用配置" + #file ErrFileCanNotRead: "此文件不支持預覽" @@ -231,4 +235,6 @@ Stop: '停止', Image: '鏡像', AppLink: '關聯應用' EnableSSL: "開啟 HTTPS" +AppStore: "應用商店" +TaskSync: "同步" diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml index be35323ef..c260dd2e1 100644 --- a/agent/i18n/lang/zh.yaml +++ b/agent/i18n/lang/zh.yaml @@ -62,6 +62,9 @@ PullImageStart: "开始拉取镜像 {{ .name }}" PullImageSuccess: "镜像拉取成功" UpgradeAppStart: "开始升级应用 {{ .name }}" UpgradeAppSuccess: "应用 {{ .name }} 升级成功" +AppStoreIsLastVersion: "应用商店已经是最新版本" +AppStoreSyncSuccess: "应用商店同步成功" +SyncAppDetail: "同步应用配置" #file ErrFileCanNotRead: "此文件不支持预览" @@ -124,7 +127,7 @@ ExecShellSuccess: "脚本执行成功" ErrUserIsExist: "当前用户已存在,请重新输入" ErrDatabaseIsExist: "当前数据库已存在,请重新输入" ErrExecTimeOut: "SQL 执行超时,请检查数据库" -ErrRemoteExist: "远程数据库已存在该名称,请修改后重试"vv +ErrRemoteExist: "远程数据库已存在该名称,请修改后重试" ErrLocalExist: "本地数据库已存在该名称,请修改后重试" #redis @@ -233,3 +236,5 @@ Stop: "停止" Image: "镜像" AppLink: "关联应用" EnableSSL: "开启 HTTPS" +AppStore: "应用商店" +TaskSync: "同步" diff --git a/agent/init/business/business.go b/agent/init/business/business.go index c2cc61238..663036fc4 100644 --- a/agent/init/business/business.go +++ b/agent/init/business/business.go @@ -15,7 +15,7 @@ func Init() { func syncApp() { _ = 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") return } diff --git a/agent/init/db/db.go b/agent/init/db/db.go index 53b014a3b..b125149c1 100644 --- a/agent/init/db/db.go +++ b/agent/init/db/db.go @@ -13,6 +13,21 @@ import ( "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() { if _, err := os.Stat(global.CONF.System.DbPath); err != nil { if err := os.MkdirAll(global.CONF.System.DbPath, os.ModePerm); err != nil { @@ -29,27 +44,37 @@ func Init() { } initMonitorDB() + initTaskDB() - db, err := gorm.Open(sqlite.Open(fullPath), &gorm.Config{ - DisableForeignKeyConstraintWhenMigrating: true, - Logger: newLogger(), - }) + db, err := GetDBWithPath(fullPath) if err != nil { 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.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() { fullPath := path.Join(global.CONF.System.DbPath, "monitor.db") if _, err := os.Stat(fullPath); err != nil { diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index a642cb2ed..bf5d3e3c4 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -19,6 +19,7 @@ func Init() { migrations.UpdateWebsite, migrations.UpdateWebsiteDomain, migrations.UpdateApp, + migrations.AddTaskDB, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index ed15646a0..dfbc2272c 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -242,3 +242,12 @@ var UpdateApp = &gormigrate.Migration{ &model.App{}) }, } + +var AddTaskDB = &gormigrate.Migration{ + ID: "20240822-add-task-table", + Migrate: func(tx *gorm.DB) error { + return global.TaskDB.AutoMigrate( + &model.Task{}, + ) + }, +} diff --git a/agent/server/server.go b/agent/server/server.go index 6e7721607..4139a89c6 100644 --- a/agent/server/server.go +++ b/agent/server/server.go @@ -3,6 +3,7 @@ package server import ( "crypto/tls" "fmt" + "github.com/1Panel-dev/1Panel/agent/init/business" "net" "net/http" "os" @@ -12,7 +13,6 @@ import ( "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" "github.com/1Panel-dev/1Panel/agent/init/db" "github.com/1Panel-dev/1Panel/agent/init/hook" "github.com/1Panel-dev/1Panel/agent/init/log" @@ -36,7 +36,6 @@ func Start() { gin.SetMode("debug") cron.Run() InitOthers() - business.Init() hook.Init() rootRouter := router.Routers() @@ -75,6 +74,7 @@ func Start() { ClientAuth: tls.RequireAnyClientCert, } global.LOG.Info("listen at https://0.0.0.0:9999") + business.Init() if err := server.ListenAndServeTLS("", ""); err != nil { panic(err) } diff --git a/frontend/src/api/interface/app.ts b/frontend/src/api/interface/app.ts index ccc01e033..46b36f87c 100644 --- a/frontend/src/api/interface/app.ts +++ b/frontend/src/api/interface/app.ts @@ -251,4 +251,8 @@ export namespace App { appInstallID: number; updateVersion?: string; } + + export interface AppStoreSync { + taskID: string; + } } diff --git a/frontend/src/api/modules/app.ts b/frontend/src/api/modules/app.ts index f337b9920..0b5c01b5e 100644 --- a/frontend/src/api/modules/app.ts +++ b/frontend/src/api/modules/app.ts @@ -3,8 +3,8 @@ import { ResPage } from '../interface'; import { App } from '../interface/app'; import { TimeoutEnum } from '@/enums/http-enum'; -export const SyncApp = () => { - return http.post('apps/sync', {}); +export const SyncApp = (req: App.AppStoreSync) => { + return http.post('apps/sync', req); }; export const GetAppListUpdate = () => { diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index f73253c0c..ca2472f74 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1857,7 +1857,7 @@ const message = { syncAllAppHelper: 'All applications are about to be synchronized. Do you want to continue? ', hostModeHelper: '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', upgradeWarn: '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', supportedArchitectures: 'Architectures', link: 'Link', - showCurrentArch: 'Show current architecture App', + showCurrentArch: 'Architecture', }, website: { website: 'Website', diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts index 224a57c72..1a05a17e9 100644 --- a/frontend/src/utils/util.ts +++ b/frontend/src/utils/util.ts @@ -3,6 +3,7 @@ import i18n from '@/lang'; import useClipboard from 'vue-clipboard3'; const { toClipboard } = useClipboard(); import { MsgError, MsgSuccess } from '@/utils/message'; +import { v4 as uuidv4 } from 'uuid'; export function deepCopy(obj: any): T { let newObj: any; @@ -616,3 +617,7 @@ export const getFileType = (extension: string) => { }); return type; }; + +export const newUUID = () => { + return uuidv4(); +}; diff --git a/frontend/src/views/app-store/apps/index.vue b/frontend/src/views/app-store/apps/index.vue index 347c0d8f2..aa9e32c3f 100644 --- a/frontend/src/views/app-store/apps/index.vue +++ b/frontend/src/views/app-store/apps/index.vue @@ -168,6 +168,7 @@ +