feat: expose MinIO operator console
This commit is contained in:
@@ -6,7 +6,7 @@ import type { ApiResponse } from '@/api/types'
|
||||
* gated by our own admin permissions. The backend registry is
|
||||
* config/internal_surfaces.php; the surface id must match a key there.
|
||||
*/
|
||||
export type InternalSurface = 'db-studio'
|
||||
export type InternalSurface = 'db-studio' | 'minio-console'
|
||||
|
||||
export interface InternalSurfaceSession {
|
||||
kind: 'internal_surface_session'
|
||||
|
||||
@@ -36,6 +36,7 @@ export const PERM = {
|
||||
// internal operator consoles — `auth:` prefixed, so super-admin only
|
||||
// (the `admin` role is synced to admin:% keys and would otherwise inherit it)
|
||||
DB_STUDIO_ACCESS: 'auth:db-studio:access',
|
||||
MINIO_CONSOLE_ACCESS: 'auth:minio-console:access',
|
||||
// role management — only super-admin holds these
|
||||
ROLE_READ: 'auth:role:read',
|
||||
ROLE_CREATE: 'auth:role:create',
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Flag,
|
||||
FolderTree,
|
||||
Hash,
|
||||
HardDrive,
|
||||
Inbox,
|
||||
KeyRound,
|
||||
Languages,
|
||||
@@ -131,6 +132,12 @@ export const NAV_GROUPS: NavGroup[] = [
|
||||
icon: Database,
|
||||
permission: 'auth:db-studio:access',
|
||||
},
|
||||
{
|
||||
label: '对象存储管理台',
|
||||
to: '/minio-console',
|
||||
icon: HardDrive,
|
||||
permission: 'auth:minio-console:access',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Route as AuthedIndexRouteImport } from './routes/_authed/index'
|
||||
import { Route as AuthedSidRouteImport } from './routes/_authed/sid'
|
||||
import { Route as AuthedRolesRouteImport } from './routes/_authed/roles'
|
||||
import { Route as AuthedRobotsRouteImport } from './routes/_authed/robots'
|
||||
import { Route as AuthedMinioConsoleRouteImport } from './routes/_authed/minio-console'
|
||||
import { Route as AuthedLocalesRouteImport } from './routes/_authed/locales'
|
||||
import { Route as AuthedEventReportsRouteImport } from './routes/_authed/event-reports'
|
||||
import { Route as AuthedDimzouTranslationsRouteImport } from './routes/_authed/dimzou-translations'
|
||||
@@ -75,6 +76,11 @@ const AuthedRobotsRoute = AuthedRobotsRouteImport.update({
|
||||
path: '/robots',
|
||||
getParentRoute: () => AuthedRoute,
|
||||
} as any)
|
||||
const AuthedMinioConsoleRoute = AuthedMinioConsoleRouteImport.update({
|
||||
id: '/minio-console',
|
||||
path: '/minio-console',
|
||||
getParentRoute: () => AuthedRoute,
|
||||
} as any)
|
||||
const AuthedLocalesRoute = AuthedLocalesRouteImport.update({
|
||||
id: '/locales',
|
||||
path: '/locales',
|
||||
@@ -213,6 +219,7 @@ export interface FileRoutesByFullPath {
|
||||
'/dimzou-translations': typeof AuthedDimzouTranslationsRoute
|
||||
'/event-reports': typeof AuthedEventReportsRoute
|
||||
'/locales': typeof AuthedLocalesRoute
|
||||
'/minio-console': typeof AuthedMinioConsoleRoute
|
||||
'/robots': typeof AuthedRobotsRoute
|
||||
'/roles': typeof AuthedRolesRoute
|
||||
'/sid': typeof AuthedSidRoute
|
||||
@@ -244,6 +251,7 @@ export interface FileRoutesByTo {
|
||||
'/dimzou-translations': typeof AuthedDimzouTranslationsRoute
|
||||
'/event-reports': typeof AuthedEventReportsRoute
|
||||
'/locales': typeof AuthedLocalesRoute
|
||||
'/minio-console': typeof AuthedMinioConsoleRoute
|
||||
'/robots': typeof AuthedRobotsRoute
|
||||
'/roles': typeof AuthedRolesRoute
|
||||
'/sid': typeof AuthedSidRoute
|
||||
@@ -278,6 +286,7 @@ export interface FileRoutesById {
|
||||
'/_authed/dimzou-translations': typeof AuthedDimzouTranslationsRoute
|
||||
'/_authed/event-reports': typeof AuthedEventReportsRoute
|
||||
'/_authed/locales': typeof AuthedLocalesRoute
|
||||
'/_authed/minio-console': typeof AuthedMinioConsoleRoute
|
||||
'/_authed/robots': typeof AuthedRobotsRoute
|
||||
'/_authed/roles': typeof AuthedRolesRoute
|
||||
'/_authed/sid': typeof AuthedSidRoute
|
||||
@@ -313,6 +322,7 @@ export interface FileRouteTypes {
|
||||
| '/dimzou-translations'
|
||||
| '/event-reports'
|
||||
| '/locales'
|
||||
| '/minio-console'
|
||||
| '/robots'
|
||||
| '/roles'
|
||||
| '/sid'
|
||||
@@ -344,6 +354,7 @@ export interface FileRouteTypes {
|
||||
| '/dimzou-translations'
|
||||
| '/event-reports'
|
||||
| '/locales'
|
||||
| '/minio-console'
|
||||
| '/robots'
|
||||
| '/roles'
|
||||
| '/sid'
|
||||
@@ -377,6 +388,7 @@ export interface FileRouteTypes {
|
||||
| '/_authed/dimzou-translations'
|
||||
| '/_authed/event-reports'
|
||||
| '/_authed/locales'
|
||||
| '/_authed/minio-console'
|
||||
| '/_authed/robots'
|
||||
| '/_authed/roles'
|
||||
| '/_authed/sid'
|
||||
@@ -457,6 +469,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AuthedRobotsRouteImport
|
||||
parentRoute: typeof AuthedRoute
|
||||
}
|
||||
'/_authed/minio-console': {
|
||||
id: '/_authed/minio-console'
|
||||
path: '/minio-console'
|
||||
fullPath: '/minio-console'
|
||||
preLoaderRoute: typeof AuthedMinioConsoleRouteImport
|
||||
parentRoute: typeof AuthedRoute
|
||||
}
|
||||
'/_authed/locales': {
|
||||
id: '/_authed/locales'
|
||||
path: '/locales'
|
||||
@@ -636,6 +655,7 @@ interface AuthedRouteChildren {
|
||||
AuthedDimzouTranslationsRoute: typeof AuthedDimzouTranslationsRoute
|
||||
AuthedEventReportsRoute: typeof AuthedEventReportsRoute
|
||||
AuthedLocalesRoute: typeof AuthedLocalesRoute
|
||||
AuthedMinioConsoleRoute: typeof AuthedMinioConsoleRoute
|
||||
AuthedRobotsRoute: typeof AuthedRobotsRoute
|
||||
AuthedRolesRoute: typeof AuthedRolesRoute
|
||||
AuthedSidRoute: typeof AuthedSidRoute
|
||||
@@ -667,6 +687,7 @@ const AuthedRouteChildren: AuthedRouteChildren = {
|
||||
AuthedDimzouTranslationsRoute: AuthedDimzouTranslationsRoute,
|
||||
AuthedEventReportsRoute: AuthedEventReportsRoute,
|
||||
AuthedLocalesRoute: AuthedLocalesRoute,
|
||||
AuthedMinioConsoleRoute: AuthedMinioConsoleRoute,
|
||||
AuthedRobotsRoute: AuthedRobotsRoute,
|
||||
AuthedRolesRoute: AuthedRolesRoute,
|
||||
AuthedSidRoute: AuthedSidRoute,
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { openInternalSurface } from '@/api/modules/internal-surfaces'
|
||||
import { PERM, requirePermission } from '@/auth/permissions'
|
||||
import { PageHeader } from '@/components/page-header'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { handleApiError } from '@/lib/errors'
|
||||
|
||||
export const Route = createFileRoute('/_authed/minio-console')({
|
||||
beforeLoad: () => requirePermission(PERM.MINIO_CONSOLE_ACCESS),
|
||||
component: MinioConsolePage,
|
||||
})
|
||||
|
||||
function MinioConsolePage() {
|
||||
const [busy, setBusy] = useState(false)
|
||||
|
||||
const open = async () => {
|
||||
setBusy(true)
|
||||
const tab = window.open('about:blank', '_blank')
|
||||
|
||||
try {
|
||||
const res = await openInternalSurface('minio-console')
|
||||
if (tab) {
|
||||
tab.opener = null
|
||||
tab.location.replace(res.data.url)
|
||||
} else {
|
||||
window.location.assign(res.data.url)
|
||||
}
|
||||
} catch (error) {
|
||||
tab?.close()
|
||||
handleApiError(error)
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
title="对象存储管理台"
|
||||
description="在浏览器中检查和管理 MinIO 对象存储"
|
||||
/>
|
||||
<Card className="max-w-lg">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">打开管理台</CardTitle>
|
||||
<CardDescription>
|
||||
将在新标签页中打开。平台入口链接一次性有效、60 秒内过期,会话约
|
||||
30 分钟后失效。
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
平台权限仅控制管理台入口。打开后仍需使用 MinIO
|
||||
凭据登录;对象删除和权限修改可能影响线上文件访问。
|
||||
</p>
|
||||
<Button onClick={open} disabled={busy}>
|
||||
{busy ? '正在打开…' : '打开对象存储管理台'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user