mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-12-09 20:05:54 +08:00
feat: 应用商店增加可更新页
This commit is contained in:
parent
45cda28d01
commit
8cf6f2fe64
26 changed files with 370 additions and 214 deletions
0
backend/app.yaml
Normal file
0
backend/app.yaml
Normal 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
3
frontend/components.d.ts
vendored
3
frontend/components.d.ts
vendored
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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: '网站',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
// },
|
||||
// },
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
})
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
92
frontend/src/views/app-store/installed/update/index.vue
Normal file
92
frontend/src/views/app-store/installed/update/index.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue