fix: track live downstream cmpp connections

This commit is contained in:
hectorzhao
2026-07-11 19:16:52 +08:00
parent 76e0417ccf
commit 2fea15ef25
21 changed files with 477 additions and 141 deletions
+40 -20
View File
@@ -1,7 +1,8 @@
import { useEffect, useMemo, useState } from 'react';
import { Plus, Search } from 'lucide-react';
import { Database, ListFilter, Plus, RotateCcw, Search } from 'lucide-react';
import { Breadcrumb, Button, Input, Modal, Pagination, Select, Table, Tabs, type TableColumn } from '@/components/ui';
import { adminApi, type DictionaryItem } from '@/api/adminApi';
import { formatDateTime } from '@/utils/dateTime';
type PhoneSegment = DictionaryItem & {
prefix?: string;
@@ -42,20 +43,12 @@ export function AdminPhoneSegmentsPage() {
const [rulePage, setRulePage] = useState(1);
const [reloadKey, setReloadKey] = useState(0);
useEffect(() => {
const timer = window.setTimeout(() => {
setSegmentQuery(keyword.trim());
setPage(1);
}, 300);
return () => window.clearTimeout(timer);
}, [keyword]);
useEffect(() => {
let cancelled = false;
setLoading(true);
Promise.all([
adminApi.listPhoneSegments({ keyword: segmentQuery || undefined, page, pageSize }),
adminApi.listPhoneCarrierRules({ keyword: activeTab === 'rules' ? segmentQuery || undefined : undefined, page: rulePage, pageSize }),
adminApi.listPhoneCarrierRules({ keyword: segmentQuery || undefined, page: rulePage, pageSize }),
])
.then(([segmentPage, ruleResponse]) => {
if (cancelled) return;
@@ -74,7 +67,7 @@ export function AdminPhoneSegmentsPage() {
return () => {
cancelled = true;
};
}, [activeTab, page, reloadKey, rulePage, segmentQuery]);
}, [page, reloadKey, rulePage, segmentQuery]);
const segmentTotalPages = Math.max(1, Math.ceil(segmentTotal / pageSize));
const ruleTotalPages = Math.max(1, Math.ceil(ruleTotal / pageSize));
@@ -103,12 +96,25 @@ export function AdminPhoneSegmentsPage() {
.catch((failure: Error) => setError(failure.message || '运营商区分规则新增失败'));
}
function query() {
setSegmentQuery(keyword.trim());
setPage(1);
setRulePage(1);
}
function reset() {
setKeyword('');
setSegmentQuery('');
setPage(1);
setRulePage(1);
}
const columns = useMemo<Array<TableColumn<PhoneSegment>>>(() => [
{ key: 'segment', title: '手机号段(手机号码前7位)', width: '230px', render: (record) => <strong>{record.prefix}</strong> },
{ key: 'carrier', title: '运营商', width: '150px', render: (record) => record.carrier ?? '-' },
{ key: 'province', title: '省份', width: '140px', render: (record) => record.province ?? '-' },
{ key: 'city', title: '城市', width: '140px', render: (record) => record.city ?? '-' },
{ key: 'createdAt', title: '创建时间', width: '190px', render: (record) => record.createdAt ?? '-' },
{ key: 'createdAt', title: '创建时间', width: '190px', render: (record) => formatDateTime(record.createdAt) },
], []);
const ruleColumns = useMemo<Array<TableColumn<CarrierRule>>>(() => [
@@ -119,28 +125,42 @@ export function AdminPhoneSegmentsPage() {
], []);
return (
<section className="page-stack admin-system-page">
<section className="page-stack admin-system-page phone-segment-workbench">
<div className="page-heading">
<div>
<Breadcrumb items={['系统管理', '手机号段库']} />
<h1></h1>
</div>
</div>
{error ? <p className="form-error">{error}</p> : null}
<div className="surface admin-system-toolbar phone-segment-toolbar">
<Input onChange={(event) => setKeyword(event.target.value)} placeholder={activeTab === 'segments' ? '搜索手机号段、运营商、省份或城市' : '搜索运营商、正则或备注'} prefix={<Search size={16} />} value={keyword} />
<Button icon={<Plus size={16} />} onClick={() => activeTab === 'segments' ? setCreating(true) : setCreatingRule(true)}>
{activeTab === 'segments' ? '新增号段' : '新增规则'}
</Button>
</div>
{error ? <p className="form-error">{error}</p> : null}
<div className="phone-segment-overview" aria-label="号段数据概览">
<section>
<span><Database size={20} /></span>
<div><strong>{segmentTotal.toLocaleString('zh-CN')}</strong><p></p></div>
</section>
<section>
<span><ListFilter size={20} /></span>
<div><strong>{ruleTotal.toLocaleString('zh-CN')}</strong><p></p></div>
</section>
</div>
<div className="surface phone-segment-query">
<Input label="关键词" onChange={(event) => setKeyword(event.target.value)} placeholder={activeTab === 'segments' ? '手机号段、运营商、省份或城市' : '运营商、正则或备注'} prefix={<Search size={16} />} value={keyword} />
<div className="phone-segment-query__actions">
<Button icon={<Search size={16} />} onClick={query}></Button>
<Button icon={<RotateCcw size={16} />} onClick={reset} variant="ghost"></Button>
</div>
</div>
<div className="surface admin-system-table-card">
<Tabs
className="phone-segment-tabs"
className="phone-segment-workbench__tabs"
onChange={(value) => {
setActiveTab(value as 'segments' | 'rules');
setRulePage(1);
}}
value={activeTab}
items={[