fix: 解决应用安装升级状态错误的问题 (#5260)

This commit is contained in:
zhengkunwang 2024-06-03 15:41:45 +08:00 committed by GitHub
parent e45f43da9a
commit bdf8ba45ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 192 additions and 129 deletions

View file

@ -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"`

View file

@ -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
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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;

View file

@ -40,7 +40,7 @@ export const ChangePort = (params: App.ChangePort) => {
};
export const SearchAppInstalled = (search: App.AppInstallSearch) => {
return http.post<ResPage<App.AppInstalled>>('apps/installed/search', search);
return http.post<ResPage<App.AppInstallDto>>('apps/installed/search', search);
};
export const ListAppInstalled = () => {

View file

@ -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;

View file

@ -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();

View file

@ -100,11 +100,11 @@
<el-card class="e-card">
<el-row :gutter="20">
<el-col :xs="3" :sm="3" :md="3" :lg="4" :xl="4">
<div class="icon" @click.stop="openDetail(installed.app)">
<div class="icon" @click.stop="openDetail(installed.appKey)">
<el-avatar
shape="square"
:size="66"
:src="'data:image/png;base64,' + installed.app.icon"
:src="'data:image/png;base64,' + installed.icon"
/>
</div>
</el-col>
@ -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();