@oneuptime/common 12.0.31 → 12.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/AlertState.ts +0 -2
- package/Models/DatabaseModels/Domain.ts +1 -1
- package/Models/DatabaseModels/Incident.ts +1 -1
- package/Models/DatabaseModels/IncidentTemplate.ts +1 -1
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/MetricType.ts +0 -2
- package/Models/DatabaseModels/MonitorStatusTimeline.ts +0 -2
- package/Models/DatabaseModels/MonitorTest.ts +0 -2
- package/Models/DatabaseModels/NetworkDevice.ts +121 -2
- package/Models/DatabaseModels/NetworkDeviceOidTemplate.ts +3 -1
- package/Models/DatabaseModels/NetworkDeviceRole.ts +648 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -1
- package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -1
- package/Models/DatabaseModels/StatusPageAnnouncement.ts +0 -2
- package/Models/DatabaseModels/StatusPageAnnouncementTemplate.ts +0 -2
- package/Models/DatabaseModels/StatusPagePrivateUser.ts +0 -2
- package/Models/DatabaseModels/StatusPageResource.ts +0 -2
- package/Models/DatabaseModels/StatusPageSubscriber.ts +0 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.ts +72 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/DatabaseService.ts +39 -9
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/NetworkDeviceRoleService.ts +90 -0
- package/Server/Services/ProjectService.ts +85 -0
- package/Server/Services/UserNotificationSettingService.ts +0 -6
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +31 -1
- package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +35 -8
- package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +107 -3
- package/Tests/App/Dashboard/DashboardWideChartZoom.test.tsx +401 -0
- package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +318 -0
- package/Tests/App/Dashboard/NetworkDeviceBulkDelete.test.tsx +357 -0
- package/Tests/App/Dashboard/NetworkSideMenu.test.tsx +590 -0
- package/Tests/App/Dashboard/SideMenuHarness.tsx +11 -3
- package/Tests/Models/DatabaseModels/ModelRegistryInvariants.test.ts +115 -41
- package/Tests/Server/Infrastructure/Postgres/NetworkDeviceRoleTableMigration.test.ts +476 -0
- package/Tests/Server/Services/DatabaseServiceGenerateSlug.test.ts +215 -0
- package/Tests/Server/Services/NetworkDeviceRoleModel.test.ts +672 -0
- package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +1 -1
- package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +67 -4
- package/Tests/Server/Services/ScheduledMaintenanceSubscriberEndTime.test.ts +369 -0
- package/Tests/Server/Services/UserNotificationSettingMonitorDefaults.test.ts +248 -0
- package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +43 -2
- package/Tests/Server/Utils/BasicCron.test.ts +283 -0
- package/Tests/Server/Utils/IncomingCallPhoneNumber.test.ts +174 -0
- package/Tests/Types/NetworkDevice/DefaultNetworkDeviceRole.test.ts +417 -0
- package/Tests/UI/Components/Charts/ChartClickDoubleClickDisambiguation.test.tsx +221 -0
- package/Tests/UI/Components/Charts/ChartDoubleClickReset.test.tsx +220 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +751 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +475 -43
- package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +89 -0
- package/Tests/UI/Components/NavBarCommandKGating.test.tsx +103 -8
- package/Tests/UI/Components/SideMenuItem.test.tsx +30 -0
- package/Tests/UI/Utils/TableColumnsToCsv.test.ts +30 -0
- package/Tests/UI/Utils/UseDashboardTimeRangeZoom.test.tsx +187 -0
- package/Tests/Utils/Dashboard/DashboardResourceListSharedArgs.test.ts +302 -0
- package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +242 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleCatalog.test.ts +981 -0
- package/Tests/Utils/Monitor/NetworkTopologyRoleCatalog.test.ts +865 -0
- package/Tests/Utils/NetworkDevice/DeviceRoleKeyUtil.test.ts +538 -0
- package/Tests/Utils/Slo/SloEvaluation.test.ts +171 -0
- package/Tests/Utils/Slug.test.ts +61 -1
- package/Tests/jest.setup.ts +19 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +61 -0
- package/Types/NetworkDevice/DefaultNetworkDeviceRole.ts +145 -0
- package/Types/Permission.ts +45 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +7 -0
- package/UI/Components/Charts/Bar/BarChart.tsx +6 -0
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +9 -1
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +97 -35
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +14 -0
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +97 -35
- package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +13 -0
- package/UI/Components/Charts/Line/LineChart.tsx +7 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +50 -15
- package/UI/Components/Navbar/NavBar.tsx +62 -13
- package/UI/Components/Navbar/NavBarMenuModal.tsx +7 -2
- package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +2 -0
- package/UI/Components/SideMenu/SideMenu.tsx +12 -2
- package/UI/Components/SideMenu/SideMenuItem.tsx +4 -1
- package/UI/Components/SideMenu/SideMenuSection.tsx +14 -2
- package/UI/Utils/UseDashboardTimeRangeZoom.ts +77 -0
- package/Utils/Dashboard/DashboardTimeRangeZoom.ts +118 -0
- package/Utils/Monitor/NetworkDeviceRoleCatalog.ts +232 -0
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +2 -1
- package/Utils/Monitor/NetworkTopologyUtil.ts +116 -33
- package/Utils/NetworkDevice/DeviceRoleKeyUtil.ts +137 -0
- package/Utils/Rules/RulePatternMatchUtil.ts +1 -1
- package/Utils/Slug.ts +34 -1
- package/build/dist/Models/DatabaseModels/AlertState.js +0 -2
- package/build/dist/Models/DatabaseModels/AlertState.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Domain.js +1 -1
- package/build/dist/Models/DatabaseModels/Domain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MetricType.js +0 -2
- package/build/dist/Models/DatabaseModels/MetricType.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +0 -2
- package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTest.js +0 -2
- package/build/dist/Models/DatabaseModels/MonitorTest.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +124 -3
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceOidTemplate.js +3 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceOidTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceRole.js +677 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceRole.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageAnnouncementTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageResource.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +0 -2
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.js +39 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790800000000-AddNetworkDeviceRoleTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +31 -5
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceRoleService.js +70 -0
- package/build/dist/Server/Services/NetworkDeviceRoleService.js.map +1 -0
- package/build/dist/Server/Services/ProjectService.js +65 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +0 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Types/NetworkDevice/DefaultNetworkDeviceRole.js +105 -0
- package/build/dist/Types/NetworkDevice/DefaultNetworkDeviceRole.js.map +1 -0
- package/build/dist/Types/Permission.js +40 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +6 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +79 -31
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +7 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +79 -31
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +14 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -0
- package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +39 -13
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +44 -9
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +1 -1
- package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenu.js +9 -6
- package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenuItem.js +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenuItem.js.map +1 -1
- package/build/dist/UI/Components/SideMenu/SideMenuSection.js +7 -2
- package/build/dist/UI/Components/SideMenu/SideMenuSection.js.map +1 -1
- package/build/dist/UI/Utils/UseDashboardTimeRangeZoom.js +42 -0
- package/build/dist/UI/Utils/UseDashboardTimeRangeZoom.js.map +1 -0
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +74 -0
- package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleCatalog.js +126 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleCatalog.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +2 -1
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +67 -32
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceRoleKeyUtil.js +114 -0
- package/build/dist/Utils/NetworkDevice/DeviceRoleKeyUtil.js.map +1 -0
- package/build/dist/Utils/Rules/RulePatternMatchUtil.js +1 -1
- package/build/dist/Utils/Slug.js +31 -1
- package/build/dist/Utils/Slug.js.map +1 -1
- package/package.json +1 -1
|
@@ -129,6 +129,11 @@ export interface ComponentProps {
|
|
|
129
129
|
| undefined;
|
|
130
130
|
searchPlaceholder?: string | undefined;
|
|
131
131
|
noResultsText?: string | undefined;
|
|
132
|
+
/*
|
|
133
|
+
* Defaults to visible for direct/legacy consumers. Dashboard hides it when
|
|
134
|
+
* its command palette owns Cmd/Ctrl+K instead of this products menu.
|
|
135
|
+
*/
|
|
136
|
+
showCommandKShortcutHint?: boolean | undefined;
|
|
132
137
|
keyboardHint?: string | undefined;
|
|
133
138
|
recentLabel?: string | undefined;
|
|
134
139
|
onClose: () => void;
|
|
@@ -502,14 +507,14 @@ const NavBarMenuModal: FunctionComponent<ComponentProps> = (
|
|
|
502
507
|
>
|
|
503
508
|
<Icon icon={IconProp.Close} className="h-4 w-4" />
|
|
504
509
|
</button>
|
|
505
|
-
) : (
|
|
510
|
+
) : props.showCommandKShortcutHint !== false ? (
|
|
506
511
|
/* OS-appropriate hint: ⌘ K on a Mac, Ctrl K elsewhere. */
|
|
507
512
|
<KeyboardShortcut
|
|
508
513
|
keys={[KeyboardKey.Mod, "K"]}
|
|
509
514
|
size={KeyboardShortcutSize.Small}
|
|
510
515
|
className="hidden sm:inline-flex"
|
|
511
516
|
/>
|
|
512
|
-
)}
|
|
517
|
+
) : null}
|
|
513
518
|
</div>
|
|
514
519
|
|
|
515
520
|
{/* Body */}
|
|
@@ -21,6 +21,7 @@ export const REFRESH_SIDEBAR_COUNT_EVENT: string = "REFRESH_SIDEBAR_COUNTS";
|
|
|
21
21
|
|
|
22
22
|
export interface ComponentProps<TBaseModel extends BaseModel> {
|
|
23
23
|
link: Link;
|
|
24
|
+
activeRoute?: Link["to"] | undefined;
|
|
24
25
|
modelType?: { new (): TBaseModel } | undefined;
|
|
25
26
|
badgeType?: BadgeType | undefined;
|
|
26
27
|
countQuery?: Query<TBaseModel> | undefined;
|
|
@@ -136,6 +137,7 @@ const CountModelSideMenuItem: <TBaseModel extends BaseModel>(
|
|
|
136
137
|
return (
|
|
137
138
|
<SideMenuItem
|
|
138
139
|
link={props.link}
|
|
140
|
+
activeRoute={props.activeRoute}
|
|
139
141
|
badge={!isLoading && !error ? count : undefined}
|
|
140
142
|
badgeType={props.badgeType}
|
|
141
143
|
icon={props.icon}
|
|
@@ -20,6 +20,8 @@ import { RequestOptions } from "../../Utils/ModelAPI/ModelAPI";
|
|
|
20
20
|
|
|
21
21
|
export interface SideMenuItemProps {
|
|
22
22
|
link: Link;
|
|
23
|
+
// Use a different route for selection when the link target also changes query state.
|
|
24
|
+
activeRoute?: Link["to"] | undefined;
|
|
23
25
|
showAlert?: boolean;
|
|
24
26
|
showWarning?: boolean;
|
|
25
27
|
badge?: number;
|
|
@@ -106,7 +108,7 @@ const SideMenu: FunctionComponent<ComponentProps> = (props: ComponentProps) => {
|
|
|
106
108
|
if (props.sections) {
|
|
107
109
|
for (const section of props.sections) {
|
|
108
110
|
for (const item of section.items) {
|
|
109
|
-
if (Navigation.isOnThisPage(item.link.to)) {
|
|
111
|
+
if (Navigation.isOnThisPage(item.activeRoute || item.link.to)) {
|
|
110
112
|
const result: {
|
|
111
113
|
sectionTitle: string;
|
|
112
114
|
itemTitle: string;
|
|
@@ -127,7 +129,7 @@ const SideMenu: FunctionComponent<ComponentProps> = (props: ComponentProps) => {
|
|
|
127
129
|
// Check direct items
|
|
128
130
|
if (props.items) {
|
|
129
131
|
for (const item of props.items) {
|
|
130
|
-
if (Navigation.isOnThisPage(item.link.to)) {
|
|
132
|
+
if (Navigation.isOnThisPage(item.activeRoute || item.link.to)) {
|
|
131
133
|
const result: {
|
|
132
134
|
itemTitle: string;
|
|
133
135
|
icon?: IconProp;
|
|
@@ -184,9 +186,13 @@ const SideMenu: FunctionComponent<ComponentProps> = (props: ComponentProps) => {
|
|
|
184
186
|
icon?: IconProp;
|
|
185
187
|
defaultCollapsed?: boolean;
|
|
186
188
|
collapsible?: boolean;
|
|
189
|
+
isActive: boolean;
|
|
187
190
|
} = {
|
|
188
191
|
key: `section-${sectionIndex}`,
|
|
189
192
|
title: section.title,
|
|
193
|
+
isActive: section.items.some((item: SideMenuItemProps): boolean => {
|
|
194
|
+
return Navigation.isOnThisPage(item.activeRoute || item.link.to);
|
|
195
|
+
}),
|
|
190
196
|
};
|
|
191
197
|
|
|
192
198
|
if (section.icon) {
|
|
@@ -209,6 +215,7 @@ const SideMenu: FunctionComponent<ComponentProps> = (props: ComponentProps) => {
|
|
|
209
215
|
<CountModelSideMenuItem
|
|
210
216
|
key={`section-${sectionIndex}-count-item-${itemIndex}`}
|
|
211
217
|
link={item.link}
|
|
218
|
+
activeRoute={item.activeRoute}
|
|
212
219
|
badgeType={item.badgeType}
|
|
213
220
|
modelType={item.modelType as any}
|
|
214
221
|
countQuery={item.countQuery as any}
|
|
@@ -225,6 +232,7 @@ const SideMenu: FunctionComponent<ComponentProps> = (props: ComponentProps) => {
|
|
|
225
232
|
<SideMenuItem
|
|
226
233
|
key={`section-${sectionIndex}-item-${itemIndex}`}
|
|
227
234
|
link={item.link}
|
|
235
|
+
activeRoute={item.activeRoute}
|
|
228
236
|
showAlert={item.showAlert}
|
|
229
237
|
showWarning={item.showWarning}
|
|
230
238
|
badge={item.badge}
|
|
@@ -252,6 +260,7 @@ const SideMenu: FunctionComponent<ComponentProps> = (props: ComponentProps) => {
|
|
|
252
260
|
<CountModelSideMenuItem
|
|
253
261
|
key={`count-item-${itemIndex}`}
|
|
254
262
|
link={item.link}
|
|
263
|
+
activeRoute={item.activeRoute}
|
|
255
264
|
badgeType={item.badgeType}
|
|
256
265
|
modelType={item.modelType as any}
|
|
257
266
|
countQuery={item.countQuery as any}
|
|
@@ -267,6 +276,7 @@ const SideMenu: FunctionComponent<ComponentProps> = (props: ComponentProps) => {
|
|
|
267
276
|
<SideMenuItem
|
|
268
277
|
key={`item-${itemIndex}`}
|
|
269
278
|
link={item.link}
|
|
279
|
+
activeRoute={item.activeRoute}
|
|
270
280
|
showAlert={item.showAlert}
|
|
271
281
|
showWarning={item.showWarning}
|
|
272
282
|
badge={item.badge}
|
|
@@ -9,6 +9,7 @@ import React, { FunctionComponent } from "react";
|
|
|
9
9
|
|
|
10
10
|
export interface ComponentProps {
|
|
11
11
|
link: Link;
|
|
12
|
+
activeRoute?: Link["to"] | undefined;
|
|
12
13
|
showAlert?: undefined | boolean;
|
|
13
14
|
showWarning?: undefined | boolean;
|
|
14
15
|
badge?: undefined | number;
|
|
@@ -23,7 +24,9 @@ const SideMenuItem: FunctionComponent<ComponentProps> = (
|
|
|
23
24
|
props: ComponentProps,
|
|
24
25
|
) => {
|
|
25
26
|
const { translateString } = useTranslateValue();
|
|
26
|
-
const isActive: boolean = Navigation.isOnThisPage(
|
|
27
|
+
const isActive: boolean = Navigation.isOnThisPage(
|
|
28
|
+
props.activeRoute || props.link.to,
|
|
29
|
+
);
|
|
27
30
|
const isSubItemActive: boolean = props.subItemLink
|
|
28
31
|
? Navigation.isOnThisPage(props.subItemLink.to)
|
|
29
32
|
: false;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import Icon from "../Icon/Icon";
|
|
2
2
|
import useTranslateValue from "../../Utils/Translation";
|
|
3
3
|
import IconProp from "../../../Types/Icon/IconProp";
|
|
4
|
-
import React, {
|
|
4
|
+
import React, {
|
|
5
|
+
FunctionComponent,
|
|
6
|
+
ReactElement,
|
|
7
|
+
useEffect,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
5
10
|
|
|
6
11
|
export interface ComponentProps {
|
|
7
12
|
title: string;
|
|
@@ -9,6 +14,7 @@ export interface ComponentProps {
|
|
|
9
14
|
defaultCollapsed?: boolean;
|
|
10
15
|
collapsible?: boolean;
|
|
11
16
|
icon?: IconProp;
|
|
17
|
+
isActive?: boolean;
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
const SideMenuSection: FunctionComponent<ComponentProps> = (
|
|
@@ -16,12 +22,18 @@ const SideMenuSection: FunctionComponent<ComponentProps> = (
|
|
|
16
22
|
) => {
|
|
17
23
|
const { translateString } = useTranslateValue();
|
|
18
24
|
const [isCollapsed, setIsCollapsed] = useState<boolean>(
|
|
19
|
-
props.defaultCollapsed
|
|
25
|
+
Boolean(props.defaultCollapsed && !props.isActive),
|
|
20
26
|
);
|
|
21
27
|
const translatedTitle: string = translateString(props.title) || props.title;
|
|
22
28
|
|
|
23
29
|
const isCollapsible: boolean = props.collapsible ?? true;
|
|
24
30
|
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (props.isActive) {
|
|
33
|
+
setIsCollapsed(false);
|
|
34
|
+
}
|
|
35
|
+
}, [props.isActive]);
|
|
36
|
+
|
|
25
37
|
const handleToggle: () => void = (): void => {
|
|
26
38
|
if (isCollapsible) {
|
|
27
39
|
setIsCollapsed(!isCollapsed);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import DashboardTimeRangeZoomUtil, {
|
|
2
|
+
DashboardTimeRangeZoomState,
|
|
3
|
+
} from "../../Utils/Dashboard/DashboardTimeRangeZoom";
|
|
4
|
+
import RangeStartAndEndDateTime from "../../Types/Time/RangeStartAndEndDateTime";
|
|
5
|
+
import { useCallback, useState } from "react";
|
|
6
|
+
|
|
7
|
+
export interface DashboardTimeRangeZoom {
|
|
8
|
+
/** The window to hand every widget on the board. */
|
|
9
|
+
startAndEndDate: RangeStartAndEndDateTime;
|
|
10
|
+
/** True while a panel drag-selection is narrowing the board. */
|
|
11
|
+
isZoomed: boolean;
|
|
12
|
+
/** An explicit pick from the time-range picker — a new baseline. */
|
|
13
|
+
setStartAndEndDate: (range: RangeStartAndEndDateTime) => void;
|
|
14
|
+
/** A drag-selection on a time-series panel — zooms the whole board. */
|
|
15
|
+
zoomToTimeRange: (startTime: Date, endTime: Date) => void;
|
|
16
|
+
/** Double-click on a panel, or the toolbar's reset — undoes the zoom. */
|
|
17
|
+
resetZoom: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type UseDashboardTimeRangeZoomFunction = (
|
|
21
|
+
initialRange: RangeStartAndEndDateTime,
|
|
22
|
+
) => DashboardTimeRangeZoom;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Owns the dashboard-wide time range and the one level of undo that makes
|
|
26
|
+
* drag-to-zoom reversible. Both dashboard shells (the authenticated
|
|
27
|
+
* DashboardView and the public DashboardViewPage) use this so a panel
|
|
28
|
+
* gesture means the same thing on both.
|
|
29
|
+
*
|
|
30
|
+
* All three callbacks are identity-stable for the life of the dashboard:
|
|
31
|
+
* they are handed down through the canvas into React.memo'd widgets whose
|
|
32
|
+
* comparators do not look at function props, so a changing reference would
|
|
33
|
+
* leave a widget holding a stale closure.
|
|
34
|
+
*/
|
|
35
|
+
const useDashboardTimeRangeZoom: UseDashboardTimeRangeZoomFunction = (
|
|
36
|
+
initialRange: RangeStartAndEndDateTime,
|
|
37
|
+
): DashboardTimeRangeZoom => {
|
|
38
|
+
const [state, setState] = useState<DashboardTimeRangeZoomState>(() => {
|
|
39
|
+
return DashboardTimeRangeZoomUtil.getInitialState(initialRange);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const setStartAndEndDate: (range: RangeStartAndEndDateTime) => void =
|
|
43
|
+
useCallback((range: RangeStartAndEndDateTime): void => {
|
|
44
|
+
setState((previous: DashboardTimeRangeZoomState) => {
|
|
45
|
+
return DashboardTimeRangeZoomUtil.selectRange(previous, range);
|
|
46
|
+
});
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const zoomToTimeRange: (startTime: Date, endTime: Date) => void = useCallback(
|
|
50
|
+
(startTime: Date, endTime: Date): void => {
|
|
51
|
+
setState((previous: DashboardTimeRangeZoomState) => {
|
|
52
|
+
return DashboardTimeRangeZoomUtil.zoomToWindow(
|
|
53
|
+
previous,
|
|
54
|
+
startTime,
|
|
55
|
+
endTime,
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
[],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const resetZoom: () => void = useCallback((): void => {
|
|
63
|
+
setState((previous: DashboardTimeRangeZoomState) => {
|
|
64
|
+
return DashboardTimeRangeZoomUtil.resetZoom(previous);
|
|
65
|
+
});
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
startAndEndDate: state.current,
|
|
70
|
+
isZoomed: DashboardTimeRangeZoomUtil.isZoomed(state),
|
|
71
|
+
setStartAndEndDate,
|
|
72
|
+
zoomToTimeRange,
|
|
73
|
+
resetZoom,
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default useDashboardTimeRangeZoom;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import InBetween from "../../Types/BaseDatabase/InBetween";
|
|
2
|
+
import RangeStartAndEndDateTime from "../../Types/Time/RangeStartAndEndDateTime";
|
|
3
|
+
import TimeRange from "../../Types/Time/TimeRange";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The dashboard's time window, plus the window to go back to when a zoom
|
|
7
|
+
* is undone.
|
|
8
|
+
*
|
|
9
|
+
* A dashboard has exactly ONE time range and every widget renders it, so
|
|
10
|
+
* drag-selecting a window on a time-series panel is a statement about the
|
|
11
|
+
* whole board, not about that one panel. `baseline` is what makes the
|
|
12
|
+
* gesture reversible: the first zoom records the range that was in effect
|
|
13
|
+
* before it, later zooms keep that same original, and a reset restores it.
|
|
14
|
+
* `baseline === null` is the canonical "not zoomed" signal — the toolbar's
|
|
15
|
+
* reset affordance and the panels' double-click-to-reset both key off it.
|
|
16
|
+
*/
|
|
17
|
+
export interface DashboardTimeRangeZoomState {
|
|
18
|
+
/** The window every widget on the board is currently querying. */
|
|
19
|
+
current: RangeStartAndEndDateTime;
|
|
20
|
+
/** Window a reset returns to; null when no zoom is active. */
|
|
21
|
+
baseline: RangeStartAndEndDateTime | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default class DashboardTimeRangeZoomUtil {
|
|
25
|
+
public static getInitialState(
|
|
26
|
+
range: RangeStartAndEndDateTime,
|
|
27
|
+
): DashboardTimeRangeZoomState {
|
|
28
|
+
return {
|
|
29
|
+
current: range,
|
|
30
|
+
baseline: null,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public static isZoomed(state: DashboardTimeRangeZoomState): boolean {
|
|
35
|
+
return state.baseline !== null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* An explicit pick from the time-range picker. That is a new baseline,
|
|
40
|
+
* not a zoom: it drops any pending "return to" window, so the reset
|
|
41
|
+
* affordance disappears instead of offering to jump back to a range the
|
|
42
|
+
* user has deliberately moved on from.
|
|
43
|
+
*/
|
|
44
|
+
public static selectRange(
|
|
45
|
+
_state: DashboardTimeRangeZoomState,
|
|
46
|
+
range: RangeStartAndEndDateTime,
|
|
47
|
+
): DashboardTimeRangeZoomState {
|
|
48
|
+
return {
|
|
49
|
+
current: range,
|
|
50
|
+
baseline: null,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Drag-selected window from a time-series panel. Returns the state
|
|
56
|
+
* unchanged (same reference, so callers can skip a render) when the
|
|
57
|
+
* selection is not a usable window — the chart library can hand back a
|
|
58
|
+
* zero-width or invalid pair when a drag never left its starting bucket.
|
|
59
|
+
*/
|
|
60
|
+
public static zoomToWindow(
|
|
61
|
+
state: DashboardTimeRangeZoomState,
|
|
62
|
+
startTime: Date,
|
|
63
|
+
endTime: Date,
|
|
64
|
+
): DashboardTimeRangeZoomState {
|
|
65
|
+
if (!(startTime instanceof Date) || !(endTime instanceof Date)) {
|
|
66
|
+
return state;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const startTimeInMs: number = startTime.getTime();
|
|
70
|
+
const endTimeInMs: number = endTime.getTime();
|
|
71
|
+
|
|
72
|
+
if (Number.isNaN(startTimeInMs) || Number.isNaN(endTimeInMs)) {
|
|
73
|
+
return state;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// A drag right-to-left is the same window as left-to-right.
|
|
77
|
+
const lowerInMs: number = Math.min(startTimeInMs, endTimeInMs);
|
|
78
|
+
const upperInMs: number = Math.max(startTimeInMs, endTimeInMs);
|
|
79
|
+
|
|
80
|
+
if (lowerInMs === upperInMs) {
|
|
81
|
+
return state;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
current: {
|
|
86
|
+
range: TimeRange.CUSTOM,
|
|
87
|
+
startAndEndDate: new InBetween<Date>(
|
|
88
|
+
new Date(lowerInMs),
|
|
89
|
+
new Date(upperInMs),
|
|
90
|
+
),
|
|
91
|
+
},
|
|
92
|
+
/*
|
|
93
|
+
* Zooming again while already zoomed keeps the ORIGINAL baseline, so
|
|
94
|
+
* one reset always lands back where the investigation started rather
|
|
95
|
+
* than unwinding one drag at a time.
|
|
96
|
+
*/
|
|
97
|
+
baseline: state.baseline || state.current,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Undo the zoom. A no-op (same state reference) when nothing is zoomed,
|
|
103
|
+
* so a stray double-click on an unzoomed dashboard cannot retime the
|
|
104
|
+
* board or spend a refetch.
|
|
105
|
+
*/
|
|
106
|
+
public static resetZoom(
|
|
107
|
+
state: DashboardTimeRangeZoomState,
|
|
108
|
+
): DashboardTimeRangeZoomState {
|
|
109
|
+
if (state.baseline === null) {
|
|
110
|
+
return state;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
current: state.baseline,
|
|
115
|
+
baseline: null,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NetworkTopologyDeviceRole,
|
|
3
|
+
NetworkTopologyNode,
|
|
4
|
+
NetworkTopologyNodeShape,
|
|
5
|
+
} from "../../Types/Monitor/SnmpMonitor/NetworkTopology";
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* The project's configured device roles, reduced to what the topology needs.
|
|
9
|
+
*
|
|
10
|
+
* WHY THIS EXISTS. Roles are rows now (NetworkDeviceRole), which means the
|
|
11
|
+
* label a role is drawn with, the silhouette it uses, whether it sits at the
|
|
12
|
+
* core and whether it is worth walking with SNMP are all per-project answers.
|
|
13
|
+
* None of them can be compiled into the client any more.
|
|
14
|
+
*
|
|
15
|
+
* The client is NOT given the table to look things up in, though. The topology
|
|
16
|
+
* builder stamps the four answers onto each node as it builds it, so every
|
|
17
|
+
* consumer downstream - the shape module, three layouts, the legend, the
|
|
18
|
+
* detail panel, the accessible label - keeps its current pure signature and
|
|
19
|
+
* needs no catalogue threaded through it. This module is the one place that
|
|
20
|
+
* turns rows into that stamp.
|
|
21
|
+
*
|
|
22
|
+
* Pure and dependency-free so it runs identically on the server, in the
|
|
23
|
+
* builder, and in the unit tests.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** One configured role, as the topology builder needs it. */
|
|
27
|
+
export interface TopologyDeviceRoleInput {
|
|
28
|
+
id?: string | undefined;
|
|
29
|
+
key: string;
|
|
30
|
+
name: string;
|
|
31
|
+
topologyShape?: string | undefined;
|
|
32
|
+
isCoreLayer?: boolean | undefined;
|
|
33
|
+
isSnmpWalkable?: boolean | undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** What gets stamped onto a node. Every field optional - see stampForRoleKey. */
|
|
37
|
+
export interface TopologyNodeRoleStamp {
|
|
38
|
+
roleId?: string | undefined;
|
|
39
|
+
roleKey?: string | undefined;
|
|
40
|
+
roleLabel?: string | undefined;
|
|
41
|
+
roleShape?: NetworkTopologyNodeShape | undefined;
|
|
42
|
+
isCoreLayerRole?: boolean | undefined;
|
|
43
|
+
isSnmpWalkableRole?: boolean | undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* The silhouettes the renderer can actually draw. A row's topologyShape is a
|
|
48
|
+
* free-text column - the geometry lives in the client and adding a shape must
|
|
49
|
+
* not need a migration - so a value that is not one of these is dropped rather
|
|
50
|
+
* than passed through to a renderer that would have no path for it.
|
|
51
|
+
*/
|
|
52
|
+
const KNOWN_SHAPES: ReadonlySet<string> = new Set<string>([
|
|
53
|
+
"circle",
|
|
54
|
+
"rounded-square",
|
|
55
|
+
"diamond",
|
|
56
|
+
"triangle",
|
|
57
|
+
"hexagon",
|
|
58
|
+
"tower",
|
|
59
|
+
"cylinder",
|
|
60
|
+
"rect",
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
export type NormalizeRoleKeyFunction = (
|
|
64
|
+
value: string | undefined | null,
|
|
65
|
+
) => string | undefined;
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
* Keys are matched case- and whitespace-insensitively, the same way
|
|
69
|
+
* parseDeviceRoleOverride reads the stored column, so a hand-edited or
|
|
70
|
+
* imported "Router " still finds the Router row.
|
|
71
|
+
*/
|
|
72
|
+
export const normalizeRoleKey: NormalizeRoleKeyFunction = (
|
|
73
|
+
value: string | undefined | null,
|
|
74
|
+
): string | undefined => {
|
|
75
|
+
const normalized: string = (value || "").trim().toLowerCase();
|
|
76
|
+
return normalized || undefined;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Index the project's roles for lookup by key.
|
|
81
|
+
*
|
|
82
|
+
* Later rows do not overwrite earlier ones on a key clash. The key is unique
|
|
83
|
+
* per project in the database, so a clash here means the caller passed rows
|
|
84
|
+
* from more than one project, and quietly taking the last one would draw the
|
|
85
|
+
* wrong project's shapes.
|
|
86
|
+
*/
|
|
87
|
+
export type BuildRoleIndexFunction = (
|
|
88
|
+
roles: ReadonlyArray<TopologyDeviceRoleInput>,
|
|
89
|
+
) => Map<string, TopologyDeviceRoleInput>;
|
|
90
|
+
|
|
91
|
+
export const buildDeviceRoleIndex: BuildRoleIndexFunction = (
|
|
92
|
+
roles: ReadonlyArray<TopologyDeviceRoleInput>,
|
|
93
|
+
): Map<string, TopologyDeviceRoleInput> => {
|
|
94
|
+
const index: Map<string, TopologyDeviceRoleInput> = new Map<
|
|
95
|
+
string,
|
|
96
|
+
TopologyDeviceRoleInput
|
|
97
|
+
>();
|
|
98
|
+
|
|
99
|
+
for (const role of roles) {
|
|
100
|
+
const key: string | undefined = normalizeRoleKey(role.key);
|
|
101
|
+
if (!key || index.has(key)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
index.set(key, role);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return index;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export type StampForRoleKeyFunction = (
|
|
111
|
+
roleKey: string | undefined,
|
|
112
|
+
index: Map<string, TopologyDeviceRoleInput>,
|
|
113
|
+
) => TopologyNodeRoleStamp;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The stamp for one role key.
|
|
117
|
+
*
|
|
118
|
+
* Returns an EMPTY object when the project has no row for the key - which is
|
|
119
|
+
* the normal case for a project that has not been backfilled yet, and the
|
|
120
|
+
* permanent case for a project that deleted a seeded role the classifier can
|
|
121
|
+
* still produce. Every field a node does not get falls back to the built-in
|
|
122
|
+
* behaviour on the client, so an empty stamp draws exactly the map that was
|
|
123
|
+
* drawn before roles were configurable.
|
|
124
|
+
*
|
|
125
|
+
* The one field always present when a row IS found is `roleKey`, because the
|
|
126
|
+
* legend groups by it and a group with no key cannot be grouped.
|
|
127
|
+
*/
|
|
128
|
+
export const stampForRoleKey: StampForRoleKeyFunction = (
|
|
129
|
+
roleKey: string | undefined,
|
|
130
|
+
index: Map<string, TopologyDeviceRoleInput>,
|
|
131
|
+
): TopologyNodeRoleStamp => {
|
|
132
|
+
const normalized: string | undefined = normalizeRoleKey(roleKey);
|
|
133
|
+
if (!normalized) {
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const role: TopologyDeviceRoleInput | undefined = index.get(normalized);
|
|
138
|
+
if (!role) {
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const stamp: TopologyNodeRoleStamp = {
|
|
143
|
+
roleKey: role.key,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
if (role.id) {
|
|
147
|
+
stamp.roleId = role.id;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (role.name) {
|
|
151
|
+
stamp.roleLabel = role.name;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (role.topologyShape && KNOWN_SHAPES.has(role.topologyShape)) {
|
|
155
|
+
stamp.roleShape = role.topologyShape as NetworkTopologyNodeShape;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (role.isCoreLayer !== undefined) {
|
|
159
|
+
stamp.isCoreLayerRole = Boolean(role.isCoreLayer);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (role.isSnmpWalkable !== undefined) {
|
|
163
|
+
stamp.isSnmpWalkableRole = Boolean(role.isSnmpWalkable);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return stamp;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export type RoleKeyForNodeFunction = (
|
|
170
|
+
assignedRoleKey: string | undefined,
|
|
171
|
+
classifiedRole: NetworkTopologyDeviceRole,
|
|
172
|
+
) => string | undefined;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Which key a node's presentation should come from.
|
|
176
|
+
*
|
|
177
|
+
* The operator's assignment wins, exactly as it does for `role` itself. With
|
|
178
|
+
* no assignment the classifier's answer is used, so a project that renamed
|
|
179
|
+
* "Router" to "Edge Router" sees the new name on devices it never had to
|
|
180
|
+
* touch. "unknown" is not a key any row holds and is deliberately not looked
|
|
181
|
+
* up: it is the classifier declining to answer, and a neutral node is the
|
|
182
|
+
* honest drawing of that.
|
|
183
|
+
*/
|
|
184
|
+
export const roleKeyForNode: RoleKeyForNodeFunction = (
|
|
185
|
+
assignedRoleKey: string | undefined,
|
|
186
|
+
classifiedRole: NetworkTopologyDeviceRole,
|
|
187
|
+
): string | undefined => {
|
|
188
|
+
const assigned: string | undefined = normalizeRoleKey(assignedRoleKey);
|
|
189
|
+
if (assigned) {
|
|
190
|
+
return assigned;
|
|
191
|
+
}
|
|
192
|
+
if (classifiedRole === "unknown") {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
return classifiedRole;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export type ApplyRoleStampFunction = (
|
|
199
|
+
node: NetworkTopologyNode,
|
|
200
|
+
stamp: TopologyNodeRoleStamp,
|
|
201
|
+
) => void;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Copy a stamp onto a node, omitting the fields it does not carry.
|
|
205
|
+
*
|
|
206
|
+
* Assigning undefined rather than omitting would break the payload's own
|
|
207
|
+
* contract - "absent means fall back" - for any consumer that checks with
|
|
208
|
+
* `in` or serialises the node, so absent stays absent.
|
|
209
|
+
*/
|
|
210
|
+
export const applyRoleStamp: ApplyRoleStampFunction = (
|
|
211
|
+
node: NetworkTopologyNode,
|
|
212
|
+
stamp: TopologyNodeRoleStamp,
|
|
213
|
+
): void => {
|
|
214
|
+
if (stamp.roleId !== undefined) {
|
|
215
|
+
node.roleId = stamp.roleId;
|
|
216
|
+
}
|
|
217
|
+
if (stamp.roleKey !== undefined) {
|
|
218
|
+
node.roleKey = stamp.roleKey;
|
|
219
|
+
}
|
|
220
|
+
if (stamp.roleLabel !== undefined) {
|
|
221
|
+
node.roleLabel = stamp.roleLabel;
|
|
222
|
+
}
|
|
223
|
+
if (stamp.roleShape !== undefined) {
|
|
224
|
+
node.roleShape = stamp.roleShape;
|
|
225
|
+
}
|
|
226
|
+
if (stamp.isCoreLayerRole !== undefined) {
|
|
227
|
+
node.isCoreLayerRole = stamp.isCoreLayerRole;
|
|
228
|
+
}
|
|
229
|
+
if (stamp.isSnmpWalkableRole !== undefined) {
|
|
230
|
+
node.isSnmpWalkableRole = stamp.isSnmpWalkableRole;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
@@ -538,7 +538,8 @@ export function labelForDeviceRole(
|
|
|
538
538
|
* The classifier above is evidence-driven, and its evidence is SNMP: a
|
|
539
539
|
* device nothing walks — a ping-only device imported by discovery, say —
|
|
540
540
|
* offers it a hostname and nothing else, so it comes back "unknown" and
|
|
541
|
-
* stays there forever.
|
|
541
|
+
* stays there forever. The role an operator ASSIGNED the device
|
|
542
|
+
* (NetworkDevice.networkDeviceRole, whose key is what arrives here) is them saying
|
|
542
543
|
* what the box actually is, and it is the only statement about a role that
|
|
543
544
|
* is not an inference, so it outranks everything the classifier can find.
|
|
544
545
|
*
|