@oneuptime/common 12.0.12 → 12.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +32 -5
- package/Models/DatabaseModels/NetworkDevice.ts +64 -0
- package/Models/DatabaseModels/StatusPage.ts +59 -0
- package/Models/DatabaseModels/StatusPageOidc.ts +2 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -2
- package/Server/API/AIChatAPI.ts +55 -56
- package/Server/API/CommonAPI.ts +89 -0
- package/Server/API/MicrosoftTeamsAPI.ts +20 -9
- package/Server/API/SlackAPI.ts +13 -10
- package/Server/API/StatusPageAPI.ts +2197 -2128
- package/Server/API/TelemetryAPI.ts +72 -3
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.ts +30 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/UserAuthorization.ts +11 -2
- package/Server/Services/ApiKeyPermissionService.ts +116 -2
- package/Server/Services/DatabaseService.ts +17 -6
- package/Server/Services/LogAggregationService.ts +34 -0
- package/Server/Services/LogService.ts +21 -0
- package/Server/Services/NetworkSiteService.ts +12 -0
- package/Server/Services/SpanService.ts +21 -0
- package/Server/Services/TraceAggregationService.ts +15 -0
- package/Server/Services/WorkspaceNotificationRuleService.ts +172 -220
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
- package/Server/Types/Database/Permissions/AccessControlPermission.ts +47 -16
- package/Server/Utils/APIKey/AccessPermission.ts +16 -40
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
- package/Server/Utils/LogRedaction.ts +576 -0
- package/Server/Utils/Logger.ts +123 -46
- package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
- package/Server/Utils/Monitor/MonitorResource.ts +29 -9
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
- package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
- package/Server/Utils/StartServer.ts +30 -9
- package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
- package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
- package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
- package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
- package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
- package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
- package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
- package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
- package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
- package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
- package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
- package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
- package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
- package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
- package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
- package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
- package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
- package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
- package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
- package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
- package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
- package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
- package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
- package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
- package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
- package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
- package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
- package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
- package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
- package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
- package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
- package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
- package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
- package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
- package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
- package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
- package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
- package/Tests/UI/Components/MasterPage.test.tsx +36 -1
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
- package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
- package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
- package/Types/API/Hostname.ts +79 -0
- package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
- package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
- package/Types/Database/DatabaseProperty.ts +38 -0
- package/Types/Database/TableColumn.ts +34 -7
- package/Types/Events/Recurring.ts +16 -1
- package/Types/Monitor/MonitorStep.ts +13 -1
- package/Types/Monitor/MonitorType.ts +345 -40
- package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
- package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
- package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
- package/UI/Components/CardSelect/CardSelect.tsx +714 -105
- package/UI/Components/EmptyState/EmptyState.tsx +8 -1
- package/UI/Components/Footer/Footer.tsx +32 -6
- package/UI/Components/Forms/Fields/FormField.tsx +3 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Loader/PageLoader.tsx +29 -8
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
- package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
- package/UI/Components/MasterPage/MasterPage.tsx +24 -11
- package/Utils/Monitor/MonitorMetricType.ts +16 -1
- package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
- package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
- package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
- package/Utils/Telemetry/CrossSignalScope.ts +75 -4
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +48 -35
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +59 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +13 -10
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +745 -673
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +34 -3
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +6 -2
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +15 -6
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +2 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/LogService.js +16 -0
- package/build/dist/Server/Services/LogService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +12 -0
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/SpanService.js +16 -0
- package/build/dist/Server/Services/SpanService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +2 -0
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +21 -13
- package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/LogRedaction.js +449 -0
- package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
- package/build/dist/Server/Utils/Logger.js +98 -45
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +10 -7
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
- package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Hostname.js +63 -0
- package/build/dist/Types/API/Hostname.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
- package/build/dist/Types/Database/DatabaseProperty.js +38 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Database/TableColumn.js +24 -6
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +13 -1
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +9 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +330 -31
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
- package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
- package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
- package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
- package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
- package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
- package/build/dist/UI/Components/Footer/Footer.js +7 -4
- package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
- package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
- package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ExpressResponse } from "./Express";
|
|
2
|
+
import {
|
|
3
|
+
NOINDEX_ROBOTS_DIRECTIVE,
|
|
4
|
+
X_ROBOTS_TAG_HEADER_NAME,
|
|
5
|
+
} from "../../Types/StatusPage/SearchEngineIndexing";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sets X-Robots-Tag: noindex, nofollow when the status page has search engine
|
|
9
|
+
* indexing turned off, and sets nothing at all when it is on.
|
|
10
|
+
*
|
|
11
|
+
* The "sets nothing" half is the point. An explicit `index, follow` header
|
|
12
|
+
* would be the default behaviour restated, and it would also override a
|
|
13
|
+
* noindex that an operator put in front of OneUptime (a CDN rule, an ingress
|
|
14
|
+
* annotation) - headers do not merge, the last writer wins. Staying silent
|
|
15
|
+
* leaves those in place.
|
|
16
|
+
*
|
|
17
|
+
* Callers must invoke this before the response is sent; a header set after
|
|
18
|
+
* headers are flushed is dropped by Node, silently.
|
|
19
|
+
*/
|
|
20
|
+
export default function applyStatusPageRobotsHeader(
|
|
21
|
+
res: ExpressResponse,
|
|
22
|
+
isSearchEngineIndexingEnabled: boolean,
|
|
23
|
+
): void {
|
|
24
|
+
if (isSearchEngineIndexingEnabled) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (res.headersSent) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
res.set(X_ROBOTS_TAG_HEADER_NAME, NOINDEX_ROBOTS_DIRECTIVE);
|
|
33
|
+
}
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
2
|
+
import PositiveNumber from "../../../Types/PositiveNumber";
|
|
3
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
4
|
+
import HostModel from "../../../Models/DatabaseModels/Host";
|
|
5
|
+
import DockerHostModel from "../../../Models/DatabaseModels/DockerHost";
|
|
6
|
+
import PodmanHostModel from "../../../Models/DatabaseModels/PodmanHost";
|
|
7
|
+
import KubernetesClusterModel from "../../../Models/DatabaseModels/KubernetesCluster";
|
|
8
|
+
import HostService from "../../Services/HostService";
|
|
9
|
+
import DockerHostService from "../../Services/DockerHostService";
|
|
10
|
+
import PodmanHostService from "../../Services/PodmanHostService";
|
|
11
|
+
import KubernetesClusterService from "../../Services/KubernetesClusterService";
|
|
12
|
+
import {
|
|
13
|
+
keyForHost,
|
|
14
|
+
keyForKubernetesCluster,
|
|
15
|
+
} from "../../../Utils/Telemetry/EntityKey";
|
|
16
|
+
import {
|
|
17
|
+
ResourceEntityFacetSelections,
|
|
18
|
+
isResourceEntityFacetKey,
|
|
19
|
+
parseResourceEntityFacetSelections,
|
|
20
|
+
} from "../../../Types/Telemetry/ResourceEntityFacet";
|
|
21
|
+
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
22
|
+
import TableColumnType from "../../../Types/AnalyticsDatabase/TableColumnType";
|
|
23
|
+
import { SQL, Statement } from "../AnalyticsDatabase/Statement";
|
|
24
|
+
import logger from "../Logger";
|
|
25
|
+
import CaptureSpan from "./CaptureSpan";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* One resource facet's selection, compiled into the three ways a signal row
|
|
29
|
+
* can prove membership of that resource. The three are OR-ed together; a
|
|
30
|
+
* row matching any of them belongs to the selected resource.
|
|
31
|
+
*
|
|
32
|
+
* - `entityIds` -> `primaryEntityId IN (...)`. Agent-ingested telemetry
|
|
33
|
+
* has the resource as its primary entity, so its id is
|
|
34
|
+
* literally in the column. Also the only thing that
|
|
35
|
+
* matches rows written before `entityKeys` existed.
|
|
36
|
+
* - `entityKeys` -> `hasAny(entityKeys, [...])`. The general membership
|
|
37
|
+
* read: OTLP telemetry primary-keyed on its Service
|
|
38
|
+
* still carries the host / cluster key here.
|
|
39
|
+
* - `attribute*` -> `attributes['resource.<attr>'] IN (...)`. The
|
|
40
|
+
* pre-`entityKeys` fallback, mirroring the entityScope
|
|
41
|
+
* contract the resource detail pages already use, so
|
|
42
|
+
* rows ingested before the column shipped (no backfill
|
|
43
|
+
* by decision) keep matching.
|
|
44
|
+
*/
|
|
45
|
+
export interface ResourceEntityScope {
|
|
46
|
+
entityIds: Array<string>;
|
|
47
|
+
entityKeys: Array<string>;
|
|
48
|
+
attributeKey?: string | undefined;
|
|
49
|
+
attributeValues?: Array<string> | undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface ResourceFacetDefinition {
|
|
53
|
+
/** Signal attribute the identifying value is stamped under. */
|
|
54
|
+
attributeKey: string;
|
|
55
|
+
/** Read-side entity-key helper, mirroring the ingest-side resolver. */
|
|
56
|
+
entityKeyFor: (projectId: string, identifier: string) => string;
|
|
57
|
+
findIdentifiers: (data: {
|
|
58
|
+
projectId: ObjectID;
|
|
59
|
+
ids: Array<ObjectID>;
|
|
60
|
+
}) => Promise<Array<string>>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* `Host` / `DockerHost` / `PodmanHost` all key on the canonicalized
|
|
65
|
+
* `host.name` (see `HostService.findOrCreateByHostIdentifier` and its
|
|
66
|
+
* Docker / Podman siblings), which is exactly the Host entity's identity —
|
|
67
|
+
* so all three resolve through `keyForHost`. A Kubernetes cluster keys on
|
|
68
|
+
* `k8s.cluster.name` (`InventoryItem.k8sClusterIdentity`).
|
|
69
|
+
*
|
|
70
|
+
* The Docker / Podman facets need this as much as the Kubernetes one does:
|
|
71
|
+
* `getServiceNameFromAttributes` synthesises a per-container service name
|
|
72
|
+
* for agent-collected container telemetry, so those rows are primary-keyed
|
|
73
|
+
* on a Service too and an id-only predicate finds almost nothing.
|
|
74
|
+
*
|
|
75
|
+
* Resolving them to the Host entity means "Docker Host web-1" also matches
|
|
76
|
+
* non-container telemetry from web-1 when the Infrastructure agent runs
|
|
77
|
+
* alongside. That is the machine the facet names, so the wider match is the
|
|
78
|
+
* honest reading — and it is the only identity an OTLP resource carries;
|
|
79
|
+
* DockerHost / PodmanHost are inventory-mirrored types that no resource
|
|
80
|
+
* ever declares (see EntityType).
|
|
81
|
+
*/
|
|
82
|
+
const FACET_DEFINITIONS: Record<string, ResourceFacetDefinition> = {
|
|
83
|
+
hostId: {
|
|
84
|
+
attributeKey: "resource.host.name",
|
|
85
|
+
entityKeyFor: keyForHost,
|
|
86
|
+
findIdentifiers: async (data: {
|
|
87
|
+
projectId: ObjectID;
|
|
88
|
+
ids: Array<ObjectID>;
|
|
89
|
+
}): Promise<Array<string>> => {
|
|
90
|
+
const rows: Array<HostModel> = await HostService.findBy({
|
|
91
|
+
query: { projectId: data.projectId, _id: new Includes(data.ids) },
|
|
92
|
+
select: { hostIdentifier: true },
|
|
93
|
+
limit: new PositiveNumber(data.ids.length),
|
|
94
|
+
skip: new PositiveNumber(0),
|
|
95
|
+
props: { isRoot: true },
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return rows.map((row: HostModel): string => {
|
|
99
|
+
return row.hostIdentifier || "";
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
dockerHostId: {
|
|
104
|
+
attributeKey: "resource.host.name",
|
|
105
|
+
entityKeyFor: keyForHost,
|
|
106
|
+
findIdentifiers: async (data: {
|
|
107
|
+
projectId: ObjectID;
|
|
108
|
+
ids: Array<ObjectID>;
|
|
109
|
+
}): Promise<Array<string>> => {
|
|
110
|
+
const rows: Array<DockerHostModel> = await DockerHostService.findBy({
|
|
111
|
+
query: { projectId: data.projectId, _id: new Includes(data.ids) },
|
|
112
|
+
select: { hostIdentifier: true },
|
|
113
|
+
limit: new PositiveNumber(data.ids.length),
|
|
114
|
+
skip: new PositiveNumber(0),
|
|
115
|
+
props: { isRoot: true },
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return rows.map((row: DockerHostModel): string => {
|
|
119
|
+
return row.hostIdentifier || "";
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
podmanHostId: {
|
|
124
|
+
attributeKey: "resource.host.name",
|
|
125
|
+
entityKeyFor: keyForHost,
|
|
126
|
+
findIdentifiers: async (data: {
|
|
127
|
+
projectId: ObjectID;
|
|
128
|
+
ids: Array<ObjectID>;
|
|
129
|
+
}): Promise<Array<string>> => {
|
|
130
|
+
const rows: Array<PodmanHostModel> = await PodmanHostService.findBy({
|
|
131
|
+
query: { projectId: data.projectId, _id: new Includes(data.ids) },
|
|
132
|
+
select: { hostIdentifier: true },
|
|
133
|
+
limit: new PositiveNumber(data.ids.length),
|
|
134
|
+
skip: new PositiveNumber(0),
|
|
135
|
+
props: { isRoot: true },
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return rows.map((row: PodmanHostModel): string => {
|
|
139
|
+
return row.hostIdentifier || "";
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
kubernetesClusterId: {
|
|
144
|
+
attributeKey: "resource.k8s.cluster.name",
|
|
145
|
+
entityKeyFor: keyForKubernetesCluster,
|
|
146
|
+
findIdentifiers: async (data: {
|
|
147
|
+
projectId: ObjectID;
|
|
148
|
+
ids: Array<ObjectID>;
|
|
149
|
+
}): Promise<Array<string>> => {
|
|
150
|
+
const rows: Array<KubernetesClusterModel> =
|
|
151
|
+
await KubernetesClusterService.findBy({
|
|
152
|
+
query: { projectId: data.projectId, _id: new Includes(data.ids) },
|
|
153
|
+
select: { clusterIdentifier: true },
|
|
154
|
+
limit: new PositiveNumber(data.ids.length),
|
|
155
|
+
skip: new PositiveNumber(0),
|
|
156
|
+
props: { isRoot: true },
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return rows.map((row: KubernetesClusterModel): string => {
|
|
160
|
+
return row.clusterIdentifier || "";
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Append the resolved resource scopes to a hand-written aggregation
|
|
168
|
+
* statement (the Log / Trace histogram, facet and analytics queries, which
|
|
169
|
+
* build SQL directly rather than through StatementGenerator).
|
|
170
|
+
*
|
|
171
|
+
* Emits one `AND ( ... OR ... )` group per scope, mirroring exactly what
|
|
172
|
+
* StatementGenerator compiles for the list query — the chart and the list
|
|
173
|
+
* must agree on which rows a facet selects, or the histogram tells a
|
|
174
|
+
* different story than the rows underneath it.
|
|
175
|
+
*
|
|
176
|
+
* A scope with no usable branch appends nothing rather than an empty
|
|
177
|
+
* `IN ()`: an unresolvable filter must not silently match zero rows.
|
|
178
|
+
*/
|
|
179
|
+
export function appendResourceScopeFilters(
|
|
180
|
+
statement: Statement,
|
|
181
|
+
scopes: Array<ResourceEntityScope> | undefined,
|
|
182
|
+
): void {
|
|
183
|
+
if (!scopes || scopes.length === 0) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const scope of scopes) {
|
|
188
|
+
if (!scope) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const branches: Array<Statement> = [];
|
|
193
|
+
|
|
194
|
+
const entityIds: Array<string> = (scope.entityIds || []).filter(
|
|
195
|
+
(id: string): boolean => {
|
|
196
|
+
return typeof id === "string" && id.length > 0;
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (entityIds.length > 0) {
|
|
201
|
+
branches.push(
|
|
202
|
+
SQL`primaryEntityId IN (${{
|
|
203
|
+
type: TableColumnType.ObjectID,
|
|
204
|
+
value: new Includes(entityIds),
|
|
205
|
+
}})`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const entityKeys: Array<string> = (scope.entityKeys || []).filter(
|
|
210
|
+
(entityKey: string): boolean => {
|
|
211
|
+
return typeof entityKey === "string" && entityKey.length > 0;
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
if (entityKeys.length > 0) {
|
|
216
|
+
branches.push(
|
|
217
|
+
SQL`hasAny(entityKeys, ${{
|
|
218
|
+
type: TableColumnType.ArrayText,
|
|
219
|
+
value: entityKeys,
|
|
220
|
+
}})`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const attributeValues: Array<string> = (scope.attributeValues || []).filter(
|
|
225
|
+
(attributeValue: string): boolean => {
|
|
226
|
+
return typeof attributeValue === "string" && attributeValue.length > 0;
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
if (scope.attributeKey && attributeValues.length > 0) {
|
|
231
|
+
branches.push(
|
|
232
|
+
SQL`attributes[${{
|
|
233
|
+
type: TableColumnType.Text,
|
|
234
|
+
value: scope.attributeKey,
|
|
235
|
+
}}] IN (${{
|
|
236
|
+
type: TableColumnType.Text,
|
|
237
|
+
value: new Includes(attributeValues),
|
|
238
|
+
}})`,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (branches.length === 0) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
statement.append(SQL` AND (`);
|
|
247
|
+
|
|
248
|
+
for (const [index, branch] of branches.entries()) {
|
|
249
|
+
if (index > 0) {
|
|
250
|
+
statement.append(SQL` OR `);
|
|
251
|
+
}
|
|
252
|
+
statement.append(branch);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
statement.append(SQL`)`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export default class ResourceEntityFilter {
|
|
260
|
+
/**
|
|
261
|
+
* Read the `resourceFilters` field off a request body (or an analytics
|
|
262
|
+
* query record) into the canonical selection shape. Delegates to the
|
|
263
|
+
* isomorphic parser so the browser and the server agree on exactly which
|
|
264
|
+
* selections are well-formed.
|
|
265
|
+
*/
|
|
266
|
+
public static parseSelections(
|
|
267
|
+
source: JSONObject | undefined | null,
|
|
268
|
+
): ResourceEntityFacetSelections {
|
|
269
|
+
return parseResourceEntityFacetSelections(source);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Compile each facet's selected ids into a scope. One scope per facet, so
|
|
274
|
+
* the caller ANDs them: selecting a cluster and a host means "in that
|
|
275
|
+
* cluster AND on that host", which is what a reader expects of two
|
|
276
|
+
* independent filter groups.
|
|
277
|
+
*
|
|
278
|
+
* The identifier lookup is best-effort: if Postgres is unreachable or an
|
|
279
|
+
* id no longer resolves, the scope keeps its `entityIds` branch and
|
|
280
|
+
* simply loses the entity-key one. That degrades to today's behavior
|
|
281
|
+
* rather than dropping the constraint (which would silently widen the
|
|
282
|
+
* result set).
|
|
283
|
+
*/
|
|
284
|
+
@CaptureSpan()
|
|
285
|
+
public static async resolveScopes(data: {
|
|
286
|
+
projectId: ObjectID;
|
|
287
|
+
selections: ResourceEntityFacetSelections;
|
|
288
|
+
}): Promise<Array<ResourceEntityScope>> {
|
|
289
|
+
const facetKeys: Array<string> = Object.keys(data.selections).filter(
|
|
290
|
+
(key: string): boolean => {
|
|
291
|
+
return (
|
|
292
|
+
isResourceEntityFacetKey(key) &&
|
|
293
|
+
(data.selections[key] || []).length > 0
|
|
294
|
+
);
|
|
295
|
+
},
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
if (facetKeys.length === 0) {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return Promise.all(
|
|
303
|
+
facetKeys.map(async (facetKey: string): Promise<ResourceEntityScope> => {
|
|
304
|
+
return ResourceEntityFilter.resolveOne({
|
|
305
|
+
projectId: data.projectId,
|
|
306
|
+
facetKey,
|
|
307
|
+
ids: data.selections[facetKey] || [],
|
|
308
|
+
});
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Query key the client puts the raw selected ids under, and the key the
|
|
315
|
+
* compiled scopes are written to. Kept together so the rewrite below is
|
|
316
|
+
* the only thing that has to know the pair.
|
|
317
|
+
*/
|
|
318
|
+
public static readonly QUERY_FILTER_KEY: string = "resourceFilters";
|
|
319
|
+
public static readonly QUERY_SCOPE_KEY: string = "resourceEntityScopes";
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Rewrite an analytics list query in place: consume the client-sent
|
|
323
|
+
* `resourceFilters` ids and replace them with the resolved
|
|
324
|
+
* `resourceEntityScopes` that StatementGenerator compiles.
|
|
325
|
+
*
|
|
326
|
+
* The compiled key is always rebuilt from scratch — a client cannot
|
|
327
|
+
* hand-craft one, so the only way a scope reaches the SQL is through this
|
|
328
|
+
* resolver. (It could only ever narrow rows, but leaving one authoritative
|
|
329
|
+
* writer keeps the predicate auditable.)
|
|
330
|
+
*
|
|
331
|
+
* Without a tenant the identifier lookup cannot be project-scoped, so the
|
|
332
|
+
* scope keeps its id branch and drops the entity-key one: narrower than
|
|
333
|
+
* the request asked for, never wider.
|
|
334
|
+
*/
|
|
335
|
+
@CaptureSpan()
|
|
336
|
+
public static async rewriteAnalyticsQuery(data: {
|
|
337
|
+
query: Record<string, unknown> | undefined;
|
|
338
|
+
projectId?: ObjectID | undefined;
|
|
339
|
+
}): Promise<void> {
|
|
340
|
+
const query: Record<string, unknown> | undefined = data.query;
|
|
341
|
+
|
|
342
|
+
if (!query || typeof query !== "object") {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const raw: unknown = query[ResourceEntityFilter.QUERY_FILTER_KEY];
|
|
347
|
+
|
|
348
|
+
delete query[ResourceEntityFilter.QUERY_FILTER_KEY];
|
|
349
|
+
delete query[ResourceEntityFilter.QUERY_SCOPE_KEY];
|
|
350
|
+
|
|
351
|
+
if (raw === undefined || raw === null) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const selections: ResourceEntityFacetSelections =
|
|
356
|
+
ResourceEntityFilter.parseSelections(raw as JSONObject);
|
|
357
|
+
|
|
358
|
+
if (Object.keys(selections).length === 0) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (!data.projectId) {
|
|
363
|
+
query[ResourceEntityFilter.QUERY_SCOPE_KEY] = Object.keys(selections).map(
|
|
364
|
+
(facetKey: string): ResourceEntityScope => {
|
|
365
|
+
return { entityIds: selections[facetKey] || [], entityKeys: [] };
|
|
366
|
+
},
|
|
367
|
+
);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const scopes: Array<ResourceEntityScope> =
|
|
372
|
+
await ResourceEntityFilter.resolveScopes({
|
|
373
|
+
projectId: data.projectId,
|
|
374
|
+
selections,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
if (scopes.length > 0) {
|
|
378
|
+
query[ResourceEntityFilter.QUERY_SCOPE_KEY] = scopes;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private static async resolveOne(data: {
|
|
383
|
+
projectId: ObjectID;
|
|
384
|
+
facetKey: string;
|
|
385
|
+
ids: Array<string>;
|
|
386
|
+
}): Promise<ResourceEntityScope> {
|
|
387
|
+
const scope: ResourceEntityScope = {
|
|
388
|
+
entityIds: data.ids,
|
|
389
|
+
entityKeys: [],
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const definition: ResourceFacetDefinition | undefined =
|
|
393
|
+
FACET_DEFINITIONS[data.facetKey];
|
|
394
|
+
|
|
395
|
+
if (!definition) {
|
|
396
|
+
return scope;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
let identifiers: Array<string> = [];
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
identifiers = await definition.findIdentifiers({
|
|
403
|
+
projectId: data.projectId,
|
|
404
|
+
ids: data.ids.map((id: string): ObjectID => {
|
|
405
|
+
return new ObjectID(id);
|
|
406
|
+
}),
|
|
407
|
+
});
|
|
408
|
+
} catch (err: unknown) {
|
|
409
|
+
logger.warn(
|
|
410
|
+
`Could not resolve ${data.facetKey} identifiers for the entity-key filter; falling back to primaryEntityId only: ${err}`,
|
|
411
|
+
);
|
|
412
|
+
return scope;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const uniqueIdentifiers: Array<string> = Array.from(
|
|
416
|
+
new Set(
|
|
417
|
+
identifiers.filter((identifier: string): boolean => {
|
|
418
|
+
return identifier.trim().length > 0;
|
|
419
|
+
}),
|
|
420
|
+
),
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
if (uniqueIdentifiers.length === 0) {
|
|
424
|
+
return scope;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const projectIdString: string = data.projectId.toString();
|
|
428
|
+
|
|
429
|
+
scope.entityKeys = Array.from(
|
|
430
|
+
new Set(
|
|
431
|
+
uniqueIdentifiers.map((identifier: string): string => {
|
|
432
|
+
return definition.entityKeyFor(projectIdString, identifier);
|
|
433
|
+
}),
|
|
434
|
+
),
|
|
435
|
+
);
|
|
436
|
+
scope.attributeKey = definition.attributeKey;
|
|
437
|
+
scope.attributeValues = uniqueIdentifiers;
|
|
438
|
+
|
|
439
|
+
return scope;
|
|
440
|
+
}
|
|
441
|
+
}
|