feat: 应用商店增加可更新页

This commit is contained in:
zhengkunwang223 2023-01-16 15:30:24 +08:00 committed by zhengkunwang223
parent 45cda28d01
commit 8cf6f2fe64
26 changed files with 370 additions and 214 deletions

0
backend/app.yaml Normal file
View file

View file

@ -115,3 +115,12 @@ func (b *BaseApi) InstallApp(c *gin.Context) {
tx.Commit()
helper.SuccessWithData(c, install)
}
func (b *BaseApi) GetAppTags(c *gin.Context) {
tags, err := appService.GetAppTags()
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, tags)
}

View file

@ -38,7 +38,7 @@ func (b *BaseApi) SearchAppInstalled(c *gin.Context) {
Total: total,
})
} else {
list, err := appInstallService.Search(req)
list, err := appInstallService.SearchForWebsite(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return

View file

@ -21,8 +21,11 @@ type AppInstallCreate struct {
type AppInstalledSearch struct {
dto.PageInfo
Type string `json:"type"`
Name string `json:"name"`
Type string `json:"type"`
Name string `json:"name"`
Tags []string `json:"tags"`
Update bool `json:"update"`
Unused bool `json:"unused"`
}
type AppBackupSearch struct {

View file

@ -19,6 +19,10 @@ type AppDTO struct {
Tags []model.Tag `json:"tags"`
}
type TagDTO struct {
model.Tag
}
type AppInstalledCheck struct {
IsExist bool `json:"isExist"`
Name string `json:"name"`

View file

@ -90,25 +90,38 @@ func (a AppService) PageApp(req request.AppSearch) (interface{}, error) {
return res, nil
}
func (a AppService) GetApp(id uint) (response.AppDTO, error) {
func (a AppService) GetAppTags() ([]response.TagDTO, error) {
tags, err := tagRepo.All()
if err != nil {
return nil, err
}
var res []response.TagDTO
for _, tag := range tags {
res = append(res, response.TagDTO{
Tag: tag,
})
}
return res, nil
}
func (a AppService) GetApp(id uint) (*response.AppDTO, error) {
var appDTO response.AppDTO
app, err := appRepo.GetFirst(commonRepo.WithByID(id))
if err != nil {
return appDTO, err
return nil, err
}
appDTO.App = app
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(app.ID))
if err != nil {
return appDTO, err
return nil, err
}
var versionsRaw []string
for _, detail := range details {
versionsRaw = append(versionsRaw, detail.Version)
}
appDTO.Versions = common.GetSortedVersions(versionsRaw)
return appDTO, nil
return &appDTO, nil
}
func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetailDTO, error) {

View file

@ -36,13 +36,33 @@ func (a AppInstallService) Page(req request.AppInstalledSearch) (int64, []respon
if req.Name != "" {
opts = append(opts, commonRepo.WithLikeName(req.Name))
}
if len(req.Tags) != 0 {
tags, err := tagRepo.GetByKeys(req.Tags)
if err != nil {
return 0, nil, err
}
var tagIds []uint
for _, t := range tags {
tagIds = append(tagIds, t.ID)
}
appTags, err := appTagRepo.GetByTagIds(tagIds)
if err != nil {
return 0, nil, err
}
var appIds []uint
for _, t := range appTags {
appIds = append(appIds, t.AppId)
}
opts = append(opts, appInstallRepo.WithAppIdsIn(appIds))
}
total, installs, err := appInstallRepo.Page(req.Page, req.PageSize, opts...)
if err != nil {
return 0, nil, err
}
installDTOs, err := handleInstalled(installs)
installDTOs, err := handleInstalled(installs, req.Update)
if err != nil {
return 0, nil, err
}
@ -98,9 +118,12 @@ func (a AppInstallService) LoadPassword(key string) (string, error) {
return app.Password, nil
}
func (a AppInstallService) Search(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) {
var installs []model.AppInstall
var err error
func (a AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstalledDTO, error) {
var (
installs []model.AppInstall
err error
opts []repo.DBOption
)
if req.Type != "" {
apps, err := appRepo.GetBy(appRepo.WithType(req.Type))
if err != nil {
@ -110,7 +133,11 @@ func (a AppInstallService) Search(req request.AppInstalledSearch) ([]response.Ap
for _, app := range apps {
ids = append(ids, app.ID)
}
installs, err = appInstallRepo.GetBy(appInstallRepo.WithAppIdsIn(ids), appInstallRepo.WithIdNotInWebsite())
if req.Unused {
opts = append(opts, appInstallRepo.WithIdNotInWebsite())
}
opts = append(opts, appInstallRepo.WithAppIdsIn(ids))
installs, err = appInstallRepo.GetBy(opts...)
if err != nil {
return nil, err
}
@ -121,7 +148,7 @@ func (a AppInstallService) Search(req request.AppInstalledSearch) ([]response.Ap
}
}
return handleInstalled(installs)
return handleInstalled(installs, false)
}
func (a AppInstallService) Operate(req request.AppInstalledOperate) error {

View file

@ -536,15 +536,12 @@ func getAppFromOss() error {
return nil
}
func handleInstalled(installed []model.AppInstall) ([]response.AppInstalledDTO, error) {
func handleInstalled(appInstallList []model.AppInstall, updated bool) ([]response.AppInstalledDTO, error) {
var res []response.AppInstalledDTO
for _, installed := range installed {
for _, installed := range appInstallList {
installDTO := response.AppInstalledDTO{
AppInstall: installed,
}
app, err := appRepo.GetFirst(commonRepo.WithByID(installed.AppId))
if err != nil {
return nil, err
@ -559,15 +556,19 @@ func handleInstalled(installed []model.AppInstall) ([]response.AppInstalledDTO,
}
versions = common.GetSortedVersions(versions)
lastVersion := versions[0]
if common.IsCrossVersion(installed.Version, lastVersion) {
installDTO.CanUpdate = app.CrossVersionUpdate
} else {
installDTO.CanUpdate = common.CompareVersion(lastVersion, installed.Version)
}
res = append(res, installDTO)
if updated {
if installDTO.CanUpdate {
res = append(res, installDTO)
}
} else {
res = append(res, installDTO)
}
}
return res, nil
}

View file

@ -20,6 +20,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
appRouter.GET("/:id", baseApi.GetApp)
appRouter.GET("/detail/:appId/:version", baseApi.GetAppDetail)
appRouter.POST("/install", baseApi.InstallApp)
appRouter.GET("/tags", baseApi.GetAppTags)
appRouter.GET("/installed/:appInstallId/versions", baseApi.GetUpdateVersions)
appRouter.GET("/installed/check/:key", baseApi.CheckAppInstalled)
appRouter.GET("/installed/loadport/:key", baseApi.LoadPort)

View file

@ -12,6 +12,7 @@ declare module 'vue' {
BackButton: typeof import('./src/components/back-button/index.vue')['default']
BreadCrumbs: typeof import('./src/components/bread-crumbs/index.vue')['default']
BreadCrumbsItem: typeof import('./src/components/bread-crumbs/bread-crumbs-item.vue')['default']
CardWithHeader: typeof import('./src/components/card-with-header/index.vue')['default']
Codemirror: typeof import('./src/components/codemirror-dialog/codemirror.vue')['default']
ComplexTable: typeof import('./src/components/complex-table/index.vue')['default']
ConfirmDialog: typeof import('./src/components/confirm-dialog/index.vue')['default']
@ -40,6 +41,7 @@ declare module 'vue' {
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElFooter: typeof import('element-plus/es')['ElFooter']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
@ -79,6 +81,7 @@ declare module 'vue' {
Loading: typeof import('element-plus/es')['ElLoadingDirective']
Logo: typeof import('./src/components/app-layout/menu/components/Logo.vue')['default']
Menu: typeof import('./src/components/app-layout/menu/index.vue')['default']
Popover: typeof import('element-plus/es')['ElPopoverDirective']
RouterButton: typeof import('./src/components/router-button/index.vue')['default']
Status: typeof import('./src/components/status/index.vue')['default']
SubItem: typeof import('./src/components/app-layout/menu/components/sub-item.vue')['default']

View file

@ -66,6 +66,9 @@ export namespace App {
export interface AppInstallSearch extends ReqPage {
name?: string;
tags?: string[];
updated?: boolean;
unused?: boolean;
}
export interface ChangePort {
key: string;

View file

@ -14,6 +14,10 @@ export const GetApp = (id: number) => {
return http.get<App.AppDTO>('apps/' + id);
};
export const GetAppTags = () => {
return http.get<App.Tag[]>('apps/tags');
};
export const GetAppDetail = (id: number, version: string) => {
return http.get<App.AppDetail>(`apps/detail/${id}/${version}`);
};

View file

@ -1,14 +1,12 @@
<template>
<div>
<div class="app-content" v-if="data.isExist">
<el-card class="app-card">
<div class="a-content" v-if="data.isExist">
<el-card class="a-card">
<el-row :gutter="20">
<el-col :xs="10" :sm="10" :md="10" :lg="10" :xl="6">
<el-tag effect="dark" type="success">{{ data.app }}</el-tag>
<Status class="status-content" :key="refresh" :status="data.status"></Status>
<el-tag class="status-content" type="primary">
{{ $t('app.version') }}:{{ data.version }}
</el-tag>
<el-tag class="status-content">{{ $t('app.version') }}:{{ data.version }}</el-tag>
</el-col>
<el-col :xs="8" :sm="8" :md="8" :lg="6" :xl="4">
<el-button type="primary" v-if="data.status != 'Running'" link @click="onOperate('up')">
@ -122,13 +120,13 @@ onMounted(() => {
});
</script>
<style lang="scss">
.app-card {
<style lang="scss" scoped>
.a-card {
font-size: 14px;
height: 60px;
}
.app-content {
.a-content {
height: 50px;
}

View file

@ -1,6 +1,9 @@
<template>
<el-tag :type="getType(status)" round effect="light">
{{ $t('commons.status.' + status) }}
<el-icon v-if="status === 'installing'" class="is-loading">
<Loading />
</el-icon>
</el-tag>
</template>

View file

@ -83,6 +83,8 @@ export default {
operateConfirm: '如果确认操作请手动输入',
inputOrSelect: '请选择或输入',
copyfailed: '复制失败',
backupSuccess: '备份成功',
restoreSuccess: '备份成功',
},
login: {
firstLogin: '首次登录请创建初始管理员用户',
@ -856,6 +858,12 @@ export default {
param: '参数配置',
syncAppList: '更新应用列表',
syncAppListSuccess: '更新成功',
port: '端口',
areadyRun: '已安装',
day: '天',
hour: '小时',
minute: '分钟',
less1Minute: '小于1分钟',
},
website: {
website: '网站',

View file

@ -59,7 +59,7 @@
import { computed, useSlots } from 'vue';
import BackButton from '@/components/back-button/index.vue';
import FormButton from './form-button.vue';
defineOptions({ name: 'LayoutContent' }); //
defineOptions({ name: 'LayoutContent' });
const slots = useSlots();
const prop = defineProps({
title: String,

View file

@ -37,18 +37,18 @@ const appStoreRouter = {
activeMenu: '/apps',
},
},
{
path: 'update',
name: 'AppUpdate',
component: () => import('@/views/app-store/installed/index.vue'),
props: true,
hidden: true,
meta: {
activeMenu: '/apps',
},
},
],
},
// {
// path: '/apps/detail/:id',
// name: 'AppDetail',
// props: true,
// hidden: true,
// component: () => import('@/views/app-store/detail/index.vue'),
// meta: {
// activeMenu: '/apps',
// },
// },
],
};

View file

@ -170,3 +170,31 @@ export function getProvider(provider: string): string {
return i18n.global.t('ssl.manualCreate');
}
}
export function getAge(d1: string): string {
const dateBegin = new Date(d1);
const dateEnd = new Date();
const dateDiff = dateEnd.getTime() - dateBegin.getTime();
const dayDiff = Math.floor(dateDiff / (24 * 3600 * 1000));
const leave1 = dateDiff % (24 * 3600 * 1000);
const hours = Math.floor(leave1 / (3600 * 1000));
const leave2 = leave1 % (3600 * 1000);
const minutes = Math.floor(leave2 / (60 * 1000));
let res = '';
if (dayDiff > 0) {
res += String(dayDiff) + i18n.global.t('app.day');
if (hours <= 0) {
return res;
}
}
if (hours > 0) {
res += String(hours) + i18n.global.t('app.hour');
return res;
}
if (minutes > 0) {
res += String(minutes) + i18n.global.t('app.minute');
return res;
}
return i18n.global.t('app.less1Minute');
}

View file

@ -18,15 +18,17 @@
</div>
</el-col>
<el-col :span="4">
<el-input
v-model="req.name"
clearable
@clear="searchByName('')"
suffix-icon="Search"
@keyup.enter="searchByName(req.name)"
@blur="searchByName(req.name)"
:placeholder="$t('commons.button.search')"
></el-input>
<div class="search-button">
<el-input
v-model="req.name"
clearable
@clear="searchByName('')"
suffix-icon="Search"
@keyup.enter="searchByName(req.name)"
@blur="searchByName(req.name)"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
</el-col>
</el-row>
</template>
@ -53,9 +55,10 @@
type="primary"
plain
round
size="small"
@click="getAppDetail(app.id)"
>
安装
{{ $t('app.install') }}
</el-button>
</div>
<div class="app-desc">
@ -84,7 +87,7 @@
import LayoutContent from '@/layout/layout-content.vue';
import { App } from '@/api/interface/app';
import { onMounted, reactive, ref } from 'vue';
import { SearchApp, SyncApp } from '@/api/modules/app';
import { GetAppTags, SearchApp, SyncApp } from '@/api/modules/app';
import { ElMessage } from 'element-plus';
import i18n from '@/lang';
import Detail from '../detail/index.vue';
@ -109,9 +112,16 @@ const getColor = (index: number) => {
};
const search = async (req: App.AppReq) => {
await SearchApp(req).then((res) => {
apps.value = res.data.items;
tags.value = res.data.tags;
loading.value = true;
await SearchApp(req)
.then((res) => {
apps.value = res.data.items;
})
.finally(() => {
loading.value = false;
});
GetAppTags().then((res) => {
tags.value = res.data;
});
};
@ -219,4 +229,8 @@ onMounted(() => {
border: 0;
border-top: 1px solid #f2f2f2;
}
.el-avatar {
--el-avatar-bg-color: #ffffff;
}
</style>

View file

@ -2,15 +2,15 @@
height: 140px;
margin-top: 10px;
cursor: pointer;
padding: 5px;
padding: 10px;
.icon {
margin-top: 10px;
// margin-top: 10px;
margin-left: 20px;
}
.a-detail {
margin-top: 10px;
// margin-top: 10px;
height: 100%;
width: 100%;
@ -71,5 +71,5 @@
}
.el-avatar {
--el-avatar-bg-color: #eaeaea;
--el-avatar-bg-color: #ffffff;
}

View file

@ -21,5 +21,9 @@ const buttons = [
label: i18n.global.t('app.installed'),
path: '/apps/installed',
},
{
label: i18n.global.t('app.canUpdate'),
path: '/apps/update',
},
];
</script>

View file

@ -1,12 +1,8 @@
<template>
<el-dialog
v-model="open"
:title="$t('app.backup') + ' - ' + installData.appInstallName"
width="70%"
:destroy-on-close="true"
:before-close="handleClose"
:close-on-click-modal="false"
>
<el-drawer v-model="open" size="50%" :destroy-on-close="true" :before-close="handleClose" :show-close="false">
<template #header>
<Header :header="$t('app.backup')" :resource="installData.appInstallName" :back="handleClose"></Header>
</template>
<ComplexTable
:pagination-config="paginationConfig"
:data="data"
@ -69,13 +65,14 @@
</span>
</template>
</el-dialog>
</el-dialog>
</el-drawer>
</template>
<script lang="ts" setup name="installBackup">
import { DelAppBackups, GetAppBackups, InstalledOp } from '@/api/modules/app';
import { reactive, ref } from 'vue';
import ComplexTable from '@/components/complex-table/index.vue';
import Header from '@/components/drawer-header/index.vue';
import { dateFromat } from '@/utils/util';
import { ElMessage } from 'element-plus';
import i18n from '@/lang';
@ -138,7 +135,7 @@ const backup = async () => {
loading.value = true;
await InstalledOp(req)
.then(() => {
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
ElMessage.success(i18n.global.t('commons.msg.backupSuccess'));
search();
})
.finally(() => {
@ -157,7 +154,7 @@ const restore = async () => {
loading.value = true;
await InstalledOp(req)
.then(() => {
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
ElMessage.success(i18n.global.t('commons.msg.restoreSuccess'));
openRestorePage.value = false;
search();
})

View file

@ -1,16 +1,20 @@
<template>
<el-dialog v-model="open" :title="$t('app.param')" width="30%" :close-on-click-modal="false">
<el-drawer v-model="open" size="40%" :show-close="false">
<template #header>
<Header :header="$t('app.param')" :back="handleClose"></Header>
</template>
<el-descriptions border :column="1">
<el-descriptions-item v-for="(param, key) in params" :label="param.label" :key="key">
{{ param.value }}
</el-descriptions-item>
</el-descriptions>
</el-dialog>
</el-drawer>
</template>
<script lang="ts" setup>
import { App } from '@/api/interface/app';
import { GetAppInstallParams } from '@/api/modules/app';
import { ref } from 'vue';
import Header from '@/components/drawer-header/index.vue';
interface ParamProps {
id: Number;
@ -30,6 +34,10 @@ const acceptParams = (props: ParamProps) => {
open.value = true;
};
const handleClose = () => {
open.value = false;
};
const get = async () => {
try {
loading.value = true;

View file

@ -1,9 +1,9 @@
<template>
<LayoutContent v-loading="loading" :title="$t('app.installed')">
<LayoutContent v-loading="loading" :title="activeName">
<template #toolbar>
<el-row :gutter="5">
<el-col :span="20">
<!-- <div>
<div>
<el-button @click="changeTag('all')" type="primary" :plain="activeTag !== 'all'">
{{ $t('app.all') }}
</el-button>
@ -17,13 +17,13 @@
{{ item.name }}
</el-button>
</div>
</div> -->
</div>
</el-col>
<el-col :span="4">
<div style="float: right">
<el-input
class="table-button"
v-model="searchName"
v-model="searchReq.name"
clearable
@clear="search()"
suffix-icon="Search"
@ -36,7 +36,7 @@
</el-row>
</template>
<template #rightButton>
<el-button @click="sync" type="primary" link>{{ $t('app.sync') }}</el-button>
<el-button @click="sync" type="primary" link v-if="mode === 'installed'">{{ $t('app.sync') }}</el-button>
</template>
<template #main>
<div class="divider"></div>
@ -70,9 +70,6 @@
</template>
</el-popover>
<span v-else>
<el-icon v-if="installed.status === 'Installing'" class="is-loading">
<Loading />
</el-icon>
<Status :key="installed.status" :status="installed.status"></Status>
</span>
</span>
@ -80,24 +77,38 @@
<el-button
class="h-button"
type="primary"
link
plain
round
size="small"
@click="openBackups(installed.id, installed.name)"
v-if="mode === 'installed'"
>
备份
{{ $t('app.backup') }}
</el-button>
<el-button
class="h-button"
type="primary"
plain
round
size="small"
@click="openOperate(installed, 'update')"
v-if="mode === 'update'"
>
{{ $t('app.update') }}
</el-button>
</div>
<div class="d-description">
<el-tag>版本{{ installed.version }}</el-tag>
<el-tag>HTTP端口{{ installed.httpPort }}</el-tag>
<!-- <span class="description">
{{ app.shortDesc }}
</span> -->
<el-tag>{{ $t('app.version') }}{{ installed.version }}</el-tag>
<el-tag>HTTP{{ $t('app.port') }}{{ installed.httpPort }}</el-tag>
<el-tag v-if="installed.httpsPort > 0">
HTTPS{{ $t('app.port') }}{{ installed.httpsPort }}
</el-tag>
<div class="description">
<span>已运行12</span>
<span>{{ $t('app.areadyRun') }} {{ getAge(installed.createdAt) }}</span>
</div>
</div>
<div class="divider"></div>
<div class="d-button">
<div class="d-button" v-if="mode === 'installed'">
<el-button
v-for="(button, key) in buttons"
:key="key"
@ -110,9 +121,6 @@
>
{{ button.label }}
</el-button>
<!-- <el-tag v-for="(tag, ind) in app.tags" :key="ind" :colr="getColor(ind)">
{{ tag.name }}
</el-tag> -->
</div>
</div>
</el-col>
@ -120,99 +128,13 @@
</div>
</el-col>
</el-row>
<!-- <ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" v-loading="loading">
<el-table-column :label="$t('app.name')" prop="name" min-width="150px" show-overflow-tooltip>
<template #default="{ row }">
<el-link :underline="false" @click="openParam(row.id)" type="primary">
{{ row.name }}
</el-link>
<el-tag round effect="dark" v-if="row.canUpdate">{{ $t('app.canUpdate') }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('app.app')" prop="app.name" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('app.version')" prop="version" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('website.port')" prop="httpPort"></el-table-column>
<el-table-column :label="$t('app.backup')">
<template #default="{ row }">
<el-link :underline="false" @click="openBackups(row.id, row.name)" type="primary">
{{ $t('app.backup') }} ({{ row.backups.length }})
</el-link>
</template>
</el-table-column>
<el-table-column :label="$t('app.status')">
<template #default="{ row }">
<el-popover
v-if="row.status === 'Error'"
placement="bottom"
:width="400"
trigger="hover"
:content="row.message"
>
<template #reference><Status :key="row.status" :status="row.status"></Status></template>
</el-popover>
<div v-else>
<el-icon v-if="row.status === 'Installing'" class="is-loading">
<Loading />
</el-icon>
<Status :key="row.status" :status="row.status"></Status>
</div>
</template>
</el-table-column>
<el-table-column
prop="createdAt"
:label="$t('commons.table.date')"
:formatter="dateFromat"
show-overflow-tooltip
/>
<fu-table-operations
width="300px"
:ellipsis="10"
:buttons="buttons"
:label="$t('commons.table.operate')"
fixed="right"
fix
/>
</ComplexTable> -->
</template>
</LayoutContent>
<el-dialog
v-model="open"
:title="$t('commons.msg.operate')"
:destroy-on-close="true"
:close-on-click-modal="false"
:before-close="handleClose"
width="30%"
>
<div style="text-align: center">
<p>{{ $t('app.versioneSelect') }}</p>
<el-select v-model="operateReq.detailId">
<el-option
v-for="(version, index) in versions"
:key="index"
:value="version.detailId"
:label="version.version"
></el-option>
</el-select>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
<el-button
type="primary"
@click="operate"
:disabled="operateReq.operate == 'update' && versions == null"
>
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-dialog>
<Backups ref="backupRef" @close="search"></Backups>
<AppResources ref="checkRef"></AppResources>
<AppDelete ref="deleteRef" @close="search"></AppDelete>
<AppParams ref="appParamRef"></AppParams>
<Backups ref="backupRef" @close="search" />
<AppResources ref="checkRef" />
<AppDelete ref="deleteRef" @close="search" />
<AppParams ref="appParamRef" />
<AppUpdate ref="updateRef" @close="search" />
</template>
<script lang="ts" setup>
@ -220,21 +142,22 @@ import {
SearchAppInstalled,
InstalledOp,
SyncInstalledApp,
GetAppUpdateVersions,
AppInstalledDeleteCheck,
GetAppTags,
} from '@/api/modules/app';
import LayoutContent from '@/layout/layout-content.vue';
import { onMounted, onUnmounted, reactive, ref } from 'vue';
// import ComplexTable from '@/components/complex-table/index.vue';
// import { dateFromat } from '@/utils/util';
import i18n from '@/lang';
import { ElMessage, ElMessageBox } from 'element-plus';
import Backups from './backups.vue';
import Backups from './backup/index.vue';
import AppResources from './check/index.vue';
import AppDelete from './delete/index.vue';
import AppParams from './detail/index.vue';
import AppUpdate from './update/index.vue';
import { App } from '@/api/interface/app';
import Status from '@/components/status/index.vue';
import { getAge } from '@/utils/util';
import { useRouter } from 'vue-router';
let data = ref<any>();
let loading = ref(false);
@ -250,12 +173,23 @@ let operateReq = reactive({
operate: '',
detailId: 0,
});
let versions = ref<App.VersionDetail[]>();
const backupRef = ref();
const checkRef = ref();
const deleteRef = ref();
const appParamRef = ref();
let searchName = ref('');
const updateRef = ref();
let tags = ref<App.Tag[]>([]);
let activeTag = ref('all');
let searchReq = reactive({
page: 1,
pageSize: 15,
name: '',
tags: [],
updated: false,
});
const router = useRouter();
let activeName = ref(i18n.global.t('app.installed'));
let mode = ref('installed');
const sync = () => {
loading.value = true;
@ -269,30 +203,32 @@ const sync = () => {
});
};
const search = () => {
const req = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
name: searchName.value,
};
const changeTag = (key: string) => {
searchReq.tags = [];
activeTag.value = key;
if (key !== 'all') {
searchReq.tags = [key];
}
search();
};
SearchAppInstalled(req).then((res) => {
const search = () => {
searchReq.page = paginationConfig.currentPage;
searchReq.pageSize = paginationConfig.pageSize;
SearchAppInstalled(searchReq).then((res) => {
data.value = res.data.items;
paginationConfig.total = res.data.total;
});
GetAppTags().then((res) => {
tags.value = res.data;
});
};
const openOperate = (row: any, op: string) => {
operateReq.installId = row.id;
operateReq.operate = op;
if (op == 'update') {
GetAppUpdateVersions(row.id).then((res) => {
versions.value = res.data;
if (res.data != null && res.data.length > 0) {
operateReq.detailId = res.data[0].detailId;
}
open.value = true;
});
updateRef.value.acceptParams(row.id, row.name);
} else if (op == 'delete') {
AppInstalledDeleteCheck(row.id).then(async (res) => {
const items = res.data;
@ -320,10 +256,6 @@ const operate = async () => {
});
};
const handleClose = () => {
open.value = false;
};
const onOperate = async (operation: string) => {
ElMessageBox.confirm(
i18n.global.t('app.operatorHelper', [i18n.global.t('app.' + operation)]),
@ -402,6 +334,12 @@ const openParam = (installId: number) => {
};
onMounted(() => {
const path = router.currentRoute.value.path;
if (path == '/apps/update') {
activeName.value = i18n.global.t('app.canUpdate');
mode.value = 'update';
searchReq.updated = true;
}
search();
timer = setInterval(() => {
search();

View file

@ -0,0 +1,92 @@
<template>
<el-drawer v-model="open" size="30%" :show-close="false">
<template #header>
<Header :header="$t('commons.msg.operate')" :resource="resourceName" :back="handleClose"></Header>
</template>
<div style="text-align: center" v-loading="loading">
<p>{{ $t('app.versioneSelect') }}</p>
<el-select v-model="operateReq.detailId">
<el-option
v-for="(version, index) in versions"
:key="index"
:value="version.detailId"
:label="version.version"
></el-option>
</el-select>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="onOperate" :disabled="versions == null || loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import { App } from '@/api/interface/app';
import { GetAppUpdateVersions, InstalledOp } from '@/api/modules/app';
import i18n from '@/lang';
import { ElMessage, ElMessageBox } from 'element-plus';
import { reactive, ref } from 'vue';
import Header from '@/components/drawer-header/index.vue';
let open = ref(false);
let loading = ref(false);
let versions = ref<App.VersionDetail[]>();
let operateReq = reactive({
detailId: 0,
operate: 'update',
installId: 0,
});
const resourceName = ref('');
const em = defineEmits(['close']);
const handleClose = () => {
open.value = false;
em('close', open);
};
const acceptParams = (id: number, name: string) => {
operateReq.installId = id;
resourceName.value = name;
GetAppUpdateVersions(id).then((res) => {
versions.value = res.data;
if (res.data != null && res.data.length > 0) {
operateReq.detailId = res.data[0].detailId;
}
open.value = true;
});
};
const operate = async () => {
loading.value = true;
await InstalledOp(operateReq)
.then(() => {
open.value = false;
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
})
.finally(() => {
loading.value = false;
});
};
const onOperate = async () => {
ElMessageBox.confirm(
i18n.global.t('app.operatorHelper', [i18n.global.t('app.update')]),
i18n.global.t('app.update'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
},
).then(() => {
operate();
});
};
defineExpose({
acceptParams,
});
</script>

View file

@ -10,7 +10,7 @@
</template>
<template v-if="nginxIsExist && !openNginxConfig" #toolbar>
<el-row :class="{ mask: nginxStatus != 'Running' }">
<el-col :span="10">
<el-col :span="20">
<el-button type="primary" icon="Plus" @click="openCreate">
{{ $t('commons.button.create') }}
</el-button>
@ -21,10 +21,9 @@
{{ $t('website.defaulServer') }}
</el-button>
</el-col>
<el-col :span="14">
<div style="float: right">
<el-col :span="4">
<div class="search-button">
<el-input
class="table-button"
v-model="req.name"
clearable
@clear="search()"
@ -366,9 +365,8 @@ onMounted(() => {
});
</script>
<style lang="scss">
.table-button {
border-radius: 20px;
.search-button {
float: right;
display: inline;
margin-right: 5px;
}
</style>