Spaces:
Sleeping
Sleeping
Ming Wang commited on
Commit ·
1f7eb48
1
Parent(s): 602851d
Phase 2: chart integration
Browse files- frontend/package-lock.json +46 -0
- frontend/package.json +2 -0
- frontend/src/app/core/mocks/charts.mock.ts +48 -0
- frontend/src/app/dashboard/chart-config.factory.ts +140 -0
- frontend/src/app/dashboard/chart-widget/chart-widget.html +6 -0
- frontend/src/app/dashboard/chart-widget/chart-widget.scss +19 -0
- frontend/src/app/dashboard/chart-widget/chart-widget.ts +35 -0
- frontend/src/app/dashboard/dashboard.html +2 -2
- frontend/src/app/dashboard/dashboard.ts +6 -2
frontend/package-lock.json
CHANGED
|
@@ -16,6 +16,8 @@
|
|
| 16 |
"@angular/material": "^21.2.3",
|
| 17 |
"@angular/platform-browser": "^21.2.0",
|
| 18 |
"@angular/router": "^21.2.0",
|
|
|
|
|
|
|
| 19 |
"rxjs": "~7.8.0",
|
| 20 |
"tslib": "^2.3.0"
|
| 21 |
},
|
|
@@ -4479,6 +4481,22 @@
|
|
| 4479 |
"node": ">= 0.4"
|
| 4480 |
}
|
| 4481 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4482 |
"node_modules/ee-first": {
|
| 4483 |
"version": "1.1.1",
|
| 4484 |
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
|
@@ -5937,6 +5955,19 @@
|
|
| 5937 |
"node": ">= 0.6"
|
| 5938 |
}
|
| 5939 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5940 |
"node_modules/node-addon-api": {
|
| 5941 |
"version": "6.1.0",
|
| 5942 |
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
|
|
@@ -7718,6 +7749,21 @@
|
|
| 7718 |
"peerDependencies": {
|
| 7719 |
"zod": "^3.25 || ^4"
|
| 7720 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7721 |
}
|
| 7722 |
}
|
| 7723 |
}
|
|
|
|
| 16 |
"@angular/material": "^21.2.3",
|
| 17 |
"@angular/platform-browser": "^21.2.0",
|
| 18 |
"@angular/router": "^21.2.0",
|
| 19 |
+
"echarts": "^6.0.0",
|
| 20 |
+
"ngx-echarts": "^21.0.0",
|
| 21 |
"rxjs": "~7.8.0",
|
| 22 |
"tslib": "^2.3.0"
|
| 23 |
},
|
|
|
|
| 4481 |
"node": ">= 0.4"
|
| 4482 |
}
|
| 4483 |
},
|
| 4484 |
+
"node_modules/echarts": {
|
| 4485 |
+
"version": "6.0.0",
|
| 4486 |
+
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
|
| 4487 |
+
"integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==",
|
| 4488 |
+
"license": "Apache-2.0",
|
| 4489 |
+
"dependencies": {
|
| 4490 |
+
"tslib": "2.3.0",
|
| 4491 |
+
"zrender": "6.0.0"
|
| 4492 |
+
}
|
| 4493 |
+
},
|
| 4494 |
+
"node_modules/echarts/node_modules/tslib": {
|
| 4495 |
+
"version": "2.3.0",
|
| 4496 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
| 4497 |
+
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
| 4498 |
+
"license": "0BSD"
|
| 4499 |
+
},
|
| 4500 |
"node_modules/ee-first": {
|
| 4501 |
"version": "1.1.1",
|
| 4502 |
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
|
|
|
| 5955 |
"node": ">= 0.6"
|
| 5956 |
}
|
| 5957 |
},
|
| 5958 |
+
"node_modules/ngx-echarts": {
|
| 5959 |
+
"version": "21.0.0",
|
| 5960 |
+
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-21.0.0.tgz",
|
| 5961 |
+
"integrity": "sha512-vivBRmGYMFlnPxK/uxliY+sexGwHqnFZ2pylGbIMF2wikt9RpZpGGSitLmuUXvzYwkVFGo6dQmiUQh1+6Pbfuw==",
|
| 5962 |
+
"license": "MIT",
|
| 5963 |
+
"dependencies": {
|
| 5964 |
+
"tslib": "^2.3.0"
|
| 5965 |
+
},
|
| 5966 |
+
"peerDependencies": {
|
| 5967 |
+
"@angular/core": ">=21.0.0",
|
| 5968 |
+
"echarts": ">=5.0.0"
|
| 5969 |
+
}
|
| 5970 |
+
},
|
| 5971 |
"node_modules/node-addon-api": {
|
| 5972 |
"version": "6.1.0",
|
| 5973 |
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
|
|
|
|
| 7749 |
"peerDependencies": {
|
| 7750 |
"zod": "^3.25 || ^4"
|
| 7751 |
}
|
| 7752 |
+
},
|
| 7753 |
+
"node_modules/zrender": {
|
| 7754 |
+
"version": "6.0.0",
|
| 7755 |
+
"resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz",
|
| 7756 |
+
"integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==",
|
| 7757 |
+
"license": "BSD-3-Clause",
|
| 7758 |
+
"dependencies": {
|
| 7759 |
+
"tslib": "2.3.0"
|
| 7760 |
+
}
|
| 7761 |
+
},
|
| 7762 |
+
"node_modules/zrender/node_modules/tslib": {
|
| 7763 |
+
"version": "2.3.0",
|
| 7764 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
| 7765 |
+
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
| 7766 |
+
"license": "0BSD"
|
| 7767 |
}
|
| 7768 |
}
|
| 7769 |
}
|
frontend/package.json
CHANGED
|
@@ -19,6 +19,8 @@
|
|
| 19 |
"@angular/material": "^21.2.3",
|
| 20 |
"@angular/platform-browser": "^21.2.0",
|
| 21 |
"@angular/router": "^21.2.0",
|
|
|
|
|
|
|
| 22 |
"rxjs": "~7.8.0",
|
| 23 |
"tslib": "^2.3.0"
|
| 24 |
},
|
|
|
|
| 19 |
"@angular/material": "^21.2.3",
|
| 20 |
"@angular/platform-browser": "^21.2.0",
|
| 21 |
"@angular/router": "^21.2.0",
|
| 22 |
+
"echarts": "^6.0.0",
|
| 23 |
+
"ngx-echarts": "^21.0.0",
|
| 24 |
"rxjs": "~7.8.0",
|
| 25 |
"tslib": "^2.3.0"
|
| 26 |
},
|
frontend/src/app/core/mocks/charts.mock.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export interface TrafficDataPoint {
|
| 2 |
+
time: string;
|
| 3 |
+
inbound: number;
|
| 4 |
+
outbound: number;
|
| 5 |
+
latency: number;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
export const TRAFFIC_MOCK: TrafficDataPoint[] = [
|
| 9 |
+
{ time: '00:00', inbound: 120, outbound: 95, latency: 12 },
|
| 10 |
+
{ time: '01:00', inbound: 85, outbound: 70, latency: 10 },
|
| 11 |
+
{ time: '02:00', inbound: 60, outbound: 52, latency: 8 },
|
| 12 |
+
{ time: '03:00', inbound: 45, outbound: 38, latency: 7 },
|
| 13 |
+
{ time: '04:00', inbound: 40, outbound: 35, latency: 6 },
|
| 14 |
+
{ time: '05:00', inbound: 55, outbound: 42, latency: 8 },
|
| 15 |
+
{ time: '06:00', inbound: 90, outbound: 78, latency: 11 },
|
| 16 |
+
{ time: '07:00', inbound: 145, outbound: 120, latency: 15 },
|
| 17 |
+
{ time: '08:00', inbound: 210, outbound: 180, latency: 22 },
|
| 18 |
+
{ time: '09:00', inbound: 280, outbound: 245, latency: 28 },
|
| 19 |
+
{ time: '10:00', inbound: 310, outbound: 270, latency: 32 },
|
| 20 |
+
{ time: '11:00', inbound: 295, outbound: 260, latency: 30 },
|
| 21 |
+
{ time: '12:00', inbound: 270, outbound: 235, latency: 27 },
|
| 22 |
+
{ time: '13:00', inbound: 285, outbound: 250, latency: 29 },
|
| 23 |
+
{ time: '14:00', inbound: 320, outbound: 280, latency: 35 },
|
| 24 |
+
{ time: '15:00', inbound: 300, outbound: 265, latency: 31 },
|
| 25 |
+
{ time: '16:00', inbound: 275, outbound: 240, latency: 26 },
|
| 26 |
+
{ time: '17:00', inbound: 250, outbound: 215, latency: 24 },
|
| 27 |
+
{ time: '18:00', inbound: 220, outbound: 190, latency: 20 },
|
| 28 |
+
{ time: '19:00', inbound: 185, outbound: 160, latency: 18 },
|
| 29 |
+
{ time: '20:00', inbound: 160, outbound: 138, latency: 16 },
|
| 30 |
+
{ time: '21:00', inbound: 140, outbound: 118, latency: 14 },
|
| 31 |
+
{ time: '22:00', inbound: 125, outbound: 105, latency: 13 },
|
| 32 |
+
{ time: '23:00', inbound: 110, outbound: 90, latency: 11 },
|
| 33 |
+
];
|
| 34 |
+
|
| 35 |
+
export interface IncidentRegionData {
|
| 36 |
+
region: string;
|
| 37 |
+
critical: number;
|
| 38 |
+
warning: number;
|
| 39 |
+
info: number;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
export const INCIDENT_REGION_MOCK: IncidentRegionData[] = [
|
| 43 |
+
{ region: 'US-East', critical: 5, warning: 12, info: 8 },
|
| 44 |
+
{ region: 'US-West', critical: 2, warning: 8, info: 15 },
|
| 45 |
+
{ region: 'EU-West', critical: 7, warning: 10, info: 6 },
|
| 46 |
+
{ region: 'AP-South', critical: 3, warning: 14, info: 11 },
|
| 47 |
+
{ region: 'AP-East', critical: 1, warning: 6, info: 9 },
|
| 48 |
+
];
|
frontend/src/app/dashboard/chart-config.factory.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { EChartsOption } from 'echarts';
|
| 2 |
+
import { TrafficDataPoint, IncidentRegionData } from '../core/mocks/charts.mock';
|
| 3 |
+
|
| 4 |
+
/**
|
| 5 |
+
* Chart color palette aligned with Material 3 violet theme.
|
| 6 |
+
* Uses CSS variable fallbacks for runtime theme integration.
|
| 7 |
+
*/
|
| 8 |
+
const PALETTE = {
|
| 9 |
+
primary: '#7b1fa2',
|
| 10 |
+
secondary: '#9c27b0',
|
| 11 |
+
tertiary: '#42a5f5',
|
| 12 |
+
surface: '#f5f0ff',
|
| 13 |
+
onSurface: '#1c1b1f',
|
| 14 |
+
outline: '#79747e',
|
| 15 |
+
critical: '#d32f2f',
|
| 16 |
+
warning: '#ed6c02',
|
| 17 |
+
info: '#0288d1',
|
| 18 |
+
} as const;
|
| 19 |
+
|
| 20 |
+
const BASE_TEXT_STYLE = {
|
| 21 |
+
fontFamily: 'Roboto, sans-serif',
|
| 22 |
+
color: PALETTE.onSurface,
|
| 23 |
+
} as const;
|
| 24 |
+
|
| 25 |
+
function baseGrid(): EChartsOption['grid'] {
|
| 26 |
+
return { left: 48, right: 24, top: 40, bottom: 32, containLabel: true };
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
export function buildTrafficLatencyOption(data: TrafficDataPoint[]): EChartsOption {
|
| 30 |
+
const times = data.map((d) => d.time);
|
| 31 |
+
|
| 32 |
+
return {
|
| 33 |
+
textStyle: BASE_TEXT_STYLE,
|
| 34 |
+
tooltip: { trigger: 'axis' },
|
| 35 |
+
legend: { top: 0, textStyle: { fontSize: 12 } },
|
| 36 |
+
grid: baseGrid(),
|
| 37 |
+
xAxis: {
|
| 38 |
+
type: 'category',
|
| 39 |
+
data: times,
|
| 40 |
+
axisLine: { lineStyle: { color: PALETTE.outline } },
|
| 41 |
+
axisLabel: { fontSize: 11 },
|
| 42 |
+
},
|
| 43 |
+
yAxis: [
|
| 44 |
+
{
|
| 45 |
+
type: 'value',
|
| 46 |
+
name: 'Mbps',
|
| 47 |
+
nameTextStyle: { fontSize: 11 },
|
| 48 |
+
axisLine: { show: false },
|
| 49 |
+
splitLine: { lineStyle: { color: PALETTE.surface } },
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
type: 'value',
|
| 53 |
+
name: 'ms',
|
| 54 |
+
nameTextStyle: { fontSize: 11 },
|
| 55 |
+
axisLine: { show: false },
|
| 56 |
+
splitLine: { show: false },
|
| 57 |
+
},
|
| 58 |
+
],
|
| 59 |
+
series: [
|
| 60 |
+
{
|
| 61 |
+
name: 'Inbound',
|
| 62 |
+
type: 'line',
|
| 63 |
+
data: data.map((d) => d.inbound),
|
| 64 |
+
smooth: true,
|
| 65 |
+
symbol: 'none',
|
| 66 |
+
lineStyle: { width: 2 },
|
| 67 |
+
itemStyle: { color: PALETTE.primary },
|
| 68 |
+
areaStyle: { color: `${PALETTE.primary}18` },
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
name: 'Outbound',
|
| 72 |
+
type: 'line',
|
| 73 |
+
data: data.map((d) => d.outbound),
|
| 74 |
+
smooth: true,
|
| 75 |
+
symbol: 'none',
|
| 76 |
+
lineStyle: { width: 2 },
|
| 77 |
+
itemStyle: { color: PALETTE.secondary },
|
| 78 |
+
areaStyle: { color: `${PALETTE.secondary}18` },
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
name: 'Latency',
|
| 82 |
+
type: 'line',
|
| 83 |
+
yAxisIndex: 1,
|
| 84 |
+
data: data.map((d) => d.latency),
|
| 85 |
+
smooth: true,
|
| 86 |
+
symbol: 'none',
|
| 87 |
+
lineStyle: { width: 2, type: 'dashed' },
|
| 88 |
+
itemStyle: { color: PALETTE.tertiary },
|
| 89 |
+
},
|
| 90 |
+
],
|
| 91 |
+
};
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
export function buildIncidentRegionOption(data: IncidentRegionData[]): EChartsOption {
|
| 95 |
+
const regions = data.map((d) => d.region);
|
| 96 |
+
|
| 97 |
+
return {
|
| 98 |
+
textStyle: BASE_TEXT_STYLE,
|
| 99 |
+
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
| 100 |
+
legend: { top: 0, textStyle: { fontSize: 12 } },
|
| 101 |
+
grid: baseGrid(),
|
| 102 |
+
xAxis: {
|
| 103 |
+
type: 'category',
|
| 104 |
+
data: regions,
|
| 105 |
+
axisLine: { lineStyle: { color: PALETTE.outline } },
|
| 106 |
+
axisLabel: { fontSize: 11 },
|
| 107 |
+
},
|
| 108 |
+
yAxis: {
|
| 109 |
+
type: 'value',
|
| 110 |
+
name: 'Incidents',
|
| 111 |
+
nameTextStyle: { fontSize: 11 },
|
| 112 |
+
axisLine: { show: false },
|
| 113 |
+
splitLine: { lineStyle: { color: PALETTE.surface } },
|
| 114 |
+
},
|
| 115 |
+
series: [
|
| 116 |
+
{
|
| 117 |
+
name: 'Critical',
|
| 118 |
+
type: 'bar',
|
| 119 |
+
stack: 'severity',
|
| 120 |
+
data: data.map((d) => d.critical),
|
| 121 |
+
itemStyle: { color: PALETTE.critical, borderRadius: [0, 0, 0, 0] },
|
| 122 |
+
},
|
| 123 |
+
{
|
| 124 |
+
name: 'Warning',
|
| 125 |
+
type: 'bar',
|
| 126 |
+
stack: 'severity',
|
| 127 |
+
data: data.map((d) => d.warning),
|
| 128 |
+
itemStyle: { color: PALETTE.warning },
|
| 129 |
+
},
|
| 130 |
+
{
|
| 131 |
+
name: 'Info',
|
| 132 |
+
type: 'bar',
|
| 133 |
+
stack: 'severity',
|
| 134 |
+
data: data.map((d) => d.info),
|
| 135 |
+
itemStyle: { color: PALETTE.info, borderRadius: [4, 4, 0, 0] },
|
| 136 |
+
barWidth: '50%',
|
| 137 |
+
},
|
| 138 |
+
],
|
| 139 |
+
};
|
| 140 |
+
}
|
frontend/src/app/dashboard/chart-widget/chart-widget.html
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<mat-card class="chart-card" appearance="outlined">
|
| 2 |
+
<mat-card-content>
|
| 3 |
+
<h3 class="chart-title">{{ title() }}</h3>
|
| 4 |
+
<div class="chart-container" echarts [options]="options()"></div>
|
| 5 |
+
</mat-card-content>
|
| 6 |
+
</mat-card>
|
frontend/src/app/dashboard/chart-widget/chart-widget.scss
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:host {
|
| 2 |
+
display: block;
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
.chart-card {
|
| 6 |
+
height: 100%;
|
| 7 |
+
border-radius: var(--pf-radius-md);
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
.chart-title {
|
| 11 |
+
font: var(--mat-sys-title-medium);
|
| 12 |
+
color: var(--mat-sys-on-surface);
|
| 13 |
+
margin: 0 0 var(--pf-spacing-md);
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
.chart-container {
|
| 17 |
+
width: 100%;
|
| 18 |
+
height: 300px;
|
| 19 |
+
}
|
frontend/src/app/dashboard/chart-widget/chart-widget.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component, input } from '@angular/core';
|
| 2 |
+
import { MatCard, MatCardContent } from '@angular/material/card';
|
| 3 |
+
import { NgxEchartsDirective, provideEchartsCore } from 'ngx-echarts';
|
| 4 |
+
import * as echarts from 'echarts/core';
|
| 5 |
+
import { LineChart, BarChart } from 'echarts/charts';
|
| 6 |
+
import {
|
| 7 |
+
TitleComponent,
|
| 8 |
+
TooltipComponent,
|
| 9 |
+
GridComponent,
|
| 10 |
+
LegendComponent,
|
| 11 |
+
} from 'echarts/components';
|
| 12 |
+
import { CanvasRenderer } from 'echarts/renderers';
|
| 13 |
+
import type { EChartsOption } from 'echarts';
|
| 14 |
+
|
| 15 |
+
echarts.use([
|
| 16 |
+
LineChart,
|
| 17 |
+
BarChart,
|
| 18 |
+
TitleComponent,
|
| 19 |
+
TooltipComponent,
|
| 20 |
+
GridComponent,
|
| 21 |
+
LegendComponent,
|
| 22 |
+
CanvasRenderer,
|
| 23 |
+
]);
|
| 24 |
+
|
| 25 |
+
@Component({
|
| 26 |
+
selector: 'app-chart-widget',
|
| 27 |
+
imports: [MatCard, MatCardContent, NgxEchartsDirective],
|
| 28 |
+
providers: [provideEchartsCore({ echarts })],
|
| 29 |
+
templateUrl: './chart-widget.html',
|
| 30 |
+
styleUrl: './chart-widget.scss',
|
| 31 |
+
})
|
| 32 |
+
export class ChartWidget {
|
| 33 |
+
title = input.required<string>();
|
| 34 |
+
options = input.required<EChartsOption>();
|
| 35 |
+
}
|
frontend/src/app/dashboard/dashboard.html
CHANGED
|
@@ -18,8 +18,8 @@
|
|
| 18 |
</div>
|
| 19 |
|
| 20 |
<div class="chart-grid">
|
| 21 |
-
<app-chart-
|
| 22 |
-
<app-chart-
|
| 23 |
</div>
|
| 24 |
|
| 25 |
<app-alert-list [alerts]="alerts" />
|
|
|
|
| 18 |
</div>
|
| 19 |
|
| 20 |
<div class="chart-grid">
|
| 21 |
+
<app-chart-widget title="24h Traffic / Latency Trend" [options]="trafficChartOptions" />
|
| 22 |
+
<app-chart-widget title="Incident Severity by Region" [options]="incidentChartOptions" />
|
| 23 |
</div>
|
| 24 |
|
| 25 |
<app-alert-list [alerts]="alerts" />
|
frontend/src/app/dashboard/dashboard.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
| 1 |
import { Component } from '@angular/core';
|
| 2 |
import { KpiCard } from './kpi-card/kpi-card';
|
| 3 |
-
import {
|
| 4 |
import { AlertList } from './alert-list/alert-list';
|
| 5 |
import { KPI_MOCKS } from '../core/mocks/kpi.mock';
|
| 6 |
import { ALERT_MOCKS } from '../core/mocks/alerts.mock';
|
|
|
|
|
|
|
| 7 |
|
| 8 |
@Component({
|
| 9 |
selector: 'app-dashboard',
|
| 10 |
-
imports: [KpiCard,
|
| 11 |
templateUrl: './dashboard.html',
|
| 12 |
styleUrl: './dashboard.scss',
|
| 13 |
})
|
| 14 |
export class Dashboard {
|
| 15 |
protected readonly kpis = KPI_MOCKS;
|
| 16 |
protected readonly alerts = ALERT_MOCKS;
|
|
|
|
|
|
|
| 17 |
}
|
|
|
|
| 1 |
import { Component } from '@angular/core';
|
| 2 |
import { KpiCard } from './kpi-card/kpi-card';
|
| 3 |
+
import { ChartWidget } from './chart-widget/chart-widget';
|
| 4 |
import { AlertList } from './alert-list/alert-list';
|
| 5 |
import { KPI_MOCKS } from '../core/mocks/kpi.mock';
|
| 6 |
import { ALERT_MOCKS } from '../core/mocks/alerts.mock';
|
| 7 |
+
import { TRAFFIC_MOCK, INCIDENT_REGION_MOCK } from '../core/mocks/charts.mock';
|
| 8 |
+
import { buildTrafficLatencyOption, buildIncidentRegionOption } from './chart-config.factory';
|
| 9 |
|
| 10 |
@Component({
|
| 11 |
selector: 'app-dashboard',
|
| 12 |
+
imports: [KpiCard, ChartWidget, AlertList],
|
| 13 |
templateUrl: './dashboard.html',
|
| 14 |
styleUrl: './dashboard.scss',
|
| 15 |
})
|
| 16 |
export class Dashboard {
|
| 17 |
protected readonly kpis = KPI_MOCKS;
|
| 18 |
protected readonly alerts = ALERT_MOCKS;
|
| 19 |
+
protected readonly trafficChartOptions = buildTrafficLatencyOption(TRAFFIC_MOCK);
|
| 20 |
+
protected readonly incidentChartOptions = buildIncidentRegionOption(INCIDENT_REGION_MOCK);
|
| 21 |
}
|