feat: expose MinIO operator console
CI / Typecheck, test & build (push) Successful in 3m4s
CI / Deploy to Cloudflare Workers (push) Successful in 1m33s

This commit is contained in:
2026-09-06 22:57:00 +08:00
parent c03617a6e6
commit e3fcaa4d38
5 changed files with 101 additions and 1 deletions
+1 -1
View File
@@ -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'
+1
View File
@@ -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',
+7
View File
@@ -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',
},
],
},
]
+21
View File
@@ -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,
+71
View File
@@ -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>
)
}