ocdp v1
This commit is contained in:
38
frontend/src/app/App.tsx
Normal file
38
frontend/src/app/App.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Main Application Component
|
||||
* 主应用组件
|
||||
*/
|
||||
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useMemo } from "react";
|
||||
import { AppRoutes } from "./routes/AppRoutes";
|
||||
import { useAuth } from "./providers";
|
||||
import { getNavItems } from "./constants/navigation";
|
||||
|
||||
/**
|
||||
* Application root component
|
||||
* Manages routing and global state
|
||||
*/
|
||||
export default function App() {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { isAuthenticated, login, logout } = useAuth();
|
||||
|
||||
// Generate navigation items based on current location
|
||||
const navItems = useMemo(
|
||||
() => getNavItems(location.pathname, navigate),
|
||||
[location.pathname, navigate]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppRoutes
|
||||
isAuthenticated={isAuthenticated}
|
||||
userName="User"
|
||||
navItems={navItems}
|
||||
onLogin={login}
|
||||
onLogout={logout}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
130
frontend/src/app/constants/navigation.tsx
Normal file
130
frontend/src/app/constants/navigation.tsx
Normal file
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Navigation Configuration
|
||||
* 导航配置 - 集中管理导航菜单项
|
||||
*/
|
||||
|
||||
import { Home, Settings, Server, Database, Package, LineChart } from "lucide-react";
|
||||
|
||||
/**
|
||||
* Navigation item type
|
||||
*/
|
||||
export interface NavItem {
|
||||
key: string;
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
active?: boolean;
|
||||
onClick?: () => void;
|
||||
children?: NavItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Page info type for header display
|
||||
*/
|
||||
export interface PageInfo {
|
||||
title: string;
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get navigation items
|
||||
* @param currentPath - Current route path
|
||||
* @param navigate - Navigation function
|
||||
* @returns Navigation items array
|
||||
*/
|
||||
export const getNavItems = (
|
||||
currentPath: string,
|
||||
navigate: (path: string) => void
|
||||
): NavItem[] => [
|
||||
{
|
||||
key: "home",
|
||||
label: "Home",
|
||||
icon: <Home className="w-4 h-4 text-secondary" />,
|
||||
active: currentPath === "/home",
|
||||
onClick: () => navigate("/home"),
|
||||
},
|
||||
// Configuration
|
||||
{
|
||||
key: "configuration",
|
||||
label: "Configuration",
|
||||
icon: <Settings className="w-4 h-4 text-brand-accent" />,
|
||||
children: [
|
||||
{
|
||||
key: "configuration-clusters",
|
||||
label: "Clusters",
|
||||
icon: <Server className="w-4 h-4 text-accent-teal" />,
|
||||
active: currentPath === "/configuration/clusters",
|
||||
onClick: () => navigate("/configuration/clusters"),
|
||||
},
|
||||
{
|
||||
key: "configuration-registries",
|
||||
label: "Registries",
|
||||
icon: <Database className="w-4 h-4 text-brand-light" />,
|
||||
active: currentPath === "/configuration/registries",
|
||||
onClick: () => navigate("/configuration/registries"),
|
||||
},
|
||||
],
|
||||
},
|
||||
// Monitoring - 监控资源状态
|
||||
{
|
||||
key: "monitoring",
|
||||
label: "Monitoring",
|
||||
icon: <LineChart className="w-4 h-4 text-accent-teal" />,
|
||||
children: [
|
||||
{
|
||||
key: "monitoring-clusters",
|
||||
label: "Clusters",
|
||||
icon: <Server className="w-4 h-4 text-accent-teal" />,
|
||||
active: currentPath === "/monitoring/clusters",
|
||||
onClick: () => navigate("/monitoring/clusters"),
|
||||
},
|
||||
],
|
||||
},
|
||||
// Artifact - 浏览和部署制品
|
||||
{
|
||||
key: "artifact",
|
||||
label: "Artifact",
|
||||
icon: <Package className="w-4 h-4 text-brand-light" />,
|
||||
children: [
|
||||
{
|
||||
key: "artifact-registries",
|
||||
label: "Registries",
|
||||
icon: <Database className="w-4 h-4 text-brand-light" />,
|
||||
active: currentPath === "/artifact/registries",
|
||||
onClick: () => navigate("/artifact/registries"),
|
||||
},
|
||||
{
|
||||
key: "artifact-instances",
|
||||
label: "Instances",
|
||||
icon: <Package className="w-4 h-4 text-brand-accent" />,
|
||||
active: currentPath === "/artifact/instances",
|
||||
onClick: () => navigate("/artifact/instances"),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Get page header info based on current path
|
||||
* @param pathname - Current route pathname
|
||||
* @returns Page info object
|
||||
*/
|
||||
export const getPageInfo = (pathname: string): PageInfo => {
|
||||
if (pathname === "/artifact/registries") {
|
||||
return { title: "Artifact Browser", icon: <Package className="w-6 h-6 text-brand-light" /> };
|
||||
}
|
||||
if (pathname === "/artifact/instances") {
|
||||
return { title: "Artifact - Instances", icon: <Package className="w-6 h-6 text-brand-accent" /> };
|
||||
}
|
||||
if (pathname === "/configuration/clusters") {
|
||||
return { title: "Configuration - Clusters", icon: <Server className="w-6 h-6 text-accent-teal" /> };
|
||||
}
|
||||
if (pathname === "/configuration/registries") {
|
||||
return { title: "Configuration - Registries", icon: <Database className="w-6 h-6 text-brand-light" /> };
|
||||
}
|
||||
if (pathname === "/monitoring/clusters") {
|
||||
return { title: "Monitoring - Clusters", icon: <LineChart className="w-6 h-6 text-accent-teal" /> };
|
||||
}
|
||||
return { title: "OCDP Platform", icon: <Home className="w-6 h-6 text-secondary" /> };
|
||||
};
|
||||
|
||||
|
||||
11
frontend/src/app/index.ts
Normal file
11
frontend/src/app/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* App Module - Unified Export
|
||||
* 应用模块统一导出
|
||||
*/
|
||||
|
||||
export { default as App } from "./App";
|
||||
export * from "./providers";
|
||||
export * from "./routes/RouteGuard";
|
||||
export * from "./constants/navigation";
|
||||
|
||||
|
||||
23
frontend/src/app/providers/AuthContext.ts
Normal file
23
frontend/src/app/providers/AuthContext.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Auth Context
|
||||
* Authentication context - separated for Fast Refresh compatibility
|
||||
*/
|
||||
|
||||
import { createContext } from "react";
|
||||
import type { AuthResponse } from "@/api";
|
||||
|
||||
export interface User {
|
||||
username: string;
|
||||
role?: string;
|
||||
}
|
||||
|
||||
export interface AuthContextType {
|
||||
token: string | null;
|
||||
user: User | null;
|
||||
isAuthenticated: boolean;
|
||||
login: (response: AuthResponse) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
export const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
103
frontend/src/app/providers/AuthProvider.tsx
Normal file
103
frontend/src/app/providers/AuthProvider.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Authentication Provider
|
||||
* 认证提供者 - 管理用户认证状态和 token
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import type { AuthResponse } from "@/api";
|
||||
import { setAuthToken } from "@/api";
|
||||
import { AuthContext, type User } from "./AuthContext";
|
||||
|
||||
interface AuthProviderProps {
|
||||
children: ReactNode;
|
||||
devMode?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auth Provider Component
|
||||
* Manages authentication state and provides auth context
|
||||
*/
|
||||
export const AuthProvider = ({ children, devMode = false }: AuthProviderProps) => {
|
||||
const [token, setToken] = useState<string | null>(devMode ? "dev-token" : null);
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
|
||||
// Initialize: read token and user from localStorage
|
||||
useEffect(() => {
|
||||
if (devMode) {
|
||||
const devUser: User = {
|
||||
username: "dev-user",
|
||||
role: "admin",
|
||||
};
|
||||
localStorage.setItem("access_token", "dev-token");
|
||||
localStorage.setItem("user", JSON.stringify(devUser));
|
||||
setToken("dev-token");
|
||||
setUser(devUser);
|
||||
return;
|
||||
}
|
||||
|
||||
const storedToken = localStorage.getItem("access_token");
|
||||
const storedUser = localStorage.getItem("user");
|
||||
|
||||
if (storedToken) {
|
||||
setToken(storedToken);
|
||||
}
|
||||
if (storedUser) {
|
||||
try {
|
||||
setUser(JSON.parse(storedUser));
|
||||
} catch (e) {
|
||||
console.error("Failed to parse stored user:", e);
|
||||
}
|
||||
}
|
||||
}, [devMode]);
|
||||
|
||||
// Sync token changes to axios headers
|
||||
useEffect(() => {
|
||||
setAuthToken(token);
|
||||
}, [token]);
|
||||
|
||||
// Handle login (JWT format)
|
||||
const login = (response: AuthResponse) => {
|
||||
// JWT 格式: { accessToken, refreshToken, username, ... }
|
||||
const accessToken = response.accessToken || "";
|
||||
const refreshToken = response.refreshToken || "";
|
||||
|
||||
localStorage.setItem("access_token", accessToken);
|
||||
localStorage.setItem("refresh_token", refreshToken);
|
||||
|
||||
const user: User = {
|
||||
username: response.username || "",
|
||||
role: "user", // 后端暂未返回 role,默认为 user
|
||||
};
|
||||
localStorage.setItem("user", JSON.stringify(user));
|
||||
|
||||
setToken(accessToken);
|
||||
setUser(user);
|
||||
};
|
||||
|
||||
// Handle logout
|
||||
const logout = () => {
|
||||
localStorage.removeItem("access_token");
|
||||
localStorage.removeItem("refresh_token");
|
||||
localStorage.removeItem("user");
|
||||
setToken(null);
|
||||
setUser(null);
|
||||
setAuthToken(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
token,
|
||||
user,
|
||||
isAuthenticated: !!token,
|
||||
login,
|
||||
logout,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// useAuth hook is now exported from ./useAuth.ts for Fast Refresh compatibility
|
||||
9
frontend/src/app/providers/index.ts
Normal file
9
frontend/src/app/providers/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Providers - Unified Export
|
||||
* 提供者统一导出
|
||||
*/
|
||||
|
||||
export { AuthProvider } from "./AuthProvider";
|
||||
export { useAuth } from "./useAuth";
|
||||
|
||||
|
||||
19
frontend/src/app/providers/useAuth.ts
Normal file
19
frontend/src/app/providers/useAuth.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* useAuth Hook
|
||||
* Auth context hook - separated for Fast Refresh compatibility
|
||||
*/
|
||||
|
||||
import { useContext } from "react";
|
||||
import { AuthContext } from "./AuthContext";
|
||||
|
||||
/**
|
||||
* Hook to use auth context
|
||||
*/
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useAuth must be used within an AuthProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
182
frontend/src/app/routes/AppRoutes.tsx
Normal file
182
frontend/src/app/routes/AppRoutes.tsx
Normal file
@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Application Routes Configuration
|
||||
* 应用路由配置
|
||||
*/
|
||||
|
||||
import { Routes, Route, Navigate } from "react-router-dom";
|
||||
import { ProtectedRoute } from "./RouteGuard";
|
||||
import AppShell from "@/shared/components/layout/AppShell";
|
||||
import { getPageInfo, type NavItem } from "../constants/navigation";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import type { AuthResponse } from "@/api";
|
||||
|
||||
// Feature pages
|
||||
import AuthPage from "@/features/auth/pages/AuthPage";
|
||||
import HomePage from "@/features/home/pages/HomePage";
|
||||
import ClusterConfigPage from "@/features/configuration/clusters/pages/ClusterConfigPage";
|
||||
import RegistryConfigPage from "@/features/configuration/registries/pages/RegistryConfigPage";
|
||||
import ArtifactBrowserPage from "@/features/artifact/registries/pages/ArtifactBrowserPage";
|
||||
import InstancesManagementPage from "@/features/artifact/instances/pages/InstancesManagementPage";
|
||||
import MonitoringClustersPage from "@/features/monitoring/clusters/pages/MonitoringClustersPage";
|
||||
import { ApiTest } from "@/components/ApiTest";
|
||||
|
||||
interface AppRoutesProps {
|
||||
isAuthenticated: boolean;
|
||||
userName?: string;
|
||||
navItems: NavItem[];
|
||||
onLogin: (tokens: AuthResponse) => void;
|
||||
onLogout: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main application routes
|
||||
*/
|
||||
export const AppRoutes = ({
|
||||
isAuthenticated,
|
||||
userName = "User",
|
||||
navItems,
|
||||
onLogin,
|
||||
onLogout,
|
||||
}: AppRoutesProps) => {
|
||||
const location = useLocation();
|
||||
const pageInfo = getPageInfo(location.pathname);
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
{/* Public route - Authentication page */}
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
isAuthenticated ? (
|
||||
<Navigate to="/home" replace />
|
||||
) : (
|
||||
<AuthPage onLogin={onLogin} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Protected routes - wrapped in AppShell */}
|
||||
<Route
|
||||
path="/home"
|
||||
element={
|
||||
<ProtectedRoute isAuthenticated={isAuthenticated}>
|
||||
<AppShell
|
||||
title={pageInfo.title}
|
||||
icon={pageInfo.icon}
|
||||
userName={userName}
|
||||
navItems={navItems}
|
||||
onSignOut={onLogout}
|
||||
>
|
||||
<HomePage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/configuration/clusters"
|
||||
element={
|
||||
<ProtectedRoute isAuthenticated={isAuthenticated}>
|
||||
<AppShell
|
||||
title={pageInfo.title}
|
||||
icon={pageInfo.icon}
|
||||
userName={userName}
|
||||
navItems={navItems}
|
||||
onSignOut={onLogout}
|
||||
>
|
||||
<ClusterConfigPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/configuration/registries"
|
||||
element={
|
||||
<ProtectedRoute isAuthenticated={isAuthenticated}>
|
||||
<AppShell
|
||||
title={pageInfo.title}
|
||||
icon={pageInfo.icon}
|
||||
userName={userName}
|
||||
navItems={navItems}
|
||||
onSignOut={onLogout}
|
||||
>
|
||||
<RegistryConfigPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/artifact/registries"
|
||||
element={
|
||||
<ProtectedRoute isAuthenticated={isAuthenticated}>
|
||||
<AppShell
|
||||
title={pageInfo.title}
|
||||
icon={pageInfo.icon}
|
||||
userName={userName}
|
||||
navItems={navItems}
|
||||
onSignOut={onLogout}
|
||||
>
|
||||
<ArtifactBrowserPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/artifact/instances"
|
||||
element={
|
||||
<ProtectedRoute isAuthenticated={isAuthenticated}>
|
||||
<AppShell
|
||||
title={pageInfo.title}
|
||||
icon={pageInfo.icon}
|
||||
userName={userName}
|
||||
navItems={navItems}
|
||||
onSignOut={onLogout}
|
||||
>
|
||||
<InstancesManagementPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/monitoring/clusters"
|
||||
element={
|
||||
<ProtectedRoute isAuthenticated={isAuthenticated}>
|
||||
<AppShell
|
||||
title={pageInfo.title}
|
||||
icon={pageInfo.icon}
|
||||
userName={userName}
|
||||
navItems={navItems}
|
||||
onSignOut={onLogout}
|
||||
>
|
||||
<MonitoringClustersPage />
|
||||
</AppShell>
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* API Test page - Public for testing */}
|
||||
<Route path="/api-test" element={<ApiTest />} />
|
||||
|
||||
{/* Legacy path compatibility - redirects */}
|
||||
<Route path="/config" element={<Navigate to="/configuration/clusters" replace />} />
|
||||
<Route path="/config/cluster" element={<Navigate to="/configuration/clusters" replace />} />
|
||||
<Route path="/config/clusters" element={<Navigate to="/configuration/clusters" replace />} />
|
||||
<Route path="/config/app" element={<Navigate to="/configuration/registries" replace />} />
|
||||
<Route path="/config/registry" element={<Navigate to="/configuration/registries" replace />} />
|
||||
<Route path="/config/registries" element={<Navigate to="/configuration/registries" replace />} />
|
||||
<Route path="/artifact/registry" element={<Navigate to="/artifact/registries" replace />} />
|
||||
<Route path="/artifact/instance" element={<Navigate to="/artifact/instances" replace />} />
|
||||
<Route path="/monitor" element={<Navigate to="/monitoring/clusters" replace />} />
|
||||
<Route path="/cluster" element={<Navigate to="/monitoring/clusters" replace />} />
|
||||
<Route path="/cluster/monitor" element={<Navigate to="/monitoring/clusters" replace />} />
|
||||
<Route path="/registry" element={<Navigate to="/artifact/registries" replace />} />
|
||||
<Route path="/register" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
39
frontend/src/app/routes/RouteGuard.tsx
Normal file
39
frontend/src/app/routes/RouteGuard.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Route Guard Component
|
||||
* 路由守卫组件 - 处理认证和授权
|
||||
*/
|
||||
|
||||
import { Navigate } from "react-router-dom";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
interface RouteGuardProps {
|
||||
isAuthenticated: boolean;
|
||||
redirectTo?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected route wrapper
|
||||
* Redirects to auth page if not authenticated
|
||||
*/
|
||||
export const ProtectedRoute = ({
|
||||
isAuthenticated,
|
||||
redirectTo = "/",
|
||||
children
|
||||
}: RouteGuardProps) => {
|
||||
return isAuthenticated ? <>{children}</> : <Navigate to={redirectTo} replace />;
|
||||
};
|
||||
|
||||
/**
|
||||
* Public route wrapper
|
||||
* Redirects to home if already authenticated
|
||||
*/
|
||||
export const PublicRoute = ({
|
||||
isAuthenticated,
|
||||
redirectTo = "/home",
|
||||
children
|
||||
}: RouteGuardProps) => {
|
||||
return !isAuthenticated ? <>{children}</> : <Navigate to={redirectTo} replace />;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user