@oneuptime/common 12.0.11 → 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.
Files changed (251) hide show
  1. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +32 -5
  2. package/Models/DatabaseModels/NetworkDevice.ts +64 -0
  3. package/Models/DatabaseModels/StatusPage.ts +59 -0
  4. package/Models/DatabaseModels/StatusPageOidc.ts +2 -0
  5. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -2
  6. package/Server/API/AIChatAPI.ts +55 -56
  7. package/Server/API/CommonAPI.ts +89 -0
  8. package/Server/API/MicrosoftTeamsAPI.ts +20 -9
  9. package/Server/API/SlackAPI.ts +13 -10
  10. package/Server/API/StatusPageAPI.ts +2197 -2128
  11. package/Server/API/TelemetryAPI.ts +72 -3
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.ts +30 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.ts +47 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  15. package/Server/Middleware/UserAuthorization.ts +11 -2
  16. package/Server/Services/ApiKeyPermissionService.ts +116 -2
  17. package/Server/Services/DatabaseService.ts +17 -6
  18. package/Server/Services/LogAggregationService.ts +34 -0
  19. package/Server/Services/LogService.ts +21 -0
  20. package/Server/Services/NetworkSiteService.ts +12 -0
  21. package/Server/Services/SpanService.ts +21 -0
  22. package/Server/Services/TraceAggregationService.ts +15 -0
  23. package/Server/Services/WorkspaceNotificationRuleService.ts +172 -220
  24. package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
  25. package/Server/Types/Database/Permissions/AccessControlPermission.ts +47 -16
  26. package/Server/Types/Workflow/Components/Conditions/IfElse.ts +9 -0
  27. package/Server/Types/Workflow/Components/JavaScript.ts +9 -0
  28. package/Server/Utils/APIKey/AccessPermission.ts +16 -40
  29. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
  30. package/Server/Utils/LogRedaction.ts +576 -0
  31. package/Server/Utils/Logger.ts +123 -46
  32. package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
  33. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +12 -0
  34. package/Server/Utils/Monitor/MonitorResource.ts +29 -9
  35. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
  36. package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
  37. package/Server/Utils/StartServer.ts +30 -9
  38. package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
  39. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
  40. package/Server/Utils/VM/VMRunner.ts +693 -874
  41. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
  42. package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
  43. package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
  44. package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
  45. package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
  46. package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
  47. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
  48. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
  49. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
  50. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
  51. package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
  52. package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
  53. package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
  54. package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
  55. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
  56. package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
  57. package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
  58. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
  59. package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
  60. package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
  61. package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
  62. package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
  63. package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
  64. package/Tests/Server/Types/Workflow/Components/CustomCodeScriptError.test.ts +121 -0
  65. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
  66. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
  67. package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
  68. package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
  69. package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
  70. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorJavascriptExpression.test.ts +92 -0
  71. package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
  72. package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
  73. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
  74. package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
  75. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
  76. package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
  77. package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
  78. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
  79. package/Tests/Server/Utils/VM/VMRunnerIsolation.test.ts +385 -0
  80. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +3 -0
  81. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
  82. package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
  83. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
  84. package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
  85. package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
  86. package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
  87. package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
  88. package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
  89. package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
  90. package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
  91. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
  92. package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
  93. package/Tests/UI/Components/MasterPage.test.tsx +36 -1
  94. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
  95. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
  96. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
  97. package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
  98. package/Types/API/Hostname.ts +79 -0
  99. package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
  100. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
  101. package/Types/Database/DatabaseProperty.ts +38 -0
  102. package/Types/Database/TableColumn.ts +34 -7
  103. package/Types/Events/Recurring.ts +16 -1
  104. package/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse.ts +14 -2
  105. package/Types/Monitor/MonitorStep.ts +13 -1
  106. package/Types/Monitor/MonitorType.ts +345 -40
  107. package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
  108. package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
  109. package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
  110. package/UI/Components/CardSelect/CardSelect.tsx +714 -105
  111. package/UI/Components/EmptyState/EmptyState.tsx +8 -1
  112. package/UI/Components/Footer/Footer.tsx +32 -6
  113. package/UI/Components/Forms/Fields/FormField.tsx +3 -0
  114. package/UI/Components/Forms/Types/Field.ts +8 -0
  115. package/UI/Components/Loader/PageLoader.tsx +29 -8
  116. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
  117. package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
  118. package/UI/Components/MasterPage/MasterPage.tsx +24 -11
  119. package/Utils/Monitor/MonitorMetricType.ts +16 -1
  120. package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
  121. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
  122. package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
  123. package/Utils/Telemetry/CrossSignalScope.ts +75 -4
  124. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
  125. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  126. package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
  127. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  128. package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
  129. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  130. package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
  131. package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
  132. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  133. package/build/dist/Server/API/AIChatAPI.js +48 -35
  134. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  135. package/build/dist/Server/API/CommonAPI.js +59 -0
  136. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  137. package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
  138. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  139. package/build/dist/Server/API/SlackAPI.js +13 -10
  140. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  141. package/build/dist/Server/API/StatusPageAPI.js +745 -673
  142. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  143. package/build/dist/Server/API/TelemetryAPI.js +34 -3
  144. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
  146. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
  147. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
  148. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js.map +1 -0
  149. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  150. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  151. package/build/dist/Server/Middleware/UserAuthorization.js +6 -2
  152. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  153. package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
  154. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  155. package/build/dist/Server/Services/DatabaseService.js +15 -6
  156. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  157. package/build/dist/Server/Services/LogAggregationService.js +2 -0
  158. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  159. package/build/dist/Server/Services/LogService.js +16 -0
  160. package/build/dist/Server/Services/LogService.js.map +1 -1
  161. package/build/dist/Server/Services/NetworkSiteService.js +12 -0
  162. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  163. package/build/dist/Server/Services/SpanService.js +16 -0
  164. package/build/dist/Server/Services/SpanService.js.map +1 -1
  165. package/build/dist/Server/Services/TraceAggregationService.js +2 -0
  166. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  167. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
  168. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  169. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
  170. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
  171. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +21 -13
  172. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
  173. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +8 -0
  174. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
  175. package/build/dist/Server/Types/Workflow/Components/JavaScript.js +8 -0
  176. package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
  177. package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
  178. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  179. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
  180. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  181. package/build/dist/Server/Utils/LogRedaction.js +449 -0
  182. package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
  183. package/build/dist/Server/Utils/Logger.js +98 -45
  184. package/build/dist/Server/Utils/Logger.js.map +1 -1
  185. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
  186. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
  187. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +12 -1
  188. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  189. package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
  190. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
  192. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  193. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
  194. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
  195. package/build/dist/Server/Utils/StartServer.js +10 -7
  196. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  197. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
  198. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
  199. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
  200. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
  201. package/build/dist/Server/Utils/VM/VMRunner.js +402 -525
  202. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  203. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
  204. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  205. package/build/dist/Types/API/Hostname.js +63 -0
  206. package/build/dist/Types/API/Hostname.js.map +1 -1
  207. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
  208. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  209. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
  210. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  211. package/build/dist/Types/Database/DatabaseProperty.js +38 -0
  212. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  213. package/build/dist/Types/Database/TableColumn.js +24 -6
  214. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  215. package/build/dist/Types/Events/Recurring.js +13 -1
  216. package/build/dist/Types/Events/Recurring.js.map +1 -1
  217. package/build/dist/Types/Monitor/MonitorStep.js +9 -1
  218. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  219. package/build/dist/Types/Monitor/MonitorType.js +330 -31
  220. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  221. package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
  222. package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
  223. package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
  224. package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
  225. package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
  226. package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
  227. package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
  228. package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
  229. package/build/dist/UI/Components/Footer/Footer.js +7 -4
  230. package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
  231. package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
  232. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  233. package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
  234. package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
  235. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
  236. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  237. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
  238. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
  239. package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
  240. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  241. package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
  242. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  243. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
  244. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  245. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
  246. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
  247. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
  248. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
  249. package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
  250. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
  251. package/package.json +1 -1
@@ -8,6 +8,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
10
  import UserMiddleware from "../Middleware/UserAuthorization";
11
+ import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
11
12
  import AcmeChallengeService from "../Services/AcmeChallengeService";
12
13
  import IncidentEpisodeService from "../Services/IncidentEpisodeService";
13
14
  import IncidentEpisodeMemberService from "../Services/IncidentEpisodeMemberService";
@@ -36,6 +37,7 @@ import StatusPageOidcService from "../Services/StatusPageOidcService";
36
37
  import StatusPageSubscriberService from "../Services/StatusPageSubscriberService";
37
38
  import QueryHelper from "../Types/Database/QueryHelper";
38
39
  import logger, { getLogAttributesFromRequest } from "../Utils/Logger";
