mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-27 12:39:01 +08:00
feat: 应用商店对接远程应用服务
This commit is contained in:
parent
d443103e2c
commit
aeabed70db
@ -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"`
|
||||
}
|
||||
|
||||
|
@ -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"`
|
||||
//Version string `json:"version"`
|
||||
//DownloadPath string `json:"downloadPath"`
|
||||
CanUpdate bool `json:"canUpdate"`
|
||||
DownloadPath string `json:"downloadPath"`
|
||||
AppStoreLastModified int `json:"appStoreLastModified"`
|
||||
List dto.AppList `json:"list"`
|
||||
}
|
||||
|
||||
type AppDTO struct {
|
||||
|
@ -34,6 +34,7 @@ type SettingInfo struct {
|
||||
DingVars string `json:"dingVars"`
|
||||
|
||||
AppStoreVersion string `json:"appStoreVersion"`
|
||||
AppStoreLastModified string `json:"appStoreLastModified"`
|
||||
}
|
||||
|
||||
type SettingUpdate struct {
|
||||
|
@ -17,6 +17,9 @@ type App struct {
|
||||
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"`
|
||||
|
@ -5,8 +5,11 @@ type AppDetail struct {
|
||||
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;"`
|
||||
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"`
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,62 +547,68 @@ 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
|
||||
updateAppArray []model.App
|
||||
deleteAppArray []model.App
|
||||
deleteIds []uint
|
||||
tagMap = make(map[string]uint, len(tags))
|
||||
)
|
||||
|
||||
@ -625,7 +616,17 @@ func (a AppService) SyncAppListFromRemote() error {
|
||||
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,37 +659,42 @@ 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 {
|
||||
if d.Status == constant.AppTakeDown {
|
||||
deleteDetails = append(deleteDetails, d)
|
||||
} else {
|
||||
updateDetails = append(updateDetails, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(addDetails) > 0 {
|
||||
if err := appDetailRepo.BatchCreate(ctx, addDetails); err != nil {
|
||||
tx.Rollback()
|
||||
@ -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
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -19,5 +19,6 @@ type System struct {
|
||||
Password string `mapstructure:"password"`
|
||||
Entrance string `mapstructure:"entrance"`
|
||||
IsDemo bool `mapstructure:"is_demo"`
|
||||
AppRepo string `mapstructure:"app_repo"`
|
||||
ChangeUserInfo bool `mapstructure:"change_user_info"`
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ const (
|
||||
Stopped = "Stopped"
|
||||
Installing = "Installing"
|
||||
Syncing = "Syncing"
|
||||
DownloadErr = "DownloadErr"
|
||||
DirNotFound = "DirNotFound"
|
||||
|
||||
ContainerPrefix = "1Panel-"
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -29,6 +29,8 @@ ErrDbUserNotValid: "存量数据库,用户名密码不匹配!"
|
||||
ErrDockerComposeNotValid: "docker-compose 文件格式错误"
|
||||
ErrUpdateBuWebsite: '应用更新成功,但是网站配置文件修改失败,请检查配置!'
|
||||
Err1PanelNetworkFailed: '默认容器网络创建失败!{{ .detail }}'
|
||||
ErrFileParse: '应用 docker-compose 文件解析失败!'
|
||||
ErrInstallDirNotFound: '安装目录不存在'
|
||||
|
||||
#file
|
||||
ErrFileCanNotRead: "此文件不支持预览"
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
},
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -11,6 +11,7 @@ export namespace App {
|
||||
author: string;
|
||||
source: string;
|
||||
type: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface AppDTO extends App {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user