release: bump version to 0.27.5
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "planet-frontend",
|
||||
"version": "0.27.4",
|
||||
"version": "0.27.5",
|
||||
"private": true,
|
||||
"packageManager": "bun@1",
|
||||
"dependencies": {
|
||||
|
||||
@@ -265,6 +265,7 @@ function Scrollbar({
|
||||
{scrollbar.y.visible ? (
|
||||
<div
|
||||
className="scrollbar__thumb scrollbar__thumb--y"
|
||||
tabIndex={0}
|
||||
style={{
|
||||
height: `${scrollbar.y.thumbSize}px`,
|
||||
transform: `translateY(${scrollbar.y.thumbOffset}px)`,
|
||||
@@ -284,6 +285,7 @@ function Scrollbar({
|
||||
{scrollbar.x.visible ? (
|
||||
<div
|
||||
className="scrollbar__thumb scrollbar__thumb--x"
|
||||
tabIndex={0}
|
||||
style={{
|
||||
width: `${scrollbar.x.thumbSize}px`,
|
||||
transform: `translateX(${scrollbar.x.thumbOffset}px)`,
|
||||
|
||||
37
frontend/src/components/Scrollbar/TableScrollRegion.tsx
Normal file
37
frontend/src/components/Scrollbar/TableScrollRegion.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { forwardRef, useImperativeHandle, useRef, type CSSProperties, type ReactNode } from 'react'
|
||||
|
||||
import ScrollbarOverlay from './ScrollbarOverlay'
|
||||
|
||||
interface TableScrollRegionProps {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
style?: CSSProperties
|
||||
targetSelector?: string
|
||||
}
|
||||
|
||||
const TableScrollRegion = forwardRef<HTMLDivElement, TableScrollRegionProps>(function TableScrollRegion(
|
||||
{
|
||||
children,
|
||||
className = '',
|
||||
style,
|
||||
targetSelector = '.ant-table-body',
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useImperativeHandle(ref, () => containerRef.current as HTMLDivElement, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={['table-scroll-region', className].filter(Boolean).join(' ')}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
<ScrollbarOverlay containerRef={containerRef} targetSelector={targetSelector} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default TableScrollRegion
|
||||
@@ -140,7 +140,8 @@ body {
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
transition: opacity 0.18s ease, background 0.18s ease;
|
||||
outline: none;
|
||||
transition: opacity 0.18s ease, background 0.18s ease, box-shadow 0.18s ease;
|
||||
}
|
||||
|
||||
.scrollbar__thumb::after {
|
||||
@@ -173,6 +174,7 @@ body {
|
||||
}
|
||||
|
||||
.scrollbar:hover .scrollbar__track--visible .scrollbar__thumb,
|
||||
.scrollbar:focus-within .scrollbar__track--visible .scrollbar__thumb,
|
||||
.scrollbar__track--visible .scrollbar__thumb,
|
||||
.scrollbar__track--dragging .scrollbar__thumb {
|
||||
opacity: 1;
|
||||
@@ -182,12 +184,15 @@ body {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.scrollbar__thumb:hover {
|
||||
background: rgba(216, 226, 240, 0.68);
|
||||
.scrollbar__thumb:hover,
|
||||
.scrollbar__thumb:focus-visible {
|
||||
background: rgba(125, 146, 174, 0.82);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.scrollbar__track--dragging .scrollbar__thumb {
|
||||
background: rgba(226, 235, 246, 0.82);
|
||||
background: rgba(92, 115, 146, 0.9);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.dashboard-brand {
|
||||
@@ -398,9 +403,11 @@ body {
|
||||
|
||||
.playground-card__scroll {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.playground-card__scroll .scrollbar__viewport {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(148, 163, 184, 0.88) transparent;
|
||||
}
|
||||
|
||||
.playground-card__scroll::-webkit-scrollbar,
|
||||
@@ -1251,25 +1258,21 @@ body {
|
||||
.playground-result-modal__content {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding-right: 6px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(148, 163, 184, 0.88) transparent;
|
||||
}
|
||||
|
||||
.playground-result-modal__content::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
.playground-result-modal__content .scrollbar__viewport {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.playground-result-modal__content::-webkit-scrollbar-thumb {
|
||||
background: rgba(148, 163, 184, 0.82);
|
||||
border-radius: 999px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: padding-box;
|
||||
.playground-result__blocks-scroll .scrollbar__viewport {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.playground-result-modal__content::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
.playground-result__blocks-scroll.scrollbar {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
@@ -1583,14 +1586,14 @@ body {
|
||||
padding: 10px 12px !important;
|
||||
}
|
||||
|
||||
.data-source-table-region .ant-table-body,
|
||||
.data-source-table-region .ant-table-content {
|
||||
.table-scroll-region .ant-table-body,
|
||||
.table-scroll-region .ant-table-content {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.data-source-table-region .ant-table-body::-webkit-scrollbar,
|
||||
.data-source-table-region .ant-table-content::-webkit-scrollbar {
|
||||
.table-scroll-region .ant-table-body::-webkit-scrollbar,
|
||||
.table-scroll-region .ant-table-content::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
@@ -1757,7 +1760,10 @@ body {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding-right: 4px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(148, 163, 184, 0.88) transparent;
|
||||
@@ -1836,10 +1842,11 @@ body {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
max-height: calc(100vh - 180px);
|
||||
overflow: auto;
|
||||
padding-right: 6px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(148, 163, 184, 0.88) transparent;
|
||||
}
|
||||
|
||||
.bgp-page__brief-modal-body .scrollbar__viewport {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.bgp-page__brief-evidence {
|
||||
@@ -1857,6 +1864,10 @@ body {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.alerts-brief-drawer .scrollbar__viewport {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.alerts-brief-drawer__loading {
|
||||
min-height: 160px;
|
||||
display: flex;
|
||||
@@ -1887,8 +1898,17 @@ body {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.alerts-tab-panel > .ant-space-item,
|
||||
.system-alerts-page__stack > .ant-space-item,
|
||||
.bgp-alerts-page__stack > .ant-space-item,
|
||||
.situational-alerts-page__stack > .ant-space-item {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.system-alerts-page__table-card,
|
||||
@@ -2128,29 +2148,71 @@ body {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.bgp-page__summary-grid--compact {
|
||||
flex-wrap: nowrap !important;
|
||||
.bgp-page__summary-scroll.scrollbar,
|
||||
.alerts-summary-scroll.scrollbar {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bgp-page__summary-scroll .scrollbar__viewport,
|
||||
.alerts-summary-scroll .scrollbar__viewport {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding-bottom: 4px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(148, 163, 184, 0.82) transparent;
|
||||
}
|
||||
|
||||
.bgp-page__summary-grid--compact::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
.bgp-page__summary-grid,
|
||||
.alerts-summary-grid {
|
||||
display: flex;
|
||||
flex-wrap: nowrap !important;
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.bgp-page__summary-grid--compact::-webkit-scrollbar-thumb {
|
||||
background: rgba(148, 163, 184, 0.82);
|
||||
border-radius: 999px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: padding-box;
|
||||
.bgp-page__summary-cell,
|
||||
.alerts-summary-cell {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.bgp-page__summary-grid--compact::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
.situational-alerts-page__summary-grid {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.situational-alerts-page__panels-grid {
|
||||
display: flex;
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.situational-alerts-page__panels-scroll .scrollbar__viewport {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.situational-alerts-page__summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(220px, 1fr));
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.situational-alerts-page__summary-grid .alerts-summary-cell {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.situational-alerts-page__summary-grid {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
width: auto;
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.situational-alerts-page__summary-grid .alerts-summary-cell {
|
||||
width: 220px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.bgp-page__table-card,
|
||||
@@ -2408,25 +2470,21 @@ body {
|
||||
.settings-panel-scroll {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.settings-panel-scroll.scrollbar {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.settings-panel-scroll .scrollbar__viewport {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 6px;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.settings-panel-scroll::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.settings-panel-scroll::-webkit-scrollbar-thumb {
|
||||
background: rgba(148, 163, 184, 0.8);
|
||||
border-radius: 999px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.settings-panel-scroll::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
.settings-panel-scroll .scrollbar__viewport > * {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-pane .data-source-table-region .ant-table-container {
|
||||
@@ -3273,7 +3331,6 @@ body {
|
||||
|
||||
.dashboard-restart-log {
|
||||
max-height: 180px;
|
||||
overflow-y: auto;
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
background: #0f172a;
|
||||
@@ -3281,16 +3338,11 @@ body {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.dashboard-restart-log::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.dashboard-restart-log::-webkit-scrollbar-thumb {
|
||||
border-radius: 999px;
|
||||
background: rgba(148, 163, 184, 0.55);
|
||||
.dashboard-restart-log .scrollbar__viewport {
|
||||
overflow-y: auto;
|
||||
max-height: 180px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
@@ -5,10 +5,8 @@ import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Descriptions,
|
||||
Modal,
|
||||
Row,
|
||||
Space,
|
||||
Spin,
|
||||
Statistic,
|
||||
@@ -21,6 +19,8 @@ import {
|
||||
} from 'antd'
|
||||
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
import Scrollbar from '../../components/Scrollbar/Scrollbar'
|
||||
import TableScrollRegion from '../../components/Scrollbar/TableScrollRegion'
|
||||
import type { BGPAnomaly, BGPBriefRecord, BGPIncident } from '../../services/situational-awareness'
|
||||
import { getSituationalAwarenessGateway } from '../../services/situational-awareness'
|
||||
import { formatDateTimeZhCN } from '../../utils/datetime'
|
||||
@@ -171,20 +171,22 @@ export function BGPAlertsPanel() {
|
||||
|
||||
<Alert type="info" showIcon message="这里聚焦 BGP 风险信号本身,不等同于系统平台运行告警。" />
|
||||
|
||||
<Row gutter={[12, 12]}>
|
||||
<Col xs={24} sm={12} lg={6}>
|
||||
<Card><Statistic title="活跃事件" value={summary.activeIncidents} /></Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={6}>
|
||||
<Card><Statistic title="严重事件" value={summary.criticalIncidents} valueStyle={{ color: '#ff4d4f' }} /></Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={6}>
|
||||
<Card><Statistic title="活跃异常" value={summary.activeAnomalies} /></Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={6}>
|
||||
<Card><Statistic title="高风险异常" value={summary.highRiskAnomalies} valueStyle={{ color: '#fa8c16' }} /></Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Scrollbar className="alerts-summary-scroll bgp-alerts-page__summary-scroll">
|
||||
<div className="alerts-summary-grid" style={{ gap: '12px' }}>
|
||||
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
||||
<Card><Statistic title="活跃事件" value={summary.activeIncidents} /></Card>
|
||||
</div>
|
||||
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
||||
<Card><Statistic title="严重事件" value={summary.criticalIncidents} valueStyle={{ color: '#ff4d4f' }} /></Card>
|
||||
</div>
|
||||
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
||||
<Card><Statistic title="活跃异常" value={summary.activeAnomalies} /></Card>
|
||||
</div>
|
||||
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
||||
<Card><Statistic title="高风险异常" value={summary.highRiskAnomalies} valueStyle={{ color: '#fa8c16' }} /></Card>
|
||||
</div>
|
||||
</div>
|
||||
</Scrollbar>
|
||||
|
||||
<Card className="bgp-alerts-page__table-card">
|
||||
<Tabs
|
||||
@@ -194,7 +196,7 @@ export function BGPAlertsPanel() {
|
||||
key: 'incidents',
|
||||
label: 'BGP 事件',
|
||||
children: (
|
||||
<div className="table-scroll-region bgp-alerts-page__table-region">
|
||||
<TableScrollRegion className="bgp-alerts-page__table-region">
|
||||
<Table<BGPIncident>
|
||||
columns={incidentColumns}
|
||||
dataSource={incidents}
|
||||
@@ -204,14 +206,14 @@ export function BGPAlertsPanel() {
|
||||
scroll={{ x: 1200, y: 480 }}
|
||||
tableLayout="fixed"
|
||||
/>
|
||||
</div>
|
||||
</TableScrollRegion>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'anomalies',
|
||||
label: 'BGP 异常',
|
||||
children: (
|
||||
<div className="table-scroll-region bgp-alerts-page__table-region">
|
||||
<TableScrollRegion className="bgp-alerts-page__table-region">
|
||||
<Table<BGPAnomaly>
|
||||
columns={anomalyColumns}
|
||||
dataSource={anomalies}
|
||||
@@ -221,7 +223,7 @@ export function BGPAlertsPanel() {
|
||||
scroll={{ x: 1100, y: 480 }}
|
||||
tableLayout="fixed"
|
||||
/>
|
||||
</div>
|
||||
</TableScrollRegion>
|
||||
),
|
||||
},
|
||||
]}
|
||||
@@ -244,14 +246,14 @@ export function BGPAlertsPanel() {
|
||||
<Spin tip="正在生成 BGP AI 简报..." />
|
||||
</div>
|
||||
) : brief ? (
|
||||
<div className="bgp-page__brief-modal-body">
|
||||
<Scrollbar className="bgp-page__brief-modal-body">
|
||||
<Descriptions size="small" column={3} className="bgp-page__brief-meta">
|
||||
<Descriptions.Item label="Provider">{brief.provider || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="模型">{brief.model || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="生成时间">{formatDateTimeZhCN(brief.generated_at)}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
<Typography.Paragraph className="alerts-brief-content">{brief.content_markdown}</Typography.Paragraph>
|
||||
</div>
|
||||
</Scrollbar>
|
||||
) : (
|
||||
<Text type="secondary">当前没有可查看的 BGP 简报。</Text>
|
||||
)}
|
||||
|
||||
@@ -5,10 +5,8 @@ import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Descriptions,
|
||||
Drawer,
|
||||
Row,
|
||||
Space,
|
||||
Spin,
|
||||
Statistic,
|
||||
@@ -18,6 +16,7 @@ import {
|
||||
import axios from 'axios'
|
||||
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
import Scrollbar from '../../components/Scrollbar/Scrollbar'
|
||||
import type { BGPSummarySnapshot } from '../../services/situational-awareness'
|
||||
import {
|
||||
getSituationalAwarenessGateway,
|
||||
@@ -119,42 +118,46 @@ export function SituationalAlertsPanel() {
|
||||
message="态势告警不是单一模块列表,而是把系统告警与 BGP 风险综合成一份值班研判入口。"
|
||||
/>
|
||||
|
||||
<Row gutter={[12, 12]}>
|
||||
<Col xs={24} sm={12} lg={6}>
|
||||
<Card><Statistic title="活跃系统告警" value={summary.activeSystemAlerts} prefix={<WarningOutlined />} /></Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={6}>
|
||||
<Card><Statistic title="严重系统告警" value={summary.criticalSystemAlerts} valueStyle={{ color: '#ff4d4f' }} /></Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={6}>
|
||||
<Card><Statistic title="活跃 BGP 事件" value={summary.activeBGPIncidents} prefix={<DeploymentUnitOutlined />} /></Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={6}>
|
||||
<Card><Statistic title="严重 BGP 事件" value={summary.criticalBGPIncidents} valueStyle={{ color: '#fa8c16' }} /></Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Scrollbar className="alerts-summary-scroll situational-alerts-page__summary-scroll">
|
||||
<div className="alerts-summary-grid situational-alerts-page__summary-grid" style={{ gap: '12px' }}>
|
||||
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
||||
<Card><Statistic title="活跃系统告警" value={summary.activeSystemAlerts} prefix={<WarningOutlined />} /></Card>
|
||||
</div>
|
||||
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
||||
<Card><Statistic title="严重系统告警" value={summary.criticalSystemAlerts} valueStyle={{ color: '#ff4d4f' }} /></Card>
|
||||
</div>
|
||||
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
||||
<Card><Statistic title="活跃 BGP 事件" value={summary.activeBGPIncidents} prefix={<DeploymentUnitOutlined />} /></Card>
|
||||
</div>
|
||||
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
||||
<Card><Statistic title="严重 BGP 事件" value={summary.criticalBGPIncidents} valueStyle={{ color: '#fa8c16' }} /></Card>
|
||||
</div>
|
||||
</div>
|
||||
</Scrollbar>
|
||||
|
||||
<Row gutter={[12, 12]}>
|
||||
<Col xs={24} lg={12}>
|
||||
<Card title="系统告警侧">
|
||||
<Descriptions size="small" column={1}>
|
||||
<Descriptions.Item label="严重">{String(systemStats?.critical ?? '-')}</Descriptions.Item>
|
||||
<Descriptions.Item label="警告">{String(systemStats?.warning ?? '-')}</Descriptions.Item>
|
||||
<Descriptions.Item label="信息">{String(systemStats?.info ?? '-')}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} lg={12}>
|
||||
<Card title="BGP 风险侧">
|
||||
<Descriptions size="small" column={1}>
|
||||
<Descriptions.Item label="活跃事件">{String(bgpSummary?.incidentSummary?.by_status?.active ?? '-')}</Descriptions.Item>
|
||||
<Descriptions.Item label="严重事件">{String(bgpSummary?.incidentSummary?.by_severity?.critical ?? '-')}</Descriptions.Item>
|
||||
<Descriptions.Item label="活跃观测站">{String(bgpSummary?.collectorSummary?.active_collectors ?? '-')}</Descriptions.Item>
|
||||
<Descriptions.Item label="近24h事件">{String(bgpSummary?.collectorSummary?.recent_24h_events ?? '-')}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Scrollbar className="alerts-summary-scroll situational-alerts-page__panels-scroll">
|
||||
<div className="alerts-summary-grid situational-alerts-page__panels-grid" style={{ gap: '12px' }}>
|
||||
<div className="alerts-summary-cell" style={{ width: '360px' }}>
|
||||
<Card title="系统告警侧">
|
||||
<Descriptions size="small" column={1}>
|
||||
<Descriptions.Item label="严重">{String(systemStats?.critical ?? '-')}</Descriptions.Item>
|
||||
<Descriptions.Item label="警告">{String(systemStats?.warning ?? '-')}</Descriptions.Item>
|
||||
<Descriptions.Item label="信息">{String(systemStats?.info ?? '-')}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="alerts-summary-cell" style={{ width: '360px' }}>
|
||||
<Card title="BGP 风险侧">
|
||||
<Descriptions size="small" column={1}>
|
||||
<Descriptions.Item label="活跃事件">{String(bgpSummary?.incidentSummary?.by_status?.active ?? '-')}</Descriptions.Item>
|
||||
<Descriptions.Item label="严重事件">{String(bgpSummary?.incidentSummary?.by_severity?.critical ?? '-')}</Descriptions.Item>
|
||||
<Descriptions.Item label="活跃观测站">{String(bgpSummary?.collectorSummary?.active_collectors ?? '-')}</Descriptions.Item>
|
||||
<Descriptions.Item label="近24h事件">{String(bgpSummary?.collectorSummary?.recent_24h_events ?? '-')}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Scrollbar>
|
||||
</Space>
|
||||
|
||||
<Drawer title="态势告警 AI 简报" placement="right" width={560} onClose={() => setBriefOpen(false)} open={briefOpen}>
|
||||
|
||||
@@ -5,11 +5,9 @@ import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Descriptions,
|
||||
Drawer,
|
||||
Modal,
|
||||
Row,
|
||||
Space,
|
||||
Spin,
|
||||
Statistic,
|
||||
@@ -22,6 +20,8 @@ import {
|
||||
import axios from 'axios'
|
||||
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
import Scrollbar from '../../components/Scrollbar/Scrollbar'
|
||||
import TableScrollRegion from '../../components/Scrollbar/TableScrollRegion'
|
||||
import type { AlertBriefResponse, AlertRecord } from '../../services/situational-awareness'
|
||||
import { formatDateTimeZhCN } from '../../utils/datetime'
|
||||
|
||||
@@ -216,20 +216,22 @@ export function SystemAlertsPanel() {
|
||||
|
||||
<Alert type="info" showIcon message="这里展示的是平台与采集链路告警,不等同于 BGP 态势风险本身。" />
|
||||
|
||||
<Row gutter={[12, 12]}>
|
||||
<Col xs={24} sm={8}>
|
||||
<Card><Statistic title="严重告警" value={stats.critical} valueStyle={{ color: '#ff4d4f' }} prefix={<AlertOutlined />} /></Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={8}>
|
||||
<Card><Statistic title="警告" value={stats.warning} valueStyle={{ color: '#faad14' }} prefix={<AlertOutlined />} /></Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={8}>
|
||||
<Card><Statistic title="信息" value={stats.info} valueStyle={{ color: '#1890ff' }} prefix={<InfoCircleOutlined />} /></Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Scrollbar className="alerts-summary-scroll system-alerts-page__summary-scroll">
|
||||
<div className="alerts-summary-grid" style={{ gap: '12px' }}>
|
||||
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
||||
<Card><Statistic title="严重告警" value={stats.critical} valueStyle={{ color: '#ff4d4f' }} prefix={<AlertOutlined />} /></Card>
|
||||
</div>
|
||||
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
||||
<Card><Statistic title="警告" value={stats.warning} valueStyle={{ color: '#faad14' }} prefix={<AlertOutlined />} /></Card>
|
||||
</div>
|
||||
<div className="alerts-summary-cell" style={{ width: '220px' }}>
|
||||
<Card><Statistic title="信息" value={stats.info} valueStyle={{ color: '#1890ff' }} prefix={<InfoCircleOutlined />} /></Card>
|
||||
</div>
|
||||
</div>
|
||||
</Scrollbar>
|
||||
|
||||
<Card className="system-alerts-page__table-card" title="系统告警列表">
|
||||
<div className="table-scroll-region system-alerts-page__table-region">
|
||||
<TableScrollRegion className="system-alerts-page__table-region">
|
||||
<Table<AlertRecord>
|
||||
columns={columns}
|
||||
dataSource={alerts}
|
||||
@@ -239,7 +241,7 @@ export function SystemAlertsPanel() {
|
||||
scroll={{ x: 1100, y: 480 }}
|
||||
tableLayout="fixed"
|
||||
/>
|
||||
</div>
|
||||
</TableScrollRegion>
|
||||
</Card>
|
||||
</Space>
|
||||
|
||||
@@ -260,7 +262,7 @@ export function SystemAlertsPanel() {
|
||||
</Modal>
|
||||
|
||||
<Drawer title="系统告警 AI 简报" placement="right" width={520} onClose={() => setBriefOpen(false)} open={briefOpen}>
|
||||
<div className="alerts-brief-drawer">
|
||||
<Scrollbar className="alerts-brief-drawer">
|
||||
{briefLoading ? (
|
||||
<div className="alerts-brief-drawer__loading">
|
||||
<Spin tip="正在汇总系统告警事实并生成简报..." />
|
||||
@@ -293,7 +295,7 @@ export function SystemAlertsPanel() {
|
||||
</Card>
|
||||
</Space>
|
||||
) : null}
|
||||
</div>
|
||||
</Scrollbar>
|
||||
</Drawer>
|
||||
</div>
|
||||
</AppLayout>
|
||||
|
||||
@@ -4,10 +4,8 @@ import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Descriptions,
|
||||
Modal,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Spin,
|
||||
@@ -22,6 +20,8 @@ import {
|
||||
} from 'antd'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
import MarkdownRenderer from '../../components/MarkdownRenderer/MarkdownRenderer'
|
||||
import Scrollbar from '../../components/Scrollbar/Scrollbar'
|
||||
import TableScrollRegion from '../../components/Scrollbar/TableScrollRegion'
|
||||
import { formatDateTimeZhCN } from '../../utils/datetime'
|
||||
import {
|
||||
type BGPAnomaly,
|
||||
@@ -708,37 +708,36 @@ function BGP() {
|
||||
/>
|
||||
|
||||
<Card className="bgp-page__summary-card">
|
||||
<Row
|
||||
gutter={[compactViewport ? 8 : 12, compactViewport ? 8 : 12]}
|
||||
className={`bgp-page__summary-grid${compactViewport ? ' bgp-page__summary-grid--compact' : ''}`}
|
||||
wrap={!compactViewport}
|
||||
>
|
||||
{summaryItems.map((item) => (
|
||||
<Col
|
||||
key={item.label}
|
||||
xs={24}
|
||||
sm={12}
|
||||
md={8}
|
||||
flex={compactViewport ? '180px' : undefined}
|
||||
>
|
||||
<div className="bgp-page__summary-item">
|
||||
<div className="bgp-page__summary-label">{item.label}</div>
|
||||
<Statistic className="bgp-page__summary-stat" value={item.value} />
|
||||
<Scrollbar className="bgp-page__summary-scroll">
|
||||
<div
|
||||
className="bgp-page__summary-grid"
|
||||
style={{ gap: `${compactViewport ? 8 : 12}px` }}
|
||||
>
|
||||
{summaryItems.map((item) => (
|
||||
<div
|
||||
key={item.label}
|
||||
className="bgp-page__summary-cell"
|
||||
style={{ width: compactViewport ? '180px' : '220px' }}
|
||||
>
|
||||
<div className="bgp-page__summary-item">
|
||||
<div className="bgp-page__summary-label">{item.label}</div>
|
||||
<Statistic className="bgp-page__summary-stat" value={item.value} />
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
))}
|
||||
</div>
|
||||
</Scrollbar>
|
||||
</Card>
|
||||
|
||||
<Card className="bgp-page__table-card">
|
||||
<div ref={tableRegionRef} className="table-scroll-region bgp-page__table-region">
|
||||
<TableScrollRegion ref={tableRegionRef} className="bgp-page__table-region">
|
||||
<Tabs
|
||||
className="bgp-page__tabs"
|
||||
activeKey={activeTab}
|
||||
onChange={setActiveTab}
|
||||
items={tabItems}
|
||||
/>
|
||||
</div>
|
||||
</TableScrollRegion>
|
||||
</Card>
|
||||
</Space>
|
||||
</div>
|
||||
@@ -755,7 +754,7 @@ function BGP() {
|
||||
destroyOnHidden
|
||||
>
|
||||
{brief ? (
|
||||
<div className="bgp-page__brief-modal-body">
|
||||
<Scrollbar className="bgp-page__brief-modal-body">
|
||||
{(brief.facts.length > 0 || Object.keys(brief.context || {}).length > 0) ? (
|
||||
<div className="bgp-page__brief-evidence">
|
||||
{brief.facts.length > 0 ? (
|
||||
@@ -785,7 +784,7 @@ function BGP() {
|
||||
</div>
|
||||
) : null}
|
||||
<MarkdownRenderer markdown={brief.content_markdown} />
|
||||
</div>
|
||||
</Scrollbar>
|
||||
) : (
|
||||
<Text type="secondary">当前没有可查看的简报内容。</Text>
|
||||
)}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Link } from 'react-router-dom'
|
||||
import axios from 'axios'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
import Scrollbar from '../../components/Scrollbar/Scrollbar'
|
||||
import { useWebSocket } from '../../hooks/useWebSocket'
|
||||
import { formatDateTimeZhCN } from '../../utils/datetime'
|
||||
|
||||
@@ -532,11 +533,11 @@ function Dashboard() {
|
||||
|
||||
<div className="dashboard-restart-section">
|
||||
<Text className="dashboard-restart-section__label">终端输出</Text>
|
||||
<div className="dashboard-restart-log">
|
||||
<Scrollbar className="dashboard-restart-log">
|
||||
{restartLogs.length > 0 ? restartLogs.map((line, index) => (
|
||||
<div key={`${line}-${index}`}>{line}</div>
|
||||
)) : <div>等待操作</div>}
|
||||
</div>
|
||||
</Scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '@ant-design/icons'
|
||||
import axios from 'axios'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
import TableScrollRegion from '../../components/Scrollbar/TableScrollRegion'
|
||||
import { formatDateTimeZhCN, formatDateZhCN, parseBackendDate } from '../../utils/datetime'
|
||||
|
||||
const { Title, Text } = Typography
|
||||
@@ -939,7 +940,7 @@ function DataList() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="table-scroll-region data-list-table-region" style={{ padding: isCompact ? 10 : 12 }}>
|
||||
<TableScrollRegion className="data-list-table-region" style={{ padding: isCompact ? 10 : 12 }}>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
@@ -960,7 +961,7 @@ function DataList() {
|
||||
showTotal: (count) => `共 ${count} 条`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TableScrollRegion>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -116,18 +116,39 @@ function finalizeBulkProgressBatch(batch: BulkProgressBatch | null): BulkProgres
|
||||
if (!batch || batch.sourceIds.length === 0) {
|
||||
return null
|
||||
}
|
||||
return batch
|
||||
}
|
||||
|
||||
const hasRunningItem = batch.sourceIds.some((sourceId) => batch.items[sourceId]?.is_running)
|
||||
if (hasRunningItem) {
|
||||
return batch
|
||||
function resolveTerminalBatchItem(
|
||||
sourceId: number,
|
||||
batch: BulkProgressBatch,
|
||||
builtInSources: BuiltInDataSource[],
|
||||
taskProgress: Record<number, TaskTrackerState>,
|
||||
): BulkProgressItem | null {
|
||||
const currentItem = batch.items[sourceId]
|
||||
const source = builtInSources.find((item) => item.id === sourceId)
|
||||
const trackedTask = taskProgress[sourceId]
|
||||
|
||||
const isRunning = trackedTask?.is_running ?? source?.is_running ?? currentItem?.is_running ?? false
|
||||
if (isRunning) {
|
||||
return null
|
||||
}
|
||||
|
||||
const allFinished = batch.sourceIds.every((sourceId) => {
|
||||
const status = batch.items[sourceId]?.status
|
||||
return Boolean(status && status !== 'running')
|
||||
})
|
||||
const status = trackedTask?.status ?? source?.last_status ?? currentItem?.status ?? null
|
||||
if (!status || status === 'running') {
|
||||
return null
|
||||
}
|
||||
|
||||
return allFinished ? null : batch
|
||||
return {
|
||||
task_id: trackedTask?.task_id ?? currentItem?.task_id ?? source?.task_id ?? null,
|
||||
progress:
|
||||
status === 'success'
|
||||
? 100
|
||||
: trackedTask?.progress ?? source?.progress ?? currentItem?.progress ?? 0,
|
||||
is_running: false,
|
||||
phase: trackedTask?.phase ?? source?.phase ?? currentItem?.phase ?? null,
|
||||
status,
|
||||
}
|
||||
}
|
||||
|
||||
interface WebSocketTaskMessage {
|
||||
@@ -436,6 +457,41 @@ function DataSources() {
|
||||
return () => clearInterval(interval)
|
||||
}, [builtInSources, taskProgress, taskSocketConnected, fetchData])
|
||||
|
||||
useEffect(() => {
|
||||
if (!bulkProgressBatch) return
|
||||
|
||||
let changed = false
|
||||
const nextItems = { ...bulkProgressBatch.items }
|
||||
|
||||
for (const sourceId of bulkProgressBatch.sourceIds) {
|
||||
const nextItem = resolveTerminalBatchItem(sourceId, bulkProgressBatch, builtInSources, taskProgress)
|
||||
if (!nextItem) continue
|
||||
|
||||
const previousItem = bulkProgressBatch.items[sourceId]
|
||||
if (
|
||||
previousItem?.status === nextItem.status &&
|
||||
previousItem?.is_running === nextItem.is_running &&
|
||||
previousItem?.progress === nextItem.progress &&
|
||||
previousItem?.phase === nextItem.phase
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
nextItems[sourceId] = nextItem
|
||||
changed = true
|
||||
}
|
||||
|
||||
if (!changed) return
|
||||
|
||||
setBulkProgressBatch((prev) => {
|
||||
if (!prev) return prev
|
||||
return finalizeBulkProgressBatch({
|
||||
...prev,
|
||||
items: nextItems,
|
||||
})
|
||||
})
|
||||
}, [bulkProgressBatch, builtInSources, taskProgress])
|
||||
|
||||
const triggerDatasource = async (id: number, options?: { force?: boolean }) => {
|
||||
const force = options?.force ?? false
|
||||
const res = await axios.post(`/api/v1/datasources/${id}/trigger`, null, {
|
||||
|
||||
@@ -22,6 +22,8 @@ import axios from 'axios'
|
||||
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
import MarkdownRenderer from '../../components/MarkdownRenderer/MarkdownRenderer'
|
||||
import Scrollbar from '../../components/Scrollbar/Scrollbar'
|
||||
import ScrollbarOverlay from '../../components/Scrollbar/ScrollbarOverlay'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
|
||||
const { Title, Text, Paragraph } = Typography
|
||||
@@ -239,6 +241,7 @@ function Playground() {
|
||||
const [showScrollToBottom, setShowScrollToBottom] = useState(false)
|
||||
const pollTimerRef = useRef<number | null>(null)
|
||||
const messagesContainerRef = useRef<HTMLDivElement | null>(null)
|
||||
const messagesShellRef = useRef<HTMLDivElement | null>(null)
|
||||
const forceScrollToBottomRef = useRef(true)
|
||||
|
||||
const selectedPreset = useMemo(
|
||||
@@ -575,7 +578,7 @@ function Playground() {
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<div className="playground-card__scroll">
|
||||
<Scrollbar className="playground-card__scroll">
|
||||
<Spin spinning={statusLoading}>
|
||||
{providerStatus ? (
|
||||
<div className="playground-provider-panel">
|
||||
@@ -617,7 +620,7 @@ function Playground() {
|
||||
<Alert type="warning" showIcon message="尚未获取到 AI Provider 状态" />
|
||||
)}
|
||||
</Spin>
|
||||
</div>
|
||||
</Scrollbar>
|
||||
</Card>
|
||||
)
|
||||
|
||||
@@ -699,7 +702,7 @@ function Playground() {
|
||||
</Space>
|
||||
)}
|
||||
>
|
||||
<div className="playground-chat__messages-shell">
|
||||
<div ref={messagesShellRef} className="playground-chat__messages-shell">
|
||||
<div className="playground-chat__messages" ref={messagesContainerRef} onScroll={handleMessagesScroll}>
|
||||
{messages.map((entry) => (
|
||||
<div key={entry.id} className={`playground-message playground-message--${entry.role}`}>
|
||||
@@ -760,8 +763,9 @@ function Playground() {
|
||||
<Button size="small" type="primary" loading={editSaving} onClick={() => void handleSaveEditMessage(entry)}>
|
||||
发送
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ScrollbarOverlay containerRef={messagesShellRef} targetSelector=".playground-chat__messages" />
|
||||
</div>
|
||||
</div>
|
||||
) : (entry.role !== 'assistant' || entry.status === 'answering' || entry.status === 'done' || entry.status === 'stopped' || entry.status === 'error') && entry.markdown ? (
|
||||
<div className="playground-message__markdown">
|
||||
@@ -1000,42 +1004,42 @@ function Playground() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="playground-result-modal__content">
|
||||
<Scrollbar className="playground-result-modal__content">
|
||||
<Space direction="vertical" size={16} className="playground-result__stack" style={{ width: '100%' }}>
|
||||
{analysis.text_blocks.length ? (
|
||||
<div className="playground-result__blocks">
|
||||
<Text strong>文本块</Text>
|
||||
<div className="playground-result__blocks-scroll">
|
||||
<Scrollbar className="playground-result__blocks-scroll">
|
||||
{analysis.text_blocks.map((block, index) => (
|
||||
<Card key={`${index}-${block.slice(0, 12)}`} size="small">
|
||||
<pre>{block}</pre>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Scrollbar>
|
||||
</div>
|
||||
) : null}
|
||||
{analysis.thinking_blocks.length ? (
|
||||
<div className="playground-result__blocks">
|
||||
<Text strong>Thinking Blocks</Text>
|
||||
<div className="playground-result__blocks-scroll">
|
||||
<Scrollbar className="playground-result__blocks-scroll">
|
||||
{analysis.thinking_blocks.map((block, index) => (
|
||||
<Card key={`${index}-${block.slice(0, 12)}`} size="small">
|
||||
<pre>{block}</pre>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Scrollbar>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="playground-result__blocks">
|
||||
<Text strong>Raw Response</Text>
|
||||
<div className="playground-result__blocks-scroll playground-result__blocks-scroll--raw">
|
||||
<Scrollbar className="playground-result__blocks-scroll playground-result__blocks-scroll--raw">
|
||||
<Card size="small">
|
||||
<pre>{JSON.stringify(analysis.raw_response, null, 2)}</pre>
|
||||
</Card>
|
||||
</div>
|
||||
</Scrollbar>
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
</Scrollbar>
|
||||
</div>
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
} from 'antd'
|
||||
import axios from 'axios'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
import Scrollbar from '../../components/Scrollbar/Scrollbar'
|
||||
import TableScrollRegion from '../../components/Scrollbar/TableScrollRegion'
|
||||
import { formatDateTimeZhCN } from '../../utils/datetime'
|
||||
|
||||
const { Title, Text } = Typography
|
||||
@@ -91,7 +93,7 @@ function SettingsPanel({
|
||||
return (
|
||||
<div className="settings-pane">
|
||||
<Card className="settings-panel-card" loading={loading}>
|
||||
<div className="settings-panel-scroll">{children}</div>
|
||||
<Scrollbar className="settings-panel-scroll">{children}</Scrollbar>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
@@ -642,7 +644,7 @@ function Settings() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="table-scroll-region data-source-table-region">
|
||||
<TableScrollRegion className="data-source-table-region">
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={tvSourceColumns}
|
||||
@@ -652,7 +654,7 @@ function Settings() {
|
||||
tableLayout="fixed"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</TableScrollRegion>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -668,7 +670,7 @@ function Settings() {
|
||||
loading={loading}
|
||||
styles={{ body: { padding: 0 } }}
|
||||
>
|
||||
<div ref={collectorTableRegionRef} className="table-scroll-region data-source-table-region">
|
||||
<TableScrollRegion ref={collectorTableRegionRef} className="data-source-table-region">
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={collectorColumns}
|
||||
@@ -678,7 +680,7 @@ function Settings() {
|
||||
tableLayout="fixed"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</TableScrollRegion>
|
||||
</Card>
|
||||
</div>
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Table, Tag, Card, Row, Col, Statistic, Button } from 'antd'
|
||||
import { ReloadOutlined, CheckCircleOutlined, CloseCircleOutlined, SyncOutlined } from '@ant-design/icons'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
import TableScrollRegion from '../../components/Scrollbar/TableScrollRegion'
|
||||
import { formatDateTimeZhCN } from '../../utils/datetime'
|
||||
|
||||
interface Task {
|
||||
@@ -146,9 +147,9 @@ function Tasks() {
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="table-scroll-region">
|
||||
<TableScrollRegion>
|
||||
<Table columns={columns} dataSource={tasks} rowKey="id" loading={loading} pagination={{ pageSize: 10 }} scroll={{ x: 'max-content', y: 'calc(100% - 360px)' }} tableLayout="fixed" />
|
||||
</div>
|
||||
</TableScrollRegion>
|
||||
</Card>
|
||||
</AppLayout>
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Table, Button, Tag, Space, message, Modal, Form, Input, Select } from '
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'
|
||||
import axios from 'axios'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
import TableScrollRegion from '../../components/Scrollbar/TableScrollRegion'
|
||||
|
||||
interface User {
|
||||
id: number
|
||||
@@ -144,7 +145,7 @@ function Users() {
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>添加用户</Button>
|
||||
</div>
|
||||
<div className="page-shell__body">
|
||||
<div ref={tableRegionRef} className="table-scroll-region data-source-table-region users-table-region" style={{ height: '100%' }}>
|
||||
<TableScrollRegion ref={tableRegionRef} className="data-source-table-region users-table-region" style={{ height: '100%' }}>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={users}
|
||||
@@ -153,7 +154,7 @@ function Users() {
|
||||
scroll={{ x: 'max-content', y: tableHeight }}
|
||||
tableLayout="fixed"
|
||||
/>
|
||||
</div>
|
||||
</TableScrollRegion>
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
|
||||
Reference in New Issue
Block a user