diff --git a/src/api/modules/internal-surfaces.ts b/src/api/modules/internal-surfaces.ts index 838073e..a9f6feb 100644 --- a/src/api/modules/internal-surfaces.ts +++ b/src/api/modules/internal-surfaces.ts @@ -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' diff --git a/src/auth/permissions.ts b/src/auth/permissions.ts index 95f6cfc..ca09f3b 100644 --- a/src/auth/permissions.ts +++ b/src/auth/permissions.ts @@ -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', diff --git a/src/components/layout/nav.ts b/src/components/layout/nav.ts index d8f0f02..68535db 100644 --- a/src/components/layout/nav.ts +++ b/src/components/layout/nav.ts @@ -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', + }, ], }, ] diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 1191508..eefc42b 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -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, diff --git a/src/routes/_authed/minio-console.tsx b/src/routes/_authed/minio-console.tsx new file mode 100644 index 0000000..bbf56e6 --- /dev/null +++ b/src/routes/_authed/minio-console.tsx @@ -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 ( +
+ + + + 打开管理台 + + 将在新标签页中打开。平台入口链接一次性有效、60 秒内过期,会话约 + 30 分钟后失效。 + + + +

+ 平台权限仅控制管理台入口。打开后仍需使用 MinIO + 凭据登录;对象删除和权限修改可能影响线上文件访问。 +

+ +
+
+
+ ) +}