diff --git a/backend/app/dto/response/app.go b/backend/app/dto/response/app.go index 69136c90e..630244473 100644 --- a/backend/app/dto/response/app.go +++ b/backend/app/dto/response/app.go @@ -10,7 +10,7 @@ import ( ) type AppRes struct { - Items []*AppDTO `json:"items"` + Items []*AppDto `json:"items"` Total int64 `json:"total"` } @@ -28,6 +28,21 @@ type AppDTO struct { Tags []model.Tag `json:"tags"` } +type AppDto struct { + Name string `json:"name"` + Key string `json:"key"` + ID uint `json:"ID"` + ShortDescZh string `json:"shortDescZh"` + ShortDescEn string `json:"shortDescEn"` + Icon string `json:"icon"` + Type string `json:"type"` + Status string `json:"status"` + Resource string `json:"resource"` + Installed bool `json:"installed"` + Versions []string `json:"versions"` + Tags []model.Tag `json:"tags"` +} + type TagDTO struct { model.Tag } @@ -72,6 +87,27 @@ type AppInstalledDTO struct { Path string `json:"path"` } +type AppInstallDTO struct { + ID uint `json:"id"` + Name string `json:"name"` + AppID uint `json:"appID"` + AppDetailID uint `json:"appDetailID"` + Version string `json:"version"` + Status string `json:"status"` + Message string `json:"message"` + HttpPort int `json:"httpPort"` + HttpsPort int `json:"httpsPort"` + Path string `json:"path"` + CanUpdate bool `json:"canUpdate"` + Icon string `json:"icon"` + AppName string `json:"appName"` + Ready int `json:"ready"` + Total int `json:"total"` + AppKey string `json:"appKey"` + AppType string `json:"appType"` + AppStatus string `json:"appStatus"` +} + type DatabaseConn struct { Status string `json:"status"` Username string `json:"username"` diff --git a/backend/app/service/app.go b/backend/app/service/app.go index 91d3211f8..385571714 100644 --- a/backend/app/service/app.go +++ b/backend/app/service/app.go @@ -89,14 +89,21 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) { if err != nil { return nil, err } - var appDTOs []*response.AppDTO + var appDTOs []*response.AppDto for _, ap := range apps { ap.ReadMe = "" ap.Website = "" ap.Document = "" ap.Github = "" - appDTO := &response.AppDTO{ - App: ap, + appDTO := &response.AppDto{ + ID: ap.ID, + Name: ap.Name, + Key: ap.Key, + Type: ap.Type, + Icon: ap.Icon, + ShortDescZh: ap.ShortDescZh, + ShortDescEn: ap.ShortDescEn, + Resource: ap.Resource, } appDTOs = append(appDTOs, appDTO) appTags, err := appTagRepo.GetByAppId(ap.ID) @@ -436,6 +443,14 @@ func (a AppService) Install(ctx context.Context, req request.AppInstallCreate) ( return } appInstall.Env = string(paramByte) + + containerNames, err := getContainerNames(*appInstall) + if err != nil { + return + } + if len(containerNames) > 0 { + appInstall.ContainerName = strings.Join(containerNames, ",") + } if err = appInstallRepo.Create(ctx, appInstall); err != nil { return } diff --git a/backend/app/service/app_install.go b/backend/app/service/app_install.go index 271d7b688..7888fb6f2 100644 --- a/backend/app/service/app_install.go +++ b/backend/app/service/app_install.go @@ -39,11 +39,11 @@ type AppInstallService struct { } type IAppInstallService interface { - Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) + Page(req request.AppInstalledSearch) (int64, []response.AppInstallDTO, error) CheckExist(req request.AppInstalledInfo) (*response.AppInstalledCheck, error) LoadPort(req dto.OperationWithNameAndType) (int64, error) LoadConnInfo(req dto.OperationWithNameAndType) (response.DatabaseConn, error) - SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) + SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstallDTO, error) Operate(req request.AppInstalledOperate) error Update(req request.AppInstalledUpdate) error IgnoreUpgrade(req request.AppInstalledIgnoreUpgrade) error @@ -74,7 +74,7 @@ func (a *AppInstallService) GetInstallList() ([]dto.AppInstallInfo, error) { return datas, nil } -func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstalledDTO, error) { +func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstallDTO, error) { var ( opts []repo.DBOption total int64 @@ -191,7 +191,7 @@ func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType) (resp return data, nil } -func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) { +func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstallDTO, error) { var ( installs []model.AppInstall err error @@ -705,84 +705,27 @@ func syncAppInstallStatus(appInstall *model.AppInstall) error { if appInstall.Status == constant.Installing || appInstall.Status == constant.Rebuilding || appInstall.Status == constant.Upgrading { return nil } - var err error - containerNames := []string{appInstall.ContainerName} - if appInstall.ContainerName == "" { - containerNames, err = getContainerNames(*appInstall) - if err != nil { - return err - } - } - cli, err := docker.NewClient() if err != nil { return err } defer cli.Close() - var containers []types.Container + var ( + containers []types.Container + containersMap map[string]types.Container + containerNames = strings.Split(appInstall.ContainerName, ",") + ) containers, err = cli.ListContainersByName(containerNames) if err != nil { return err } - var ( - runningCount int - exitedCount int - pausedCount int - exitedContainerNames []string - total = len(containerNames) - count = len(containers) - ) - - foundNames := make(map[string]bool) + containersMap = make(map[string]types.Container) for _, con := range containers { - foundNames[con.Names[0]] = true - switch con.State { - case "exited": - exitedContainerNames = append(exitedContainerNames, strings.TrimPrefix(con.Names[0], "/")) - exitedCount++ - case "running": - runningCount++ - case "paused": - pausedCount++ - } - } - - var notFoundNames []string - for _, name := range containerNames { - if !foundNames["/"+name] { - notFoundNames = append(notFoundNames, name) - } - } - - switch { - case count == 0: - if appInstall.Status != constant.Error { - appInstall.Status = constant.SyncErr - appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error() - } - case exitedCount == total: - appInstall.Status = constant.Stopped - case runningCount == total: - appInstall.Status = constant.Running - case pausedCount == total: - appInstall.Status = constant.Paused - default: - var msg string - if exitedCount > 0 { - msg = buserr.WithName("ErrContainerMsg", strings.Join(exitedContainerNames, ",")).Error() - } - if len(notFoundNames) > 0 { - msg += buserr.WithName("ErrContainerNotFound", strings.Join(notFoundNames, ",")).Error() - } - if msg == "" { - msg = buserr.New("ErrAppWarn").Error() - } - appInstall.Message = msg - appInstall.Status = constant.UnHealthy + containersMap[con.Names[0]] = con } + synAppInstall(containersMap, appInstall) _ = appInstallRepo.Save(context.Background(), appInstall) - return nil } diff --git a/backend/app/service/app_utils.go b/backend/app/service/app_utils.go index 2d3399d03..bceb7f655 100644 --- a/backend/app/service/app_utils.go +++ b/backend/app/service/app_utils.go @@ -937,6 +937,12 @@ func rebuildApp(appInstall model.AppInstall) error { _ = handleErr(appInstall, err, out) return } + containerNames, err := getContainerNames(appInstall) + if err != nil { + _ = handleErr(appInstall, err, out) + return + } + appInstall.ContainerName = strings.Join(containerNames, ",") appInstall.Status = constant.Running _ = appInstallRepo.Save(context.Background(), &appInstall) @@ -1119,10 +1125,65 @@ func handleErr(install model.AppInstall, err error, out string) error { return reErr } -func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool) ([]response.AppInstalledDTO, error) { +func doNotNeedSync(installed model.AppInstall) bool { + return installed.Status == constant.Installing || installed.Status == constant.Rebuilding || installed.Status == constant.Upgrading || installed.Status == constant.Syncing +} + +func synAppInstall(containers map[string]types.Container, appInstall *model.AppInstall) { + containerNames := strings.Split(appInstall.ContainerName, ",") + if len(containers) == 0 { + appInstall.Status = constant.Error + appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error() + return + } + notFoundNames := make([]string, 0) + exitNames := make([]string, 0) + exitedCount := 0 + pausedCount := 0 + runningCount := 0 + total := len(containerNames) + for _, name := range containerNames { + if con, ok := containers["/"+name]; ok { + switch con.State { + case "exited": + exitedCount++ + exitNames = append(exitNames, name) + case "running": + runningCount++ + case "paused": + pausedCount++ + } + } else { + notFoundNames = append(notFoundNames, name) + } + } + switch { + case exitedCount == total: + appInstall.Status = constant.Stopped + case runningCount == total: + appInstall.Status = constant.Running + case pausedCount == total: + appInstall.Status = constant.Paused + default: + var msg string + if exitedCount > 0 { + msg = buserr.WithName("ErrContainerMsg", strings.Join(exitNames, ",")).Error() + } + if len(notFoundNames) > 0 { + msg += buserr.WithName("ErrContainerNotFound", strings.Join(notFoundNames, ",")).Error() + } + if msg == "" { + msg = buserr.New("ErrAppWarn").Error() + } + appInstall.Message = msg + appInstall.Status = constant.UnHealthy + } +} + +func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool) ([]response.AppInstallDTO, error) { var ( - res []response.AppInstalledDTO - containers []types.Container + res []response.AppInstallDTO + containersMap map[string]types.Container ) if sync { cli, err := docker.NewClient() @@ -1130,41 +1191,38 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool) return nil, err } defer cli.Close() - containers, err = cli.ListAllContainers() + containers, err := cli.ListAllContainers() if err != nil { return nil, err } + containersMap = make(map[string]types.Container, len(containers)) + for _, contain := range containers { + containersMap[contain.Names[0]] = contain + } } for _, installed := range appInstallList { if updated && (installed.App.Type == "php" || installed.Status == constant.Installing || (installed.App.Key == constant.AppMysql && installed.Version == "5.6.51")) { continue } - if sync { - exist := false - for _, contain := range containers { - if contain.Names[0] == "/"+installed.ContainerName { - exist = true - switch contain.State { - case "exited": - installed.Status = constant.Stopped - case "running": - installed.Status = constant.Running - case "paused": - installed.Status = constant.Paused - } - break - } - } - if !exist { - installed.Status = constant.Error - installed.Message = buserr.WithName("ErrContainerNotFound", installed.ContainerName).Error() - } + if sync && !doNotNeedSync(installed) { + synAppInstall(containersMap, &installed) } - installDTO := response.AppInstalledDTO{ - AppInstall: installed, - Path: installed.GetPath(), + installDTO := response.AppInstallDTO{ + ID: installed.ID, + Name: installed.Name, + AppID: installed.AppId, + AppDetailID: installed.AppDetailId, + Version: installed.Version, + Status: installed.Status, + Message: installed.Message, + HttpPort: installed.HttpPort, + HttpsPort: installed.HttpsPort, + Icon: installed.App.Icon, + AppName: installed.App.Name, + AppKey: installed.App.Key, + AppType: installed.App.Type, } app, err := appRepo.GetFirst(commonRepo.WithByID(installed.AppId)) if err != nil { diff --git a/frontend/src/api/interface/app.ts b/frontend/src/api/interface/app.ts index 332f474ef..fa44aea49 100644 --- a/frontend/src/api/interface/app.ts +++ b/frontend/src/api/interface/app.ts @@ -121,6 +121,27 @@ export namespace App { app: App; } + export interface AppInstallDto { + id: number; + name: string; + appID: number; + appDetailID: number; + version: string; + status: string; + message: string; + httpPort: number; + httpsPort: number; + path: string; + canUpdate: boolean; + icon: string; + appName: string; + ready: number; + total: number; + appKey: string; + appType: string; + appStatus: string; + } + export interface AppInstalledInfo { id: number; key: string; diff --git a/frontend/src/api/modules/app.ts b/frontend/src/api/modules/app.ts index be2878dd5..cb49c832c 100644 --- a/frontend/src/api/modules/app.ts +++ b/frontend/src/api/modules/app.ts @@ -40,7 +40,7 @@ export const ChangePort = (params: App.ChangePort) => { }; export const SearchAppInstalled = (search: App.AppInstallSearch) => { - return http.post>('apps/installed/search', search); + return http.post>('apps/installed/search', search); }; export const ListAppInstalled = () => { diff --git a/frontend/src/views/app-store/installed/delete/index.vue b/frontend/src/views/app-store/installed/delete/index.vue index 5aaf6b4af..44296424f 100644 --- a/frontend/src/views/app-store/installed/delete/index.vue +++ b/frontend/src/views/app-store/installed/delete/index.vue @@ -73,7 +73,7 @@ const handleClose = () => { em('close', open); }; -const acceptParams = async (app: App.AppInstalled) => { +const acceptParams = async (app: App.AppInstallDto) => { deleteReq.value = { operate: 'delete', installId: 0, @@ -83,7 +83,7 @@ const acceptParams = async (app: App.AppInstalled) => { }; deleteInfo.value = ''; deleteReq.value.installId = app.id; - appType.value = app.app.type; + appType.value = app.appType; deleteHelper.value = i18n.global.t('website.deleteConfirmHelper', [app.name]); appInstallName.value = app.name; open.value = true; diff --git a/frontend/src/views/app-store/installed/detail/index.vue b/frontend/src/views/app-store/installed/detail/index.vue index 60839d3de..14250b93a 100644 --- a/frontend/src/views/app-store/installed/detail/index.vue +++ b/frontend/src/views/app-store/installed/detail/index.vue @@ -166,7 +166,6 @@ const acceptParams = async (props: ParamProps) => { submitModel.value.installId = props.id; params.value = []; paramData.value.id = props.id; - paramData.value.app = props.app; paramModel.value.params = {}; edit.value = false; await get(); diff --git a/frontend/src/views/app-store/installed/index.vue b/frontend/src/views/app-store/installed/index.vue index 383167ff8..13f3d5cfc 100644 --- a/frontend/src/views/app-store/installed/index.vue +++ b/frontend/src/views/app-store/installed/index.vue @@ -100,11 +100,11 @@ -
+
@@ -168,7 +168,7 @@ plain round size="small" - @click="openUploads(installed.app.key, installed.name)" + @click="openUploads(installed.appKey, installed.name)" v-if="mode === 'installed'" > {{ $t('database.loadBackup') }} @@ -178,9 +178,7 @@ plain round size="small" - @click=" - openBackups(installed.app.key, installed.name, installed.status) - " + @click="openBackups(installed.appKey, installed.name, installed.status)" v-if="mode === 'installed'" > {{ $t('commons.button.backup') }} @@ -203,7 +201,7 @@ :disabled=" (installed.status !== 'Running' && installed.status !== 'UpgradeErr') || - installed.app.status === 'TakeDown' + installed.appStatus === 'TakeDown' " @click="openOperate(installed, 'upgrade')" v-if="mode === 'upgrade'" @@ -414,18 +412,12 @@ const getTagValue = (key: string) => { } }; -const search = () => { - loading.value = true; +const search = async () => { searchReq.page = paginationConfig.currentPage; searchReq.pageSize = paginationConfig.pageSize; - SearchAppInstalled(searchReq) - .then((res) => { - data.value = res.data.items; - paginationConfig.total = res.data.total; - }) - .finally(() => { - loading.value = false; - }); + const res = await SearchAppInstalled(searchReq); + data.value = res.data.items; + paginationConfig.total = res.data.total; GetAppTags().then((res) => { tags.value = res.data; }); @@ -435,8 +427,8 @@ const goDashboard = async (port: any, protocol: string) => { dialogPortJumpRef.value.acceptParams({ port: port, protocol: protocol }); }; -const openDetail = (app: App.App) => { - appDetail.value.acceptParams(app.key, 'detail'); +const openDetail = (appKey: string) => { + appDetail.value.acceptParams(appKey, 'detail'); }; const openOperate = (row: any, op: string) => { @@ -448,7 +440,7 @@ const openOperate = (row: any, op: string) => { AppInstalledDeleteCheck(row.id).then(async (res) => { const items = res.data; if (res.data && res.data.length > 0) { - checkRef.value.acceptParams({ items: items, key: row.app.key, installID: row.id }); + checkRef.value.acceptParams({ items: items, key: row.appKey, installID: row.id }); } else { deleteRef.value.acceptParams(row); } @@ -475,10 +467,7 @@ const operate = async () => { }, 3000); setTimeout(() => { search(); - }, 5000); - setTimeout(() => { - search(); - }, 10000); + }, 15000); }) .catch(() => { search(); @@ -596,7 +585,7 @@ const openUploads = (key: string, name: string) => { }; const openParam = (row: any) => { - appParamRef.value.acceptParams({ app: row.app, id: row.id }); + appParamRef.value.acceptParams({ id: row.id }); }; const isAppErr = (row: any) => { @@ -618,7 +607,9 @@ onMounted(() => { mode.value = 'upgrade'; searchReq.update = true; } + loading.value = true; search(); + loading.value = false; setTimeout(() => { searchReq.sync = true; search();