feat: 应用商店对接远程应用服务

This commit is contained in:
zhengkunwang223 2023-05-15 22:40:05 +08:00 committed by zhengkunwang223
parent d443103e2c
commit aeabed70db
20 changed files with 457 additions and 333 deletions

View file

@ -31,20 +31,42 @@ type AppVersion struct {
DetailId uint `json:"detailId"`
}
//type AppList struct {
// Version string `json:"version"`
// Tags []Tag `json:"tags"`
// Items []AppDefine `json:"items"`
//}
type AppList struct {
Version string `json:"version"`
Tags []Tag `json:"tags"`
Items []AppDefine `json:"items"`
Valid bool `json:"valid"`
Violations []string `json:"violations"`
LastModified int `json:"lastModified"`
Apps []AppDefine `json:"apps"`
Extra ExtraProperties `json:"additionalProperties"`
}
type AppDefine struct {
Key string `json:"key"`
Icon string `json:"icon"`
Name string `json:"name"`
ReadMe string `json:"readMe"`
LastModified int `json:"lastModified"`
AppProperty AppProperty `json:"additionalProperties"`
Versions []AppConfigVersion `json:"versions"`
}
type ExtraProperties struct {
Tags []Tag `json:"tags"`
}
type AppProperty struct {
Name string `json:"name"`
Type string `json:"type"`
Tags []string `json:"tags"`
Versions []string `json:"versions"`
ShortDescZh string `json:"shortDescZh"`
ShortDescEn string `json:"shortDescEn"`
Type string `json:"type"`
Key string `json:"key"`
Required []string `json:"Required"`
CrossVersionUpdate bool `json:"crossVersionUpdate"`
Limit int `json:"limit"`
@ -54,8 +76,16 @@ type AppDefine struct {
Document string `json:"document"`
}
func (define AppDefine) GetRequired() string {
by, _ := json.Marshal(define.Required)
type AppConfigVersion struct {
Name string `json:"name"`
LastModified int `json:"lastModified"`
DownloadUrl string `json:"downloadUrl"`
DownloadCallBackUrl string `json:"downloadCallBackUrl"`
AppForm interface{} `json:"additionalProperties"`
}
func (config AppProperty) GetRequired() string {
by, _ := json.Marshal(config.Required)
return string(by)
}
@ -79,6 +109,7 @@ type AppFormFields struct {
Edit bool `json:"edit"`
Rule string `json:"rule"`
Multiple bool `json:"multiple"`
Child interface{} `json:"child"`
Values []AppFormValue `json:"values"`
}

View file

@ -1,6 +1,7 @@
package response
import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"time"
"github.com/1Panel-dev/1Panel/backend/app/model"
@ -12,9 +13,11 @@ type AppRes struct {
}
type AppUpdateRes struct {
Version string `json:"version"`
CanUpdate bool `json:"canUpdate"`
DownloadPath string `json:"downloadPath"`
//Version string `json:"version"`
//DownloadPath string `json:"downloadPath"`
CanUpdate bool `json:"canUpdate"`
AppStoreLastModified int `json:"appStoreLastModified"`
List dto.AppList `json:"list"`
}
type AppDTO struct {

View file

@ -33,7 +33,8 @@ type SettingInfo struct {
WeChatVars string `json:"weChatVars"`
DingVars string `json:"dingVars"`
AppStoreVersion string `json:"appStoreVersion"`
AppStoreVersion string `json:"appStoreVersion"`
AppStoreLastModified string `json:"appStoreLastModified"`
}
type SettingUpdate struct {

View file

@ -2,22 +2,25 @@ package model
type App struct {
BaseModel
Name string `json:"name" gorm:"type:varchar(64);not null"`
Key string `json:"key" gorm:"type:varchar(64);not null;uniqueIndex"`
ShortDescZh string `json:"shortDescZh" gorm:"type:longtext;"`
ShortDescEn string `json:"shortDescEn" gorm:"type:longtext;"`
Icon string `json:"icon" gorm:"type:longtext;"`
Type string `json:"type" gorm:"type:varchar(64);not null"`
Status string `json:"status" gorm:"type:varchar(64);not null"`
Required string `json:"required" gorm:"type:varchar(64);not null"`
CrossVersionUpdate bool `json:"crossVersionUpdate"`
Limit int `json:"limit" gorm:"type:Integer;not null"`
Website string `json:"website" gorm:"type:varchar(64);not null"`
Github string `json:"github" gorm:"type:varchar(64);not null"`
Document string `json:"document" gorm:"type:varchar(64);not null"`
Recommend int `json:"recommend" gorm:"type:Integer;not null"`
Resource string `json:"resource" gorm:"type:varchar;not null;default:remote"`
Details []AppDetail `json:"-" gorm:"-:migration"`
TagsKey []string `json:"-" gorm:"-"`
AppTags []AppTag `json:"-" gorm:"-:migration"`
Name string `json:"name" gorm:"type:varchar(64);not null"`
Key string `json:"key" gorm:"type:varchar(64);not null;uniqueIndex"`
ShortDescZh string `json:"shortDescZh" gorm:"type:longtext;"`
ShortDescEn string `json:"shortDescEn" gorm:"type:longtext;"`
Icon string `json:"icon" gorm:"type:longtext;"`
Type string `json:"type" gorm:"type:varchar(64);not null"`
Status string `json:"status" gorm:"type:varchar(64);not null"`
Required string `json:"required" gorm:"type:varchar(64);not null"`
CrossVersionUpdate bool `json:"crossVersionUpdate"`
Limit int `json:"limit" gorm:"type:Integer;not null"`
Website string `json:"website" gorm:"type:varchar(64);not null"`
Github string `json:"github" gorm:"type:varchar(64);not null"`
Document string `json:"document" gorm:"type:varchar(64);not null"`
Recommend int `json:"recommend" gorm:"type:Integer;not null"`
Resource string `json:"resource" gorm:"type:varchar;not null;default:remote"`
ReadMe string `json:"readMe" gorm:"type:varchar;"`
LastModified int `json:"lastModified" gorm:"type:Integer;"`
Details []AppDetail `json:"-" gorm:"-:migration"`
TagsKey []string `json:"-" gorm:"-"`
AppTags []AppTag `json:"-" gorm:"-:migration"`
}

View file

@ -2,11 +2,14 @@ package model
type AppDetail struct {
BaseModel
AppId uint `json:"appId" gorm:"type:integer;not null"`
Version string `json:"version" gorm:"type:varchar(64);not null"`
Params string `json:"-" gorm:"type:longtext;"`
DockerCompose string `json:"-" gorm:"type:longtext;not null"`
Readme string `json:"readme" gorm:"type:longtext;"`
Status string `json:"status" gorm:"type:varchar(64);not null"`
LastVersion string `json:"lastVersion" gorm:"type:varchar(64);"`
AppId uint `json:"appId" gorm:"type:integer;not null"`
Version string `json:"version" gorm:"type:varchar(64);not null"`
Params string `json:"-" gorm:"type:longtext;"`
DockerCompose string `json:"-" gorm:"type:longtext;"`
Status string `json:"status" gorm:"type:varchar(64);not null"`
LastVersion string `json:"lastVersion" gorm:"type:varchar(64);"`
LastModified int `json:"lastModified" gorm:"type:integer;"`
DownloadUrl string `json:"downloadUrl" gorm:"type:varchar;"`
DownloadCallBackUrl string `json:"downloadCallBackUrl" gorm:"type:longtext;"`
Update bool `json:"update"`
}

View file

@ -24,6 +24,7 @@ type IAppRepo interface {
GetByKey(ctx context.Context, key string) (model.App, error)
Create(ctx context.Context, app *model.App) error
Save(ctx context.Context, app *model.App) error
BatchDelete(ctx context.Context, apps []model.App) error
}
func NewIAppRepo() IAppRepo {
@ -106,3 +107,7 @@ func (a AppRepo) Create(ctx context.Context, app *model.App) error {
func (a AppRepo) Save(ctx context.Context, app *model.App) error {
return getTx(ctx).Omit(clause.Associations).Save(app).Error
}
func (a AppRepo) BatchDelete(ctx context.Context, apps []model.App) error {
return getTx(ctx).Omit(clause.Associations).Delete(&apps).Error
}

View file

@ -5,14 +5,12 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path"
"strings"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"io"
"net/http"
"path"
"strconv"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
@ -253,7 +251,7 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
}
app, err = appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId))
if err != nil {
return nil, err
return
}
if err = checkRequiredAndLimit(app); err != nil {
return
@ -276,7 +274,7 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
value, ok := composeMap["services"]
if !ok {
err = buserr.New("")
err = buserr.New(constant.ErrFileParse)
return
}
servicesMap := value.(map[string]interface{})
@ -318,20 +316,11 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
}
}
}()
if err = copyAppData(app.Key, appDetail.Version, req.Name, req.Params, app.Resource == constant.AppResourceLocal); err != nil {
return
}
fileOp := files.NewFileOp()
if err = fileOp.WriteFile(appInstall.GetComposePath(), strings.NewReader(string(composeByte)), 0775); err != nil {
return
}
paramByte, err = json.Marshal(req.Params)
if err != nil {
return
}
appInstall.Env = string(paramByte)
if err = appInstallRepo.Create(ctx, appInstall); err != nil {
return
}
@ -341,7 +330,13 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) (
if err = upAppPre(app, appInstall); err != nil {
return
}
go upApp(appInstall)
go func() {
if err = downloadApp(app, appDetail, appInstall, req); err != nil {
_ = appInstallRepo.Save(ctx, appInstall)
return
}
upApp(appInstall)
}()
go updateToolApp(appInstall)
return
}
@ -354,7 +349,7 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
if err != nil {
return nil, err
}
versionUrl := fmt.Sprintf("%s/%s/%s/appstore/apps.json", global.CONF.System.RepoUrl, global.CONF.System.Mode, setting.SystemVersion)
versionUrl := fmt.Sprintf("%s/%s/1panel.json", global.CONF.System.AppRepo, global.CONF.System.Mode)
versionRes, err := http.Get(versionUrl)
if err != nil {
return nil, err
@ -368,162 +363,165 @@ func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) {
if err = json.Unmarshal(body, list); err != nil {
return nil, err
}
res.Version = list.Version
if setting.AppStoreVersion == "" || common.CompareVersion(list.Version, setting.AppStoreVersion) {
res.AppStoreLastModified = list.LastModified
res.List = *list
appStoreLastModified, _ := strconv.Atoi(setting.AppStoreLastModified)
if setting.AppStoreLastModified == "" || list.LastModified > appStoreLastModified {
res.CanUpdate = true
res.DownloadPath = fmt.Sprintf("%s/%s/%s/appstore/apps-%s.tar.gz", global.CONF.System.RepoUrl, global.CONF.System.Mode, setting.SystemVersion, list.Version)
return res, err
}
res.CanUpdate = true
return res, nil
}
func (a AppService) SyncAppListFromLocal() {
fileOp := files.NewFileOp()
appDir := constant.LocalAppResourceDir
listFile := path.Join(appDir, "list.json")
if !fileOp.Stat(listFile) {
return
}
global.LOG.Infof("start sync local apps...")
content, err := fileOp.GetContent(listFile)
if err != nil {
global.LOG.Errorf("get list.json content failed %s", err.Error())
return
}
list := &dto.AppList{}
if err := json.Unmarshal(content, list); err != nil {
global.LOG.Errorf("unmarshal list.json failed %s", err.Error())
return
}
oldApps, _ := appRepo.GetBy(appRepo.WithResource(constant.AppResourceLocal))
appsMap := getApps(oldApps, list.Items, true)
for _, l := range list.Items {
localKey := "local" + l.Key
app := appsMap[localKey]
icon, err := os.ReadFile(path.Join(appDir, l.Key, "metadata", "logo.png"))
if err != nil {
global.LOG.Errorf("get [%s] icon error: %s", l.Name, err.Error())
continue
}
iconStr := base64.StdEncoding.EncodeToString(icon)
app.Icon = iconStr
app.TagsKey = append(l.Tags, "Local")
app.Recommend = 9999
versions := l.Versions
detailsMap := getAppDetails(app.Details, versions)
for _, v := range versions {
detail := detailsMap[v]
detailPath := path.Join(appDir, l.Key, "versions", v)
if _, err := os.Stat(detailPath); err != nil {
global.LOG.Errorf("get [%s] folder error: %s", detailPath, err.Error())
continue
}
readmeStr, err := os.ReadFile(path.Join(detailPath, "README.md"))
if err != nil {
global.LOG.Errorf("get [%s] README error: %s", detailPath, err.Error())
}
detail.Readme = string(readmeStr)
dockerComposeStr, err := os.ReadFile(path.Join(detailPath, "docker-compose.yml"))
if err != nil {
global.LOG.Errorf("get [%s] docker-compose.yml error: %s", detailPath, err.Error())
continue
}
detail.DockerCompose = string(dockerComposeStr)
paramStr, err := os.ReadFile(path.Join(detailPath, "config.json"))
if err != nil {
global.LOG.Errorf("get [%s] form.json error: %s", detailPath, err.Error())
}
detail.Params = string(paramStr)
detailsMap[v] = detail
}
var newDetails []model.AppDetail
for _, v := range detailsMap {
newDetails = append(newDetails, v)
}
app.Details = newDetails
appsMap[localKey] = app
}
var (
addAppArray []model.App
updateArray []model.App
appIds []uint
)
for _, v := range appsMap {
if v.ID == 0 {
addAppArray = append(addAppArray, v)
} else {
updateArray = append(updateArray, v)
appIds = append(appIds, v.ID)
}
}
tx, ctx := getTxAndContext()
if len(addAppArray) > 0 {
if err := appRepo.BatchCreate(ctx, addAppArray); err != nil {
tx.Rollback()
return
}
}
for _, update := range updateArray {
if err := appRepo.Save(ctx, &update); err != nil {
tx.Rollback()
return
}
}
if err := appTagRepo.DeleteByAppIds(ctx, appIds); err != nil {
tx.Rollback()
return
}
apps := append(addAppArray, updateArray...)
var (
addDetails []model.AppDetail
updateDetails []model.AppDetail
appTags []*model.AppTag
)
tags, _ := tagRepo.All()
tagMap := make(map[string]uint, len(tags))
for _, app := range tags {
tagMap[app.Key] = app.ID
}
for _, a := range apps {
for _, t := range a.TagsKey {
tagId, ok := tagMap[t]
if ok {
appTags = append(appTags, &model.AppTag{
AppId: a.ID,
TagId: tagId,
})
}
}
for _, d := range a.Details {
d.AppId = a.ID
if d.ID == 0 {
addDetails = append(addDetails, d)
} else {
updateDetails = append(updateDetails, d)
}
}
}
if len(addDetails) > 0 {
if err := appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
tx.Rollback()
return
}
}
for _, u := range updateDetails {
if err := appDetailRepo.Update(ctx, u); err != nil {
tx.Rollback()
return
}
}
if len(appTags) > 0 {
if err := appTagRepo.BatchCreate(ctx, appTags); err != nil {
tx.Rollback()
return
}
}
tx.Commit()
global.LOG.Infof("sync local apps success")
//fileOp := files.NewFileOp()
//appDir := constant.LocalAppResourceDir
//listFile := path.Join(appDir, "list.json")
//if !fileOp.Stat(listFile) {
// return
//}
//global.LOG.Infof("start sync local apps...")
//content, err := fileOp.GetContent(listFile)
//if err != nil {
// global.LOG.Errorf("get list.json content failed %s", err.Error())
// return
//}
//list := &dto.AppList{}
//if err := json.Unmarshal(content, list); err != nil {
// global.LOG.Errorf("unmarshal list.json failed %s", err.Error())
// return
//}
//oldApps, _ := appRepo.GetBy(appRepo.WithResource(constant.AppResourceLocal))
//appsMap := getApps(oldApps, list.Apps, true)
//for _, l := range list.Apps {
// localKey := "local" + l.Config.Key
// app := appsMap[localKey]
// icon, err := os.ReadFile(path.Join(appDir, l.Config.Key, "metadata", "logo.png"))
// if err != nil {
// global.LOG.Errorf("get [%s] icon error: %s", l.Name, err.Error())
// continue
// }
// iconStr := base64.StdEncoding.EncodeToString(icon)
// app.Icon = iconStr
// app.TagsKey = append(l.Tags, "Local")
// app.Recommend = 9999
// versions := l.Versions
// detailsMap := getAppDetails(app.Details, versions)
//
// for _, v := range versions {
// detail := detailsMap[v]
// detailPath := path.Join(appDir, l.Key, "versions", v)
// if _, err := os.Stat(detailPath); err != nil {
// global.LOG.Errorf("get [%s] folder error: %s", detailPath, err.Error())
// continue
// }
// readmeStr, err := os.ReadFile(path.Join(detailPath, "README.md"))
// if err != nil {
// global.LOG.Errorf("get [%s] README error: %s", detailPath, err.Error())
// }
// detail.Readme = string(readmeStr)
// dockerComposeStr, err := os.ReadFile(path.Join(detailPath, "docker-compose.yml"))
// if err != nil {
// global.LOG.Errorf("get [%s] docker-compose.yml error: %s", detailPath, err.Error())
// continue
// }
// detail.DockerCompose = string(dockerComposeStr)
// paramStr, err := os.ReadFile(path.Join(detailPath, "config.json"))
// if err != nil {
// global.LOG.Errorf("get [%s] form.json error: %s", detailPath, err.Error())
// }
// detail.Params = string(paramStr)
// detailsMap[v] = detail
// }
// var newDetails []model.AppDetail
// for _, v := range detailsMap {
// newDetails = append(newDetails, v)
// }
// app.Details = newDetails
// appsMap[localKey] = app
//}
//var (
// addAppArray []model.App
// updateArray []model.App
// appIds []uint
//)
//for _, v := range appsMap {
// if v.ID == 0 {
// addAppArray = append(addAppArray, v)
// } else {
// updateArray = append(updateArray, v)
// appIds = append(appIds, v.ID)
// }
//}
//tx, ctx := getTxAndContext()
//if len(addAppArray) > 0 {
// if err := appRepo.BatchCreate(ctx, addAppArray); err != nil {
// tx.Rollback()
// return
// }
//}
//for _, update := range updateArray {
// if err := appRepo.Save(ctx, &update); err != nil {
// tx.Rollback()
// return
// }
//}
//if err := appTagRepo.DeleteByAppIds(ctx, appIds); err != nil {
// tx.Rollback()
// return
//}
//apps := append(addAppArray, updateArray...)
//var (
// addDetails []model.AppDetail
// updateDetails []model.AppDetail
// appTags []*model.AppTag
//)
//tags, _ := tagRepo.All()
//tagMap := make(map[string]uint, len(tags))
//for _, app := range tags {
// tagMap[app.Key] = app.ID
//}
//for _, a := range apps {
// for _, t := range a.TagsKey {
// tagId, ok := tagMap[t]
// if ok {
// appTags = append(appTags, &model.AppTag{
// AppId: a.ID,
// TagId: tagId,
// })
// }
// }
// for _, d := range a.Details {
// d.AppId = a.ID
// if d.ID == 0 {
// addDetails = append(addDetails, d)
// } else {
// updateDetails = append(updateDetails, d)
// }
// }
//}
//if len(addDetails) > 0 {
// if err := appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
// tx.Rollback()
// return
// }
//}
//for _, u := range updateDetails {
// if err := appDetailRepo.Update(ctx, u); err != nil {
// tx.Rollback()
// return
// }
//}
//if len(appTags) > 0 {
// if err := appTagRepo.BatchCreate(ctx, appTags); err != nil {
// tx.Rollback()
// return
// }
//}
//tx.Commit()
//global.LOG.Infof("sync local apps success")
}
func (a AppService) SyncAppListFromRemote() error {
updateRes, err := a.GetAppUpdate()
@ -531,28 +529,15 @@ func (a AppService) SyncAppListFromRemote() error {
return err
}
if !updateRes.CanUpdate {
global.LOG.Infof("The latest version is [%s] The app store is already up to date", updateRes.Version)
//global.LOG.Infof("The latest version is [%s] The app store is already up to date", updateRes.Version)
return nil
}
if err := getAppFromRepo(updateRes.DownloadPath, updateRes.Version); err != nil {
global.LOG.Errorf("get app from oss error: %s", err.Error())
return err
}
appDir := constant.AppResourceDir
listFile := path.Join(appDir, "list.json")
content, err := os.ReadFile(listFile)
if err != nil {
return err
}
list := &dto.AppList{}
if err := json.Unmarshal(content, list); err != nil {
return err
}
var (
tags []*model.Tag
appTags []*model.AppTag
list = updateRes.List
)
for _, t := range list.Tags {
for _, t := range list.Extra.Tags {
tags = append(tags, &model.Tag{
Key: t.Key,
Name: t.Name,
@ -562,70 +547,86 @@ func (a AppService) SyncAppListFromRemote() error {
if err != nil {
return err
}
appsMap := getApps(oldApps, list.Items, false)
for _, l := range list.Items {
app := appsMap[l.Key]
icon, err := os.ReadFile(path.Join(appDir, l.Key, "metadata", "logo.png"))
baseRemoteUrl := fmt.Sprintf("%s/%s/1panel", global.CONF.System.AppRepo, global.CONF.System.Mode)
appsMap := getApps(oldApps, list.Apps, false)
for _, l := range list.Apps {
app := appsMap[l.AppProperty.Key]
iconRes, err := http.Get(l.Icon)
if err != nil {
global.LOG.Errorf("get [%s] icon error: %s", l.Name, err.Error())
continue
return err
}
iconStr := base64.StdEncoding.EncodeToString(icon)
body, err := io.ReadAll(iconRes.Body)
if err != nil {
return err
}
iconStr := base64.StdEncoding.EncodeToString(body)
app.Icon = iconStr
app.TagsKey = l.Tags
if l.Recommend > 0 {
app.Recommend = l.Recommend
app.TagsKey = l.AppProperty.Tags
if l.AppProperty.Recommend > 0 {
app.Recommend = l.AppProperty.Recommend
} else {
app.Recommend = 9999
}
app.ReadMe = l.ReadMe
app.LastModified = l.LastModified
versions := l.Versions
detailsMap := getAppDetails(app.Details, versions)
for _, v := range versions {
detail := detailsMap[v]
detailPath := path.Join(appDir, l.Key, "versions", v)
if _, err := os.Stat(detailPath); err != nil {
global.LOG.Errorf("get [%s] folder error: %s", detailPath, err.Error())
continue
}
readmeStr, err := os.ReadFile(path.Join(detailPath, "README.md"))
version := v.Name
detail := detailsMap[version]
dockerComposeUrl := fmt.Sprintf("%s/%s/%s/%s", baseRemoteUrl, app.Key, version, "docker-compose.yml")
composeRes, err := http.Get(dockerComposeUrl)
if err != nil {
global.LOG.Errorf("get [%s] README error: %s", detailPath, err.Error())
return err
}
detail.Readme = string(readmeStr)
dockerComposeStr, err := os.ReadFile(path.Join(detailPath, "docker-compose.yml"))
bodyContent, err := io.ReadAll(composeRes.Body)
if err != nil {
global.LOG.Errorf("get [%s] docker-compose.yml error: %s", detailPath, err.Error())
continue
return err
}
detail.DockerCompose = string(dockerComposeStr)
paramStr, err := os.ReadFile(path.Join(detailPath, "config.json"))
if err != nil {
global.LOG.Errorf("get [%s] form.json error: %s", detailPath, err.Error())
detail.DockerCompose = string(bodyContent)
paramByte, _ := json.Marshal(v.AppForm)
detail.Params = string(paramByte)
detail.DownloadUrl = v.DownloadUrl
detail.DownloadCallBackUrl = v.DownloadCallBackUrl
if v.LastModified > detail.LastModified {
detail.Update = true
detail.LastModified = v.LastModified
}
detail.Params = string(paramStr)
detailsMap[v] = detail
detailsMap[version] = detail
}
var newDetails []model.AppDetail
for _, v := range detailsMap {
newDetails = append(newDetails, v)
for _, detail := range detailsMap {
newDetails = append(newDetails, detail)
}
app.Details = newDetails
appsMap[l.Key] = app
appsMap[l.AppProperty.Key] = app
}
var (
addAppArray []model.App
updateArray []model.App
tagMap = make(map[string]uint, len(tags))
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 {
updateArray = append(updateArray, v)
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)
}
}
}
tx, ctx := getTxAndContext()
@ -635,6 +636,16 @@ func (a AppService) SyncAppListFromRemote() error {
return err
}
}
if len(deleteAppArray) > 0 {
if err := appRepo.BatchDelete(ctx, deleteAppArray); err != nil {
tx.Rollback()
return err
}
if err := appDetailRepo.DeleteByAppIds(ctx, deleteIds); err != nil {
tx.Rollback()
return err
}
}
if err := tagRepo.DeleteAll(ctx); err != nil {
tx.Rollback()
return err
@ -648,34 +659,39 @@ func (a AppService) SyncAppListFromRemote() error {
tagMap[t.Key] = t.ID
}
}
for _, update := range updateArray {
for _, update := range updateAppArray {
if err := appRepo.Save(ctx, &update); err != nil {
tx.Rollback()
return err
}
}
apps := append(addAppArray, updateArray...)
apps := append(addAppArray, updateAppArray...)
var (
addDetails []model.AppDetail
updateDetails []model.AppDetail
deleteDetails []model.AppDetail
)
for _, a := range apps {
for _, t := range a.TagsKey {
for _, app := range apps {
for _, t := range app.TagsKey {
tagId, ok := tagMap[t]
if ok {
appTags = append(appTags, &model.AppTag{
AppId: a.ID,
AppId: app.ID,
TagId: tagId,
})
}
}
for _, d := range a.Details {
d.AppId = a.ID
for _, d := range app.Details {
d.AppId = app.ID
if d.ID == 0 {
addDetails = append(addDetails, d)
} else {
updateDetails = append(updateDetails, d)
if d.Status == constant.AppTakeDown {
deleteDetails = append(deleteDetails, d)
} else {
updateDetails = append(updateDetails, d)
}
}
}
}
@ -691,6 +707,7 @@ func (a AppService) SyncAppListFromRemote() error {
return err
}
}
if err := appTagRepo.DeleteAll(ctx); err != nil {
tx.Rollback()
return err
@ -702,5 +719,8 @@ func (a AppService) SyncAppListFromRemote() error {
}
}
tx.Commit()
if err := NewISettingService().Update("AppStoreLastModified", strconv.Itoa(list.LastModified)); err != nil {
return err
}
return nil
}

View file

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"math"
"os"
"path"
@ -183,6 +184,9 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
if err != nil {
return err
}
if !req.ForceDelete && !files.NewFileOp().Stat(install.GetPath()) {
return buserr.New(constant.ErrInstallDirNotFound)
}
dockerComposePath := install.GetComposePath()
switch req.Operate {
case constant.Rebuild:

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/compose-spec/compose-go/types"
"github.com/subosito/gotenv"
"math"
@ -225,7 +226,7 @@ func upgradeInstall(installId uint, detailId uint) error {
return err
}
detailDir := path.Join(constant.ResourceDir, "apps", install.App.Key, "versions", detail.Version)
detailDir := path.Join(constant.ResourceDir, "apps", install.App.Resource, install.App.Key, detail.Version)
if install.App.Resource == constant.AppResourceLocal {
detailDir = path.Join(constant.ResourceDir, "localApps", strings.TrimPrefix(install.App.Key, "local"), "versions", detail.Version)
}
@ -359,24 +360,48 @@ func handleMap(params map[string]interface{}, envParams map[string]string) {
}
}
func copyAppData(key, version, installName string, params map[string]interface{}, isLocal bool) (err error) {
func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, req request.AppInstallCreate) (err error) {
fileOp := files.NewFileOp()
appResourceDir := constant.AppResourceDir
installAppDir := path.Join(constant.AppInstallDir, key)
appKey := key
if isLocal {
appResourceDir := path.Join(constant.AppResourceDir, app.Resource)
if app.Resource == constant.AppResourceRemote && appDetail.Update {
appDownloadDir := path.Join(appResourceDir, app.Key)
if !fileOp.Stat(appDownloadDir) {
_ = fileOp.CreateDir(appDownloadDir, 0755)
}
appVersionDir := path.Join(appDownloadDir, appDetail.Version)
if !fileOp.Stat(appVersionDir) {
_ = fileOp.CreateDir(appVersionDir, 0755)
}
global.LOG.Infof("download app[%s] from %s", app.Name, appDetail.DownloadUrl)
filePath := path.Join(appVersionDir, appDetail.Version+".tar.gz")
if err = fileOp.DownloadFile(appDetail.DownloadUrl, filePath); err != nil {
appInstall.Status = constant.DownloadErr
global.LOG.Errorf("download app[%s] error %v", app.Name, err)
return
}
if err = fileOp.Decompress(filePath, appVersionDir, files.TarGz); err != nil {
global.LOG.Errorf("decompress app[%s] error %v", app.Name, err)
appInstall.Status = constant.DownloadErr
return
}
_ = fileOp.DeleteFile(filePath)
}
appKey := app.Key
installAppDir := path.Join(constant.AppInstallDir, app.Key)
if app.Resource == constant.AppResourceLocal {
appResourceDir = constant.LocalAppResourceDir
appKey = strings.TrimPrefix(key, "local")
appKey = strings.TrimPrefix(app.Resource, "local")
installAppDir = path.Join(constant.LocalAppInstallDir, appKey)
}
resourceDir := path.Join(appResourceDir, appKey, "versions", version)
resourceDir := path.Join(appResourceDir, appKey, appDetail.Version)
if !fileOp.Stat(installAppDir) {
if err = fileOp.CreateDir(installAppDir, 0755); err != nil {
return
}
}
appDir := path.Join(installAppDir, installName)
appDir := path.Join(installAppDir, req.Name)
if fileOp.Stat(appDir) {
if err = fileOp.DeleteDir(appDir); err != nil {
return
@ -385,14 +410,14 @@ func copyAppData(key, version, installName string, params map[string]interface{}
if err = fileOp.Copy(resourceDir, installAppDir); err != nil {
return
}
versionDir := path.Join(installAppDir, version)
versionDir := path.Join(installAppDir, appDetail.Version)
if err = fileOp.Rename(versionDir, appDir); err != nil {
return
}
envPath := path.Join(appDir, ".env")
envParams := make(map[string]string, len(params))
handleMap(params, envParams)
envParams := make(map[string]string, len(req.Params))
handleMap(req.Params, envParams)
if err = env.Write(envParams, envPath); err != nil {
return
}
@ -473,21 +498,21 @@ func rebuildApp(appInstall model.AppInstall) error {
return syncById(appInstall.ID)
}
func getAppDetails(details []model.AppDetail, versions []string) map[string]model.AppDetail {
func getAppDetails(details []model.AppDetail, versions []dto.AppConfigVersion) map[string]model.AppDetail {
appDetails := make(map[string]model.AppDetail, len(details))
for _, old := range details {
old.Status = constant.AppTakeDown
appDetails[old.Version] = old
}
for _, v := range versions {
detail, ok := appDetails[v]
version := v.Name
detail, ok := appDetails[version]
if ok {
detail.Status = constant.AppNormal
appDetails[v] = detail
appDetails[version] = detail
} else {
appDetails[v] = model.AppDetail{
Version: v,
appDetails[version] = model.AppDetail{
Version: version,
Status: constant.AppNormal,
}
}
@ -502,7 +527,8 @@ func getApps(oldApps []model.App, items []dto.AppDefine, isLocal bool) map[strin
apps[old.Key] = old
}
for _, item := range items {
key := item.Key
config := item.AppProperty
key := config.Key
if isLocal {
key = "local" + key
}
@ -516,17 +542,19 @@ func getApps(oldApps []model.App, items []dto.AppDefine, isLocal bool) map[strin
app.Resource = constant.AppResourceRemote
}
app.Name = item.Name
app.Limit = item.Limit
app.Limit = config.Limit
app.Key = key
app.ShortDescZh = item.ShortDescZh
app.ShortDescEn = item.ShortDescEn
app.Website = item.Website
app.Document = item.Document
app.Github = item.Github
app.Type = item.Type
app.CrossVersionUpdate = item.CrossVersionUpdate
app.Required = item.GetRequired()
app.ShortDescZh = config.ShortDescZh
app.ShortDescEn = config.ShortDescEn
app.Website = config.Website
app.Document = config.Document
app.Github = config.Github
app.Type = config.Type
app.CrossVersionUpdate = config.CrossVersionUpdate
app.Required = config.GetRequired()
app.Status = constant.AppNormal
app.LastModified = item.LastModified
app.ReadMe = item.ReadMe
apps[key] = app
}
return apps

View file

@ -1,23 +1,24 @@
package configs
type System struct {
Port string `mapstructure:"port"`
SSL string `mapstructure:"ssl"`
DbFile string `mapstructure:"db_file"`
DbPath string `mapstructure:"db_path"`
LogPath string `mapstructure:"log_path"`
DataDir string `mapstructure:"data_dir"`
TmpDir string `mapstructure:"tmp_dir"`
Cache string `mapstructure:"cache"`
Backup string `mapstructure:"backup"`
EncryptKey string `mapstructure:"encrypt_key"`
BaseDir string `mapstructure:"base_dir"`
Mode string `mapstructure:"mode"`
RepoUrl string `mapstructure:"repo_url"`
Version string `mapstructure:"version"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Entrance string `mapstructure:"entrance"`
IsDemo bool `mapstructure:"is_demo"`
Port string `mapstructure:"port"`
SSL string `mapstructure:"ssl"`
DbFile string `mapstructure:"db_file"`
DbPath string `mapstructure:"db_path"`
LogPath string `mapstructure:"log_path"`
DataDir string `mapstructure:"data_dir"`
TmpDir string `mapstructure:"tmp_dir"`
Cache string `mapstructure:"cache"`
Backup string `mapstructure:"backup"`
EncryptKey string `mapstructure:"encrypt_key"`
BaseDir string `mapstructure:"base_dir"`
Mode string `mapstructure:"mode"`
RepoUrl string `mapstructure:"repo_url"`
Version string `mapstructure:"version"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Entrance string `mapstructure:"entrance"`
IsDemo bool `mapstructure:"is_demo"`
AppRepo string `mapstructure:"app_repo"`
ChangeUserInfo bool `mapstructure:"change_user_info"`
}

View file

@ -1,12 +1,14 @@
package constant
const (
Running = "Running"
UnHealthy = "UnHealthy"
Error = "Error"
Stopped = "Stopped"
Installing = "Installing"
Syncing = "Syncing"
Running = "Running"
UnHealthy = "UnHealthy"
Error = "Error"
Stopped = "Stopped"
Installing = "Installing"
Syncing = "Syncing"
DownloadErr = "DownloadErr"
DirNotFound = "DirNotFound"
ContainerPrefix = "1Panel-"

View file

@ -30,23 +30,16 @@ var (
ErrInvalidParams = errors.New("ErrInvalidParams")
ErrTokenParse = errors.New("ErrTokenParse")
ErrPageGenerate = errors.New("generate page info failed")
ErrRepoNotValid = "ErrRepoNotValid"
)
// api
var (
ErrTypeInternalServer = "ErrInternalServer"
ErrTypeInvalidParams = "ErrInvalidParams"
ErrTypeToken = "ErrToken"
ErrTypeTokenTimeOut = "ErrTokenTimeOut"
ErrTypeNotLogin = "ErrNotLogin"
ErrTypePasswordExpired = "ErrPasswordExpired"
ErrTypeNotSafety = "ErrNotSafety"
ErrNameIsExist = "ErrNameIsExist"
ErrDemoEnvironment = "ErrDemoEnvironment"
ErrInitUser = "ErrInitUser"
)
// app
@ -55,20 +48,20 @@ var (
ErrAppLimit = "ErrAppLimit"
ErrAppRequired = "ErrAppRequired"
ErrFileCanNotRead = "ErrFileCanNotRead"
ErrFileToLarge = "ErrFileToLarge"
ErrNotInstall = "ErrNotInstall"
ErrPortInOtherApp = "ErrPortInOtherApp"
ErrDbUserNotValid = "ErrDbUserNotValid"
ErrUpdateBuWebsite = "ErrUpdateBuWebsite"
Err1PanelNetworkFailed = "Err1PanelNetworkFailed"
ErrCmdTimeout = "ErrCmdTimeout"
ErrFileParse = "ErrFileParse"
ErrInstallDirNotFound = "ErrInstallDirNotFound"
)
// website
var (
ErrDomainIsExist = "ErrDomainIsExist"
ErrAliasIsExist = "ErrAliasIsExist"
ErrAppDelete = "ErrAppDelete"
ErrGroupIsUsed = "ErrGroupIsUsed"
ErrUsernameIsExist = "ErrUsernameIsExist"
ErrUsernameIsNotExist = "ErrUsernameIsNotExist"

View file

@ -29,6 +29,8 @@ ErrDbUserNotValid: "Stock database, username and password do not match"
ErrDockerComposeNotValid: "docker-compose file format error!"
ErrUpdateBuWebsite: 'The application was updated successfully, but the modification of the website configuration file failed, please check the configuration!'
Err1PanelNetworkFailed: 'Default container network creation failed! {{ .detail }}'
ErrFileParse: 'Application docker-compose file parsing failed!'
ErrInstallDirNotFound: 'installation directory does not exist'
#file
ErrFileCanNotRead: "File can not read"

View file

@ -29,6 +29,8 @@ ErrDbUserNotValid: "存量数据库,用户名密码不匹配!"
ErrDockerComposeNotValid: "docker-compose 文件格式错误"
ErrUpdateBuWebsite: '应用更新成功,但是网站配置文件修改失败,请检查配置!'
Err1PanelNetworkFailed: '默认容器网络创建失败!{{ .detail }}'
ErrFileParse: '应用 docker-compose 文件解析失败!'
ErrInstallDirNotFound: '安装目录不存在'
#file
ErrFileCanNotRead: "此文件不支持预览"

View file

@ -26,6 +26,8 @@ func Init() {
migrations.UpdateTableHost,
migrations.UpdateTableWebsite,
migrations.AddEntranceAndSSL,
migrations.UpdateTableSetting,
migrations.UpdateTableAppDetail,
})
if err := m.Migrate(); err != nil {
global.LOG.Error(err)

View file

@ -312,3 +312,26 @@ var AddEntranceAndSSL = &gormigrate.Migration{
return tx.AutoMigrate(&model.Website{})
},
}
var UpdateTableSetting = &gormigrate.Migration{
ID: "20200511-update-table-setting",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.App{}); err != nil {
return err
}
if err := tx.Create(&model.Setting{Key: "AppStoreLastModified", Value: ""}).Error; err != nil {
return err
}
return nil
},
}
var UpdateTableAppDetail = &gormigrate.Migration{
ID: "20200513-update-table-app-detail",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.AppDetail{}); err != nil {
return err
}
return nil
},
}

View file

@ -3,6 +3,7 @@ system:
base_dir: /opt
mode: dev
repo_url: https://resource.fit2cloud.com/1panel/package
app_repo: https://apps-assets.fit2cloud.com
is_demo: false
port: 9999
username: admin

View file

@ -11,6 +11,7 @@ export namespace App {
author: string;
source: string;
type: string;
status: string;
}
export interface AppDTO extends App {

View file

@ -84,6 +84,9 @@
{{ language == 'zh' ? tag.name : tag.key }}
</span>
</el-tag>
<el-tag v-if="app.status === 'TakeDown'" style="margin-right: 5px">
<span style="color: red">已废弃</span>
</el-tag>
</div>
</div>
</el-col>

View file

@ -82,12 +82,8 @@
</el-row>
</div>
</div>
<div v-loading="loadingDetail" style="margin-left: 10px">
<MdEditor
v-model="appDetail.readme"
previewOnly
:theme="globalStore.$state.themeConfig.theme || 'light'"
/>
<div style="margin-left: 10px">
<MdEditor v-model="app.readMe" previewOnly :theme="globalStore.$state.themeConfig.theme || 'light'" />
</div>
</template>
</LayoutContent>