mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-30 17:05:19 +08:00
feat: 为应用页面添加"已安装"Tab选项卡 (#1535)
* Initial plan * feat: 为应用页面添加"已安装"Tab选项卡 - 后端:app/list 和 environment/list 添加 installed 过滤参数 - 前端:创建 InstalledView 组件展示已安装的应用和环境 - 前端:IndexView 添加"已安装"Tab 并设为默认选项 Agent-Logs-Url: https://github.com/acepanel/panel/sessions/9b460f4b-c69e-4a5b-8131-c68099edcb7c Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com> * refactor: 移除 InstalledView 中未使用的 VersionModal 组件 Agent-Logs-Url: https://github.com/acepanel/panel/sessions/9b460f4b-c69e-4a5b-8131-c68099edcb7c Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: h2zi <5967335+h2zi@users.noreply.github.com>
This commit is contained in:
@@ -37,6 +37,7 @@ func (s *AppService) Categories(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *AppService) List(w http.ResponseWriter, r *http.Request) {
|
||||
category := r.URL.Query().Get("category")
|
||||
query := strings.ToLower(r.URL.Query().Get("query"))
|
||||
onlyInstalled := r.URL.Query().Get("installed") == "true"
|
||||
|
||||
all := s.appRepo.All()
|
||||
installedApps, err := s.appRepo.Installed()
|
||||
@@ -60,6 +61,9 @@ func (s *AppService) List(w http.ResponseWriter, r *http.Request) {
|
||||
updateExist = s.appRepo.UpdateExist(item.Slug)
|
||||
show = installedAppMap[item.Slug].Show
|
||||
}
|
||||
if onlyInstalled && !installed {
|
||||
continue
|
||||
}
|
||||
if category != "" && !strings.Contains(strings.Join(item.Categories, ","), category) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ func (s *EnvironmentService) Types(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *EnvironmentService) List(w http.ResponseWriter, r *http.Request) {
|
||||
typ := r.URL.Query().Get("type")
|
||||
query := strings.ToLower(r.URL.Query().Get("query"))
|
||||
onlyInstalled := r.URL.Query().Get("installed") == "true"
|
||||
all := s.environmentRepo.All()
|
||||
var environments []types.EnvironmentDetail
|
||||
for _, item := range all {
|
||||
@@ -43,6 +44,10 @@ func (s *EnvironmentService) List(w http.ResponseWriter, r *http.Request) {
|
||||
!strings.Contains(strings.ToLower(item.Description), query) {
|
||||
continue
|
||||
}
|
||||
installed := s.environmentRepo.IsInstalled(item.Type, item.Slug)
|
||||
if onlyInstalled && !installed {
|
||||
continue
|
||||
}
|
||||
environments = append(environments, types.EnvironmentDetail{
|
||||
Type: item.Type,
|
||||
Name: item.Name,
|
||||
@@ -50,7 +55,7 @@ func (s *EnvironmentService) List(w http.ResponseWriter, r *http.Request) {
|
||||
Slug: item.Slug,
|
||||
Version: item.Version,
|
||||
InstalledVersion: s.environmentRepo.InstalledVersion(item.Type, item.Slug),
|
||||
Installed: s.environmentRepo.IsInstalled(item.Type, item.Slug),
|
||||
Installed: installed,
|
||||
HasUpdate: s.environmentRepo.HasUpdate(item.Type, item.Slug),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ export default {
|
||||
// 获取分类列表
|
||||
categories: (): any => http.Get('/app/categories'),
|
||||
// 获取应用列表
|
||||
list: (page: number, limit: number, category?: string, query?: string): any =>
|
||||
http.Get('/app/list', { params: { page, limit, category, query } }),
|
||||
list: (page: number, limit: number, category?: string, query?: string, installed?: boolean): any =>
|
||||
http.Get('/app/list', { params: { page, limit, category, query, installed } }),
|
||||
// 安装应用
|
||||
install: (slug: string, channel: string | null): any =>
|
||||
http.Post('/app/install', { slug, channel }),
|
||||
|
||||
@@ -4,8 +4,8 @@ export default {
|
||||
// 获取环境类型列表
|
||||
types: (): any => http.Get('/environment/types'),
|
||||
// 获取环境列表
|
||||
list: (page: number, limit: number, type?: string, query?: string): any =>
|
||||
http.Get('/environment/list', { params: { page, limit, type, query } }),
|
||||
list: (page: number, limit: number, type?: string, query?: string, installed?: boolean): any =>
|
||||
http.Get('/environment/list', { params: { page, limit, type, query, installed } }),
|
||||
// 安装环境
|
||||
install: (type: string, slug: string): any => http.Post('/environment/install', { type, slug }),
|
||||
// 卸载环境
|
||||
|
||||
@@ -10,12 +10,13 @@ import app from '@/api/panel/app'
|
||||
import { useTabStore } from '@/stores'
|
||||
import AppView from '@/views/app/AppView.vue'
|
||||
import EnvironmentView from '@/views/app/EnvironmentView.vue'
|
||||
import InstalledView from '@/views/app/InstalledView.vue'
|
||||
import TemplateView from '@/views/app/TemplateView.vue'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
const tabStore = useTabStore()
|
||||
|
||||
const currentTab = ref('app')
|
||||
const currentTab = ref('installed')
|
||||
const updateCacheLoading = ref(false)
|
||||
|
||||
const handleUpdateCache = () => {
|
||||
@@ -36,6 +37,7 @@ const handleUpdateCache = () => {
|
||||
<template #tabbar>
|
||||
<div class="flex items-center justify-between">
|
||||
<n-tabs v-model:value="currentTab" animated class="flex-1">
|
||||
<n-tab name="installed" :tab="$gettext('Installed')" />
|
||||
<n-tab name="app" :tab="$gettext('Native App')" />
|
||||
<n-tab name="environment" :tab="$gettext('Operating Environment')" />
|
||||
<n-tab name="template" :tab="$gettext('Container Template')" />
|
||||
@@ -45,6 +47,7 @@ const handleUpdateCache = () => {
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
<installed-view v-if="currentTab === 'installed'" />
|
||||
<app-view v-if="currentTab === 'app'" />
|
||||
<environment-view v-if="currentTab === 'environment'" />
|
||||
<template-view v-if="currentTab === 'template'" />
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NDataTable, NFlex, NPopconfirm, NSwitch, NH3 } from 'naive-ui'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
import app from '@/api/panel/app'
|
||||
import environment from '@/api/panel/environment'
|
||||
import { router } from '@/router'
|
||||
import { renderLocalIcon } from '@/utils'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
|
||||
// 应用表格列
|
||||
const appColumns: any = [
|
||||
{
|
||||
key: 'icon',
|
||||
fixed: 'left',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
render(row: any) {
|
||||
return renderLocalIcon('app', row.slug, { size: 26 })()
|
||||
}
|
||||
},
|
||||
{
|
||||
title: $gettext('App Name'),
|
||||
key: 'name',
|
||||
width: 200,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: $gettext('Description'),
|
||||
key: 'description',
|
||||
minWidth: 300,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: $gettext('Installed Version'),
|
||||
key: 'installed_version',
|
||||
width: 160,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: $gettext('Show in Home'),
|
||||
key: 'show',
|
||||
width: 140,
|
||||
render(row: any) {
|
||||
return h(NSwitch, {
|
||||
size: 'small',
|
||||
rubberBand: false,
|
||||
value: row.show,
|
||||
onUpdateValue: () => handleAppShowChange(row)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: $gettext('Actions'),
|
||||
key: 'actions',
|
||||
width: 350,
|
||||
hideInExcel: true,
|
||||
render(row: any) {
|
||||
return h(NFlex, null, {
|
||||
default: () => [
|
||||
row.update_exist
|
||||
? h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => handleAppUpdate(row.slug)
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
return $gettext(
|
||||
'Updating app %{ app } may reset related configurations to default state, are you sure to continue?',
|
||||
{ app: row.name }
|
||||
)
|
||||
},
|
||||
trigger: () => {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'warning'
|
||||
},
|
||||
{
|
||||
default: () => $gettext('Update')
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
: null,
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'info',
|
||||
onClick: () => handleAppManage(row.slug)
|
||||
},
|
||||
{
|
||||
default: () => $gettext('Manage')
|
||||
}
|
||||
),
|
||||
h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => handleAppUninstall(row.slug)
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
if (row.categories.includes('webserver')) {
|
||||
return $gettext(
|
||||
'Reinstalling/Switching to a different web server will reset the configuration of all websites, are you sure to continue?'
|
||||
)
|
||||
}
|
||||
return $gettext('Are you sure to uninstall app %{ app }?', { app: row.name })
|
||||
},
|
||||
trigger: () => {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'error'
|
||||
},
|
||||
{
|
||||
default: () => $gettext('Uninstall')
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 环境表格列
|
||||
const envColumns: any = [
|
||||
{
|
||||
key: 'icon',
|
||||
fixed: 'left',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
render(row: any) {
|
||||
return renderLocalIcon('environment', row.type, { size: 26 })()
|
||||
}
|
||||
},
|
||||
{
|
||||
title: $gettext('Name'),
|
||||
key: 'name',
|
||||
width: 200,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: $gettext('Description'),
|
||||
key: 'description',
|
||||
minWidth: 300,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: $gettext('Installed Version'),
|
||||
key: 'installed_version',
|
||||
width: 160,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
{
|
||||
title: $gettext('Actions'),
|
||||
key: 'actions',
|
||||
width: 240,
|
||||
hideInExcel: true,
|
||||
render(row: any) {
|
||||
return h(NFlex, null, {
|
||||
default: () => [
|
||||
row.has_update
|
||||
? h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => handleEnvUpdate(row.type, row.slug)
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
return $gettext('Are you sure to update environment %{ environment }?', {
|
||||
environment: row.name
|
||||
})
|
||||
},
|
||||
trigger: () => {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'warning'
|
||||
},
|
||||
{
|
||||
default: () => $gettext('Update')
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
: null,
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'info',
|
||||
onClick: () => handleEnvManage(row.type, row.slug)
|
||||
},
|
||||
{
|
||||
default: () => $gettext('Manage')
|
||||
}
|
||||
),
|
||||
h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => handleEnvUninstall(row.type, row.slug)
|
||||
},
|
||||
{
|
||||
default: () => {
|
||||
return $gettext('Are you sure to uninstall environment %{ environment }?', {
|
||||
environment: row.name
|
||||
})
|
||||
},
|
||||
trigger: () => {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'error'
|
||||
},
|
||||
{
|
||||
default: () => $gettext('Uninstall')
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 获取已安装应用
|
||||
const {
|
||||
loading: appLoading,
|
||||
data: appData,
|
||||
page: appPage,
|
||||
total: appTotal,
|
||||
pageSize: appPageSize,
|
||||
pageCount: appPageCount,
|
||||
refresh: appRefresh
|
||||
} = usePagination(
|
||||
(page, pageSize) => app.list(page, pageSize, undefined, undefined, true),
|
||||
{
|
||||
initialData: { total: 0, list: [] },
|
||||
initialPageSize: 20,
|
||||
total: (res: any) => res.total,
|
||||
data: (res: any) => res.items
|
||||
}
|
||||
)
|
||||
|
||||
// 获取已安装环境(环境数量通常很少,无需分页)
|
||||
const {
|
||||
loading: envLoading,
|
||||
data: envData,
|
||||
send: envRefresh
|
||||
} = useRequest(() => environment.list(1, 200, undefined, undefined, true), {
|
||||
initialData: []
|
||||
})
|
||||
|
||||
// 应用操作
|
||||
const handleAppShowChange = (row: any) => {
|
||||
useRequest(app.updateShow(row.slug, !row.show)).onSuccess(() => {
|
||||
row.show = !row.show
|
||||
window.$message.success($gettext('Setup successfully'))
|
||||
})
|
||||
}
|
||||
|
||||
const handleAppUpdate = (slug: string) => {
|
||||
useRequest(app.update(slug)).onSuccess(() => {
|
||||
window.$message.success(
|
||||
$gettext('Task submitted, please check the progress in background tasks')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const handleAppUninstall = (slug: string) => {
|
||||
useRequest(app.uninstall(slug)).onSuccess(() => {
|
||||
window.$message.success(
|
||||
$gettext('Task submitted, please check the progress in background tasks')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const handleAppManage = (slug: string) => {
|
||||
router.push({ name: 'apps-' + slug + '-index' })
|
||||
}
|
||||
|
||||
// 环境操作
|
||||
const handleEnvUpdate = (type: string, slug: string) => {
|
||||
useRequest(environment.update(type, slug)).onSuccess(() => {
|
||||
window.$message.success(
|
||||
$gettext('Task submitted, please check the progress in background tasks')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const handleEnvUninstall = (type: string, slug: string) => {
|
||||
useRequest(environment.uninstall(type, slug)).onSuccess(() => {
|
||||
window.$message.success(
|
||||
$gettext('Task submitted, please check the progress in background tasks')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const handleEnvManage = (type: string, slug: string) => {
|
||||
router.push({ name: 'environment-' + type, params: { slug } })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
appRefresh()
|
||||
envRefresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-flex vertical :size="24">
|
||||
<!-- 已安装应用 -->
|
||||
<n-flex vertical>
|
||||
<n-h3 prefix="bar">{{ $gettext('Native App') }}</n-h3>
|
||||
<n-data-table
|
||||
striped
|
||||
remote
|
||||
:scroll-x="1200"
|
||||
:loading="appLoading"
|
||||
:columns="appColumns"
|
||||
:data="appData"
|
||||
:row-key="(row: any) => row.slug"
|
||||
v-model:page="appPage"
|
||||
v-model:pageSize="appPageSize"
|
||||
:pagination="{
|
||||
page: appPage,
|
||||
pageCount: appPageCount,
|
||||
pageSize: appPageSize,
|
||||
itemCount: appTotal,
|
||||
showQuickJumper: true,
|
||||
showSizePicker: true,
|
||||
pageSizes: [20, 50, 100, 200]
|
||||
}"
|
||||
/>
|
||||
</n-flex>
|
||||
<!-- 已安装环境 -->
|
||||
<n-flex vertical>
|
||||
<n-h3 prefix="bar">{{ $gettext('Operating Environment') }}</n-h3>
|
||||
<n-data-table
|
||||
striped
|
||||
:scroll-x="1000"
|
||||
:loading="envLoading"
|
||||
:columns="envColumns"
|
||||
:data="envData"
|
||||
:row-key="(row: any) => row.slug"
|
||||
/>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</template>
|
||||
Reference in New Issue
Block a user