Files
lislgosms/src/layouts/AdminLayout.tsx
T

271 lines
11 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react';
import {
Activity,
AlertTriangle,
BarChart3,
Building2,
FileCheck2,
FileText,
FileSpreadsheet,
FilePenLine,
Gauge,
Hash,
ImageIcon,
ListChecks,
Layers3,
MessageSquare,
Phone,
RefreshCw,
TrendingUp,
RadioTower,
ReceiptText,
ClipboardList,
Send,
ServerCog,
ScanSearch,
Settings,
Shield,
ShieldOff,
ShieldCheck,
Users,
UserX,
} from 'lucide-react';
import { adminApi, type PendingAuditCounts } from '@/api/adminApi';
import { getLastUserActivityAt, readSession, type LoginSession } from '@/api/session';
import { AppShell } from '@/layouts/AppShell';
import { PortalSessionBoundary } from '@/layouts/PortalSessionBoundary';
const EMPTY_PENDING_AUDITS: Omit<PendingAuditCounts, 'total'> = {
enterpriseCertifications: 0,
smsAudits: 0,
templates: 0,
signatures: 0,
drainageInfos: 0,
};
export function AdminLayout() {
return (
<PortalSessionBoundary portal="admin">
{(session) => <AdminAuthenticatedLayout session={session} />}
</PortalSessionBoundary>
);
}
function AdminAuthenticatedLayout({ session }: { session: LoginSession }) {
const [pendingAudits, setPendingAudits] = useState(EMPTY_PENDING_AUDITS);
const [retirementUnreadCount, setRetirementUnreadCount] = useState(0);
const [securityAlertSummary, setSecurityAlertSummary] = useState({ count: 0, criticalCount: 0 });
const [infrastructureAlertSummary, setInfrastructureAlertSummary] = useState({ count: 0, criticalCount: 0 });
const [sessionLocked, setSessionLocked] = useState(Boolean(session.locked));
const loadPendingAuditCount = useCallback(() => {
const currentSession = readSession('admin');
if (
!currentSession ||
currentSession.locked ||
Date.now() - getLastUserActivityAt() >= currentSession.idleTimeoutSeconds * 1000
) {
return;
}
// This runs globally and on a timer, so it must not fan out through the full dashboard aggregation.
Promise.allSettled([
adminApi.getPendingAudits(),
adminApi.getSignatureRetirementUnreadCount(),
adminApi.getSecurityNotificationSummary(),
adminApi.getInfrastructureMonitoringNotificationSummary(),
])
.then(([audits, retirement, security, infrastructure]) => {
setPendingAudits(audits.status === 'fulfilled' ? audits.value : EMPTY_PENDING_AUDITS);
setRetirementUnreadCount(retirement.status === 'fulfilled' ? retirement.value.count : 0);
setSecurityAlertSummary(security.status === 'fulfilled' ? security.value : { count: 0, criticalCount: 0 });
setInfrastructureAlertSummary(
infrastructure.status === 'fulfilled' ? infrastructure.value : { count: 0, criticalCount: 0 },
);
})
.catch(() => {
setPendingAudits(EMPTY_PENDING_AUDITS);
setRetirementUnreadCount(0);
setSecurityAlertSummary({ count: 0, criticalCount: 0 });
setInfrastructureAlertSummary({ count: 0, criticalCount: 0 });
});
}, []);
useEffect(() => {
if (session.portal !== 'admin' || sessionLocked) {
return;
}
loadPendingAuditCount();
const timer = window.setInterval(loadPendingAuditCount, 30000);
const onFocus = () => loadPendingAuditCount();
const onAuditRefresh = () => loadPendingAuditCount();
window.addEventListener('focus', onFocus);
window.addEventListener('cmpp-audit-count-refresh', onAuditRefresh);
window.addEventListener('cmpp-retirement-count-refresh', onAuditRefresh);
window.addEventListener('cmpp-security-alert-count-refresh', onAuditRefresh);
window.addEventListener('cmpp-infrastructure-alert-count-refresh', onAuditRefresh);
return () => {
window.clearInterval(timer);
window.removeEventListener('focus', onFocus);
window.removeEventListener('cmpp-audit-count-refresh', onAuditRefresh);
window.removeEventListener('cmpp-retirement-count-refresh', onAuditRefresh);
window.removeEventListener('cmpp-security-alert-count-refresh', onAuditRefresh);
window.removeEventListener('cmpp-infrastructure-alert-count-refresh', onAuditRefresh);
};
}, [loadPendingAuditCount, session.portal, sessionLocked]);
return (
<AppShell
title="CMPP 运营端"
subtitle="平台运营管理中心"
workspaceName="平台运营工作区"
loginPath="/admin/login"
portal="admin"
userName={session.user.displayName}
userRole="平台管理员"
onSessionLockedChange={setSessionLocked}
alertNotifications={[
{
label: '签名清退预警',
count: retirementUnreadCount,
description: '今日未读且未抑制',
to: '/admin/signature-retirement',
},
{
label: '安全检测与封禁',
count: securityAlertSummary.count,
description:
securityAlertSummary.criticalCount > 0
? `${securityAlertSummary.criticalCount} 条严重告警待处置`
: '待处置安全告警',
to: '/admin/security-detection',
},
{
label: '系统监控告警',
count: infrastructureAlertSummary.count,
description:
infrastructureAlertSummary.criticalCount > 0
? `${infrastructureAlertSummary.criticalCount} 条 Prometheus 严重告警`
: 'Prometheus 活动告警',
to: '/admin/system-monitoring#active-alerts',
},
]}
auditNotifications={[
{ label: '企业认证待审', count: pendingAudits.enterpriseCertifications, to: '/admin/enterprise-audit' },
{ label: '短信审核待审', count: pendingAudits.smsAudits, to: '/admin/sms-audit' },
{ label: '模板待审', count: pendingAudits.templates, to: '/admin/templates' },
{ label: '签名待审', count: pendingAudits.signatures, to: '/admin/signatures' },
{
label: '签名导入待审',
count: pendingAudits.signatureImports ?? 0,
to: '/admin/signatures?tab=import',
},
{ label: '引流信息待审', count: pendingAudits.drainageInfos, to: '/admin/drainage-audits' },
]}
navSections={[
{
title: '运营概览',
icon: Gauge,
items: [
{ label: '运营看板', to: '/admin', icon: Gauge },
{ label: '发送监控', to: '/admin/monitor', icon: Activity },
{ label: '网关异常', to: '/admin/gateway-submit-exceptions', icon: AlertTriangle },
{ label: '签名质量检测', to: '/admin/analytics', icon: BarChart3 },
],
},
{
title: '客户管理',
icon: Building2,
items: [
{ label: '企业管理', to: '/admin/customer-enterprises', icon: Building2 },
{ label: '企业应用管理', to: '/admin/enterprise-applications', icon: Layers3 },
{ label: '企业签名管理', to: '/admin/enterprise-signatures', icon: FilePenLine },
{ label: '企业模板管理', to: '/admin/enterprise-templates', icon: FileCheck2 },
],
},
{
title: '审核中心',
icon: FileCheck2,
items: [
{ label: '企业认证审核', to: '/admin/enterprise-audit', icon: ShieldCheck },
{ label: '短信审核', to: '/admin/sms-audit', icon: MessageSquare },
{ label: '短信模板审核', to: '/admin/templates', icon: FileCheck2 },
{ label: '短信签名审核', to: '/admin/signatures', icon: FilePenLine },
{ label: '引流信息审核', to: '/admin/drainage-audits', icon: FilePenLine },
],
},
{
title: '通道管理',
icon: RadioTower,
items: [
{ label: '短信通道管理', to: '/admin/channels', icon: RadioTower },
{ label: '彩信通道管理', to: '/admin/mms-channels', icon: ImageIcon, pending: true },
{ label: '短信通道组管理', to: '/admin/channel-groups', icon: Layers3 },
],
},
{
title: '报备工作台',
icon: ClipboardList,
items: [
{ label: '报备资料池', to: '/admin/report-materials', icon: FileSpreadsheet },
{ label: '报备批次', to: '/admin/report-batches', icon: Layers3 },
{ label: '通道报备明细', to: '/admin/report-tasks', icon: ClipboardList },
{ label: '状态记录', to: '/admin/report-records', icon: ListChecks },
],
},
{
title: '发送任务',
icon: Send,
items: [
{ label: '短信任务进度', to: '/admin/sms-task-progress', icon: TrendingUp },
{ label: '彩信任务进度', to: '/admin/mms-task-progress', icon: BarChart3, pending: true },
],
},
{
title: '数据详单',
icon: ReceiptText,
items: [
{ label: '短信记录', to: '/admin/sms-records', icon: MessageSquare },
{ label: '彩信记录', to: '/admin/mms-records', icon: ImageIcon, pending: true },
{ label: '短信上行记录', to: '/admin/sms-uplink-records', icon: MessageSquare },
{ label: '下游投递记录', to: '/admin/downstream-deliveries', icon: Send },
{ label: '恢复状态管理', to: '/admin/downstream-recovery-statuses', icon: RefreshCw },
{ label: '充值记录', to: '/admin/recharge-records', icon: ReceiptText },
],
},
{
title: '报表对账',
icon: BarChart3,
items: [
{ label: '对账单', to: '/admin/reconciliation-reports', icon: ReceiptText },
{ label: '利润报表', to: '/admin/profit-reports', icon: TrendingUp },
{ label: '发送质量报表', to: '/admin/quality-reports', icon: BarChart3 },
],
},
{
title: '安全控制',
icon: Shield,
items: [
{ label: '风控规则', to: '/admin/risk-rules', icon: Shield },
{ label: '安全检测与封禁', to: '/admin/security-detection', icon: ShieldCheck },
{ label: '签名清退预警', to: '/admin/signature-retirement', icon: AlertTriangle },
{ label: '企业黑名单', to: '/admin/enterprise-blacklist', icon: UserX },
{ label: '全局黑名单', to: '/admin/global-blacklist', icon: ShieldOff },
{ label: '敏感词管理', to: '/admin/sensitive-words', icon: Shield },
],
},
{
title: '系统管理',
icon: Settings,
items: [
{ label: '用户管理', to: '/admin/users', icon: Users },
{ label: '手机号段库', to: '/admin/phone-segments', icon: Phone },
{ label: '报备字段库', to: '/admin/drainage-fields', icon: Hash },
{ label: '引流识别规则', to: '/admin/drainage-detection-rules', icon: ScanSearch },
{ label: '系统日志', to: '/admin/system-logs', icon: FileText },
{ label: '系统监控', to: '/admin/system-monitoring', icon: ServerCog },
],
},
]}
/>
);
}