@oneuptime/common 13.0.8 → 14.0.1
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/AnalyticsModels/AuditLog.ts +23 -15
- package/Models/DatabaseModels/GlobalConfig.ts +24 -0
- package/Models/DatabaseModels/Index.ts +4 -0
- package/Models/DatabaseModels/Monitor.ts +3 -2
- package/Models/DatabaseModels/MonitorTemplate.ts +2 -1
- package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
- package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
- package/Scripts/generate-service-worker.js +65 -4
- package/Server/API/BaseAPI.ts +26 -5
- package/Server/API/GlobalConfigAPI.ts +228 -462
- package/Server/API/ProjectOIDC.ts +25 -16
- package/Server/API/ProjectSSO.ts +24 -16
- package/Server/API/StatusPageAPI.ts +100 -42
- package/Server/API/TeamMemberAPI.ts +10 -1
- package/Server/Enterprise/EnterpriseEdition.ts +649 -0
- package/Server/Enterprise/EnterpriseFeature.ts +48 -0
- package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +192 -0
- package/Server/Enterprise/EnterpriseServerModule.ts +233 -0
- package/Server/EnvironmentConfig.ts +147 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Middleware/UserAuthorization.ts +38 -16
- package/Server/Services/AuditLogService.ts +84 -1079
- package/Server/Services/DatabaseService.ts +10 -0
- package/Server/Services/EnterpriseLicenseService.ts +38 -0
- package/Server/Services/GlobalConfigService.ts +80 -1
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/MonitorProbeService.ts +116 -20
- package/Server/Services/MonitorService.ts +59 -6
- package/Server/Services/MonitorStatusTimelineService.ts +284 -0
- package/Server/Services/MonitorTemplateService.ts +26 -0
- package/Server/Services/ProjectService.ts +284 -0
- package/Server/Services/RumSessionPinService.ts +25 -6
- package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
- package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
- package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
- package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
- package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
- package/Server/Services/StatusPagePrivateUserService.ts +7 -3
- package/Server/Services/StatusPageService.ts +78 -1
- package/Server/Services/TeamMemberService.ts +11 -0
- package/Server/Services/TeamService.ts +10 -0
- package/Server/Services/UserService.ts +10 -17
- package/Server/Types/Database/Permissions/CreatePermission.ts +15 -0
- package/Server/Types/Database/Permissions/EditionPermission.ts +208 -18
- package/Server/Types/Database/Permissions/TablePermission.ts +10 -3
- package/Server/Types/Database/Permissions/UpdatePermission.ts +29 -0
- package/Server/Types/Markdown.ts +41 -0
- package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
- package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
- package/Server/Utils/CommunityEditionSsoReport.ts +361 -0
- package/Server/Utils/EditionEnforcement.ts +233 -0
- package/Server/Utils/FrontendEnvironment.ts +41 -2
- package/Server/Utils/InstanceHealth/RedisHealth.ts +12 -277
- package/Server/Utils/Monitor/AffectedResourceList.ts +148 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
- package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +10 -1
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +720 -465
- package/Server/Utils/Monitor/MonitoringIntervalValidator.ts +116 -0
- package/Server/Utils/Monitor/RootCauseList.ts +139 -0
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
- package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
- package/Server/Utils/Workspace/Slack/Slack.ts +423 -10
- package/Tests/App/Accounts/LoginPasskey.test.tsx +18 -0
- package/Tests/App/Accounts/LoginSsoEdition.test.tsx +616 -0
- package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +217 -0
- package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +357 -0
- package/Tests/App/AdminDashboard/EnterpriseLicensesShell.test.tsx +214 -0
- package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +271 -0
- package/Tests/App/AdminDashboard/HealthShells.test.tsx +659 -0
- package/Tests/App/Dashboard/AuditLogsAndComplianceShells.test.tsx +572 -0
- package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +547 -0
- package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +667 -0
- package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
- package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
- package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
- package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +319 -0
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
- package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +185 -0
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
- package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
- package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
- package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +45 -9
- package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +399 -0
- package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +214 -0
- package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +1 -83
- package/Tests/App/Dashboard/SecurityEventsNavTabs.test.tsx +248 -0
- package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +1203 -0
- package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
- package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
- package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
- package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
- package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
- package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
- package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +11 -91
- package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +319 -0
- package/Tests/Helpers/RefreshAwareApiScan.ts +757 -0
- package/Tests/Helpers/TenantScopedRequestScan.ts +515 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
- package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
- package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/Scripts/GenerateServiceWorker.test.ts +931 -0
- package/Tests/Server/API/BaseAPICreateItemDoesNotMutateModel.test.ts +130 -0
- package/Tests/Server/API/GlobalConfigLicense.test.ts +742 -497
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +35 -909
- package/Tests/Server/API/SsoProviderListingsEdition.test.ts +432 -0
- package/Tests/Server/API/TeamMemberLeaveScimLockEdition.test.ts +252 -0
- package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +824 -0
- package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +643 -0
- package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +294 -0
- package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +93 -0
- package/Tests/Server/Enterprise/EnterpriseOnlyModulesLiveInEe.test.ts +582 -0
- package/Tests/Server/Enterprise/EnterpriseServerModule.test.ts +247 -0
- package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +502 -0
- package/Tests/Server/Enterprise/TestBillingFlag.ts +70 -0
- package/Tests/Server/EnvironmentConfigEnterpriseEdition.test.ts +314 -0
- package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +289 -0
- package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +5 -1
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
- package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
- package/Tests/Server/Middleware/UserAuthorization.test.ts +37 -2
- package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +516 -0
- package/Tests/Server/Services/AuditLogService.test.ts +460 -990
- package/Tests/Server/Services/CreateAndReadResponsesKeepIds.test.ts +263 -0
- package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
- package/Tests/Server/Services/EnterpriseLicenseServiceKeyGeneration.test.ts +151 -0
- package/Tests/Server/Services/GlobalConfigServiceLicenseColumns.test.ts +357 -0
- package/Tests/Server/Services/MonitorDestinationInfoIPv6.test.ts +101 -0
- package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +333 -0
- package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +303 -0
- package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +701 -0
- package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +31 -35
- package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
- package/Tests/Server/Services/ScimPushGroupsTeamLocksEdition.test.ts +431 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
- package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +419 -0
- package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +769 -0
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
- package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +94 -0
- package/Tests/Server/Services/UserAuthenticationService.test.ts +35 -0
- package/Tests/Server/Services/UserServiceSeatLimit.test.ts +94 -56
- package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
- package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +1823 -0
- package/Tests/Server/Types/Markdown.test.ts +183 -1
- package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
- package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
- package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
- package/Tests/Server/Utils/CommunityEditionSsoReport.test.ts +565 -0
- package/Tests/Server/Utils/EditionEnforcement.test.ts +303 -0
- package/Tests/Server/Utils/FrontendEnvironment.test.ts +367 -0
- package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +38 -418
- package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +518 -0
- package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +24 -4
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +1339 -96
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
- package/Tests/Server/Utils/Monitor/MonitoringIntervalValidator.test.ts +188 -0
- package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
- package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
- package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
- package/Tests/Server/Utils/Workspace/Slack/SlackMarkdownSectionSplit.test.ts +1209 -0
- package/Tests/Types/API/HostnameFromAuthority.test.ts +20 -10
- package/Tests/Types/API/HostnameIPv6.test.ts +149 -0
- package/Tests/Types/API/Route.test.ts +56 -0
- package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
- package/Tests/Types/Database/CanonicalModelInstance.test.ts +190 -0
- package/Tests/UI/Components/EditionLabelLapseCopy.ts +104 -0
- package/Tests/UI/Components/EditionLabelLicenseManagerWiring.test.tsx +254 -0
- package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +84 -178
- package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +1586 -0
- package/Tests/UI/Components/EditionLabelLicenseWritesGuard.test.tsx +157 -0
- package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
- package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
- package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +49 -2
- package/Tests/UI/Components/Icon.test.tsx +26 -0
- package/Tests/UI/Components/MarkdownConverters.test.ts +684 -0
- package/Tests/UI/Components/ModelListEmptyState.test.tsx +289 -0
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerEmptyContent.test.tsx +131 -0
- package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
- package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
- package/Tests/UI/EsbuildEnterpriseAlias.test.ts +1127 -0
- package/Tests/Utils/HostAddressUtil.test.ts +138 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
- package/Tests/Utils/Monitor/MonitorDestinationUtil.test.ts +313 -0
- package/Tests/Utils/Monitor/MonitoringIntervalUtil.test.ts +304 -0
- package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
- package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
- package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
- package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
- package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +248 -0
- package/Types/API/Hostname.ts +63 -16
- package/Types/API/Route.ts +19 -2
- package/Types/API/URL.ts +26 -2
- package/Types/Database/AccessControl/ColumnAccessControl.ts +19 -7
- package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +19 -7
- package/Types/Database/AccessControl/OwnerOnlyColumn.ts +15 -6
- package/Types/Database/CanonicalModelInstance.ts +102 -0
- package/Types/Database/TableColumn.ts +20 -6
- package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +29 -0
- package/Types/IP/IPv6.ts +6 -1
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
- package/Types/Monitor/MonitorStep.ts +6 -1
- package/Types/Monitor/ProxmoxAlertTemplates.ts +1 -1
- package/Types/Permission.ts +90 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
- package/Types/Rules/RuleRun.ts +4 -0
- package/Types/StatusPage/UptimeDailyAggregate.ts +110 -0
- package/UI/Components/EditionLabel/EditionLabel.tsx +796 -1195
- package/UI/Components/EditionLabel/LicenseManager.ts +119 -0
- package/UI/Components/Feed/FeedOptions.ts +427 -0
- package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
- package/UI/Components/Feed/useFeedItems.ts +28 -9
- package/UI/Components/Feed/useFeedOptions.ts +225 -0
- package/UI/Components/Graphs/DayUptimeGraph.tsx +123 -15
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
- package/UI/Components/ModelList/ModelList.tsx +11 -1
- package/UI/Components/MonitorGraphs/Uptime.tsx +69 -0
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +28 -12
- package/UI/esbuild-enterprise.js +263 -0
- package/Utils/EnterpriseLicense/EnterpriseLicenseUsage.ts +7 -2
- package/Utils/HostAddressUtil.ts +94 -0
- package/Utils/LabelRuleImportExport.ts +27 -9
- package/Utils/Monitor/MetricValueFormatter.ts +2 -2
- package/Utils/Monitor/MonitorDestinationUtil.ts +157 -0
- package/Utils/Monitor/MonitoringIntervalUtil.ts +436 -0
- package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
- package/Utils/Slo/SloDuration.ts +22 -6
- package/Utils/Slo/SloHealth.ts +8 -7
- package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
- package/Utils/Slo/SloOverviewText.ts +33 -0
- package/Utils/Slo/SloProjection.ts +4 -3
- package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +192 -0
- package/build/dist/Models/AnalyticsModels/AuditLog.js +23 -15
- package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +25 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +2 -2
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js +2 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
- package/build/dist/Server/API/BaseAPI.js +26 -5
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/GlobalConfigAPI.js +148 -344
- package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
- package/build/dist/Server/API/ProjectOIDC.js +24 -16
- package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
- package/build/dist/Server/API/ProjectSSO.js +24 -16
- package/build/dist/Server/API/ProjectSSO.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +93 -43
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TeamMemberAPI.js +8 -1
- package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
- package/build/dist/Server/Enterprise/EnterpriseEdition.js +466 -0
- package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -0
- package/build/dist/Server/Enterprise/EnterpriseFeature.js +39 -0
- package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -0
- package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +70 -0
- package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js.map +1 -0
- package/build/dist/Server/Enterprise/EnterpriseServerModule.js +84 -0
- package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +84 -2
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793900000000-AddEnterpriseEditionFirstSeenAt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +30 -11
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AuditLogService.js +71 -740
- package/build/dist/Server/Services/AuditLogService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +7 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseService.js +34 -0
- package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
- package/build/dist/Server/Services/GlobalConfigService.js +63 -0
- package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorProbeService.js +90 -17
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +48 -6
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +242 -0
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +16 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +214 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RumSessionPinService.js +24 -6
- package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserService.js +7 -3
- package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +71 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +10 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TeamService.js +9 -0
- package/build/dist/Server/Services/TeamService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +10 -16
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +10 -0
- package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +154 -14
- package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js +10 -3
- package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +17 -0
- package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +31 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js +223 -0
- package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +1 -0
- package/build/dist/Server/Utils/EditionEnforcement.js +187 -0
- package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -0
- package/build/dist/Server/Utils/FrontendEnvironment.js +31 -2
- package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -1
- package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +6 -193
- package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +67 -0
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +8 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +585 -367
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js +88 -0
- package/build/dist/Server/Utils/Monitor/MonitoringIntervalValidator.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
- package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +331 -11
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Types/API/Hostname.js +59 -11
- package/build/dist/Types/API/Hostname.js.map +1 -1
- package/build/dist/Types/API/Route.js +18 -2
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/API/URL.js +23 -2
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +19 -7
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +19 -7
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +13 -6
- package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -1
- package/build/dist/Types/Database/CanonicalModelInstance.js +88 -0
- package/build/dist/Types/Database/CanonicalModelInstance.js.map +1 -0
- package/build/dist/Types/Database/TableColumn.js +19 -6
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +28 -0
- package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js.map +1 -0
- package/build/dist/Types/IP/IPv6.js +6 -1
- package/build/dist/Types/IP/IPv6.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +5 -0
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +1 -1
- package/build/dist/Types/Permission.js +84 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/Types/StatusPage/UptimeDailyAggregate.js +32 -0
- package/build/dist/Types/StatusPage/UptimeDailyAggregate.js.map +1 -0
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +414 -585
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/LicenseManager.js +2 -0
- package/build/dist/UI/Components/EditionLabel/LicenseManager.js.map +1 -0
- package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
- package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
- package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
- package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
- package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
- package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +90 -17
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
- package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
- package/build/dist/UI/Components/ModelList/ModelList.js +4 -1
- package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js +42 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +2 -2
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js +6 -2
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseUsage.js.map +1 -1
- package/build/dist/Utils/HostAddressUtil.js +80 -0
- package/build/dist/Utils/HostAddressUtil.js.map +1 -0
- package/build/dist/Utils/LabelRuleImportExport.js +22 -6
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
- package/build/dist/Utils/Monitor/MonitorDestinationUtil.js +127 -0
- package/build/dist/Utils/Monitor/MonitorDestinationUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js +374 -0
- package/build/dist/Utils/Monitor/MonitoringIntervalUtil.js.map +1 -0
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
- package/build/dist/Utils/Slo/SloDuration.js +13 -5
- package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
- package/build/dist/Utils/Slo/SloHealth.js +8 -7
- package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
- package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
- package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
- package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
- package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
- package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -0
- package/jest.config.json +3 -1
- package/package.json +1 -1
- package/test-setup.sh +103 -34
- package/tsconfig.json +13 -0
- package/Server/API/EnterpriseLicenseAPI.ts +0 -925
- package/Server/API/TeamComplianceAPI.ts +0 -158
- package/Server/Middleware/SCIMAuthorization.ts +0 -141
- package/Server/Services/TeamComplianceService.ts +0 -733
- package/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.ts +0 -186
- package/Server/Utils/InstanceHealth/PostgresHealth.ts +0 -286
- package/Tests/App/AdminDashboard/EnterpriseLicenseInstanceActivity.test.tsx +0 -451
- package/Tests/App/Dashboard/AuditLogsTable.test.tsx +0 -598
- package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +0 -761
- package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +0 -247
- package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +0 -382
- package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +0 -321
- package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +0 -1851
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +0 -525
- package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +0 -1845
- package/Tests/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.test.ts +0 -495
- package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +0 -337
- package/Tests/Utils/EnterpriseLicenseSeats.test.ts +0 -576
- package/Tests/Utils/EnterpriseLicenseSync.test.ts +0 -568
- package/Utils/EnterpriseLicense/EnterpriseLicenseSeats.ts +0 -287
- package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +0 -244
- package/build/dist/Server/API/EnterpriseLicenseAPI.js +0 -646
- package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +0 -1
- package/build/dist/Server/API/TeamComplianceAPI.js +0 -111
- package/build/dist/Server/API/TeamComplianceAPI.js.map +0 -1
- package/build/dist/Server/Middleware/SCIMAuthorization.js +0 -124
- package/build/dist/Server/Middleware/SCIMAuthorization.js.map +0 -1
- package/build/dist/Server/Services/TeamComplianceService.js +0 -537
- package/build/dist/Server/Services/TeamComplianceService.js.map +0 -1
- package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js +0 -145
- package/build/dist/Server/Utils/EnterpriseLicense/EnterpriseLicenseSeatUtil.js.map +0 -1
- package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +0 -194
- package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +0 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js +0 -168
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSeats.js.map +0 -1
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +0 -174
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +0 -1
|
@@ -30,6 +30,14 @@ import ExternalStatusPageMonitorCriteria from "./Criteria/ExternalStatusPageMoni
|
|
|
30
30
|
import MonitorCriteriaMessageBuilder from "./MonitorCriteriaMessageBuilder";
|
|
31
31
|
import MonitorCriteriaDataExtractor from "./MonitorCriteriaDataExtractor";
|
|
32
32
|
import MonitorCriteriaMessageFormatter from "./MonitorCriteriaMessageFormatter";
|
|
33
|
+
import AffectedResourceList, {
|
|
34
|
+
AffectedResourceListDetail,
|
|
35
|
+
AffectedResourceListEntry,
|
|
36
|
+
} from "./AffectedResourceList";
|
|
37
|
+
import RootCauseList, {
|
|
38
|
+
RootCauseListDetail,
|
|
39
|
+
RootCauseListItem,
|
|
40
|
+
} from "./RootCauseList";
|
|
33
41
|
import DataToProcess from "./DataToProcess";
|
|
34
42
|
import Monitor from "../../../Models/DatabaseModels/Monitor";
|
|
35
43
|
import MonitorCriteria from "../../../Types/Monitor/MonitorCriteria";
|
|
@@ -120,6 +128,15 @@ export interface TelemetryExplorerDeepLink {
|
|
|
120
128
|
dropped: Array<string>;
|
|
121
129
|
}
|
|
122
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Which affected-resources rows breached the matched criteria, and which
|
|
133
|
+
* end of the value range is the worst.
|
|
134
|
+
*/
|
|
135
|
+
interface AffectedResourceBreachPredicate {
|
|
136
|
+
matches: (value: number) => boolean;
|
|
137
|
+
worstIsLowest: boolean;
|
|
138
|
+
}
|
|
139
|
+
|
|
123
140
|
export default class MonitorCriteriaEvaluator {
|
|
124
141
|
public static async processMonitorStep(input: {
|
|
125
142
|
dataToProcess: DataToProcess;
|
|
@@ -1552,9 +1569,7 @@ ${contextBlock}
|
|
|
1552
1569
|
samples: breachingSamples,
|
|
1553
1570
|
totalSamples: ctx.totalSamplesInWindow,
|
|
1554
1571
|
unit: ctx.unit,
|
|
1555
|
-
metricName: ctx.metricName,
|
|
1556
1572
|
unitHeuristicMetricName: unitHeuristicMetricName,
|
|
1557
|
-
alias: ctx.alias,
|
|
1558
1573
|
components: ctx.components || [],
|
|
1559
1574
|
})}`,
|
|
1560
1575
|
);
|
|
@@ -1610,11 +1625,25 @@ ${contextBlock}
|
|
|
1610
1625
|
}
|
|
1611
1626
|
|
|
1612
1627
|
/**
|
|
1613
|
-
* Build the **Breaching Samples** markdown section — a
|
|
1614
|
-
*
|
|
1615
|
-
*
|
|
1616
|
-
*
|
|
1617
|
-
*
|
|
1628
|
+
* Build the **Breaching Samples** markdown section — a numbered list of
|
|
1629
|
+
* the samples that breached, oldest first. Caps the list so a 30-minute
|
|
1630
|
+
* window at 1-second granularity doesn't dump thousands of lines onto
|
|
1631
|
+
* the root-cause page; the caller can always drill in via the metric
|
|
1632
|
+
* explorer link.
|
|
1633
|
+
*
|
|
1634
|
+
* 1. `2026-08-14T10:30:00.000Z` — **1.07 GB**
|
|
1635
|
+
* - `a`: 537 MB
|
|
1636
|
+
* - `b`: 1.5 sec
|
|
1637
|
+
* - `k8s.pod.name`: `web-1`
|
|
1638
|
+
*
|
|
1639
|
+
* It used to be a table — Timestamp | Metric | Alias | Value, a column
|
|
1640
|
+
* per formula component and one per attribute key — which grew wider
|
|
1641
|
+
* with every attribute and read badly in the email card, in Slack and on
|
|
1642
|
+
* the dashboard (see RootCauseList). Its Metric and Alias columns
|
|
1643
|
+
* repeated the same two values on every row, the ones the Metric
|
|
1644
|
+
* Details list directly above already states, so the list leaves them
|
|
1645
|
+
* out; and a component or attribute a sample has no value for is left
|
|
1646
|
+
* out rather than printed as "-".
|
|
1618
1647
|
*
|
|
1619
1648
|
* Timestamps are emitted as inline code wrapping ISO 8601 strings so
|
|
1620
1649
|
* the client-side markdown viewer can localize them to the viewer's
|
|
@@ -1624,16 +1653,14 @@ ${contextBlock}
|
|
|
1624
1653
|
samples: Array<MetricBreachingSample>;
|
|
1625
1654
|
totalSamples?: number | undefined;
|
|
1626
1655
|
unit: string | null;
|
|
1627
|
-
metricName: string;
|
|
1628
1656
|
/**
|
|
1629
1657
|
* The metric name to reason about, or undefined for a formula.
|
|
1630
1658
|
* See MonitorCriteriaEvaluator.metricNameForUnitHeuristics.
|
|
1631
1659
|
*/
|
|
1632
1660
|
unitHeuristicMetricName: string | undefined;
|
|
1633
|
-
alias: string;
|
|
1634
1661
|
components: Array<MetricComponent>;
|
|
1635
1662
|
}): string {
|
|
1636
|
-
const
|
|
1663
|
+
const MAX_SAMPLES_SHOWN: number = 20;
|
|
1637
1664
|
|
|
1638
1665
|
// Sort chronologically and de-duplicate any accidental repeats
|
|
1639
1666
|
const sorted: Array<MetricBreachingSample> = [...input.samples].sort(
|
|
@@ -1646,10 +1673,13 @@ ${contextBlock}
|
|
|
1646
1673
|
|
|
1647
1674
|
const displayedSamples: Array<MetricBreachingSample> = sorted.slice(
|
|
1648
1675
|
0,
|
|
1649
|
-
|
|
1676
|
+
MAX_SAMPLES_SHOWN,
|
|
1650
1677
|
);
|
|
1651
1678
|
|
|
1652
|
-
|
|
1679
|
+
/*
|
|
1680
|
+
* Collect attribute keys that appear on any displayed sample, so every
|
|
1681
|
+
* item lists the attributes it has in the same order.
|
|
1682
|
+
*/
|
|
1653
1683
|
const attrKeySet: Set<string> = new Set<string>();
|
|
1654
1684
|
for (const s of displayedSamples) {
|
|
1655
1685
|
for (const k of Object.keys(s.attributes || {})) {
|
|
@@ -1658,107 +1688,74 @@ ${contextBlock}
|
|
|
1658
1688
|
}
|
|
1659
1689
|
const attrKeys: Array<string> = Array.from(attrKeySet);
|
|
1660
1690
|
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
* don't break GitHub-flavored-markdown tables.
|
|
1665
|
-
*/
|
|
1666
|
-
const escapeCell: (value: string) => string = (value: string): string => {
|
|
1667
|
-
return value.replace(/\|/g, "\\|");
|
|
1668
|
-
};
|
|
1669
|
-
|
|
1670
|
-
/*
|
|
1671
|
-
* Column layout:
|
|
1672
|
-
* Timestamp | Metric | Alias | Value | <component_1> | ... | <attr_1> | ...
|
|
1673
|
-
*
|
|
1674
|
-
* The component columns let the reader see what each variable of a
|
|
1675
|
-
* formula resolved to at the breach time — e.g. "when c = a + b
|
|
1676
|
-
* breached 100, a was 55, b was 46". They are omitted for plain
|
|
1677
|
-
* metric criteria.
|
|
1678
|
-
*/
|
|
1679
|
-
const headerCells: Array<string> = [
|
|
1680
|
-
"Timestamp",
|
|
1681
|
-
"Metric",
|
|
1682
|
-
"Alias",
|
|
1683
|
-
"Value",
|
|
1684
|
-
];
|
|
1685
|
-
|
|
1686
|
-
/*
|
|
1687
|
-
* The component columns carry no unit in their header any more. Each
|
|
1688
|
-
* cell now names the unit it actually landed on, and auto-scaling
|
|
1689
|
-
* means neighbouring rows can land on different ones — a header that
|
|
1690
|
-
* read "a (By)" over cells reading "900 KB" and "1.2 MB" states a
|
|
1691
|
-
* unit that neither row uses. The configured unit is not lost: the
|
|
1692
|
-
* Components bullet list above the table still records it.
|
|
1693
|
-
*/
|
|
1694
|
-
for (const component of input.components) {
|
|
1695
|
-
headerCells.push(component.alias);
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
headerCells.push(...attrKeys);
|
|
1699
|
-
|
|
1700
|
-
const headerRow: string = `| ${headerCells.join(" | ")} |`;
|
|
1701
|
-
const dividerRow: string = `| ${headerCells
|
|
1702
|
-
.map(() => {
|
|
1703
|
-
return "---";
|
|
1704
|
-
})
|
|
1705
|
-
.join(" | ")} |`;
|
|
1706
|
-
|
|
1707
|
-
const metricCell: string = `\`${escapeCell(input.metricName)}\``;
|
|
1708
|
-
const aliasCell: string = input.alias
|
|
1709
|
-
? `\`${escapeCell(input.alias)}\``
|
|
1710
|
-
: "-";
|
|
1711
|
-
|
|
1712
|
-
const dataRows: Array<string> = displayedSamples.map(
|
|
1713
|
-
(s: MetricBreachingSample) => {
|
|
1714
|
-
const timestampIso: string = new Date(s.timestamp).toISOString();
|
|
1715
|
-
const cells: Array<string> = [
|
|
1716
|
-
`\`${timestampIso}\``,
|
|
1717
|
-
metricCell,
|
|
1718
|
-
aliasCell,
|
|
1719
|
-
MetricValueFormatter.format({
|
|
1720
|
-
value: s.value,
|
|
1721
|
-
unit: input.unit,
|
|
1722
|
-
metricName: input.unitHeuristicMetricName,
|
|
1723
|
-
}),
|
|
1724
|
-
];
|
|
1691
|
+
const items: Array<RootCauseListItem> = displayedSamples.map(
|
|
1692
|
+
(s: MetricBreachingSample): RootCauseListItem => {
|
|
1693
|
+
const details: Array<RootCauseListDetail> = [];
|
|
1725
1694
|
|
|
1695
|
+
/*
|
|
1696
|
+
* What each variable of a formula resolved to at the breach time —
|
|
1697
|
+
* e.g. "when c = a + b breached 100, a was 55, b was 46". Absent
|
|
1698
|
+
* for plain metric criteria.
|
|
1699
|
+
*/
|
|
1726
1700
|
for (const component of input.components) {
|
|
1727
1701
|
const match: MetricComponentValue | undefined = (
|
|
1728
1702
|
s.componentValues || []
|
|
1729
1703
|
).find((cv: MetricComponentValue) => {
|
|
1730
1704
|
return cv.alias === component.alias;
|
|
1731
1705
|
});
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
* indexed off the per-query series and never go through the
|
|
1736
|
-
* sample→threshold-unit conversion the main Value column
|
|
1737
|
-
* does, so they are in the component's legendUnit — mixing
|
|
1738
|
-
* units across the row is deliberate, and is why every cell
|
|
1739
|
-
* has to say which one it is in.
|
|
1740
|
-
*/
|
|
1741
|
-
cells.push(
|
|
1742
|
-
MetricValueFormatter.format({
|
|
1743
|
-
value: match.value,
|
|
1744
|
-
unit: component.unit,
|
|
1745
|
-
metricName:
|
|
1746
|
-
MonitorCriteriaEvaluator.metricNameForUnitHeuristics({
|
|
1747
|
-
metricName: component.name,
|
|
1748
|
-
isFormula: component.isFormula,
|
|
1749
|
-
}),
|
|
1750
|
-
}),
|
|
1751
|
-
);
|
|
1752
|
-
} else {
|
|
1753
|
-
cells.push("-");
|
|
1706
|
+
|
|
1707
|
+
if (!match || typeof match.value !== "number") {
|
|
1708
|
+
continue;
|
|
1754
1709
|
}
|
|
1710
|
+
|
|
1711
|
+
/*
|
|
1712
|
+
* Each component keeps its OWN unit. Component values are
|
|
1713
|
+
* indexed off the per-query series and never go through the
|
|
1714
|
+
* sample→threshold-unit conversion the sample's value does, so
|
|
1715
|
+
* they are in the component's legendUnit — which is why each
|
|
1716
|
+
* one names the unit it is in. Its configured unit is in the
|
|
1717
|
+
* Components list of the Metric Details above.
|
|
1718
|
+
*/
|
|
1719
|
+
details.push({
|
|
1720
|
+
label: RootCauseList.code(component.alias),
|
|
1721
|
+
value: MetricValueFormatter.format({
|
|
1722
|
+
value: match.value,
|
|
1723
|
+
unit: component.unit,
|
|
1724
|
+
metricName: MonitorCriteriaEvaluator.metricNameForUnitHeuristics({
|
|
1725
|
+
metricName: component.name,
|
|
1726
|
+
isFormula: component.isFormula,
|
|
1727
|
+
}),
|
|
1728
|
+
}),
|
|
1729
|
+
});
|
|
1755
1730
|
}
|
|
1756
1731
|
|
|
1732
|
+
/*
|
|
1733
|
+
* The attributes identify the series the sample came from. Keys
|
|
1734
|
+
* and values are telemetry, so both go in code spans:
|
|
1735
|
+
* RootCauseList.code keeps whatever they contain inside the span.
|
|
1736
|
+
*/
|
|
1757
1737
|
for (const k of attrKeys) {
|
|
1758
1738
|
const v: unknown = (s.attributes as Record<string, unknown>)[k];
|
|
1759
|
-
|
|
1739
|
+
|
|
1740
|
+
if (v === undefined || v === null) {
|
|
1741
|
+
continue;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
details.push({
|
|
1745
|
+
label: RootCauseList.code(k),
|
|
1746
|
+
value: RootCauseList.code(String(v)),
|
|
1747
|
+
});
|
|
1760
1748
|
}
|
|
1761
|
-
|
|
1749
|
+
|
|
1750
|
+
return {
|
|
1751
|
+
title: RootCauseList.code(new Date(s.timestamp).toISOString()),
|
|
1752
|
+
value: `**${MetricValueFormatter.format({
|
|
1753
|
+
value: s.value,
|
|
1754
|
+
unit: input.unit,
|
|
1755
|
+
metricName: input.unitHeuristicMetricName,
|
|
1756
|
+
})}**`,
|
|
1757
|
+
details: details,
|
|
1758
|
+
};
|
|
1762
1759
|
},
|
|
1763
1760
|
);
|
|
1764
1761
|
|
|
@@ -1769,14 +1766,17 @@ ${contextBlock}
|
|
|
1769
1766
|
totalSamples: input.totalSamples,
|
|
1770
1767
|
}),
|
|
1771
1768
|
"",
|
|
1772
|
-
|
|
1773
|
-
dividerRow,
|
|
1774
|
-
...dataRows,
|
|
1769
|
+
RootCauseList.render(items),
|
|
1775
1770
|
];
|
|
1776
1771
|
|
|
1777
1772
|
if (sorted.length > displayedSamples.length) {
|
|
1773
|
+
/*
|
|
1774
|
+
* The blank line matters: without it the note is a lazy
|
|
1775
|
+
* continuation of the last item, not a paragraph of its own.
|
|
1776
|
+
*/
|
|
1778
1777
|
lines.push(
|
|
1779
|
-
|
|
1778
|
+
"",
|
|
1779
|
+
`_Showing the first ${displayedSamples.length} of ${sorted.length} breaching samples._`,
|
|
1780
1780
|
);
|
|
1781
1781
|
}
|
|
1782
1782
|
|
|
@@ -2018,7 +2018,7 @@ ${contextBlock}
|
|
|
2018
2018
|
}
|
|
2019
2019
|
|
|
2020
2020
|
/**
|
|
2021
|
-
* The unit for a value in a per-platform "Affected Resources"
|
|
2021
|
+
* The unit for a value in a per-platform "Affected Resources" list.
|
|
2022
2022
|
*
|
|
2023
2023
|
* These rows are NOT the same numbers as the criteria's breaching
|
|
2024
2024
|
* samples: the worker collects them with a raw `MetricService.findBy`
|
|
@@ -2060,10 +2060,11 @@ ${contextBlock}
|
|
|
2060
2060
|
}
|
|
2061
2061
|
|
|
2062
2062
|
/**
|
|
2063
|
-
* Render
|
|
2063
|
+
* Render the value of one resource in a platform "Affected Resources"
|
|
2064
|
+
* list.
|
|
2064
2065
|
*
|
|
2065
|
-
* Bold, because that is how
|
|
2066
|
-
* it is the one number
|
|
2066
|
+
* Bold, because that is how the breakdown has always drawn the value —
|
|
2067
|
+
* it is the one number on the line a reader scans for.
|
|
2067
2068
|
*/
|
|
2068
2069
|
private static formatPlatformResourceValue(input: {
|
|
2069
2070
|
platform: "kubernetes" | "proxmox" | "vmware" | "dockerSwarm" | "ceph";
|
|
@@ -2080,10 +2081,94 @@ ${contextBlock}
|
|
|
2080
2081
|
})}**`;
|
|
2081
2082
|
}
|
|
2082
2083
|
|
|
2084
|
+
/*
|
|
2085
|
+
* One Kubernetes resource as an "Affected Resources" list item.
|
|
2086
|
+
*
|
|
2087
|
+
* The item is titled by the most specific object the series names —
|
|
2088
|
+
* container, then pod, then workload, then node, then namespace — because
|
|
2089
|
+
* that is the thing that is actually breaching; everything above it in
|
|
2090
|
+
* the hierarchy is listed underneath as context. A series with no
|
|
2091
|
+
* Kubernetes identity at all is a cluster-level series, so it is titled
|
|
2092
|
+
* by the cluster.
|
|
2093
|
+
*
|
|
2094
|
+
* A container is titled together with its pod. Every replica of a
|
|
2095
|
+
* workload runs a container of the same name, so "Container `checkout`"
|
|
2096
|
+
* alone would title three crash-looping replicas identically and leave
|
|
2097
|
+
* the pod that tells them apart buried in the bullets.
|
|
2098
|
+
*/
|
|
2099
|
+
private static getKubernetesAffectedResourceEntry(input: {
|
|
2100
|
+
resource: KubernetesAffectedResource;
|
|
2101
|
+
clusterName: string;
|
|
2102
|
+
value: string;
|
|
2103
|
+
}): AffectedResourceListEntry {
|
|
2104
|
+
const resource: KubernetesAffectedResource = input.resource;
|
|
2105
|
+
|
|
2106
|
+
// "Deployment", "StatefulSet", ... — the worker only ever sets one of those.
|
|
2107
|
+
const workloadLabel: string = resource.workloadType || "Workload";
|
|
2108
|
+
|
|
2109
|
+
let kind: string = "Cluster";
|
|
2110
|
+
let name: string = AffectedResourceList.code(input.clusterName);
|
|
2111
|
+
|
|
2112
|
+
if (resource.containerName) {
|
|
2113
|
+
kind = "Container";
|
|
2114
|
+
name = resource.podName
|
|
2115
|
+
? `${AffectedResourceList.code(resource.containerName)} in pod ${AffectedResourceList.code(resource.podName)}`
|
|
2116
|
+
: AffectedResourceList.code(resource.containerName);
|
|
2117
|
+
} else if (resource.podName) {
|
|
2118
|
+
kind = "Pod";
|
|
2119
|
+
name = AffectedResourceList.code(resource.podName);
|
|
2120
|
+
} else if (resource.workloadName) {
|
|
2121
|
+
kind = workloadLabel;
|
|
2122
|
+
name = AffectedResourceList.code(resource.workloadName);
|
|
2123
|
+
} else if (resource.nodeName) {
|
|
2124
|
+
kind = "Node";
|
|
2125
|
+
name = AffectedResourceList.code(resource.nodeName);
|
|
2126
|
+
} else if (resource.namespace) {
|
|
2127
|
+
kind = "Namespace";
|
|
2128
|
+
name = AffectedResourceList.code(resource.namespace);
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
const details: Array<AffectedResourceListDetail> = [];
|
|
2132
|
+
|
|
2133
|
+
if (resource.namespace && kind !== "Namespace") {
|
|
2134
|
+
details.push({
|
|
2135
|
+
label: "Namespace",
|
|
2136
|
+
value: AffectedResourceList.code(resource.namespace),
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
const titledByWorkload: boolean =
|
|
2141
|
+
!resource.containerName &&
|
|
2142
|
+
!resource.podName &&
|
|
2143
|
+
Boolean(resource.workloadName);
|
|
2144
|
+
|
|
2145
|
+
if (resource.workloadName && !titledByWorkload) {
|
|
2146
|
+
details.push({
|
|
2147
|
+
label: workloadLabel,
|
|
2148
|
+
value: AffectedResourceList.code(resource.workloadName),
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
if (resource.nodeName && kind !== "Node") {
|
|
2153
|
+
details.push({
|
|
2154
|
+
label: "Node",
|
|
2155
|
+
value: AffectedResourceList.code(resource.nodeName),
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
return {
|
|
2160
|
+
kind: kind,
|
|
2161
|
+
name: name,
|
|
2162
|
+
value: input.value,
|
|
2163
|
+
details: details,
|
|
2164
|
+
};
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2083
2167
|
private static async buildKubernetesRootCauseContext(input: {
|
|
2084
2168
|
dataToProcess: DataToProcess;
|
|
2085
2169
|
monitorStep: MonitorStep;
|
|
2086
2170
|
monitor: Monitor;
|
|
2171
|
+
criteriaInstance?: MonitorCriteriaInstance | undefined;
|
|
2087
2172
|
}): Promise<string | null> {
|
|
2088
2173
|
const metricResponse: MetricMonitorResponse =
|
|
2089
2174
|
input.dataToProcess as MetricMonitorResponse;
|
|
@@ -2116,20 +2201,40 @@ ${contextBlock}
|
|
|
2116
2201
|
|
|
2117
2202
|
// Affected resources
|
|
2118
2203
|
if (breakdown.affectedResources && breakdown.affectedResources.length > 0) {
|
|
2119
|
-
|
|
2204
|
+
/*
|
|
2205
|
+
* Keep the rows that satisfy the criteria that just matched, worst
|
|
2206
|
+
* first — NOT simply the non-zero rows. k8s-node-not-ready fires on
|
|
2207
|
+
* `k8s.node.condition_ready = 0`, so the zero rows are the NotReady
|
|
2208
|
+
* nodes; the old hardcoded `> 0` dropped exactly those and named a
|
|
2209
|
+
* healthy node in the analysis below.
|
|
2210
|
+
*/
|
|
2211
|
+
const breach: AffectedResourceBreachPredicate =
|
|
2212
|
+
MonitorCriteriaEvaluator.getAffectedResourceBreachPredicate({
|
|
2213
|
+
criteriaInstance: input.criteriaInstance,
|
|
2214
|
+
floorEqualityFiresOnFall: true,
|
|
2215
|
+
});
|
|
2120
2216
|
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2217
|
+
const sortedResources: Array<KubernetesAffectedResource> =
|
|
2218
|
+
breakdown.affectedResources
|
|
2219
|
+
.map((r: KubernetesAffectedResource) => {
|
|
2220
|
+
/*
|
|
2221
|
+
* A fall criteria breached on the resource's lowest sample
|
|
2222
|
+
* in the window, not its highest.
|
|
2223
|
+
*/
|
|
2224
|
+
return breach.worstIsLowest
|
|
2225
|
+
? { ...r, metricValue: r.lowestMetricValue ?? r.metricValue }
|
|
2226
|
+
: r;
|
|
2227
|
+
})
|
|
2228
|
+
.filter((r: KubernetesAffectedResource) => {
|
|
2229
|
+
return breach.matches(r.metricValue);
|
|
2230
|
+
})
|
|
2231
|
+
.sort(
|
|
2232
|
+
(a: KubernetesAffectedResource, b: KubernetesAffectedResource) => {
|
|
2233
|
+
return breach.worstIsLowest
|
|
2234
|
+
? a.metricValue - b.metricValue
|
|
2235
|
+
: b.metricValue - a.metricValue;
|
|
2236
|
+
},
|
|
2237
|
+
);
|
|
2133
2238
|
|
|
2134
2239
|
if (sortedResources.length === 0) {
|
|
2135
2240
|
return sections.join("\n");
|
|
@@ -2137,108 +2242,31 @@ ${contextBlock}
|
|
|
2137
2242
|
|
|
2138
2243
|
// Show top 10 affected resources
|
|
2139
2244
|
const resourcesToShow: Array<KubernetesAffectedResource> =
|
|
2140
|
-
sortedResources.slice(0,
|
|
2141
|
-
|
|
2142
|
-
// Determine which columns are present across all resources
|
|
2143
|
-
const hasNamespace: boolean = resourcesToShow.some(
|
|
2144
|
-
(r: KubernetesAffectedResource) => {
|
|
2145
|
-
return r.namespace;
|
|
2146
|
-
},
|
|
2147
|
-
);
|
|
2148
|
-
const hasWorkload: boolean = resourcesToShow.some(
|
|
2149
|
-
(r: KubernetesAffectedResource) => {
|
|
2150
|
-
return r.workloadType && r.workloadName;
|
|
2151
|
-
},
|
|
2152
|
-
);
|
|
2153
|
-
const hasPod: boolean = resourcesToShow.some(
|
|
2154
|
-
(r: KubernetesAffectedResource) => {
|
|
2155
|
-
return r.podName;
|
|
2156
|
-
},
|
|
2157
|
-
);
|
|
2158
|
-
const hasContainer: boolean = resourcesToShow.some(
|
|
2159
|
-
(r: KubernetesAffectedResource) => {
|
|
2160
|
-
return r.containerName;
|
|
2161
|
-
},
|
|
2162
|
-
);
|
|
2163
|
-
const hasNode: boolean = resourcesToShow.some(
|
|
2164
|
-
(r: KubernetesAffectedResource) => {
|
|
2165
|
-
return r.nodeName;
|
|
2166
|
-
},
|
|
2167
|
-
);
|
|
2168
|
-
|
|
2169
|
-
// Build table header
|
|
2170
|
-
const headerCells: Array<string> = [];
|
|
2171
|
-
if (hasNamespace) {
|
|
2172
|
-
headerCells.push("Namespace");
|
|
2173
|
-
}
|
|
2174
|
-
if (hasWorkload) {
|
|
2175
|
-
headerCells.push("Workload Type");
|
|
2176
|
-
headerCells.push("Workload");
|
|
2177
|
-
}
|
|
2178
|
-
if (hasPod) {
|
|
2179
|
-
headerCells.push("Pod");
|
|
2180
|
-
}
|
|
2181
|
-
if (hasContainer) {
|
|
2182
|
-
headerCells.push("Container");
|
|
2183
|
-
}
|
|
2184
|
-
if (hasNode) {
|
|
2185
|
-
headerCells.push("Node");
|
|
2186
|
-
}
|
|
2187
|
-
headerCells.push("Value");
|
|
2188
|
-
|
|
2189
|
-
const headerRow: string = `| ${headerCells.join(" | ")} |`;
|
|
2190
|
-
const separatorRow: string = `| ${headerCells
|
|
2191
|
-
.map(() => {
|
|
2192
|
-
return "---";
|
|
2193
|
-
})
|
|
2194
|
-
.join(" | ")} |`;
|
|
2195
|
-
|
|
2196
|
-
resourceLines.push(headerRow);
|
|
2197
|
-
resourceLines.push(separatorRow);
|
|
2198
|
-
|
|
2199
|
-
for (const resource of resourcesToShow) {
|
|
2200
|
-
const cells: Array<string> = [];
|
|
2201
|
-
|
|
2202
|
-
if (hasNamespace) {
|
|
2203
|
-
cells.push(resource.namespace ? `\`${resource.namespace}\`` : "-");
|
|
2204
|
-
}
|
|
2205
|
-
if (hasWorkload) {
|
|
2206
|
-
cells.push(resource.workloadType ? `${resource.workloadType}` : "-");
|
|
2207
|
-
cells.push(
|
|
2208
|
-
resource.workloadName ? `\`${resource.workloadName}\`` : "-",
|
|
2209
|
-
);
|
|
2210
|
-
}
|
|
2211
|
-
if (hasPod) {
|
|
2212
|
-
cells.push(resource.podName ? `\`${resource.podName}\`` : "-");
|
|
2213
|
-
}
|
|
2214
|
-
if (hasContainer) {
|
|
2215
|
-
cells.push(
|
|
2216
|
-
resource.containerName ? `\`${resource.containerName}\`` : "-",
|
|
2217
|
-
);
|
|
2218
|
-
}
|
|
2219
|
-
if (hasNode) {
|
|
2220
|
-
cells.push(resource.nodeName ? `\`${resource.nodeName}\`` : "-");
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
|
-
cells.push(
|
|
2224
|
-
MonitorCriteriaEvaluator.formatPlatformResourceValue({
|
|
2225
|
-
platform: "kubernetes",
|
|
2226
|
-
metricName: breakdown.metricName,
|
|
2227
|
-
value: resource.metricValue,
|
|
2228
|
-
}),
|
|
2229
|
-
);
|
|
2230
|
-
|
|
2231
|
-
resourceLines.push(`| ${cells.join(" | ")} |`);
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
if (sortedResources.length > 10) {
|
|
2235
|
-
resourceLines.push(
|
|
2236
|
-
`\n*... and ${sortedResources.length - 10} more affected resources*`,
|
|
2237
|
-
);
|
|
2238
|
-
}
|
|
2245
|
+
sortedResources.slice(0, AffectedResourceList.MAX_ENTRIES);
|
|
2239
2246
|
|
|
2240
2247
|
sections.push(
|
|
2241
|
-
|
|
2248
|
+
AffectedResourceList.render({
|
|
2249
|
+
heading: "Affected Resources",
|
|
2250
|
+
overflowNoun: "affected resources",
|
|
2251
|
+
totalCount: sortedResources.length,
|
|
2252
|
+
entries: resourcesToShow.map(
|
|
2253
|
+
(
|
|
2254
|
+
resource: KubernetesAffectedResource,
|
|
2255
|
+
): AffectedResourceListEntry => {
|
|
2256
|
+
return MonitorCriteriaEvaluator.getKubernetesAffectedResourceEntry(
|
|
2257
|
+
{
|
|
2258
|
+
resource: resource,
|
|
2259
|
+
clusterName: breakdown.clusterName,
|
|
2260
|
+
value: MonitorCriteriaEvaluator.formatPlatformResourceValue({
|
|
2261
|
+
platform: "kubernetes",
|
|
2262
|
+
metricName: breakdown.metricName,
|
|
2263
|
+
value: resource.metricValue,
|
|
2264
|
+
}),
|
|
2265
|
+
},
|
|
2266
|
+
);
|
|
2267
|
+
},
|
|
2268
|
+
),
|
|
2269
|
+
}),
|
|
2242
2270
|
);
|
|
2243
2271
|
|
|
2244
2272
|
// Add root cause analysis based on metric type
|
|
@@ -2509,6 +2537,92 @@ ${contextBlock}
|
|
|
2509
2537
|
return sections.length > 0 ? sections.join("\n") : null;
|
|
2510
2538
|
}
|
|
2511
2539
|
|
|
2540
|
+
/*
|
|
2541
|
+
* What a Proxmox resource is, from the `pve.type` / `pve.scope` the
|
|
2542
|
+
* agent's transform/pve-identity processor derives from its `id`
|
|
2543
|
+
* (node/…, qemu/…, lxc/…, storage/…, cluster/…). A value that processor
|
|
2544
|
+
* does not produce is shown as it arrived rather than guessed at.
|
|
2545
|
+
*/
|
|
2546
|
+
private static getProxmoxAffectedResourceKind(
|
|
2547
|
+
resource: ProxmoxAffectedResource,
|
|
2548
|
+
): string {
|
|
2549
|
+
switch (resource.resourceType) {
|
|
2550
|
+
case "qemu":
|
|
2551
|
+
return "Virtual Machine";
|
|
2552
|
+
case "lxc":
|
|
2553
|
+
return "Container";
|
|
2554
|
+
case "node":
|
|
2555
|
+
return "Node";
|
|
2556
|
+
case "storage":
|
|
2557
|
+
return "Storage";
|
|
2558
|
+
default:
|
|
2559
|
+
break;
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
switch (resource.scope) {
|
|
2563
|
+
case "guest":
|
|
2564
|
+
return "Guest";
|
|
2565
|
+
case "node":
|
|
2566
|
+
return "Node";
|
|
2567
|
+
case "storage":
|
|
2568
|
+
return "Storage";
|
|
2569
|
+
case "cluster":
|
|
2570
|
+
return "Cluster";
|
|
2571
|
+
default:
|
|
2572
|
+
break;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
return resource.resourceType || resource.scope || "Resource";
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
/*
|
|
2579
|
+
* One Proxmox resource as an "Affected Resources" list item: its kind,
|
|
2580
|
+
* its name next to its `qemu/100`-style id, and the node it runs on.
|
|
2581
|
+
*/
|
|
2582
|
+
private static getProxmoxAffectedResourceEntry(input: {
|
|
2583
|
+
resource: ProxmoxAffectedResource;
|
|
2584
|
+
clusterName: string;
|
|
2585
|
+
value: string;
|
|
2586
|
+
}): AffectedResourceListEntry {
|
|
2587
|
+
const resource: ProxmoxAffectedResource = input.resource;
|
|
2588
|
+
|
|
2589
|
+
let kind: string =
|
|
2590
|
+
MonitorCriteriaEvaluator.getProxmoxAffectedResourceKind(resource);
|
|
2591
|
+
let name: string = AffectedResourceList.codeWithId({
|
|
2592
|
+
name: resource.resourceName,
|
|
2593
|
+
id: resource.resourceId,
|
|
2594
|
+
});
|
|
2595
|
+
|
|
2596
|
+
if (!name && resource.nodeName) {
|
|
2597
|
+
// A per-node series that carries no id of its own.
|
|
2598
|
+
kind = "Node";
|
|
2599
|
+
name = AffectedResourceList.code(resource.nodeName);
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
if (!name) {
|
|
2603
|
+
// Nothing narrower than the cluster — a cluster-wide series.
|
|
2604
|
+
kind = "Cluster";
|
|
2605
|
+
name = AffectedResourceList.code(input.clusterName);
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
const details: Array<AffectedResourceListDetail> = [];
|
|
2609
|
+
|
|
2610
|
+
// A node's own row would only repeat its name as "Node: …".
|
|
2611
|
+
if (resource.nodeName && kind !== "Node") {
|
|
2612
|
+
details.push({
|
|
2613
|
+
label: "Node",
|
|
2614
|
+
value: AffectedResourceList.code(resource.nodeName),
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
return {
|
|
2619
|
+
kind: kind,
|
|
2620
|
+
name: name,
|
|
2621
|
+
value: input.value,
|
|
2622
|
+
details: details,
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2512
2626
|
private static buildProxmoxRootCauseContext(input: {
|
|
2513
2627
|
dataToProcess: DataToProcess;
|
|
2514
2628
|
monitorStep: MonitorStep;
|
|
@@ -2577,8 +2691,8 @@ ${contextBlock}
|
|
|
2577
2691
|
);
|
|
2578
2692
|
}
|
|
2579
2693
|
|
|
2580
|
-
// Affected resources
|
|
2581
|
-
let
|
|
2694
|
+
// Affected resources: a ranked list of kind, name and node
|
|
2695
|
+
let renderedBreakdownList: boolean = false;
|
|
2582
2696
|
|
|
2583
2697
|
if (breakdown && breakdown.affectedResources.length > 0) {
|
|
2584
2698
|
/*
|
|
@@ -2586,7 +2700,7 @@ ${contextBlock}
|
|
|
2586
2700
|
* first, top 10. Note that for availability metrics (pve_up)
|
|
2587
2701
|
* zero-valued rows ARE the down resources — those still drive
|
|
2588
2702
|
* alerting through the per-series criteria evaluation; this
|
|
2589
|
-
*
|
|
2703
|
+
* list is supplementary context only.
|
|
2590
2704
|
*/
|
|
2591
2705
|
const sortedResources: Array<ProxmoxAffectedResource> = [
|
|
2592
2706
|
...breakdown.affectedResources,
|
|
@@ -2599,7 +2713,7 @@ ${contextBlock}
|
|
|
2599
2713
|
});
|
|
2600
2714
|
|
|
2601
2715
|
/*
|
|
2602
|
-
* Skip the
|
|
2716
|
+
* Skip the list when no row carries any identity label
|
|
2603
2717
|
* (cluster-wide series) — it would add nothing.
|
|
2604
2718
|
*/
|
|
2605
2719
|
const hasIdentity: boolean = sortedResources.some(
|
|
@@ -2610,55 +2724,41 @@ ${contextBlock}
|
|
|
2610
2724
|
|
|
2611
2725
|
if (sortedResources.length > 0 && hasIdentity) {
|
|
2612
2726
|
const resourcesToShow: Array<ProxmoxAffectedResource> =
|
|
2613
|
-
sortedResources.slice(0,
|
|
2614
|
-
|
|
2615
|
-
const resourceLines: Array<string> = [];
|
|
2616
|
-
resourceLines.push(`| Resource | Type | Node | Value |`);
|
|
2617
|
-
resourceLines.push(`| --- | --- | --- | --- |`);
|
|
2618
|
-
|
|
2619
|
-
for (const resource of resourcesToShow) {
|
|
2620
|
-
let resourceCell: string = "-";
|
|
2621
|
-
if (resource.resourceName && resource.resourceId) {
|
|
2622
|
-
resourceCell = `\`${resource.resourceName}\` (\`${resource.resourceId}\`)`;
|
|
2623
|
-
} else if (resource.resourceId) {
|
|
2624
|
-
resourceCell = `\`${resource.resourceId}\``;
|
|
2625
|
-
} else if (resource.resourceName) {
|
|
2626
|
-
resourceCell = `\`${resource.resourceName}\``;
|
|
2627
|
-
}
|
|
2628
|
-
|
|
2629
|
-
const typeCell: string =
|
|
2630
|
-
resource.resourceType || resource.scope || "-";
|
|
2631
|
-
const nodeCell: string = resource.nodeName
|
|
2632
|
-
? `\`${resource.nodeName}\``
|
|
2633
|
-
: "-";
|
|
2634
|
-
|
|
2635
|
-
resourceLines.push(
|
|
2636
|
-
`| ${resourceCell} | ${typeCell} | ${nodeCell} | ${MonitorCriteriaEvaluator.formatPlatformResourceValue(
|
|
2637
|
-
{
|
|
2638
|
-
platform: "proxmox",
|
|
2639
|
-
metricName: breakdown.metricName,
|
|
2640
|
-
value: resource.metricValue,
|
|
2641
|
-
},
|
|
2642
|
-
)} |`,
|
|
2643
|
-
);
|
|
2644
|
-
}
|
|
2645
|
-
|
|
2646
|
-
if (sortedResources.length > 10) {
|
|
2647
|
-
resourceLines.push(
|
|
2648
|
-
`\n*... and ${sortedResources.length - 10} more affected resources*`,
|
|
2649
|
-
);
|
|
2650
|
-
}
|
|
2727
|
+
sortedResources.slice(0, AffectedResourceList.MAX_ENTRIES);
|
|
2651
2728
|
|
|
2652
2729
|
sections.push(
|
|
2653
|
-
|
|
2730
|
+
AffectedResourceList.render({
|
|
2731
|
+
heading: "Affected Resources",
|
|
2732
|
+
overflowNoun: "affected resources",
|
|
2733
|
+
totalCount: sortedResources.length,
|
|
2734
|
+
entries: resourcesToShow.map(
|
|
2735
|
+
(
|
|
2736
|
+
resource: ProxmoxAffectedResource,
|
|
2737
|
+
): AffectedResourceListEntry => {
|
|
2738
|
+
return MonitorCriteriaEvaluator.getProxmoxAffectedResourceEntry(
|
|
2739
|
+
{
|
|
2740
|
+
resource: resource,
|
|
2741
|
+
clusterName: breakdown.clusterName,
|
|
2742
|
+
value: MonitorCriteriaEvaluator.formatPlatformResourceValue(
|
|
2743
|
+
{
|
|
2744
|
+
platform: "proxmox",
|
|
2745
|
+
metricName: breakdown.metricName,
|
|
2746
|
+
value: resource.metricValue,
|
|
2747
|
+
},
|
|
2748
|
+
),
|
|
2749
|
+
},
|
|
2750
|
+
);
|
|
2751
|
+
},
|
|
2752
|
+
),
|
|
2753
|
+
}),
|
|
2654
2754
|
);
|
|
2655
|
-
|
|
2755
|
+
renderedBreakdownList = true;
|
|
2656
2756
|
}
|
|
2657
2757
|
}
|
|
2658
2758
|
|
|
2659
|
-
// Metric results summary (fallback context when no
|
|
2759
|
+
// Metric results summary (fallback context when no list rendered)
|
|
2660
2760
|
if (
|
|
2661
|
-
!
|
|
2761
|
+
!renderedBreakdownList &&
|
|
2662
2762
|
metricResponse.metricResult &&
|
|
2663
2763
|
metricResponse.metricResult.length > 0
|
|
2664
2764
|
) {
|
|
@@ -2684,8 +2784,8 @@ ${contextBlock}
|
|
|
2684
2784
|
* Which vSphere object one VMware breakdown row describes, resolved
|
|
2685
2785
|
* from the identity attributes the vcenter receiver stamped on it —
|
|
2686
2786
|
* most specific first, in the same order the ingest inventory scan
|
|
2687
|
-
* uses, so the
|
|
2688
|
-
* disagree about the same series. A VM row carries its parent host
|
|
2787
|
+
* uses, so the kind an Affected Resources item is titled with and the
|
|
2788
|
+
* inventory's `kind` never disagree about the same series. A VM row carries its parent host
|
|
2689
2789
|
* (and often a resource pool), so the VM check must run before the
|
|
2690
2790
|
* host and pool checks.
|
|
2691
2791
|
*/
|
|
@@ -2719,6 +2819,98 @@ ${contextBlock}
|
|
|
2719
2819
|
return undefined;
|
|
2720
2820
|
}
|
|
2721
2821
|
|
|
2822
|
+
/*
|
|
2823
|
+
* One vSphere object as an "Affected Resources" list item, titled by
|
|
2824
|
+
* the object itself, with the ESXi host and cluster it sits under
|
|
2825
|
+
* listed beneath it.
|
|
2826
|
+
*/
|
|
2827
|
+
private static getVMwareAffectedResourceEntry(input: {
|
|
2828
|
+
resource: VMwareAffectedResource;
|
|
2829
|
+
vcenterName: string;
|
|
2830
|
+
value: string;
|
|
2831
|
+
}): AffectedResourceListEntry {
|
|
2832
|
+
const resource: VMwareAffectedResource = input.resource;
|
|
2833
|
+
|
|
2834
|
+
const kind: string | undefined =
|
|
2835
|
+
MonitorCriteriaEvaluator.getVMwareAffectedResourceKind(resource);
|
|
2836
|
+
|
|
2837
|
+
let name: string = "";
|
|
2838
|
+
let showHost: boolean = false;
|
|
2839
|
+
|
|
2840
|
+
switch (kind) {
|
|
2841
|
+
case "Virtual Machine":
|
|
2842
|
+
/*
|
|
2843
|
+
* The VM's display name, falling back to its instance UUID
|
|
2844
|
+
* (`vcenter.vm.id`) when the receiver stamped no name. The Host
|
|
2845
|
+
* detail is the ESXi host the VM is running on.
|
|
2846
|
+
*/
|
|
2847
|
+
name = AffectedResourceList.code(resource.vmName || resource.vmId);
|
|
2848
|
+
showHost = true;
|
|
2849
|
+
break;
|
|
2850
|
+
case "Resource Pool":
|
|
2851
|
+
/*
|
|
2852
|
+
* Pool names are only unique within a parent, so the inventory
|
|
2853
|
+
* path is shown next to the name. The Host detail is the owner
|
|
2854
|
+
* host for a pool on a standalone ESXi host (pools under a
|
|
2855
|
+
* cluster show the cluster).
|
|
2856
|
+
*/
|
|
2857
|
+
name = AffectedResourceList.codeWithId({
|
|
2858
|
+
name: resource.resourcePoolName,
|
|
2859
|
+
id: resource.resourcePoolPath,
|
|
2860
|
+
});
|
|
2861
|
+
showHost = true;
|
|
2862
|
+
break;
|
|
2863
|
+
case "Host":
|
|
2864
|
+
// The host IS the resource; a "Host:" detail would repeat it.
|
|
2865
|
+
name = AffectedResourceList.code(resource.hostName);
|
|
2866
|
+
break;
|
|
2867
|
+
case "Datastore":
|
|
2868
|
+
name = AffectedResourceList.code(resource.datastoreName);
|
|
2869
|
+
break;
|
|
2870
|
+
case "Cluster":
|
|
2871
|
+
name = AffectedResourceList.code(resource.clusterName);
|
|
2872
|
+
break;
|
|
2873
|
+
case "Datacenter":
|
|
2874
|
+
name = AffectedResourceList.code(resource.datacenterName);
|
|
2875
|
+
break;
|
|
2876
|
+
default:
|
|
2877
|
+
break;
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
const details: Array<AffectedResourceListDetail> = [];
|
|
2881
|
+
|
|
2882
|
+
if (showHost && resource.hostName) {
|
|
2883
|
+
details.push({
|
|
2884
|
+
label: "Host",
|
|
2885
|
+
value: AffectedResourceList.code(resource.hostName),
|
|
2886
|
+
});
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
if (kind !== "Cluster" && resource.clusterName) {
|
|
2890
|
+
details.push({
|
|
2891
|
+
label: "Cluster",
|
|
2892
|
+
value: AffectedResourceList.code(resource.clusterName),
|
|
2893
|
+
});
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
if (!kind || !name) {
|
|
2897
|
+
// No vSphere identity on the series — it describes the vCenter as a whole.
|
|
2898
|
+
return {
|
|
2899
|
+
kind: "vCenter",
|
|
2900
|
+
name: AffectedResourceList.code(input.vcenterName),
|
|
2901
|
+
value: input.value,
|
|
2902
|
+
details: details,
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
return {
|
|
2907
|
+
kind: kind,
|
|
2908
|
+
name: name,
|
|
2909
|
+
value: input.value,
|
|
2910
|
+
details: details,
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2722
2914
|
private static buildVMwareRootCauseContext(input: {
|
|
2723
2915
|
dataToProcess: DataToProcess;
|
|
2724
2916
|
monitorStep: MonitorStep;
|
|
@@ -2802,18 +2994,18 @@ ${contextBlock}
|
|
|
2802
2994
|
sections.push(`**vCenter Details**\n${vcenterDetails.join("\n")}`);
|
|
2803
2995
|
}
|
|
2804
2996
|
|
|
2805
|
-
// Affected resources
|
|
2806
|
-
let
|
|
2997
|
+
// Affected resources: a ranked list of object, host and cluster
|
|
2998
|
+
let renderedBreakdownList: boolean = false;
|
|
2807
2999
|
|
|
2808
3000
|
if (breakdown && breakdown.affectedResources.length > 0) {
|
|
2809
3001
|
/*
|
|
2810
3002
|
* K8s/Proxmox-parity render: drop zero-value rows, worst (highest)
|
|
2811
3003
|
* first, top 10. For count metrics filtered to an unhealthy state
|
|
2812
3004
|
* (`vcenter.datacenter.host.count{status=red}`) the non-zero rows
|
|
2813
|
-
* ARE the objects with a problem, so the
|
|
3005
|
+
* ARE the objects with a problem, so the list reads correctly;
|
|
2814
3006
|
* a zero row means "nothing in that state" and adds nothing. The
|
|
2815
3007
|
* per-series criteria evaluation is what actually alerts; this
|
|
2816
|
-
*
|
|
3008
|
+
* list is supplementary context only.
|
|
2817
3009
|
*/
|
|
2818
3010
|
const sortedResources: Array<VMwareAffectedResource> = [
|
|
2819
3011
|
...breakdown.affectedResources,
|
|
@@ -2826,7 +3018,7 @@ ${contextBlock}
|
|
|
2826
3018
|
});
|
|
2827
3019
|
|
|
2828
3020
|
/*
|
|
2829
|
-
* Skip the
|
|
3021
|
+
* Skip the list when no row carries any identity attribute — it
|
|
2830
3022
|
* would add nothing. Every vcenter-receiver series carries at
|
|
2831
3023
|
* least `vcenter.datacenter.name`, so this only trips on a
|
|
2832
3024
|
* breakdown the worker could not attribute at all.
|
|
@@ -2841,96 +3033,35 @@ ${contextBlock}
|
|
|
2841
3033
|
|
|
2842
3034
|
if (sortedResources.length > 0 && hasIdentity) {
|
|
2843
3035
|
const resourcesToShow: Array<VMwareAffectedResource> =
|
|
2844
|
-
sortedResources.slice(0,
|
|
2845
|
-
|
|
2846
|
-
const resourceLines: Array<string> = [];
|
|
2847
|
-
resourceLines.push(`| Resource | Kind | Host | Cluster | Value |`);
|
|
2848
|
-
resourceLines.push(`| --- | --- | --- | --- | --- |`);
|
|
2849
|
-
|
|
2850
|
-
for (const resource of resourcesToShow) {
|
|
2851
|
-
const kind: string | undefined =
|
|
2852
|
-
MonitorCriteriaEvaluator.getVMwareAffectedResourceKind(resource);
|
|
2853
|
-
|
|
2854
|
-
let resourceCell: string = "-";
|
|
2855
|
-
let hostCell: string = "-";
|
|
2856
|
-
|
|
2857
|
-
switch (kind) {
|
|
2858
|
-
case "Virtual Machine":
|
|
2859
|
-
/*
|
|
2860
|
-
* The VM's display name, falling back to its instance
|
|
2861
|
-
* UUID (`vcenter.vm.id`) when the receiver stamped no
|
|
2862
|
-
* name. The Host column carries the ESXi host the VM is
|
|
2863
|
-
* running on.
|
|
2864
|
-
*/
|
|
2865
|
-
resourceCell = resource.vmName
|
|
2866
|
-
? `\`${resource.vmName}\``
|
|
2867
|
-
: `\`${resource.vmId}\``;
|
|
2868
|
-
hostCell = resource.hostName ? `\`${resource.hostName}\`` : "-";
|
|
2869
|
-
break;
|
|
2870
|
-
case "Resource Pool":
|
|
2871
|
-
/*
|
|
2872
|
-
* Pool names are only unique within a parent, so the
|
|
2873
|
-
* inventory path is shown next to the name. The Host
|
|
2874
|
-
* column is the owner host for a pool on a standalone
|
|
2875
|
-
* ESXi host (pools under a cluster show the cluster).
|
|
2876
|
-
*/
|
|
2877
|
-
if (resource.resourcePoolName && resource.resourcePoolPath) {
|
|
2878
|
-
resourceCell = `\`${resource.resourcePoolName}\` (\`${resource.resourcePoolPath}\`)`;
|
|
2879
|
-
} else {
|
|
2880
|
-
resourceCell = `\`${resource.resourcePoolPath || resource.resourcePoolName}\``;
|
|
2881
|
-
}
|
|
2882
|
-
hostCell = resource.hostName ? `\`${resource.hostName}\`` : "-";
|
|
2883
|
-
break;
|
|
2884
|
-
case "Host":
|
|
2885
|
-
// The host IS the resource; repeating it in the Host column adds nothing.
|
|
2886
|
-
resourceCell = `\`${resource.hostName}\``;
|
|
2887
|
-
break;
|
|
2888
|
-
case "Datastore":
|
|
2889
|
-
resourceCell = `\`${resource.datastoreName}\``;
|
|
2890
|
-
break;
|
|
2891
|
-
case "Cluster":
|
|
2892
|
-
resourceCell = `\`${resource.clusterName}\``;
|
|
2893
|
-
break;
|
|
2894
|
-
case "Datacenter":
|
|
2895
|
-
resourceCell = `\`${resource.datacenterName}\``;
|
|
2896
|
-
break;
|
|
2897
|
-
default:
|
|
2898
|
-
break;
|
|
2899
|
-
}
|
|
2900
|
-
|
|
2901
|
-
const kindCell: string = kind || "-";
|
|
2902
|
-
const clusterCell: string =
|
|
2903
|
-
kind !== "Cluster" && resource.clusterName
|
|
2904
|
-
? `\`${resource.clusterName}\``
|
|
2905
|
-
: "-";
|
|
2906
|
-
|
|
2907
|
-
resourceLines.push(
|
|
2908
|
-
`| ${resourceCell} | ${kindCell} | ${hostCell} | ${clusterCell} | ${MonitorCriteriaEvaluator.formatPlatformResourceValue(
|
|
2909
|
-
{
|
|
2910
|
-
platform: "vmware",
|
|
2911
|
-
metricName: breakdown.metricName,
|
|
2912
|
-
value: resource.metricValue,
|
|
2913
|
-
},
|
|
2914
|
-
)} |`,
|
|
2915
|
-
);
|
|
2916
|
-
}
|
|
2917
|
-
|
|
2918
|
-
if (sortedResources.length > 10) {
|
|
2919
|
-
resourceLines.push(
|
|
2920
|
-
`\n*... and ${sortedResources.length - 10} more affected resources*`,
|
|
2921
|
-
);
|
|
2922
|
-
}
|
|
3036
|
+
sortedResources.slice(0, AffectedResourceList.MAX_ENTRIES);
|
|
2923
3037
|
|
|
2924
3038
|
sections.push(
|
|
2925
|
-
|
|
3039
|
+
AffectedResourceList.render({
|
|
3040
|
+
heading: "Affected Resources",
|
|
3041
|
+
overflowNoun: "affected resources",
|
|
3042
|
+
totalCount: sortedResources.length,
|
|
3043
|
+
entries: resourcesToShow.map(
|
|
3044
|
+
(resource: VMwareAffectedResource): AffectedResourceListEntry => {
|
|
3045
|
+
return MonitorCriteriaEvaluator.getVMwareAffectedResourceEntry({
|
|
3046
|
+
resource: resource,
|
|
3047
|
+
vcenterName: breakdown.vcenterName,
|
|
3048
|
+
value: MonitorCriteriaEvaluator.formatPlatformResourceValue({
|
|
3049
|
+
platform: "vmware",
|
|
3050
|
+
metricName: breakdown.metricName,
|
|
3051
|
+
value: resource.metricValue,
|
|
3052
|
+
}),
|
|
3053
|
+
});
|
|
3054
|
+
},
|
|
3055
|
+
),
|
|
3056
|
+
}),
|
|
2926
3057
|
);
|
|
2927
|
-
|
|
3058
|
+
renderedBreakdownList = true;
|
|
2928
3059
|
}
|
|
2929
3060
|
}
|
|
2930
3061
|
|
|
2931
|
-
// Metric results summary (fallback context when no
|
|
3062
|
+
// Metric results summary (fallback context when no list rendered)
|
|
2932
3063
|
if (
|
|
2933
|
-
!
|
|
3064
|
+
!renderedBreakdownList &&
|
|
2934
3065
|
metricResponse.metricResult &&
|
|
2935
3066
|
metricResponse.metricResult.length > 0
|
|
2936
3067
|
) {
|
|
@@ -2952,6 +3083,55 @@ ${contextBlock}
|
|
|
2952
3083
|
return sections.length > 0 ? sections.join("\n") : null;
|
|
2953
3084
|
}
|
|
2954
3085
|
|
|
3086
|
+
/*
|
|
3087
|
+
* One Docker Swarm task as an "Affected Tasks" list item, with the
|
|
3088
|
+
* service it belongs to and the node it is scheduled on beneath it.
|
|
3089
|
+
*/
|
|
3090
|
+
private static getDockerSwarmAffectedResourceEntry(input: {
|
|
3091
|
+
resource: DockerSwarmAffectedResource;
|
|
3092
|
+
clusterName: string;
|
|
3093
|
+
value: string;
|
|
3094
|
+
}): AffectedResourceListEntry {
|
|
3095
|
+
const resource: DockerSwarmAffectedResource = input.resource;
|
|
3096
|
+
|
|
3097
|
+
let kind: string = "Cluster";
|
|
3098
|
+
let name: string = AffectedResourceList.code(input.clusterName);
|
|
3099
|
+
|
|
3100
|
+
if (resource.containerName) {
|
|
3101
|
+
kind = "Task";
|
|
3102
|
+
name = AffectedResourceList.code(resource.containerName);
|
|
3103
|
+
} else if (resource.serviceName) {
|
|
3104
|
+
kind = "Service";
|
|
3105
|
+
name = AffectedResourceList.code(resource.serviceName);
|
|
3106
|
+
} else if (resource.nodeName) {
|
|
3107
|
+
kind = "Node";
|
|
3108
|
+
name = AffectedResourceList.code(resource.nodeName);
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
const details: Array<AffectedResourceListDetail> = [];
|
|
3112
|
+
|
|
3113
|
+
if (resource.serviceName && kind !== "Service") {
|
|
3114
|
+
details.push({
|
|
3115
|
+
label: "Service",
|
|
3116
|
+
value: AffectedResourceList.code(resource.serviceName),
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3120
|
+
if (resource.nodeName && kind !== "Node") {
|
|
3121
|
+
details.push({
|
|
3122
|
+
label: "Node",
|
|
3123
|
+
value: AffectedResourceList.code(resource.nodeName),
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
return {
|
|
3128
|
+
kind: kind,
|
|
3129
|
+
name: name,
|
|
3130
|
+
value: input.value,
|
|
3131
|
+
details: details,
|
|
3132
|
+
};
|
|
3133
|
+
}
|
|
3134
|
+
|
|
2955
3135
|
private static buildDockerSwarmRootCauseContext(input: {
|
|
2956
3136
|
dataToProcess: DataToProcess;
|
|
2957
3137
|
monitorStep: MonitorStep;
|
|
@@ -3020,15 +3200,15 @@ ${contextBlock}
|
|
|
3020
3200
|
);
|
|
3021
3201
|
}
|
|
3022
3202
|
|
|
3023
|
-
// Affected
|
|
3024
|
-
let
|
|
3203
|
+
// Affected tasks: a ranked list of task, service and node
|
|
3204
|
+
let renderedBreakdownList: boolean = false;
|
|
3025
3205
|
|
|
3026
3206
|
if (breakdown && breakdown.affectedResources.length > 0) {
|
|
3027
3207
|
/*
|
|
3028
3208
|
* K8s/Proxmox-parity render: drop zero-value rows, worst (highest)
|
|
3029
3209
|
* first, top 10. For the task-down template the breaching rows ARE
|
|
3030
3210
|
* the zero-uptime ones — those still drive alerting through the
|
|
3031
|
-
* per-series criteria evaluation; this
|
|
3211
|
+
* per-series criteria evaluation; this list is supplementary
|
|
3032
3212
|
* context only.
|
|
3033
3213
|
*/
|
|
3034
3214
|
const sortedResources: Array<DockerSwarmAffectedResource> = [
|
|
@@ -3044,7 +3224,7 @@ ${contextBlock}
|
|
|
3044
3224
|
);
|
|
3045
3225
|
|
|
3046
3226
|
/*
|
|
3047
|
-
* Skip the
|
|
3227
|
+
* Skip the list when no row carries any identity label
|
|
3048
3228
|
* (cluster-wide series) — it would add nothing.
|
|
3049
3229
|
*/
|
|
3050
3230
|
const hasIdentity: boolean = sortedResources.some(
|
|
@@ -3055,50 +3235,41 @@ ${contextBlock}
|
|
|
3055
3235
|
|
|
3056
3236
|
if (sortedResources.length > 0 && hasIdentity) {
|
|
3057
3237
|
const resourcesToShow: Array<DockerSwarmAffectedResource> =
|
|
3058
|
-
sortedResources.slice(0,
|
|
3059
|
-
|
|
3060
|
-
const resourceLines: Array<string> = [];
|
|
3061
|
-
resourceLines.push(`| Task / Container | Service | Node | Value |`);
|
|
3062
|
-
resourceLines.push(`| --- | --- | --- | --- |`);
|
|
3063
|
-
|
|
3064
|
-
for (const resource of resourcesToShow) {
|
|
3065
|
-
const taskCell: string = resource.containerName
|
|
3066
|
-
? `\`${resource.containerName}\``
|
|
3067
|
-
: "-";
|
|
3068
|
-
const serviceCell: string = resource.serviceName
|
|
3069
|
-
? `\`${resource.serviceName}\``
|
|
3070
|
-
: "-";
|
|
3071
|
-
const nodeCell: string = resource.nodeName
|
|
3072
|
-
? `\`${resource.nodeName}\``
|
|
3073
|
-
: "-";
|
|
3074
|
-
|
|
3075
|
-
resourceLines.push(
|
|
3076
|
-
`| ${taskCell} | ${serviceCell} | ${nodeCell} | ${MonitorCriteriaEvaluator.formatPlatformResourceValue(
|
|
3077
|
-
{
|
|
3078
|
-
platform: "dockerSwarm",
|
|
3079
|
-
metricName: breakdown.metricName,
|
|
3080
|
-
value: resource.metricValue,
|
|
3081
|
-
},
|
|
3082
|
-
)} |`,
|
|
3083
|
-
);
|
|
3084
|
-
}
|
|
3085
|
-
|
|
3086
|
-
if (sortedResources.length > 10) {
|
|
3087
|
-
resourceLines.push(
|
|
3088
|
-
`\n*... and ${sortedResources.length - 10} more affected tasks*`,
|
|
3089
|
-
);
|
|
3090
|
-
}
|
|
3238
|
+
sortedResources.slice(0, AffectedResourceList.MAX_ENTRIES);
|
|
3091
3239
|
|
|
3092
3240
|
sections.push(
|
|
3093
|
-
|
|
3241
|
+
AffectedResourceList.render({
|
|
3242
|
+
heading: "Affected Tasks",
|
|
3243
|
+
overflowNoun: "affected tasks",
|
|
3244
|
+
totalCount: sortedResources.length,
|
|
3245
|
+
entries: resourcesToShow.map(
|
|
3246
|
+
(
|
|
3247
|
+
resource: DockerSwarmAffectedResource,
|
|
3248
|
+
): AffectedResourceListEntry => {
|
|
3249
|
+
return MonitorCriteriaEvaluator.getDockerSwarmAffectedResourceEntry(
|
|
3250
|
+
{
|
|
3251
|
+
resource: resource,
|
|
3252
|
+
clusterName: breakdown.clusterName,
|
|
3253
|
+
value: MonitorCriteriaEvaluator.formatPlatformResourceValue(
|
|
3254
|
+
{
|
|
3255
|
+
platform: "dockerSwarm",
|
|
3256
|
+
metricName: breakdown.metricName,
|
|
3257
|
+
value: resource.metricValue,
|
|
3258
|
+
},
|
|
3259
|
+
),
|
|
3260
|
+
},
|
|
3261
|
+
);
|
|
3262
|
+
},
|
|
3263
|
+
),
|
|
3264
|
+
}),
|
|
3094
3265
|
);
|
|
3095
|
-
|
|
3266
|
+
renderedBreakdownList = true;
|
|
3096
3267
|
}
|
|
3097
3268
|
}
|
|
3098
3269
|
|
|
3099
|
-
// Metric results summary (fallback context when no
|
|
3270
|
+
// Metric results summary (fallback context when no list rendered)
|
|
3100
3271
|
if (
|
|
3101
|
-
!
|
|
3272
|
+
!renderedBreakdownList &&
|
|
3102
3273
|
metricResponse.metricResult &&
|
|
3103
3274
|
metricResponse.metricResult.length > 0
|
|
3104
3275
|
) {
|
|
@@ -3121,37 +3292,61 @@ ${contextBlock}
|
|
|
3121
3292
|
}
|
|
3122
3293
|
|
|
3123
3294
|
/*
|
|
3124
|
-
* Which rows of
|
|
3295
|
+
* Which rows of an affected-resources list actually BREACHED.
|
|
3125
3296
|
*
|
|
3126
3297
|
* The blanket `metricValue > 0` below is right for every count- or
|
|
3127
3298
|
* level-style criteria ("> 0 active health checks", "> 85 % used"), but
|
|
3128
|
-
* it is exactly backwards for the availability signals: the OSD
|
|
3129
|
-
* OSD Out / Quorum Degraded criteria fire on `< 1` over
|
|
3130
|
-
* ceph_osd_in / ceph_mon_quorum_status, so the ZERO rows
|
|
3131
|
-
* daemons. Dropping them rendered a
|
|
3132
|
-
* an incident that tells the reader to "check the root
|
|
3133
|
-
* affected ceph_daemon label".
|
|
3299
|
+
* it is exactly backwards for the availability signals: the Ceph OSD
|
|
3300
|
+
* Down / OSD Out / Quorum Degraded criteria fire on `< 1` over
|
|
3301
|
+
* ceph_osd_up / ceph_osd_in / ceph_mon_quorum_status, so the ZERO rows
|
|
3302
|
+
* are the down daemons. Dropping them rendered a list of the HEALTHY
|
|
3303
|
+
* daemons under an incident that tells the reader to "check the root
|
|
3304
|
+
* cause for the affected ceph_daemon label".
|
|
3134
3305
|
*
|
|
3135
3306
|
* Invert only for a criteria that fires when the metric FALLS.
|
|
3136
3307
|
* Everything else — every `> 0` health check, PG count, latency and
|
|
3137
|
-
* capacity ratio, and every `= 0` recovery criteria —
|
|
3138
|
-
* existing `> 0`, worst-highest-first
|
|
3308
|
+
* capacity ratio, and (by default) every `= 0` recovery criteria —
|
|
3309
|
+
* keeps the existing `> 0`, worst-highest-first list byte for byte.
|
|
3310
|
+
*
|
|
3311
|
+
* `floorEqualityFiresOnFall` additionally counts a FIRING `= 0` (or
|
|
3312
|
+
* below) as a fall. Kubernetes needs it: k8s-node-not-ready and
|
|
3313
|
+
* k8s-etcd-no-leader fire on `= 0` over k8s.node.condition_ready /
|
|
3314
|
+
* etcd_server_has_leader, and zero is the floor of those metrics, so
|
|
3315
|
+
* the only way to reach it is down. It is gated on the criteria opening
|
|
3316
|
+
* an incident or alert because the same `= 0` is also how the restart,
|
|
3317
|
+
* failed-pod and unavailable-replica templates express RECOVERY, and
|
|
3318
|
+
* that all-clear must not become a list of healthy zero rows. Ceph
|
|
3319
|
+
* leaves it off: all of its `= 0` criteria are recoveries.
|
|
3139
3320
|
*/
|
|
3140
|
-
private static
|
|
3141
|
-
criteriaInstance?: MonitorCriteriaInstance | undefined
|
|
3142
|
-
|
|
3321
|
+
private static getAffectedResourceBreachPredicate(input: {
|
|
3322
|
+
criteriaInstance?: MonitorCriteriaInstance | undefined;
|
|
3323
|
+
floorEqualityFiresOnFall?: boolean | undefined;
|
|
3324
|
+
}): AffectedResourceBreachPredicate {
|
|
3143
3325
|
const metricFilters: Array<CriteriaFilter> = (
|
|
3144
|
-
criteriaInstance?.data?.filters || []
|
|
3326
|
+
input.criteriaInstance?.data?.filters || []
|
|
3145
3327
|
).filter((f: CriteriaFilter) => {
|
|
3146
3328
|
return f.checkOn === CheckOn.MetricValue && typeof f.value === "number";
|
|
3147
3329
|
});
|
|
3148
3330
|
|
|
3331
|
+
const opensIncidentOrAlert: boolean =
|
|
3332
|
+
input.criteriaInstance?.data?.createIncidents === true ||
|
|
3333
|
+
input.criteriaInstance?.data?.createAlerts === true;
|
|
3334
|
+
|
|
3149
3335
|
const firesWhenMetricFalls: boolean =
|
|
3150
3336
|
metricFilters.length > 0 &&
|
|
3151
3337
|
metricFilters.every((f: CriteriaFilter) => {
|
|
3152
|
-
|
|
3338
|
+
if (
|
|
3153
3339
|
f.filterType === FilterType.LessThan ||
|
|
3154
3340
|
f.filterType === FilterType.LessThanOrEqualTo
|
|
3341
|
+
) {
|
|
3342
|
+
return true;
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
return (
|
|
3346
|
+
input.floorEqualityFiresOnFall === true &&
|
|
3347
|
+
opensIncidentOrAlert &&
|
|
3348
|
+
f.filterType === FilterType.EqualTo &&
|
|
3349
|
+
(f.value as number) <= 0
|
|
3155
3350
|
);
|
|
3156
3351
|
});
|
|
3157
3352
|
|
|
@@ -3169,6 +3364,10 @@ ${contextBlock}
|
|
|
3169
3364
|
return metricFilters.some((f: CriteriaFilter) => {
|
|
3170
3365
|
const threshold: number = f.value as number;
|
|
3171
3366
|
|
|
3367
|
+
if (f.filterType === FilterType.EqualTo) {
|
|
3368
|
+
return value === threshold;
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3172
3371
|
return f.filterType === FilterType.LessThan
|
|
3173
3372
|
? value < threshold
|
|
3174
3373
|
: value <= threshold;
|
|
@@ -3182,6 +3381,85 @@ ${contextBlock}
|
|
|
3182
3381
|
};
|
|
3183
3382
|
}
|
|
3184
3383
|
|
|
3384
|
+
/*
|
|
3385
|
+
* One Ceph daemon or pool as an "Affected Resources" list item, with
|
|
3386
|
+
* the pool and host it belongs to beneath it.
|
|
3387
|
+
*/
|
|
3388
|
+
private static getCephAffectedResourceEntry(input: {
|
|
3389
|
+
resource: CephAffectedResource;
|
|
3390
|
+
clusterName: string;
|
|
3391
|
+
metricName: string;
|
|
3392
|
+
value: string;
|
|
3393
|
+
}): AffectedResourceListEntry {
|
|
3394
|
+
const resource: CephAffectedResource = input.resource;
|
|
3395
|
+
|
|
3396
|
+
/*
|
|
3397
|
+
* The worker stores every series' `name` label as poolName, but only
|
|
3398
|
+
* ceph_pool_metadata's `name` is a pool — and that series always
|
|
3399
|
+
* carries pool_id too. On ceph_health_detail, `name` is the health
|
|
3400
|
+
* check (RECENT_CRASH, OSD_NEARFULL, ...), and titling it "Pool" told
|
|
3401
|
+
* the reader a pool called OSD_NEARFULL was the problem. So a name is
|
|
3402
|
+
* treated as a pool only when it arrives with a pool id.
|
|
3403
|
+
*/
|
|
3404
|
+
const pool: string = resource.poolId
|
|
3405
|
+
? AffectedResourceList.codeWithId({
|
|
3406
|
+
name: resource.poolName,
|
|
3407
|
+
id: resource.poolId,
|
|
3408
|
+
})
|
|
3409
|
+
: "";
|
|
3410
|
+
|
|
3411
|
+
const otherName: string = resource.poolId
|
|
3412
|
+
? ""
|
|
3413
|
+
: AffectedResourceList.code(resource.poolName);
|
|
3414
|
+
|
|
3415
|
+
const otherNameKind: string =
|
|
3416
|
+
input.metricName === "ceph_health_detail" ? "Health Check" : "Resource";
|
|
3417
|
+
|
|
3418
|
+
let kind: string = "Cluster";
|
|
3419
|
+
let name: string = AffectedResourceList.code(input.clusterName);
|
|
3420
|
+
|
|
3421
|
+
if (resource.daemon) {
|
|
3422
|
+
kind = "Daemon";
|
|
3423
|
+
name = AffectedResourceList.code(resource.daemon);
|
|
3424
|
+
} else if (pool) {
|
|
3425
|
+
kind = "Pool";
|
|
3426
|
+
name = pool;
|
|
3427
|
+
} else if (otherName) {
|
|
3428
|
+
kind = otherNameKind;
|
|
3429
|
+
name = otherName;
|
|
3430
|
+
} else if (resource.hostname) {
|
|
3431
|
+
kind = "Host";
|
|
3432
|
+
name = AffectedResourceList.code(resource.hostname);
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
const details: Array<AffectedResourceListDetail> = [];
|
|
3436
|
+
|
|
3437
|
+
if (pool && name !== pool) {
|
|
3438
|
+
details.push({ label: "Pool", value: pool });
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
if (otherName && name !== otherName) {
|
|
3442
|
+
details.push({
|
|
3443
|
+
label: otherNameKind === "Resource" ? "Name" : otherNameKind,
|
|
3444
|
+
value: otherName,
|
|
3445
|
+
});
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
if (resource.hostname && kind !== "Host") {
|
|
3449
|
+
details.push({
|
|
3450
|
+
label: "Host",
|
|
3451
|
+
value: AffectedResourceList.code(resource.hostname),
|
|
3452
|
+
});
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3455
|
+
return {
|
|
3456
|
+
kind: kind,
|
|
3457
|
+
name: name,
|
|
3458
|
+
value: input.value,
|
|
3459
|
+
details: details,
|
|
3460
|
+
};
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3185
3463
|
private static buildCephRootCauseContext(input: {
|
|
3186
3464
|
dataToProcess: DataToProcess;
|
|
3187
3465
|
monitorStep: MonitorStep;
|
|
@@ -3237,8 +3515,8 @@ ${contextBlock}
|
|
|
3237
3515
|
sections.push(`**Ceph Cluster Details**\n${clusterDetails.join("\n")}`);
|
|
3238
3516
|
}
|
|
3239
3517
|
|
|
3240
|
-
// Affected resources
|
|
3241
|
-
let
|
|
3518
|
+
// Affected resources: a ranked list of daemon, pool and host
|
|
3519
|
+
let renderedBreakdownList: boolean = false;
|
|
3242
3520
|
|
|
3243
3521
|
if (breakdown && breakdown.affectedResources.length > 0) {
|
|
3244
3522
|
/*
|
|
@@ -3247,12 +3525,10 @@ ${contextBlock}
|
|
|
3247
3525
|
* ceph_osd_in / ceph_mon_quorum_status the criteria is `< 1`, so
|
|
3248
3526
|
* the zero rows are the whole point of the incident.
|
|
3249
3527
|
*/
|
|
3250
|
-
const breach:
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
input.criteriaInstance,
|
|
3255
|
-
);
|
|
3528
|
+
const breach: AffectedResourceBreachPredicate =
|
|
3529
|
+
MonitorCriteriaEvaluator.getAffectedResourceBreachPredicate({
|
|
3530
|
+
criteriaInstance: input.criteriaInstance,
|
|
3531
|
+
});
|
|
3256
3532
|
|
|
3257
3533
|
const sortedResources: Array<CephAffectedResource> = [
|
|
3258
3534
|
...breakdown.affectedResources,
|
|
@@ -3267,7 +3543,7 @@ ${contextBlock}
|
|
|
3267
3543
|
});
|
|
3268
3544
|
|
|
3269
3545
|
/*
|
|
3270
|
-
* Skip the
|
|
3546
|
+
* Skip the list when no row carries any identity label
|
|
3271
3547
|
* (cluster-wide series like ceph_health_status) — it would add
|
|
3272
3548
|
* nothing.
|
|
3273
3549
|
*/
|
|
@@ -3279,57 +3555,36 @@ ${contextBlock}
|
|
|
3279
3555
|
|
|
3280
3556
|
if (sortedResources.length > 0 && hasIdentity) {
|
|
3281
3557
|
const resourcesToShow: Array<CephAffectedResource> =
|
|
3282
|
-
sortedResources.slice(0,
|
|
3283
|
-
|
|
3284
|
-
const resourceLines: Array<string> = [];
|
|
3285
|
-
resourceLines.push(`| Daemon | Pool | Host | Value |`);
|
|
3286
|
-
resourceLines.push(`| --- | --- | --- | --- |`);
|
|
3287
|
-
|
|
3288
|
-
for (const resource of resourcesToShow) {
|
|
3289
|
-
const daemonCell: string = resource.daemon
|
|
3290
|
-
? `\`${resource.daemon}\``
|
|
3291
|
-
: "-";
|
|
3292
|
-
|
|
3293
|
-
let poolCell: string = "-";
|
|
3294
|
-
if (resource.poolName && resource.poolId) {
|
|
3295
|
-
poolCell = `\`${resource.poolName}\` (\`${resource.poolId}\`)`;
|
|
3296
|
-
} else if (resource.poolName) {
|
|
3297
|
-
poolCell = `\`${resource.poolName}\``;
|
|
3298
|
-
} else if (resource.poolId) {
|
|
3299
|
-
poolCell = `\`${resource.poolId}\``;
|
|
3300
|
-
}
|
|
3301
|
-
|
|
3302
|
-
const hostCell: string = resource.hostname
|
|
3303
|
-
? `\`${resource.hostname}\``
|
|
3304
|
-
: "-";
|
|
3305
|
-
|
|
3306
|
-
resourceLines.push(
|
|
3307
|
-
`| ${daemonCell} | ${poolCell} | ${hostCell} | ${MonitorCriteriaEvaluator.formatPlatformResourceValue(
|
|
3308
|
-
{
|
|
3309
|
-
platform: "ceph",
|
|
3310
|
-
metricName: breakdown.metricName,
|
|
3311
|
-
value: resource.metricValue,
|
|
3312
|
-
},
|
|
3313
|
-
)} |`,
|
|
3314
|
-
);
|
|
3315
|
-
}
|
|
3316
|
-
|
|
3317
|
-
if (sortedResources.length > 10) {
|
|
3318
|
-
resourceLines.push(
|
|
3319
|
-
`\n*... and ${sortedResources.length - 10} more affected resources*`,
|
|
3320
|
-
);
|
|
3321
|
-
}
|
|
3558
|
+
sortedResources.slice(0, AffectedResourceList.MAX_ENTRIES);
|
|
3322
3559
|
|
|
3323
3560
|
sections.push(
|
|
3324
|
-
|
|
3561
|
+
AffectedResourceList.render({
|
|
3562
|
+
heading: "Affected Resources",
|
|
3563
|
+
overflowNoun: "affected resources",
|
|
3564
|
+
totalCount: sortedResources.length,
|
|
3565
|
+
entries: resourcesToShow.map(
|
|
3566
|
+
(resource: CephAffectedResource): AffectedResourceListEntry => {
|
|
3567
|
+
return MonitorCriteriaEvaluator.getCephAffectedResourceEntry({
|
|
3568
|
+
resource: resource,
|
|
3569
|
+
clusterName: breakdown.clusterName,
|
|
3570
|
+
metricName: breakdown.metricName,
|
|
3571
|
+
value: MonitorCriteriaEvaluator.formatPlatformResourceValue({
|
|
3572
|
+
platform: "ceph",
|
|
3573
|
+
metricName: breakdown.metricName,
|
|
3574
|
+
value: resource.metricValue,
|
|
3575
|
+
}),
|
|
3576
|
+
});
|
|
3577
|
+
},
|
|
3578
|
+
),
|
|
3579
|
+
}),
|
|
3325
3580
|
);
|
|
3326
|
-
|
|
3581
|
+
renderedBreakdownList = true;
|
|
3327
3582
|
}
|
|
3328
3583
|
}
|
|
3329
3584
|
|
|
3330
|
-
// Metric results summary (fallback context when no
|
|
3585
|
+
// Metric results summary (fallback context when no list rendered)
|
|
3331
3586
|
if (
|
|
3332
|
-
!
|
|
3587
|
+
!renderedBreakdownList &&
|
|
3333
3588
|
metricResponse.metricResult &&
|
|
3334
3589
|
metricResponse.metricResult.length > 0
|
|
3335
3590
|
) {
|