40
+ import { SEARCH_ENGINE_INDEXING_FLAG_NAME, isSearchEngineIndexingEnabled, } from "../../Types/StatusPage/SearchEngineIndexing";
39
41
  import Response from "../Utils/Response";
40
42
  import BaseAPI from "./BaseAPI";
41
43
  import BaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
@@ -120,7 +122,11 @@ const resolveStatusPageIdOrThrow = async (statusPageIdOrDomain) => {
120
122
  }
121
123
  return statusPageId;
122
124
  };
123
- export default class StatusPageAPI extends BaseAPI {
125
+ class StatusPageAPI extends BaseAPI {
126
+ static clearOverviewResponseCache() {
127
+ this.overviewResponseCache.clear();
128
+ this.overviewResponseInFlight.clear();
129
+ }
124
130
  constructor() {
125
131
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9;
126
132
  super(StatusPage, StatusPageService);
@@ -142,6 +148,7 @@ export default class StatusPageAPI extends BaseAPI {
142
148
  pageDescription: true,
143
149
  name: true,
144
150
  defaultLanguage: true,
151
+ enableSearchEngineIndexing: true,
145
152
  },
146
153
  props: {
147
154
  isRoot: true,
@@ -155,6 +162,12 @@ export default class StatusPageAPI extends BaseAPI {
155
162
  description: statusPage.pageDescription,
156
163
  _id: (_a = statusPage._id) === null || _a === void 0 ? void 0 : _a.toString(),
157
164
  defaultLanguage: statusPage.defaultLanguage || null,
165
+ /*
166
+ * Drives <meta name="robots"> and X-Robots-Tag on the rendered page.
167
+ * Normalised here rather than shipping the raw column, so the two
168
+ * frontend servers never have to decide what a missing value means.
169
+ */
170
+ [SEARCH_ENGINE_INDEXING_FLAG_NAME]: isSearchEngineIndexingEnabled(statusPage.enableSearchEngineIndexing),
158
171
  });
159
172
  });
160
173
  // favicon api.
@@ -1056,686 +1069,42 @@ export default class StatusPageAPI extends BaseAPI {
1056
1069
  * can never drift between the two methods. It does not read req.body.
1057
1070
  */
1058
1071
  const overviewHandler = async (req, res, next) => {
1059
- var _a;
1060
1072
  try {
1061
1073
  const statusPageId = await resolveStatusPageIdOrThrow(req.params["statusPageIdOrDomain"]);
1062
- await this.checkHasReadAccess({
1063
- statusPageId: statusPageId,
1064
- req: req,
1065
- });
1066
1074
  /*
1067
- * The timeline window comes from the status page's configured
1068
- * showUptimeHistoryInDays. getStatusPageResourcesAndTimelines
1069
- * fetches the status page row anyway, so let it compute the window
1070
- * (clamped to 1..90 days, ending now) instead of issuing a separate
1071
- * StatusPage query here just to read that one column.
1075
+ * checkHasReadAccess MUST stay before any cache read: it is what keeps
1076
+ * private pages, IP whitelists, and master passwords enforced on every
1077
+ * request even when the payload below is served from cache.
1072
1078
  */
1073
- const { monitorStatuses, monitorGroupCurrentStatuses, statusPageResources, statusPage, monitorsOnStatusPage, monitorStatusTimelines, statusPageGroups, monitorsInGroup, startDateForMonitorTimeline: startDate, endDateForMonitorTimeline: endDate, } = await this.getStatusPageResourcesAndTimelines({
1079
+ await this.checkHasReadAccess({
1074
1080
  statusPageId: statusPageId,
1081
+ req: req,
1075
1082
  });
1076
- // check if status page has active incident.
1077
- let activeIncidents = [];
1078
- if (monitorsOnStatusPage.length > 0) {
1079
- let select = {
1080
- createdAt: true,
1081
- declaredAt: true,
1082
- updatedAt: true,
1083
- title: true,
1084
- description: true,
1085
- _id: true,
1086
- postmortemNote: true,
1087
- postmortemPostedAt: true,
1088
- showPostmortemOnStatusPage: true,
1089
- postmortemAttachments: {
1090
- _id: true,
1091
- name: true,
1092
- },
1093
- incidentSeverity: {
1094
- name: true,
1095
- color: true,
1096
- },
1097
- currentIncidentState: {
1098
- _id: true,
1099
- name: true,
1100
- color: true,
1101
- order: true,
1102
- },
1103
- monitors: {
1104
- _id: true,
1105
- },
1106
- };
1107
- if (statusPage.showIncidentLabelsOnStatusPage) {
1108
- select = Object.assign(Object.assign({}, select), { labels: {
1109
- name: true,
1110
- color: true,
1111
- } });
1112
- }
1113
- const unresolvedIncidentStates = await IncidentStateService.getUnresolvedIncidentStates(statusPage.projectId, {
1114
- isRoot: true,
1115
- });
1116
- const unresolvedIncidentStateIds = unresolvedIncidentStates.map((state) => {
1117
- return state.id;
1118
- });
1119
- if (statusPage.showIncidentsOnStatusPage) {
1120
- activeIncidents = await IncidentService.findBy({
1121
- query: {
1122
- monitors: monitorsOnStatusPage,
1123
- currentIncidentStateId: QueryHelper.any(unresolvedIncidentStateIds),
1124
- isVisibleOnStatusPage: true,
1125
- projectId: statusPage.projectId,
1126
- },
1127
- select: select,
1128
- sort: {
1129
- declaredAt: SortOrder.Descending,
1130
- createdAt: SortOrder.Descending,
1131
- },
1132
- skip: 0,
1133
- limit: LIMIT_PER_PROJECT,
1134
- props: {
1135
- isRoot: true,
1136
- },
1137
- });
1138
- }
1139
- }
1140
- const incidentsOnStatusPage = activeIncidents.map((incident) => {
1141
- return incident.id;
1142
- });
1143
- let incidentPublicNotes = [];
1144
- if (incidentsOnStatusPage.length > 0) {
1145
- incidentPublicNotes = await IncidentPublicNoteService.findBy({
1146
- query: {
1147
- incidentId: QueryHelper.any(incidentsOnStatusPage),
1148
- projectId: statusPage.projectId,
1149
- },
1150
- select: {
1151
- note: true,
1152
- incidentId: true,
1153
- postedAt: true,
1154
- attachments: {
1155
- _id: true,
1156
- name: true,
1157
- },
1158
- },
1159
- sort: {
1160
- postedAt: SortOrder.Descending, // new note first
1161
- },
1162
- skip: 0,
1163
- limit: LIMIT_PER_PROJECT,
1164
- props: {
1165
- isRoot: true,
1166
- },
1167
- });
1168
- }
1169
- let incidentStateTimelines = [];
1170
- if (incidentsOnStatusPage.length > 0) {
1171
- incidentStateTimelines = await IncidentStateTimelineService.findBy({
1172
- query: {
1173
- incidentId: QueryHelper.any(incidentsOnStatusPage),
1174
- projectId: statusPage.projectId,
1175
- },
1176
- select: {
1177
- _id: true,
1178
- createdAt: true,
1179
- startsAt: true,
1180
- incidentId: true,
1181
- incidentState: {
1182
- _id: true,
1183
- name: true,
1184
- color: true,
1185
- isCreatedState: true,
1186
- isResolvedState: true,
1187
- isAcknowledgedState: true,
1188
- },
1189
- },
1190
- sort: {
1191
- startsAt: SortOrder.Descending, // newer state changes first
1192
- },
1193
- skip: 0,
1194
- limit: LIMIT_PER_PROJECT,
1195
- props: {
1196
- isRoot: true,
1197
- },
1198
- });
1199
- }
1200
- // Fetch active episodes (similar to incidents)
1201
- let activeEpisodes = [];
1202
- let activeEpisodesJson = [];
1203
- let episodePublicNotes = [];
1204
- let episodeStateTimelines = [];
1083
+ // Resolved id, so domain-served and id-served views share one entry.
1084
+ const cacheKey = statusPageId.toString();
1205
1085
  /*
1206
- * Cheap guard before the expensive part: the block below scans every
1207
- * incident ever attached to this page's monitors (a many-to-many
1208
- * join, up to LIMIT_PER_PROJECT rows) just to discover episode
1209
- * membership — on every overview view, even though most pages have
1210
- * zero active episodes most of the time. One indexed COUNT of the
1211
- * project's unresolved, visible episodes lets us skip all of it in
1212
- * the common case. Behavior-preserving: the final activeEpisodes
1213
- * query applies exactly these three constraints, so count == 0
1214
- * implies the block's outputs stay empty.
1086
+ * The cached JSONObject is shared across requests and must never be
1087
+ * mutated after build.
1215
1088
  */
1216
- let unresolvedIncidentStateIds = [];
1217
- let hasActiveEpisodes = false;
1218
- if (statusPage.showEpisodesOnStatusPage &&
1219
- monitorsOnStatusPage.length > 0) {
1220
- const unresolvedIncidentStates = await IncidentStateService.getUnresolvedIncidentStates(statusPage.projectId, { isRoot: true });
1221
- unresolvedIncidentStateIds = unresolvedIncidentStates.map((state) => {
1222
- return state.id;
1223
- });
1224
- const activeEpisodeCount = await IncidentEpisodeService.countBy({
1225
- query: {
1226
- projectId: statusPage.projectId,
1227
- isVisibleOnStatusPage: true,
1228
- currentIncidentStateId: QueryHelper.any(unresolvedIncidentStateIds),
1229
- },
1230
- props: {
1231
- isRoot: true,
1232
- },
1233
- });
1234
- hasActiveEpisodes = activeEpisodeCount.toNumber() > 0;
1235
- }
1236
- if (hasActiveEpisodes) {
1237
- // First, get incidents that have monitors on status page
1238
- const incidentsForEpisodes = await IncidentService.findBy({
1239
- query: {
1240
- monitors: monitorsOnStatusPage,
1241
- isVisibleOnStatusPage: true,
1242
- projectId: statusPage.projectId,
1243
- },
1244
- select: {
1245
- _id: true,
1246
- },
1247
- skip: 0,
1248
- limit: LIMIT_PER_PROJECT,
1249
- props: {
1250
- isRoot: true,
1251
- },
1252
- });
1253
- const incidentIdsForEpisodes = incidentsForEpisodes.map((incident) => {
1254
- return incident.id;
1255
- });
1256
- // Get episode members for these incidents
1257
- let episodeMembers = [];
1258
- if (incidentIdsForEpisodes.length > 0) {
1259
- episodeMembers = await IncidentEpisodeMemberService.findBy({
1260
- query: {
1261
- incidentId: QueryHelper.any(incidentIdsForEpisodes),
1262
- projectId: statusPage.projectId,
1263
- },
1264
- select: {
1265
- incidentEpisodeId: true,
1266
- incidentId: true,
1267
- },
1268
- skip: 0,
1269
- limit: LIMIT_PER_PROJECT,
1270
- props: {
1271
- isRoot: true,
1272
- },
1273
- });
1274
- }
1275
- // Get unique episode IDs
1276
- const episodeIdsFromMembers = new Set();
1277
- for (const member of episodeMembers) {
1278
- if (member.incidentEpisodeId) {
1279
- episodeIdsFromMembers.add(member.incidentEpisodeId.toString());
1280
- }
1281
- }
1282
- // Fetch active (unresolved) episodes
1283
- if (episodeIdsFromMembers.size > 0) {
1284
- // unresolvedIncidentStateIds was fetched by the guard above.
1285
- let selectEpisodes = {
1286
- createdAt: true,
1287
- declaredAt: true,
1288
- updatedAt: true,
1289
- title: true,
1290
- description: true,
1291
- _id: true,
1292
- episodeNumber: true,
1293
- incidentSeverity: {
1294
- name: true,
1295
- color: true,
1296
- },
1297
- currentIncidentState: {
1298
- name: true,
1299
- color: true,
1300
- _id: true,
1301
- order: true,
1302
- isCreatedState: true,
1303
- isAcknowledgedState: true,
1304
- isResolvedState: true,
1305
- },
1306
- incidentCount: true,
1307
- };
1308
- if (statusPage.showEpisodeLabelsOnStatusPage) {
1309
- selectEpisodes = Object.assign(Object.assign({}, selectEpisodes), { labels: {
1310
- name: true,
1311
- color: true,
1312
- } });
1313
- }
1314
- activeEpisodes = await IncidentEpisodeService.findBy({
1315
- query: {
1316
- _id: QueryHelper.any(Array.from(episodeIdsFromMembers).map((id) => {
1317
- return new ObjectID(id);
1318
- })),
1319
- currentIncidentStateId: QueryHelper.any(unresolvedIncidentStateIds),
1320
- isVisibleOnStatusPage: true,
1321
- projectId: statusPage.projectId,
1322
- },
1323
- select: selectEpisodes,
1324
- sort: {
1325
- declaredAt: SortOrder.Descending,
1326
- createdAt: SortOrder.Descending,
1327
- },
1328
- skip: 0,
1329
- limit: LIMIT_PER_PROJECT,
1330
- props: {
1331
- isRoot: true,
1332
- },
1089
+ let response = StatusPageAPI.overviewResponseCache.get(cacheKey);
1090
+ if (!response) {
1091
+ let inFlight = StatusPageAPI.overviewResponseInFlight.get(cacheKey);
1092
+ if (!inFlight) {
1093
+ inFlight = this.buildOverviewResponse(statusPageId);
1094
+ StatusPageAPI.overviewResponseInFlight.set(cacheKey, inFlight);
1095
+ inFlight
1096
+ .then((builtResponse) => {
1097
+ StatusPageAPI.overviewResponseCache.set(cacheKey, builtResponse, StatusPageAPI.OVERVIEW_CACHE_TTL_MS);
1098
+ })
1099
+ .catch(() => {
1100
+ // Failed builds are never cached; the next request rebuilds.
1101
+ })
1102
+ .finally(() => {
1103
+ StatusPageAPI.overviewResponseInFlight.delete(cacheKey);
1333
1104
  });
1334
- // Build episode monitors map
1335
- if (activeEpisodes.length > 0) {
1336
- // Collect all incident IDs from episode members for active episodes
1337
- const activeEpisodeIds = new Set(activeEpisodes.map((e) => {
1338
- return e.id.toString();
1339
- }));
1340
- const memberIncidentIds = [];
1341
- for (const member of episodeMembers) {
1342
- if (member.incidentEpisodeId &&
1343
- activeEpisodeIds.has(member.incidentEpisodeId.toString()) &&
1344
- member.incidentId &&
1345
- !memberIncidentIds.some((id) => {
1346
- return id.toString() === member.incidentId.toString();
1347
- })) {
1348
- memberIncidentIds.push(member.incidentId);
1349
- }
1350
- }
1351
- // Fetch incidents with monitors
1352
- let memberIncidents = [];
1353
- if (memberIncidentIds.length > 0) {
1354
- memberIncidents = await IncidentService.findBy({
1355
- query: {
1356
- _id: QueryHelper.any(memberIncidentIds),
1357
- isVisibleOnStatusPage: true,
1358
- projectId: statusPage.projectId,
1359
- },
1360
- select: {
1361
- _id: true,
1362
- monitors: {
1363
- _id: true,
1364
- },
1365
- },
1366
- skip: 0,
1367
- limit: LIMIT_PER_PROJECT,
1368
- props: {
1369
- isRoot: true,
1370
- },
1371
- });
1372
- }
1373
- // Build incident -> monitors map
1374
- const incidentMonitorsMap = new Map();
1375
- for (const incident of memberIncidents) {
1376
- const incidentIdStr = incident.id.toString();
1377
- const monitorIds = (incident.monitors || [])
1378
- .map((m) => {
1379
- var _a, _b;
1380
- return new ObjectID(((_a = m._id) === null || _a === void 0 ? void 0 : _a.toString()) || ((_b = m.id) === null || _b === void 0 ? void 0 : _b.toString()) || "");
1381
- })
1382
- .filter((id) => {
1383
- return id.toString() !== "";
1384
- });
1385
- incidentMonitorsMap.set(incidentIdStr, monitorIds);
1386
- }
1387
- // Build episode -> monitors map
1388
- const episodeMonitorsMap = new Map();
1389
- for (const member of episodeMembers) {
1390
- if (member.incidentEpisodeId &&
1391
- member.incidentId &&
1392
- activeEpisodeIds.has(member.incidentEpisodeId.toString())) {
1393
- const episodeIdStr = member.incidentEpisodeId.toString();
1394
- const incidentIdStr = member.incidentId.toString();
1395
- if (!episodeMonitorsMap.has(episodeIdStr)) {
1396
- episodeMonitorsMap.set(episodeIdStr, []);
1397
- }
1398
- const episodeMonitors = episodeMonitorsMap.get(episodeIdStr);
1399
- const incidentMonitors = incidentMonitorsMap.get(incidentIdStr) || [];
1400
- for (const monitorId of incidentMonitors) {
1401
- if (!episodeMonitors.some((m) => {
1402
- return m.toString() === monitorId.toString();
1403
- })) {
1404
- episodeMonitors.push(monitorId);
1405
- }
1406
- }
1407
- }
1408
- }
1409
- // Serialize episodes and add monitors
1410
- activeEpisodesJson = BaseModel.toJSONArray(activeEpisodes, IncidentEpisode);
1411
- for (const episodeJson of activeEpisodesJson) {
1412
- const episodeObj = episodeJson;
1413
- const episodeId = (_a = episodeObj["_id"]) === null || _a === void 0 ? void 0 : _a.toString();
1414
- if (episodeId) {
1415
- const monitorIds = episodeMonitorsMap.get(episodeId) || [];
1416
- episodeObj["monitors"] = monitorIds.map((id) => {
1417
- return { _id: id.toString() };
1418
- });
1419
- }
1420
- }
1421
- // Get episode public notes
1422
- const episodesOnStatusPage = activeEpisodes.map((episode) => {
1423
- return episode.id;
1424
- });
1425
- if (episodesOnStatusPage.length > 0) {
1426
- episodePublicNotes =
1427
- await IncidentEpisodePublicNoteService.findBy({
1428
- query: {
1429
- incidentEpisodeId: QueryHelper.any(episodesOnStatusPage),
1430
- projectId: statusPage.projectId,
1431
- },
1432
- select: {
1433
- postedAt: true,
1434
- note: true,
1435
- incidentEpisodeId: true,
1436
- attachments: {
1437
- _id: true,
1438
- name: true,
1439
- },
1440
- },
1441
- sort: {
1442
- postedAt: SortOrder.Descending,
1443
- },
1444
- skip: 0,
1445
- limit: LIMIT_PER_PROJECT,
1446
- props: {
1447
- isRoot: true,
1448
- },
1449
- });
1450
- // Get episode state timelines
1451
- episodeStateTimelines =
1452
- await IncidentEpisodeStateTimelineService.findBy({
1453
- query: {
1454
- incidentEpisodeId: QueryHelper.any(episodesOnStatusPage),
1455
- projectId: statusPage.projectId,
1456
- },
1457
- select: {
1458
- _id: true,
1459
- createdAt: true,
1460
- startsAt: true,
1461
- incidentEpisodeId: true,
1462
- incidentState: {
1463
- name: true,
1464
- color: true,
1465
- isCreatedState: true,
1466
- isAcknowledgedState: true,
1467
- isResolvedState: true,
1468
- },
1469
- },
1470
- sort: {
1471
- startsAt: SortOrder.Descending,
1472
- },
1473
- skip: 0,
1474
- limit: LIMIT_PER_PROJECT,
1475
- props: {
1476
- isRoot: true,
1477
- },
1478
- });
1479
- }
1480
- }
1481
1105
  }
1106
+ response = await inFlight;
1482
1107
  }
1483
- // check if status page has active announcement.
1484
- const today = OneUptimeDate.getCurrentDate();
1485
- let activeAnnouncements = [];
1486
- if (statusPage.showAnnouncementsOnStatusPage) {
1487
- activeAnnouncements = await StatusPageAnnouncementService.findBy({
1488
- query: {
1489
- statusPages: statusPageId,
1490
- showAnnouncementAt: QueryHelper.lessThan(today),
1491
- endAnnouncementAt: QueryHelper.greaterThanOrNull(today),
1492
- projectId: statusPage.projectId,
1493
- },
1494
- select: {
1495
- createdAt: true,
1496
- title: true,
1497
- description: true,
1498
- _id: true,
1499
- showAnnouncementAt: true,
1500
- endAnnouncementAt: true,
1501
- },
1502
- skip: 0,
1503
- limit: LIMIT_PER_PROJECT,
1504
- props: {
1505
- isRoot: true,
1506
- },
1507
- });
1508
- }
1509
- // check if status page has active scheduled events.
1510
- let scheduledEventsSelect = {
1511
- createdAt: true,
1512
- title: true,
1513
- description: true,
1514
- _id: true,
1515
- endsAt: true,
1516
- startsAt: true,
1517
- currentScheduledMaintenanceState: {
1518
- name: true,
1519
- color: true,
1520
- isScheduledState: true,
1521
- isResolvedState: true,
1522
- isOngoingState: true,
1523
- },
1524
- monitors: {
1525
- _id: true,
1526
- },
1527
- };
1528
- if (statusPage.showScheduledEventLabelsOnStatusPage) {
1529
- scheduledEventsSelect = Object.assign(Object.assign({}, scheduledEventsSelect), { labels: {
1530
- name: true,
1531
- color: true,
1532
- } });
1533
- }
1534
- let scheduledMaintenanceEvents = [];
1535
- if (statusPage.showScheduledMaintenanceEventsOnStatusPage) {
1536
- scheduledMaintenanceEvents = await ScheduledMaintenanceService.findBy({
1537
- query: {
1538
- currentScheduledMaintenanceState: {
1539
- isOngoingState: true,
1540
- },
1541
- statusPages: statusPageId,
1542
- projectId: statusPage.projectId,
1543
- isVisibleOnStatusPage: true,
1544
- },
1545
- select: scheduledEventsSelect,
1546
- sort: {
1547
- startsAt: SortOrder.Ascending,
1548
- },
1549
- skip: 0,
1550
- limit: LIMIT_PER_PROJECT,
1551
- props: {
1552
- isRoot: true,
1553
- },
1554
- });
1555
- }
1556
- let futureScheduledMaintenanceEvents = [];
1557
- if (statusPage.showScheduledMaintenanceEventsOnStatusPage) {
1558
- futureScheduledMaintenanceEvents =
1559
- await ScheduledMaintenanceService.findBy({
1560
- query: {
1561
- currentScheduledMaintenanceState: {
1562
- isScheduledState: true,
1563
- },
1564
- statusPages: statusPageId,
1565
- projectId: statusPage.projectId,
1566
- isVisibleOnStatusPage: true,
1567
- },
1568
- select: scheduledEventsSelect,
1569
- sort: {
1570
- startsAt: SortOrder.Ascending,
1571
- },
1572
- skip: 0,
1573
- limit: LIMIT_PER_PROJECT,
1574
- props: {
1575
- isRoot: true,
1576
- },
1577
- });
1578
- }
1579
- futureScheduledMaintenanceEvents.forEach((event) => {
1580
- scheduledMaintenanceEvents.push(event);
1581
- });
1582
- const scheduledMaintenanceEventsOnStatusPage = scheduledMaintenanceEvents.map((event) => {
1583
- return event.id;
1584
- });
1585
- let scheduledMaintenanceEventsPublicNotes = [];
1586
- if (scheduledMaintenanceEventsOnStatusPage.length > 0) {
1587
- scheduledMaintenanceEventsPublicNotes =
1588
- await ScheduledMaintenancePublicNoteService.findBy({
1589
- query: {
1590
- scheduledMaintenanceId: QueryHelper.any(scheduledMaintenanceEventsOnStatusPage),
1591
- projectId: statusPage.projectId,
1592
- },
1593
- select: {
1594
- postedAt: true,
1595
- note: true,
1596
- scheduledMaintenanceId: true,
1597
- attachments: {
1598
- _id: true,
1599
- name: true,
1600
- },
1601
- },
1602
- sort: {
1603
- postedAt: SortOrder.Ascending,
1604
- },
1605
- skip: 0,
1606
- limit: LIMIT_PER_PROJECT,
1607
- props: {
1608
- isRoot: true,
1609
- },
1610
- });
1611
- }
1612
- let scheduledMaintenanceStateTimelines = [];
1613
- if (scheduledMaintenanceEventsOnStatusPage.length > 0) {
1614
- scheduledMaintenanceStateTimelines =
1615
- await ScheduledMaintenanceStateTimelineService.findBy({
1616
- query: {
1617
- scheduledMaintenanceId: QueryHelper.any(scheduledMaintenanceEventsOnStatusPage),
1618
- projectId: statusPage.projectId,
1619
- },
1620
- select: {
1621
- _id: true,
1622
- createdAt: true,
1623
- startsAt: true,
1624
- scheduledMaintenanceId: true,
1625
- scheduledMaintenanceState: {
1626
- _id: true,
1627
- color: true,
1628
- name: true,
1629
- isScheduledState: true,
1630
- isResolvedState: true,
1631
- isOngoingState: true,
1632
- },
1633
- },
1634
- sort: {
1635
- startsAt: SortOrder.Descending, // newer state changes first
1636
- },
1637
- skip: 0,
1638
- limit: LIMIT_PER_PROJECT,
1639
- props: {
1640
- isRoot: true,
1641
- },
1642
- });
1643
- }
1644
- // get all status page bar chart rules
1645
- const statusPageHistoryChartBarColorRules = await StatusPageHistoryChartBarColorRuleService.findBy({
1646
- query: {
1647
- statusPageId: statusPageId,
1648
- },
1649
- select: {
1650
- _id: true,
1651
- barColor: true,
1652
- order: true,
1653
- statusPageId: true,
1654
- uptimePercentGreaterThanOrEqualTo: true,
1655
- },
1656
- sort: {
1657
- order: SortOrder.Ascending,
1658
- },
1659
- skip: 0,
1660
- limit: LIMIT_PER_PROJECT,
1661
- props: {
1662
- isRoot: true,
1663
- },
1664
- });
1665
- /*
1666
- * Fetch all incidents (active + resolved) in the timeline date range
1667
- * for the uptime bar tooltip and click-through
1668
- */
1669
- let timelineIncidents = [];
1670
- if (monitorsOnStatusPage.length > 0 &&
1671
- statusPage.showIncidentsOnStatusPage) {
1672
- timelineIncidents = await IncidentService.findBy({
1673
- query: {
1674
- monitors: monitorsOnStatusPage,
1675
- declaredAt: QueryHelper.inBetween(startDate, endDate),
1676
- isVisibleOnStatusPage: true,
1677
- projectId: statusPage.projectId,
1678
- },
1679
- select: {
1680
- _id: true,
1681
- title: true,
1682
- declaredAt: true,
1683
- incidentSeverity: {
1684
- name: true,
1685
- color: true,
1686
- },
1687
- currentIncidentState: {
1688
- _id: true,
1689
- name: true,
1690
- color: true,
1691
- },
1692
- monitors: {
1693
- _id: true,
1694
- },
1695
- },
1696
- sort: {
1697
- declaredAt: SortOrder.Descending,
1698
- },
1699
- skip: 0,
1700
- limit: LIMIT_PER_PROJECT,
1701
- props: {
1702
- isRoot: true,
1703
- },
1704
- });
1705
- }
1706
- const overallStatus = StatusPageService.getOverallMonitorStatus({
1707
- statusPageResources,
1708
- monitorStatuses,
1709
- monitorGroupCurrentStatuses,
1710
- });
1711
- const response = {
1712
- overallStatus: overallStatus
1713
- ? BaseModel.toJSON(overallStatus, MonitorStatus)
1714
- : null,
1715
- scheduledMaintenanceEventsPublicNotes: BaseModel.toJSONArray(scheduledMaintenanceEventsPublicNotes, ScheduledMaintenancePublicNote),
1716
- statusPageHistoryChartBarColorRules: BaseModel.toJSONArray(statusPageHistoryChartBarColorRules, StatusPageHistoryChartBarColorRule),
1717
- scheduledMaintenanceEvents: BaseModel.toJSONArray(scheduledMaintenanceEvents, ScheduledMaintenance),
1718
- activeAnnouncements: BaseModel.toJSONArray(activeAnnouncements, StatusPageAnnouncement),
1719
- incidentPublicNotes: BaseModel.toJSONArray(incidentPublicNotes, IncidentPublicNote),
1720
- activeIncidents: BaseModel.toJSONArray(activeIncidents, Incident),
1721
- activeEpisodes: activeEpisodesJson,
1722
- episodePublicNotes: BaseModel.toJSONArray(episodePublicNotes, IncidentEpisodePublicNote),
1723
- episodeStateTimelines: BaseModel.toJSONArray(episodeStateTimelines, IncidentEpisodeStateTimeline),
1724
- monitorStatusTimelines: BaseModel.toJSONArray(monitorStatusTimelines, MonitorStatusTimeline),
1725
- resourceGroups: BaseModel.toJSONArray(statusPageGroups, StatusPageGroup),
1726
- monitorStatuses: BaseModel.toJSONArray(monitorStatuses, MonitorStatus),
1727
- statusPageResources: BaseModel.toJSONArray(statusPageResources, StatusPageResource),
1728
- incidentStateTimelines: BaseModel.toJSONArray(incidentStateTimelines, IncidentStateTimeline),
1729
- statusPage: (() => {
1730
- const statusPageJson = BaseModel.toJSONObject(statusPage, StatusPage);
1731
- delete statusPageJson["projectId"];
1732
- return statusPageJson;
1733
- })(),
1734
- scheduledMaintenanceStateTimelines: BaseModel.toJSONArray(scheduledMaintenanceStateTimelines, ScheduledMaintenanceStateTimeline),
1735
- monitorGroupCurrentStatuses: JSONFunctions.serialize(monitorGroupCurrentStatuses),
1736
- monitorsInGroup: JSONFunctions.serialize(monitorsInGroup),
1737
- timelineIncidents: BaseModel.toJSONArray(timelineIncidents, Incident),
1738
- };
1739
1108
  // These can serve private-page data on a GET; never let shared caches store them.
1740
1109
  Response.setNoCacheHeaders(res);
1741
1110
  return Response.sendJsonObjectResponse(req, res, response);
@@ -3609,10 +2978,693 @@ export default class StatusPageAPI extends BaseAPI {
3609
2978
  endDateForMonitorTimeline,
3610
2979
  };
3611
2980
  }
3612
- async getStatusPageAnnouncementAttachment(req, res) {
2981
+ /*
2982
+ * Builds the full overview payload for one status page. Everything below
2983
+ * is a pure function of the statusPageId — every query runs with isRoot
2984
+ * props and nothing is read from the request — which is what makes the
2985
+ * result safe to cache and share across requests. The returned JSONObject
2986
+ * must never be mutated after build.
2987
+ */
2988
+ async buildOverviewResponse(statusPageId) {
3613
2989
  var _a;
3614
- const statusPageIdParam = req.params["statusPageId"];
3615
- const announcementIdParam = req.params["announcementId"];
2990
+ /*
2991
+ * The timeline window comes from the status page's configured
2992
+ * showUptimeHistoryInDays. getStatusPageResourcesAndTimelines
2993
+ * fetches the status page row anyway, so let it compute the window
2994
+ * (clamped to 1..90 days, ending now) instead of issuing a separate
2995
+ * StatusPage query here just to read that one column.
2996
+ */
2997
+ const { monitorStatuses, monitorGroupCurrentStatuses, statusPageResources, statusPage, monitorsOnStatusPage, monitorStatusTimelines, statusPageGroups, monitorsInGroup, startDateForMonitorTimeline: startDate, endDateForMonitorTimeline: endDate, } = await this.getStatusPageResourcesAndTimelines({
2998
+ statusPageId: statusPageId,
2999
+ });
3000
+ // check if status page has active incident.
3001
+ let activeIncidents = [];
3002
+ if (monitorsOnStatusPage.length > 0) {
3003
+ let select = {
3004
+ createdAt: true,
3005
+ declaredAt: true,
3006
+ updatedAt: true,
3007
+ title: true,
3008
+ description: true,
3009
+ _id: true,
3010
+ postmortemNote: true,
3011
+ postmortemPostedAt: true,
3012
+ showPostmortemOnStatusPage: true,
3013
+ postmortemAttachments: {
3014
+ _id: true,
3015
+ name: true,
3016
+ },
3017
+ incidentSeverity: {
3018
+ name: true,
3019
+ color: true,
3020
+ },
3021
+ currentIncidentState: {
3022
+ _id: true,
3023
+ name: true,
3024
+ color: true,
3025
+ order: true,
3026
+ },
3027
+ monitors: {
3028
+ _id: true,
3029
+ },
3030
+ };
3031
+ if (statusPage.showIncidentLabelsOnStatusPage) {
3032
+ select = Object.assign(Object.assign({}, select), { labels: {
3033
+ name: true,
3034
+ color: true,
3035
+ } });
3036
+ }
3037
+ const unresolvedIncidentStates = await IncidentStateService.getUnresolvedIncidentStates(statusPage.projectId, {
3038
+ isRoot: true,
3039
+ });
3040
+ const unresolvedIncidentStateIds = unresolvedIncidentStates.map((state) => {
3041
+ return state.id;
3042
+ });
3043
+ if (statusPage.showIncidentsOnStatusPage) {
3044
+ activeIncidents = await IncidentService.findBy({
3045
+ query: {
3046
+ monitors: monitorsOnStatusPage,
3047
+ currentIncidentStateId: QueryHelper.any(unresolvedIncidentStateIds),
3048
+ isVisibleOnStatusPage: true,
3049
+ projectId: statusPage.projectId,
3050
+ },
3051
+ select: select,
3052
+ sort: {
3053
+ declaredAt: SortOrder.Descending,
3054
+ createdAt: SortOrder.Descending,
3055
+ },
3056
+ skip: 0,
3057
+ limit: LIMIT_PER_PROJECT,
3058
+ props: {
3059
+ isRoot: true,
3060
+ },
3061
+ });
3062
+ }
3063
+ }
3064
+ const incidentsOnStatusPage = activeIncidents.map((incident) => {
3065
+ return incident.id;
3066
+ });
3067
+ let incidentPublicNotes = [];
3068
+ if (incidentsOnStatusPage.length > 0) {
3069
+ incidentPublicNotes = await IncidentPublicNoteService.findBy({
3070
+ query: {
3071
+ incidentId: QueryHelper.any(incidentsOnStatusPage),
3072
+ projectId: statusPage.projectId,
3073
+ },
3074
+ select: {
3075
+ note: true,
3076
+ incidentId: true,
3077
+ postedAt: true,
3078
+ attachments: {
3079
+ _id: true,
3080
+ name: true,
3081
+ },
3082
+ },
3083
+ sort: {
3084
+ postedAt: SortOrder.Descending, // new note first
3085
+ },
3086
+ skip: 0,
3087
+ limit: LIMIT_PER_PROJECT,
3088
+ props: {
3089
+ isRoot: true,
3090
+ },
3091
+ });
3092
+ }
3093
+ let incidentStateTimelines = [];
3094
+ if (incidentsOnStatusPage.length > 0) {
3095
+ incidentStateTimelines = await IncidentStateTimelineService.findBy({
3096
+ query: {
3097
+ incidentId: QueryHelper.any(incidentsOnStatusPage),
3098
+ projectId: statusPage.projectId,
3099
+ },
3100
+ select: {
3101
+ _id: true,
3102
+ createdAt: true,
3103
+ startsAt: true,
3104
+ incidentId: true,
3105
+ incidentState: {
3106
+ _id: true,
3107
+ name: true,
3108
+ color: true,
3109
+ isCreatedState: true,
3110
+ isResolvedState: true,
3111
+ isAcknowledgedState: true,
3112
+ },
3113
+ },
3114
+ sort: {
3115
+ startsAt: SortOrder.Descending, // newer state changes first
3116
+ },
3117
+ skip: 0,
3118
+ limit: LIMIT_PER_PROJECT,
3119
+ props: {
3120
+ isRoot: true,
3121
+ },
3122
+ });
3123
+ }
3124
+ // Fetch active episodes (similar to incidents)
3125
+ let activeEpisodes = [];
3126
+ let activeEpisodesJson = [];
3127
+ let episodePublicNotes = [];
3128
+ let episodeStateTimelines = [];
3129
+ /*
3130
+ * Cheap guard before the expensive part: the block below scans every
3131
+ * incident ever attached to this page's monitors (a many-to-many
3132
+ * join, up to LIMIT_PER_PROJECT rows) just to discover episode
3133
+ * membership — on every overview view, even though most pages have
3134
+ * zero active episodes most of the time. One indexed COUNT of the
3135
+ * project's unresolved, visible episodes lets us skip all of it in
3136
+ * the common case. Behavior-preserving: the final activeEpisodes
3137
+ * query applies exactly these three constraints, so count == 0
3138
+ * implies the block's outputs stay empty.
3139
+ */
3140
+ let unresolvedIncidentStateIds = [];
3141
+ let hasActiveEpisodes = false;
3142
+ if (statusPage.showEpisodesOnStatusPage &&
3143
+ monitorsOnStatusPage.length > 0) {
3144
+ const unresolvedIncidentStates = await IncidentStateService.getUnresolvedIncidentStates(statusPage.projectId, { isRoot: true });
3145
+ unresolvedIncidentStateIds = unresolvedIncidentStates.map((state) => {
3146
+ return state.id;
3147
+ });
3148
+ const activeEpisodeCount = await IncidentEpisodeService.countBy({
3149
+ query: {
3150
+ projectId: statusPage.projectId,
3151
+ isVisibleOnStatusPage: true,
3152
+ currentIncidentStateId: QueryHelper.any(unresolvedIncidentStateIds),
3153
+ },
3154
+ props: {
3155
+ isRoot: true,
3156
+ },
3157
+ });
3158
+ hasActiveEpisodes = activeEpisodeCount.toNumber() > 0;
3159
+ }
3160
+ if (hasActiveEpisodes) {
3161
+ // First, get incidents that have monitors on status page
3162
+ const incidentsForEpisodes = await IncidentService.findBy({
3163
+ query: {
3164
+ monitors: monitorsOnStatusPage,
3165
+ isVisibleOnStatusPage: true,
3166
+ projectId: statusPage.projectId,
3167
+ },
3168
+ select: {
3169
+ _id: true,
3170
+ },
3171
+ skip: 0,
3172
+ limit: LIMIT_PER_PROJECT,
3173
+ props: {
3174
+ isRoot: true,
3175
+ },
3176
+ });
3177
+ const incidentIdsForEpisodes = incidentsForEpisodes.map((incident) => {
3178
+ return incident.id;
3179
+ });
3180
+ // Get episode members for these incidents
3181
+ let episodeMembers = [];
3182
+ if (incidentIdsForEpisodes.length > 0) {
3183
+ episodeMembers = await IncidentEpisodeMemberService.findBy({
3184
+ query: {
3185
+ incidentId: QueryHelper.any(incidentIdsForEpisodes),
3186
+ projectId: statusPage.projectId,
3187
+ },
3188
+ select: {
3189
+ incidentEpisodeId: true,
3190
+ incidentId: true,
3191
+ },
3192
+ skip: 0,
3193
+ limit: LIMIT_PER_PROJECT,
3194
+ props: {
3195
+ isRoot: true,
3196
+ },
3197
+ });
3198
+ }
3199
+ // Get unique episode IDs
3200
+ const episodeIdsFromMembers = new Set();
3201
+ for (const member of episodeMembers) {
3202
+ if (member.incidentEpisodeId) {
3203
+ episodeIdsFromMembers.add(member.incidentEpisodeId.toString());
3204
+ }
3205
+ }
3206
+ // Fetch active (unresolved) episodes
3207
+ if (episodeIdsFromMembers.size > 0) {
3208
+ // unresolvedIncidentStateIds was fetched by the guard above.
3209
+ let selectEpisodes = {
3210
+ createdAt: true,
3211
+ declaredAt: true,
3212
+ updatedAt: true,
3213
+ title: true,
3214
+ description: true,
3215
+ _id: true,
3216
+ episodeNumber: true,
3217
+ incidentSeverity: {
3218
+ name: true,
3219
+ color: true,
3220
+ },
3221
+ currentIncidentState: {
3222
+ name: true,
3223
+ color: true,
3224
+ _id: true,
3225
+ order: true,
3226
+ isCreatedState: true,
3227
+ isAcknowledgedState: true,
3228
+ isResolvedState: true,
3229
+ },
3230
+ incidentCount: true,
3231
+ };
3232
+ if (statusPage.showEpisodeLabelsOnStatusPage) {
3233
+ selectEpisodes = Object.assign(Object.assign({}, selectEpisodes), { labels: {
3234
+ name: true,
3235
+ color: true,
3236
+ } });
3237
+ }
3238
+ activeEpisodes = await IncidentEpisodeService.findBy({
3239
+ query: {
3240
+ _id: QueryHelper.any(Array.from(episodeIdsFromMembers).map((id) => {
3241
+ return new ObjectID(id);
3242
+ })),
3243
+ currentIncidentStateId: QueryHelper.any(unresolvedIncidentStateIds),
3244
+ isVisibleOnStatusPage: true,
3245
+ projectId: statusPage.projectId,
3246
+ },
3247
+ select: selectEpisodes,
3248
+ sort: {
3249
+ declaredAt: SortOrder.Descending,
3250
+ createdAt: SortOrder.Descending,
3251
+ },
3252
+ skip: 0,
3253
+ limit: LIMIT_PER_PROJECT,
3254
+ props: {
3255
+ isRoot: true,
3256
+ },
3257
+ });
3258
+ // Build episode monitors map
3259
+ if (activeEpisodes.length > 0) {
3260
+ // Collect all incident IDs from episode members for active episodes
3261
+ const activeEpisodeIds = new Set(activeEpisodes.map((e) => {
3262
+ return e.id.toString();
3263
+ }));
3264
+ const memberIncidentIds = [];
3265
+ for (const member of episodeMembers) {
3266
+ if (member.incidentEpisodeId &&
3267
+ activeEpisodeIds.has(member.incidentEpisodeId.toString()) &&
3268
+ member.incidentId &&
3269
+ !memberIncidentIds.some((id) => {
3270
+ return id.toString() === member.incidentId.toString();
3271
+ })) {
3272
+ memberIncidentIds.push(member.incidentId);
3273
+ }
3274
+ }
3275
+ // Fetch incidents with monitors
3276
+ let memberIncidents = [];
3277
+ if (memberIncidentIds.length > 0) {
3278
+ memberIncidents = await IncidentService.findBy({
3279
+ query: {
3280
+ _id: QueryHelper.any(memberIncidentIds),
3281
+ isVisibleOnStatusPage: true,
3282
+ projectId: statusPage.projectId,
3283
+ },
3284
+ select: {
3285
+ _id: true,
3286
+ monitors: {
3287
+ _id: true,
3288
+ },
3289
+ },
3290
+ skip: 0,
3291
+ limit: LIMIT_PER_PROJECT,
3292
+ props: {
3293
+ isRoot: true,
3294
+ },
3295
+ });
3296
+ }
3297
+ // Build incident -> monitors map
3298
+ const incidentMonitorsMap = new Map();
3299
+ for (const incident of memberIncidents) {
3300
+ const incidentIdStr = incident.id.toString();
3301
+ const monitorIds = (incident.monitors || [])
3302
+ .map((m) => {
3303
+ var _a, _b;
3304
+ return new ObjectID(((_a = m._id) === null || _a === void 0 ? void 0 : _a.toString()) || ((_b = m.id) === null || _b === void 0 ? void 0 : _b.toString()) || "");
3305
+ })
3306
+ .filter((id) => {
3307
+ return id.toString() !== "";
3308
+ });
3309
+ incidentMonitorsMap.set(incidentIdStr, monitorIds);
3310
+ }
3311
+ // Build episode -> monitors map
3312
+ const episodeMonitorsMap = new Map();
3313
+ for (const member of episodeMembers) {
3314
+ if (member.incidentEpisodeId &&
3315
+ member.incidentId &&
3316
+ activeEpisodeIds.has(member.incidentEpisodeId.toString())) {
3317
+ const episodeIdStr = member.incidentEpisodeId.toString();
3318
+ const incidentIdStr = member.incidentId.toString();
3319
+ if (!episodeMonitorsMap.has(episodeIdStr)) {
3320
+ episodeMonitorsMap.set(episodeIdStr, []);
3321
+ }
3322
+ const episodeMonitors = episodeMonitorsMap.get(episodeIdStr);
3323
+ const incidentMonitors = incidentMonitorsMap.get(incidentIdStr) || [];
3324
+ for (const monitorId of incidentMonitors) {
3325
+ if (!episodeMonitors.some((m) => {
3326
+ return m.toString() === monitorId.toString();
3327
+ })) {
3328
+ episodeMonitors.push(monitorId);
3329
+ }
3330
+ }
3331
+ }
3332
+ }
3333
+ // Serialize episodes and add monitors
3334
+ activeEpisodesJson = BaseModel.toJSONArray(activeEpisodes, IncidentEpisode);
3335
+ for (const episodeJson of activeEpisodesJson) {
3336
+ const episodeObj = episodeJson;
3337
+ const episodeId = (_a = episodeObj["_id"]) === null || _a === void 0 ? void 0 : _a.toString();
3338
+ if (episodeId) {
3339
+ const monitorIds = episodeMonitorsMap.get(episodeId) || [];
3340
+ episodeObj["monitors"] = monitorIds.map((id) => {
3341
+ return { _id: id.toString() };
3342
+ });
3343
+ }
3344
+ }
3345
+ // Get episode public notes
3346
+ const episodesOnStatusPage = activeEpisodes.map((episode) => {
3347
+ return episode.id;
3348
+ });
3349
+ if (episodesOnStatusPage.length > 0) {
3350
+ episodePublicNotes = await IncidentEpisodePublicNoteService.findBy({
3351
+ query: {
3352
+ incidentEpisodeId: QueryHelper.any(episodesOnStatusPage),
3353
+ projectId: statusPage.projectId,
3354
+ },
3355
+ select: {
3356
+ postedAt: true,
3357
+ note: true,
3358
+ incidentEpisodeId: true,
3359
+ attachments: {
3360
+ _id: true,
3361
+ name: true,
3362
+ },
3363
+ },
3364
+ sort: {
3365
+ postedAt: SortOrder.Descending,
3366
+ },
3367
+ skip: 0,
3368
+ limit: LIMIT_PER_PROJECT,
3369
+ props: {
3370
+ isRoot: true,
3371
+ },
3372
+ });
3373
+ // Get episode state timelines
3374
+ episodeStateTimelines =
3375
+ await IncidentEpisodeStateTimelineService.findBy({
3376
+ query: {
3377
+ incidentEpisodeId: QueryHelper.any(episodesOnStatusPage),
3378
+ projectId: statusPage.projectId,
3379
+ },
3380
+ select: {
3381
+ _id: true,
3382
+ createdAt: true,
3383
+ startsAt: true,
3384
+ incidentEpisodeId: true,
3385
+ incidentState: {
3386
+ name: true,
3387
+ color: true,
3388
+ isCreatedState: true,
3389
+ isAcknowledgedState: true,
3390
+ isResolvedState: true,
3391
+ },
3392
+ },
3393
+ sort: {
3394
+ startsAt: SortOrder.Descending,
3395
+ },
3396
+ skip: 0,
3397
+ limit: LIMIT_PER_PROJECT,
3398
+ props: {
3399
+ isRoot: true,
3400
+ },
3401
+ });
3402
+ }
3403
+ }
3404
+ }
3405
+ }
3406
+ // check if status page has active announcement.
3407
+ const today = OneUptimeDate.getCurrentDate();
3408
+ let activeAnnouncements = [];
3409
+ if (statusPage.showAnnouncementsOnStatusPage) {
3410
+ activeAnnouncements = await StatusPageAnnouncementService.findBy({
3411
+ query: {
3412
+ statusPages: statusPageId,
3413
+ showAnnouncementAt: QueryHelper.lessThan(today),
3414
+ endAnnouncementAt: QueryHelper.greaterThanOrNull(today),
3415
+ projectId: statusPage.projectId,
3416
+ },
3417
+ select: {
3418
+ createdAt: true,
3419
+ title: true,
3420
+ description: true,
3421
+ _id: true,
3422
+ showAnnouncementAt: true,
3423
+ endAnnouncementAt: true,
3424
+ },
3425
+ skip: 0,
3426
+ limit: LIMIT_PER_PROJECT,
3427
+ props: {
3428
+ isRoot: true,
3429
+ },
3430
+ });
3431
+ }
3432
+ // check if status page has active scheduled events.
3433
+ let scheduledEventsSelect = {
3434
+ createdAt: true,
3435
+ title: true,
3436
+ description: true,
3437
+ _id: true,
3438
+ endsAt: true,
3439
+ startsAt: true,
3440
+ currentScheduledMaintenanceState: {
3441
+ name: true,
3442
+ color: true,
3443
+ isScheduledState: true,
3444
+ isResolvedState: true,
3445
+ isOngoingState: true,
3446
+ },
3447
+ monitors: {
3448
+ _id: true,
3449
+ },
3450
+ };
3451
+ if (statusPage.showScheduledEventLabelsOnStatusPage) {
3452
+ scheduledEventsSelect = Object.assign(Object.assign({}, scheduledEventsSelect), { labels: {
3453
+ name: true,
3454
+ color: true,
3455
+ } });
3456
+ }
3457
+ let scheduledMaintenanceEvents = [];
3458
+ if (statusPage.showScheduledMaintenanceEventsOnStatusPage) {
3459
+ scheduledMaintenanceEvents = await ScheduledMaintenanceService.findBy({
3460
+ query: {
3461
+ currentScheduledMaintenanceState: {
3462
+ isOngoingState: true,
3463
+ },
3464
+ statusPages: statusPageId,
3465
+ projectId: statusPage.projectId,
3466
+ isVisibleOnStatusPage: true,
3467
+ },
3468
+ select: scheduledEventsSelect,
3469
+ sort: {
3470
+ startsAt: SortOrder.Ascending,
3471
+ },
3472
+ skip: 0,
3473
+ limit: LIMIT_PER_PROJECT,
3474
+ props: {
3475
+ isRoot: true,
3476
+ },
3477
+ });
3478
+ }
3479
+ let futureScheduledMaintenanceEvents = [];
3480
+ if (statusPage.showScheduledMaintenanceEventsOnStatusPage) {
3481
+ futureScheduledMaintenanceEvents =
3482
+ await ScheduledMaintenanceService.findBy({
3483
+ query: {
3484
+ currentScheduledMaintenanceState: {
3485
+ isScheduledState: true,
3486
+ },
3487
+ statusPages: statusPageId,
3488
+ projectId: statusPage.projectId,
3489
+ isVisibleOnStatusPage: true,
3490
+ },
3491
+ select: scheduledEventsSelect,
3492
+ sort: {
3493
+ startsAt: SortOrder.Ascending,
3494
+ },
3495
+ skip: 0,
3496
+ limit: LIMIT_PER_PROJECT,
3497
+ props: {
3498
+ isRoot: true,
3499
+ },
3500
+ });
3501
+ }
3502
+ futureScheduledMaintenanceEvents.forEach((event) => {
3503
+ scheduledMaintenanceEvents.push(event);
3504
+ });
3505
+ const scheduledMaintenanceEventsOnStatusPage = scheduledMaintenanceEvents.map((event) => {
3506
+ return event.id;
3507
+ });
3508
+ let scheduledMaintenanceEventsPublicNotes = [];
3509
+ if (scheduledMaintenanceEventsOnStatusPage.length > 0) {
3510
+ scheduledMaintenanceEventsPublicNotes =
3511
+ await ScheduledMaintenancePublicNoteService.findBy({
3512
+ query: {
3513
+ scheduledMaintenanceId: QueryHelper.any(scheduledMaintenanceEventsOnStatusPage),
3514
+ projectId: statusPage.projectId,
3515
+ },
3516
+ select: {
3517
+ postedAt: true,
3518
+ note: true,
3519
+ scheduledMaintenanceId: true,
3520
+ attachments: {
3521
+ _id: true,
3522
+ name: true,
3523
+ },
3524
+ },
3525
+ sort: {
3526
+ postedAt: SortOrder.Ascending,
3527
+ },
3528
+ skip: 0,
3529
+ limit: LIMIT_PER_PROJECT,
3530
+ props: {
3531
+ isRoot: true,
3532
+ },
3533
+ });
3534
+ }
3535
+ let scheduledMaintenanceStateTimelines = [];
3536
+ if (scheduledMaintenanceEventsOnStatusPage.length > 0) {
3537
+ scheduledMaintenanceStateTimelines =
3538
+ await ScheduledMaintenanceStateTimelineService.findBy({
3539
+ query: {
3540
+ scheduledMaintenanceId: QueryHelper.any(scheduledMaintenanceEventsOnStatusPage),
3541
+ projectId: statusPage.projectId,
3542
+ },
3543
+ select: {
3544
+ _id: true,
3545
+ createdAt: true,
3546
+ startsAt: true,
3547
+ scheduledMaintenanceId: true,
3548
+ scheduledMaintenanceState: {
3549
+ _id: true,
3550
+ color: true,
3551
+ name: true,
3552
+ isScheduledState: true,
3553
+ isResolvedState: true,
3554
+ isOngoingState: true,
3555
+ },
3556
+ },
3557
+ sort: {
3558
+ startsAt: SortOrder.Descending, // newer state changes first
3559
+ },
3560
+ skip: 0,
3561
+ limit: LIMIT_PER_PROJECT,
3562
+ props: {
3563
+ isRoot: true,
3564
+ },
3565
+ });
3566
+ }
3567
+ // get all status page bar chart rules
3568
+ const statusPageHistoryChartBarColorRules = await StatusPageHistoryChartBarColorRuleService.findBy({
3569
+ query: {
3570
+ statusPageId: statusPageId,
3571
+ },
3572
+ select: {
3573
+ _id: true,
3574
+ barColor: true,
3575
+ order: true,
3576
+ statusPageId: true,
3577
+ uptimePercentGreaterThanOrEqualTo: true,
3578
+ },
3579
+ sort: {
3580
+ order: SortOrder.Ascending,
3581
+ },
3582
+ skip: 0,
3583
+ limit: LIMIT_PER_PROJECT,
3584
+ props: {
3585
+ isRoot: true,
3586
+ },
3587
+ });
3588
+ /*
3589
+ * Fetch all incidents (active + resolved) in the timeline date range
3590
+ * for the uptime bar tooltip and click-through
3591
+ */
3592
+ let timelineIncidents = [];
3593
+ if (monitorsOnStatusPage.length > 0 &&
3594
+ statusPage.showIncidentsOnStatusPage) {
3595
+ timelineIncidents = await IncidentService.findBy({
3596
+ query: {
3597
+ monitors: monitorsOnStatusPage,
3598
+ declaredAt: QueryHelper.inBetween(startDate, endDate),
3599
+ isVisibleOnStatusPage: true,
3600
+ projectId: statusPage.projectId,
3601
+ },
3602
+ select: {
3603
+ _id: true,
3604
+ title: true,
3605
+ declaredAt: true,
3606
+ incidentSeverity: {
3607
+ name: true,
3608
+ color: true,
3609
+ },
3610
+ currentIncidentState: {
3611
+ _id: true,
3612
+ name: true,
3613
+ color: true,
3614
+ },
3615
+ monitors: {
3616
+ _id: true,
3617
+ },
3618
+ },
3619
+ sort: {
3620
+ declaredAt: SortOrder.Descending,
3621
+ },
3622
+ skip: 0,
3623
+ limit: LIMIT_PER_PROJECT,
3624
+ props: {
3625
+ isRoot: true,
3626
+ },
3627
+ });
3628
+ }
3629
+ const overallStatus = StatusPageService.getOverallMonitorStatus({
3630
+ statusPageResources,
3631
+ monitorStatuses,
3632
+ monitorGroupCurrentStatuses,
3633
+ });
3634
+ const response = {
3635
+ overallStatus: overallStatus
3636
+ ? BaseModel.toJSON(overallStatus, MonitorStatus)
3637
+ : null,
3638
+ scheduledMaintenanceEventsPublicNotes: BaseModel.toJSONArray(scheduledMaintenanceEventsPublicNotes, ScheduledMaintenancePublicNote),
3639
+ statusPageHistoryChartBarColorRules: BaseModel.toJSONArray(statusPageHistoryChartBarColorRules, StatusPageHistoryChartBarColorRule),
3640
+ scheduledMaintenanceEvents: BaseModel.toJSONArray(scheduledMaintenanceEvents, ScheduledMaintenance),
3641
+ activeAnnouncements: BaseModel.toJSONArray(activeAnnouncements, StatusPageAnnouncement),
3642
+ incidentPublicNotes: BaseModel.toJSONArray(incidentPublicNotes, IncidentPublicNote),
3643
+ activeIncidents: BaseModel.toJSONArray(activeIncidents, Incident),
3644
+ activeEpisodes: activeEpisodesJson,
3645
+ episodePublicNotes: BaseModel.toJSONArray(episodePublicNotes, IncidentEpisodePublicNote),
3646
+ episodeStateTimelines: BaseModel.toJSONArray(episodeStateTimelines, IncidentEpisodeStateTimeline),
3647
+ monitorStatusTimelines: BaseModel.toJSONArray(monitorStatusTimelines, MonitorStatusTimeline),
3648
+ resourceGroups: BaseModel.toJSONArray(statusPageGroups, StatusPageGroup),
3649
+ monitorStatuses: BaseModel.toJSONArray(monitorStatuses, MonitorStatus),
3650
+ statusPageResources: BaseModel.toJSONArray(statusPageResources, StatusPageResource),
3651
+ incidentStateTimelines: BaseModel.toJSONArray(incidentStateTimelines, IncidentStateTimeline),
3652
+ statusPage: (() => {
3653
+ const statusPageJson = BaseModel.toJSONObject(statusPage, StatusPage);
3654
+ delete statusPageJson["projectId"];
3655
+ return statusPageJson;
3656
+ })(),
3657
+ scheduledMaintenanceStateTimelines: BaseModel.toJSONArray(scheduledMaintenanceStateTimelines, ScheduledMaintenanceStateTimeline),
3658
+ monitorGroupCurrentStatuses: JSONFunctions.serialize(monitorGroupCurrentStatuses),
3659
+ monitorsInGroup: JSONFunctions.serialize(monitorsInGroup),
3660
+ timelineIncidents: BaseModel.toJSONArray(timelineIncidents, Incident),
3661
+ };
3662
+ return response;
3663
+ }
3664
+ async getStatusPageAnnouncementAttachment(req, res) {
3665
+ var _a;
3666
+ const statusPageIdParam = req.params["statusPageId"];
3667
+ const announcementIdParam = req.params["announcementId"];
3616
3668
  const fileIdParam = req.params["fileId"];
3617
3669
  if (!statusPageIdParam || !announcementIdParam || !fileIdParam) {
3618
3670
  throw new NotFoundException("Attachment not found");
@@ -4131,6 +4183,20 @@ export default class StatusPageAPI extends BaseAPI {
4131
4183
  }
4132
4184
  }
4133
4185
  }
4186
+ /*
4187
+ * Post-auth overview responses keyed by resolved statusPageId. The payload
4188
+ * is user-independent (authorization is a binary gate checked per-request,
4189
+ * before any cache read), so one short-TTL snapshot per page per process
4190
+ * serves every viewer. See buildOverviewResponse.
4191
+ */
4192
+ StatusPageAPI.overviewResponseCache = new InMemoryTTLCache(500);
4193
+ /*
4194
+ * In-flight overview builds keyed by the same cache key, so concurrent
4195
+ * cold-cache requests share one build instead of stampeding the database.
4196
+ */
4197
+ StatusPageAPI.overviewResponseInFlight = new Map();
4198
+ StatusPageAPI.OVERVIEW_CACHE_TTL_MS = 15000;
4199
+ export default StatusPageAPI;
4134
4200
  __decorate([
4135
4201
  CaptureSpan(),
4136
4202
  __metadata("design:type", Function),
@@ -4179,4 +4245,10 @@ __decorate([
4179
4245
  __metadata("design:paramtypes", [Object]),
4180
4246
  __metadata("design:returntype", Promise)
4181
4247
  ], StatusPageAPI.prototype, "getStatusPageResourcesAndTimelines", null);
4248
+ __decorate([
4249
+ CaptureSpan(),
4250
+ __metadata("design:type", Function),
4251
+ __metadata("design:paramtypes", [ObjectID]),
4252
+ __metadata("design:returntype", Promise)
4253
+ ], StatusPageAPI.prototype, "buildOverviewResponse", null);
4182
4254
  //# sourceMappingURL=StatusPageAPI.js.map