"use client"; import { Refine, type AccessControlProvider, type AuthProvider, } from "@refinedev/core"; import { RefineKbar, RefineKbarProvider } from "@refinedev/kbar"; import { RefineSnackbarProvider } from "@refinedev/mui"; import { SessionProvider, signIn, useSession } from "next-auth/react"; import { usePathname } from "next/navigation"; import React, { useEffect } from "react"; import routerProvider from "@refinedev/nextjs-router"; import { ColorModeContextProvider } from "@contexts/color-mode"; import { dataProvider } from "@providers/data-provider"; import { ProjectProvider } from "@/contexts/ProjectContext"; import { RoutePermissionGuard } from "@/components/auth/RoutePermissionGuard"; import { SessionExpiryDialog } from "@/components/auth/SessionExpiryDialog"; import { useAuthStore } from "@/store/authStore"; import { useAccessStore } from "@/store/accessStore"; import { useProjectStore } from "@/store/projectStore"; import { apiFetch } from "@/lib/apiFetch"; import { completeLogout, reportLogoutAudit } from "@/lib/logoutFlow"; import { clearSessionRecoveryDrafts } from "@/lib/sessionRecoveryDraft"; import { permissionCodes, resourcePermissions } from "@/lib/permissions"; import { config } from "@config/config"; import { useAppNotificationProvider } from "@/providers/notification-provider/useAppNotificationProvider"; import { LiaNetworkWiredSolid } from "react-icons/lia"; import { TbActivity, TbDatabaseEdit, TbLocationPin } from "react-icons/tb"; import { LuReplace } from "react-icons/lu"; import { AiOutlineSecurityScan } from "react-icons/ai"; import { MdCleaningServices, MdOutlineWaterDrop } from "react-icons/md"; import { FactCheck as FactCheckIcon, ManageAccounts as ManageAccountsIcon, MyLocation as MyLocationIcon, Search as SearchIcon, } from "@mui/icons-material"; type RefineContextProps = { defaultMode?: string; }; export const RefineContext = ( props: React.PropsWithChildren, ) => ( ); type AppProps = { defaultMode?: string; }; const App = (props: React.PropsWithChildren) => { const { data, status } = useSession(); const to = usePathname(); const setAccessToken = useAuthStore((state) => state.setAccessToken); const markSessionExpired = useAuthStore((state) => state.markSessionExpired); const clearSessionExpired = useAuthStore((state) => state.clearSessionExpired); const currentProjectId = useProjectStore((state) => state.currentProjectId); const permissions = useAccessStore((state) => state.permissions); const setAccessContext = useAccessStore((state) => state.setContext); const setAccessLoading = useAccessStore((state) => state.setLoading); const resetAccess = useAccessStore((state) => state.reset); const can = (permission: string) => permissions.includes(permission); useEffect(() => { setAccessToken( typeof data?.accessToken === "string" ? data.accessToken : null, ); }, [data?.accessToken, setAccessToken]); useEffect(() => { if (data?.error === "SessionExpired") { markSessionExpired("session_max_age"); return; } if (data?.error === "RefreshAccessTokenError") { markSessionExpired("refresh_failed"); return; } if (status === "authenticated") { clearSessionExpired(); } }, [clearSessionExpired, data?.error, markSessionExpired, status]); useEffect(() => { if (status !== "authenticated") { resetAccess(); return; } let cancelled = false; setAccessLoading(true); apiFetch(`${config.BACKEND_URL}/api/v1/access-context`, { projectHeaderMode: currentProjectId ? "include" : "omit", skipAuthRedirect: true, }) .then(async (response) => { if (cancelled) return; if (!response.ok) { resetAccess(); return; } setAccessContext(await response.json()); }) .catch(() => { if (!cancelled) resetAccess(); }); return () => { cancelled = true; }; }, [ currentProjectId, resetAccess, setAccessContext, setAccessLoading, status, ]); useEffect(() => { if (status !== "authenticated" || !data?.user?.id) return; const auditKey = `tjwater-login-audit:${data.user.id}`; if (sessionStorage.getItem(auditKey)) return; apiFetch(`${config.BACKEND_URL}/api/v1/audit-events`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ event: "login" }), projectHeaderMode: "omit", skipAuthRedirect: true, }) .then((response) => { if (response.ok) sessionStorage.setItem(auditKey, "1"); }) .catch(() => undefined); }, [data?.user?.id, status]); if (status === "loading") { return loading...; } const authProvider: AuthProvider = { login: async () => { signIn("keycloak", { callbackUrl: to ? to.toString() : "/", redirect: true, }); return { success: true }; }, logout: () => completeLogout({ reportAudit: () => reportLogoutAudit({ endpoint: `${config.BACKEND_URL}/api/v1/audit-events`, accessToken: typeof data?.accessToken === "string" ? data.accessToken : useAuthStore.getState().accessToken, }), clearLocalState: () => { if (data?.user?.id) { sessionStorage.removeItem(`tjwater-login-audit:${data.user.id}`); } clearSessionRecoveryDrafts(); }, navigate: (path) => window.location.assign(path), }), onError: async (error) => { return { error }; }, check: async () => status === "unauthenticated" ? { authenticated: false, redirectTo: "/login" } : { authenticated: true }, getPermissions: async () => permissions, getIdentity: async () => { if (!data?.user) return null; return { id: data.user.id, username: data.user.username, name: data.user.name, avatar: data.user.image, }; }, }; const accessControlProvider: AccessControlProvider = { can: async ({ resource }) => { const requiredPermission = resource ? resourcePermissions[resource] : undefined; return { can: !requiredPermission || permissions.includes(requiredPermission), reason: requiredPermission ? `需要权限:${requiredPermission}` : undefined, }; }, }; const resources = [ ...(can(permissionCodes.simulationView) ? [ { name: "管网在线模拟", list: "/network-simulation", meta: { icon: , label: "管网在线模拟", }, }, ] : []), ...(can(permissionCodes.scadaClean) ? [ { name: "SCADA 数据清洗", list: "/scada-data-cleaning", meta: { icon: , label: "SCADA 数据清洗", }, }, ] : []), ...(can(permissionCodes.optimizationRun) ? [ { name: "监测点优化布置", list: "/monitoring-place-optimization", meta: { icon: , label: "监测点优化布置", }, }, ] : []), ...(can(permissionCodes.riskRun) ? [ { name: "健康风险分析", list: "/health-risk-analysis", meta: { icon: , label: "健康风险分析", }, }, ] : []), ...(can(permissionCodes.simulationRun) || can(permissionCodes.burstRun) ? [ { name: "Hydraulic Simulation", meta: { label: "事件模拟" }, }, ] : []), ...(can(permissionCodes.burstRun) ? [ { name: "爆管模拟", list: "/hydraulic-simulation/burst-simulation", meta: { parent: "Hydraulic Simulation", icon: , label: "爆管模拟", }, }, { name: "爆管侦测", list: "/hydraulic-simulation/burst-detection", meta: { parent: "Hydraulic Simulation", icon: , label: "爆管侦测", }, }, { name: "爆管定位", list: "/hydraulic-simulation/burst-location", meta: { parent: "Hydraulic Simulation", icon: , label: "爆管定位", }, }, { name: "DMA 漏损识别", list: "/hydraulic-simulation/dma-leak-detection", meta: { parent: "Hydraulic Simulation", icon: , label: "DMA 漏损识别", }, }, ] : []), ...(can(permissionCodes.simulationRun) ? [ { name: "水质模拟", list: "/hydraulic-simulation/contaminant-simulation", meta: { parent: "Hydraulic Simulation", icon: , label: "水质模拟", }, }, { name: "管道冲洗", list: "/hydraulic-simulation/flushing-analysis", meta: { parent: "Hydraulic Simulation", icon: , label: "管道冲洗", }, }, ] : []), ...(can(permissionCodes.environmentManage) ? [ { name: "系统管理", list: "/system-admin", meta: { icon: , label: "系统管理", }, }, ] : []), ...(can(permissionCodes.auditView) ? [ { name: "审计日志", list: "/audit-logs", meta: { icon: , label: "审计日志", }, }, ] : []), ]; return ( {props.children} ); };