@oneuptime/common 10.4.7 → 10.4.9

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 (97) hide show
  1. package/Models/DatabaseModels/Service.ts +1 -1
  2. package/Server/API/TelemetryExceptionAPI.ts +62 -0
  3. package/Server/Services/OpenTelemetryIngestService.ts +8 -8
  4. package/Server/Services/TelemetryExceptionService.ts +225 -0
  5. package/Server/Types/Database/QueryHelper.ts +35 -0
  6. package/Server/Types/Database/QueryUtil.ts +74 -2
  7. package/Types/Dashboard/DashboardComponents/DashboardAlertListComponent.ts +1 -0
  8. package/Types/Dashboard/DashboardComponents/DashboardDockerContainerListComponent.ts +1 -0
  9. package/Types/Dashboard/DashboardComponents/DashboardDockerHostListComponent.ts +1 -0
  10. package/Types/Dashboard/DashboardComponents/DashboardDockerImageListComponent.ts +1 -0
  11. package/Types/Dashboard/DashboardComponents/DashboardDockerNetworkListComponent.ts +1 -0
  12. package/Types/Dashboard/DashboardComponents/DashboardDockerVolumeListComponent.ts +1 -0
  13. package/Types/Dashboard/DashboardComponents/DashboardHostListComponent.ts +1 -0
  14. package/Types/Dashboard/DashboardComponents/DashboardIncidentListComponent.ts +1 -0
  15. package/Types/Dashboard/DashboardComponents/DashboardKubernetesCronJobListComponent.ts +1 -0
  16. package/Types/Dashboard/DashboardComponents/DashboardKubernetesDaemonSetListComponent.ts +1 -0
  17. package/Types/Dashboard/DashboardComponents/DashboardKubernetesDeploymentListComponent.ts +1 -0
  18. package/Types/Dashboard/DashboardComponents/DashboardKubernetesJobListComponent.ts +1 -0
  19. package/Types/Dashboard/DashboardComponents/DashboardKubernetesNamespaceListComponent.ts +1 -0
  20. package/Types/Dashboard/DashboardComponents/DashboardKubernetesNodeListComponent.ts +1 -0
  21. package/Types/Dashboard/DashboardComponents/DashboardKubernetesPodListComponent.ts +1 -0
  22. package/Types/Dashboard/DashboardComponents/DashboardKubernetesStatefulSetListComponent.ts +1 -0
  23. package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
  24. package/Types/Dashboard/DashboardComponents/DashboardTraceListComponent.ts +1 -0
  25. package/Types/Telemetry/TelemetryRetentionConfig.ts +8 -8
  26. package/Types/Time/RangeStartAndEndDateTime.ts +8 -0
  27. package/Types/Time/TimeRange.ts +1 -0
  28. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +1 -0
  29. package/UI/Components/ModelTable/BaseModelTable.tsx +64 -17
  30. package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +11 -11
  31. package/UI/Components/Telemetry/TelemetryRetentionConfigSummary.tsx +196 -78
  32. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +1 -0
  33. package/Utils/Dashboard/Components/DashboardAlertListComponent.ts +5 -0
  34. package/Utils/Dashboard/Components/DashboardDockerContainerListComponent.ts +7 -0
  35. package/Utils/Dashboard/Components/DashboardDockerHostListComponent.ts +5 -0
  36. package/Utils/Dashboard/Components/DashboardDockerImageListComponent.ts +5 -0
  37. package/Utils/Dashboard/Components/DashboardDockerNetworkListComponent.ts +5 -0
  38. package/Utils/Dashboard/Components/DashboardDockerVolumeListComponent.ts +5 -0
  39. package/Utils/Dashboard/Components/DashboardHostListComponent.ts +3 -0
  40. package/Utils/Dashboard/Components/DashboardIncidentListComponent.ts +5 -0
  41. package/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.ts +3 -0
  42. package/Utils/Dashboard/Components/DashboardListSharedArgs.ts +31 -0
  43. package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +5 -0
  44. package/Utils/Dashboard/Components/DashboardTraceListComponent.ts +5 -0
  45. package/build/dist/Models/DatabaseModels/Service.js +1 -1
  46. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  47. package/build/dist/Server/API/TelemetryExceptionAPI.js +37 -1
  48. package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
  49. package/build/dist/Server/Services/OpenTelemetryIngestService.js +5 -5
  50. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  51. package/build/dist/Server/Services/TelemetryExceptionService.js +175 -0
  52. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  53. package/build/dist/Server/Types/Database/QueryHelper.js +35 -0
  54. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  55. package/build/dist/Server/Types/Database/QueryUtil.js +58 -2
  56. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  57. package/build/dist/Types/Telemetry/TelemetryRetentionConfig.js +3 -3
  58. package/build/dist/Types/Telemetry/TelemetryRetentionConfig.js.map +1 -1
  59. package/build/dist/Types/Time/RangeStartAndEndDateTime.js +4 -0
  60. package/build/dist/Types/Time/RangeStartAndEndDateTime.js.map +1 -1
  61. package/build/dist/Types/Time/TimeRange.js +1 -0
  62. package/build/dist/Types/Time/TimeRange.js.map +1 -1
  63. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +1 -0
  64. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  65. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +48 -13
  66. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  67. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js +9 -9
  68. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +1 -1
  69. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js +107 -41
  70. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js.map +1 -1
  71. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +1 -0
  72. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  73. package/build/dist/Utils/Dashboard/Components/DashboardAlertListComponent.js +2 -0
  74. package/build/dist/Utils/Dashboard/Components/DashboardAlertListComponent.js.map +1 -1
  75. package/build/dist/Utils/Dashboard/Components/DashboardDockerContainerListComponent.js +2 -0
  76. package/build/dist/Utils/Dashboard/Components/DashboardDockerContainerListComponent.js.map +1 -1
  77. package/build/dist/Utils/Dashboard/Components/DashboardDockerHostListComponent.js +2 -0
  78. package/build/dist/Utils/Dashboard/Components/DashboardDockerHostListComponent.js.map +1 -1
  79. package/build/dist/Utils/Dashboard/Components/DashboardDockerImageListComponent.js +2 -0
  80. package/build/dist/Utils/Dashboard/Components/DashboardDockerImageListComponent.js.map +1 -1
  81. package/build/dist/Utils/Dashboard/Components/DashboardDockerNetworkListComponent.js +2 -0
  82. package/build/dist/Utils/Dashboard/Components/DashboardDockerNetworkListComponent.js.map +1 -1
  83. package/build/dist/Utils/Dashboard/Components/DashboardDockerVolumeListComponent.js +2 -0
  84. package/build/dist/Utils/Dashboard/Components/DashboardDockerVolumeListComponent.js.map +1 -1
  85. package/build/dist/Utils/Dashboard/Components/DashboardHostListComponent.js +2 -0
  86. package/build/dist/Utils/Dashboard/Components/DashboardHostListComponent.js.map +1 -1
  87. package/build/dist/Utils/Dashboard/Components/DashboardIncidentListComponent.js +2 -0
  88. package/build/dist/Utils/Dashboard/Components/DashboardIncidentListComponent.js.map +1 -1
  89. package/build/dist/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.js +2 -0
  90. package/build/dist/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.js.map +1 -1
  91. package/build/dist/Utils/Dashboard/Components/DashboardListSharedArgs.js +21 -0
  92. package/build/dist/Utils/Dashboard/Components/DashboardListSharedArgs.js.map +1 -0
  93. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +2 -0
  94. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
  95. package/build/dist/Utils/Dashboard/Components/DashboardTraceListComponent.js +2 -0
  96. package/build/dist/Utils/Dashboard/Components/DashboardTraceListComponent.js.map +1 -1
  97. package/package.json +1 -1
@@ -8,6 +8,7 @@ export default interface DashboardMonitorListComponent extends BaseComponent {
8
8
  arguments: {
9
9
  title?: string | undefined;
10
10
  maxRows?: number | undefined;
11
+ viewMode?: "list" | "honeycomb" | undefined;
11
12
  statusFilter?: string | undefined;
12
13
  monitorStatusIds?: Array<string> | undefined;
13
14
  monitorTypes?: Array<string> | undefined;
@@ -9,5 +9,6 @@ export default interface DashboardTraceListComponent extends BaseComponent {
9
9
  title?: string | undefined;
10
10
  statusFilter?: string | undefined;
11
11
  maxRows?: number | undefined;
12
+ viewMode?: "list" | "honeycomb" | undefined;
12
13
  };
13
14
  }
@@ -8,10 +8,10 @@ import { SpanStatus } from "../../Models/AnalyticsModels/Span";
8
8
  * When a row is ingested, retention falls through, narrowest-first:
9
9
  * 1. service[pillar].byX[bucketKey]
10
10
  * 2. service[pillar].default
11
- * 3. service.retainTelemetryDataForDays (umbrella)
11
+ * 3. service.retainTelemetryDataForDays (service default)
12
12
  * 4. project[pillar].byX[bucketKey]
13
13
  * 5. project[pillar].default
14
- * 6. project.defaultTelemetryRetentionInDays (umbrella)
14
+ * 6. project.defaultTelemetryRetentionInDays (project default)
15
15
  * 7. HARDCODED_DEFAULT_TELEMETRY_RETENTION_IN_DAYS
16
16
  */
17
17
  export default interface TelemetryRetentionConfig {
@@ -74,26 +74,26 @@ export function resolveTelemetryRetentionInDays(input: {
74
74
  pillar: TelemetryPillar;
75
75
  bucketKey?: LogSeverity | SpanStatus | null;
76
76
  serviceConfig?: TelemetryRetentionConfig | null;
77
- serviceUmbrellaInDays?: number | null;
77
+ serviceRetentionInDays?: number | null;
78
78
  projectConfig?: TelemetryRetentionConfig | null;
79
- projectUmbrellaInDays?: number | null;
79
+ projectRetentionInDays?: number | null;
80
80
  }): number {
81
81
  const {
82
82
  pillar,
83
83
  bucketKey,
84
84
  serviceConfig,
85
- serviceUmbrellaInDays,
85
+ serviceRetentionInDays,
86
86
  projectConfig,
87
- projectUmbrellaInDays,
87
+ projectRetentionInDays,
88
88
  } = input;
89
89
 
90
90
  const candidates: Array<number | null> = [
91
91
  getBucketValue(serviceConfig, pillar, bucketKey),
92
92
  getPillarDefault(serviceConfig, pillar),
93
- pickPositive(serviceUmbrellaInDays),
93
+ pickPositive(serviceRetentionInDays),
94
94
  getBucketValue(projectConfig, pillar, bucketKey),
95
95
  getPillarDefault(projectConfig, pillar),
96
- pickPositive(projectUmbrellaInDays),
96
+ pickPositive(projectRetentionInDays),
97
97
  ];
98
98
 
99
99
  for (const value of candidates) {
@@ -13,6 +13,14 @@ export class RangeStartAndEndDateTimeUtil {
13
13
  ): InBetween<Date> {
14
14
  const currentDate: Date = OneUptimeDate.getCurrentDate();
15
15
 
16
+ // 5 mins.
17
+ if (dashboardStartAndEndDate.range === TimeRange.PAST_FIVE_MINS) {
18
+ return new InBetween<Date>(
19
+ OneUptimeDate.addRemoveMinutes(currentDate, -5),
20
+ currentDate,
21
+ );
22
+ }
23
+
16
24
  // 30 mins.
17
25
  if (dashboardStartAndEndDate.range === TimeRange.PAST_THIRTY_MINS) {
18
26
  return new InBetween<Date>(
@@ -1,4 +1,5 @@
1
1
  enum Range {
2
+ PAST_FIVE_MINS = "Past 5 Mins",
2
3
  PAST_THIRTY_MINS = "Past 30 Mins",
3
4
  PAST_ONE_HOUR = "Past 1 Hour",
4
5
  PAST_TWO_HOURS = "Past 2 Hours",
@@ -22,6 +22,7 @@ export interface LogTimeRangePickerProps {
22
22
 
23
23
  // Preset options to show in the dropdown (ordered for log investigation use)
24
24
  const PRESET_OPTIONS: Array<{ range: TimeRange; label: string }> = [
25
+ { range: TimeRange.PAST_FIVE_MINS, label: "Past 5 Minutes" },
25
26
  { range: TimeRange.PAST_THIRTY_MINS, label: "Past 30 Minutes" },
26
27
  { range: TimeRange.PAST_ONE_HOUR, label: "Past 1 Hour" },
27
28
  { range: TimeRange.PAST_TWO_HOURS, label: "Past 2 Hours" },
@@ -1807,17 +1807,21 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
1807
1807
  (): BulkActionButtonSchema<TBaseModel> => {
1808
1808
  return {
1809
1809
  title: "Delete",
1810
- buttonStyleType: ButtonStyleType.NORMAL,
1810
+ buttonStyleType: ButtonStyleType.DANGER,
1811
1811
  icon: IconProp.Trash,
1812
1812
  confirmMessage: (items: Array<TBaseModel>) => {
1813
- return `Are you sure you want to delete ${items.length} ${
1814
- props.pluralName || model.pluralName || "items"
1815
- }?`;
1813
+ const itemLabel: string =
1814
+ items.length === 1
1815
+ ? props.singularName || model.singularName || "item"
1816
+ : props.pluralName || model.pluralName || "items";
1817
+ return `Are you sure you want to delete ${items.length} ${itemLabel}? This action cannot be undone.`;
1816
1818
  },
1817
1819
  confirmTitle: (items: Array<TBaseModel>) => {
1818
- return `Delete ${items.length} ${
1819
- props.pluralName || model.pluralName || "items"
1820
- }`;
1820
+ const itemLabel: string =
1821
+ items.length === 1
1822
+ ? props.singularName || model.singularName || "item"
1823
+ : props.pluralName || model.pluralName || "items";
1824
+ return `Delete ${items.length} ${itemLabel}`;
1821
1825
  },
1822
1826
  confirmButtonStyleType: ButtonStyleType.DANGER,
1823
1827
  onClick: async ({
@@ -1908,25 +1912,68 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
1908
1912
  onFilterRefreshClick={async () => {
1909
1913
  await getFilterDropdownItems();
1910
1914
  }}
1911
- bulkActions={{
1912
- buttons: props.bulkActions?.buttons.map(
1915
+ bulkActions={(() => {
1916
+ const permissions: Array<Permission> =
1917
+ PermissionUtil.getAllPermissions();
1918
+ const userCanDelete: boolean =
1919
+ model.hasDeletePermissions(permissions);
1920
+
1921
+ const sourceButtons: Array<
1922
+ BulkActionButtonSchema<TBaseModel> | ModalTableBulkDefaultActions
1923
+ > = [...(props.bulkActions?.buttons ?? [])];
1924
+
1925
+ /*
1926
+ * Auto-include the default Delete bulk action whenever the user has
1927
+ * model-level delete permission, so every table that exposes row
1928
+ * selection also exposes a way to delete the selected rows. This is
1929
+ * intentionally decoupled from `isDeleteable` — that flag governs the
1930
+ * per-row Delete button in the Actions column, not bulk operations.
1931
+ * The confirmation modal is wired up via the schema's confirmMessage
1932
+ * / confirmTitle below. Skip if the table author already added it.
1933
+ */
1934
+ const alreadyHasDeleteAction: boolean = sourceButtons.some(
1913
1935
  (
1914
1936
  action:
1915
1937
  | BulkActionButtonSchema<TBaseModel>
1916
1938
  | ModalTableBulkDefaultActions,
1917
1939
  ) => {
1918
- const permissions: Array<Permission> =
1919
- PermissionUtil.getAllPermissions();
1940
+ if (action === ModalTableBulkDefaultActions.Delete) {
1941
+ return true;
1942
+ }
1920
1943
  if (
1921
- action === ModalTableBulkDefaultActions.Delete &&
1922
- model.hasDeletePermissions(permissions)
1944
+ typeof action === "object" &&
1945
+ action !== null &&
1946
+ "title" in action &&
1947
+ (action as BulkActionButtonSchema<TBaseModel>).title ===
1948
+ "Delete"
1923
1949
  ) {
1924
- return getDeleteBulkAction();
1950
+ return true;
1925
1951
  }
1926
- return action;
1952
+ return false;
1927
1953
  },
1928
- ) as Array<BulkActionButtonSchema<TBaseModel>>,
1929
- }}
1954
+ );
1955
+ if (userCanDelete && !alreadyHasDeleteAction) {
1956
+ sourceButtons.push(ModalTableBulkDefaultActions.Delete);
1957
+ }
1958
+
1959
+ return {
1960
+ buttons: sourceButtons.map(
1961
+ (
1962
+ action:
1963
+ | BulkActionButtonSchema<TBaseModel>
1964
+ | ModalTableBulkDefaultActions,
1965
+ ) => {
1966
+ if (
1967
+ action === ModalTableBulkDefaultActions.Delete &&
1968
+ userCanDelete
1969
+ ) {
1970
+ return getDeleteBulkAction();
1971
+ }
1972
+ return action;
1973
+ },
1974
+ ) as Array<BulkActionButtonSchema<TBaseModel>>,
1975
+ };
1976
+ })()}
1930
1977
  onBulkActionEnd={async () => {
1931
1978
  setBulkSelectedItems([]);
1932
1979
  await fetchItems();
@@ -178,7 +178,7 @@ const TelemetryRetentionConfigForm: FunctionComponent<ComponentProps> = (
178
178
  <FieldLabelElement title={label} description={description} />
179
179
  <Input
180
180
  type={InputType.NUMBER}
181
- placeholder="Inherit umbrella default"
181
+ placeholder="Use default retention"
182
182
  value={numberToString(
183
183
  (config[pillar] as { default?: number | null } | undefined)
184
184
  ?.default,
@@ -198,13 +198,13 @@ const TelemetryRetentionConfigForm: FunctionComponent<ComponentProps> = (
198
198
  Logs
199
199
  </h3>
200
200
  <p className="text-xs text-gray-500 dark:text-slate-400 mb-3">
201
- Retention in days for log records. Per-severity values override the
202
- logs default; leave blank to inherit.
201
+ Retention in days for log records. Per-severity values take precedence
202
+ over the logs default; leave any field blank to fall back.
203
203
  </p>
204
204
  {renderPillarDefault(
205
205
  "logs",
206
206
  "Logs default (days)",
207
- "Applies to log records whose severity has no specific override.",
207
+ "Applies to every log record unless its severity has a specific override below.",
208
208
  )}
209
209
  <div className="border-t border-gray-100 dark:border-slate-800 pt-3 mt-2">
210
210
  <p className="text-xs font-medium text-gray-700 dark:text-slate-300 mb-2">
@@ -217,7 +217,7 @@ const TelemetryRetentionConfigForm: FunctionComponent<ComponentProps> = (
217
217
  <FieldLabelElement title={severity} />
218
218
  <Input
219
219
  type={InputType.NUMBER}
220
- placeholder="Inherit logs default"
220
+ placeholder="Use logs default"
221
221
  value={numberToString(config.logs?.bySeverity?.[severity])}
222
222
  onChange={(raw: string) => {
223
223
  setLogSeverityValue(severity, parsePositiveOrNull(raw));
@@ -235,13 +235,13 @@ const TelemetryRetentionConfigForm: FunctionComponent<ComponentProps> = (
235
235
  Traces
236
236
  </h3>
237
237
  <p className="text-xs text-gray-500 dark:text-slate-400 mb-3">
238
- Retention in days for spans (and the exceptions captured on them).
239
- Per-status values override the traces default.
238
+ Retention in days for spans and the exceptions captured on them.
239
+ Per-status values take precedence over the traces default.
240
240
  </p>
241
241
  {renderPillarDefault(
242
242
  "traces",
243
243
  "Traces default (days)",
244
- "Applies to spans whose status has no specific override.",
244
+ "Applies to every span unless its status has a specific override below.",
245
245
  )}
246
246
  <div className="border-t border-gray-100 dark:border-slate-800 pt-3 mt-2">
247
247
  <p className="text-xs font-medium text-gray-700 dark:text-slate-300 mb-2">
@@ -255,7 +255,7 @@ const TelemetryRetentionConfigForm: FunctionComponent<ComponentProps> = (
255
255
  <FieldLabelElement title={entry.label} />
256
256
  <Input
257
257
  type={InputType.NUMBER}
258
- placeholder="Inherit traces default"
258
+ placeholder="Use traces default"
259
259
  value={numberToString(
260
260
  config.traces?.byStatus?.[entry.status],
261
261
  )}
@@ -284,7 +284,7 @@ const TelemetryRetentionConfigForm: FunctionComponent<ComponentProps> = (
284
284
  {renderPillarDefault(
285
285
  "metrics",
286
286
  "Metrics default (days)",
287
- "Applies to all metric points. Leave blank to inherit the umbrella default.",
287
+ "Applies to all metric points. Leave blank to use the default retention.",
288
288
  )}
289
289
  </div>
290
290
 
@@ -298,7 +298,7 @@ const TelemetryRetentionConfigForm: FunctionComponent<ComponentProps> = (
298
298
  {renderPillarDefault(
299
299
  "profiles",
300
300
  "Profiles default (days)",
301
- "Applies to all profile samples. Leave blank to inherit the umbrella default.",
301
+ "Applies to all profile samples. Leave blank to use the default retention.",
302
302
  )}
303
303
  </div>
304
304
  </div>
@@ -13,91 +13,209 @@ const SPAN_STATUS_LABEL: Record<SpanStatus, string> = {
13
13
  [SpanStatus.Error]: "Error",
14
14
  };
15
15
 
16
+ const LOG_SEVERITY_ORDER: Array<LogSeverity> = [
17
+ LogSeverity.Fatal,
18
+ LogSeverity.Error,
19
+ LogSeverity.Warning,
20
+ LogSeverity.Information,
21
+ LogSeverity.Debug,
22
+ LogSeverity.Trace,
23
+ LogSeverity.Unspecified,
24
+ ];
25
+
26
+ const SPAN_STATUS_ORDER: Array<SpanStatus> = [
27
+ SpanStatus.Error,
28
+ SpanStatus.Ok,
29
+ SpanStatus.Unset,
30
+ ];
31
+
32
+ const formatDays: (days: number) => string = (days: number): string => {
33
+ return days === 1 ? "1 day" : `${days} days`;
34
+ };
35
+
36
+ const pickPositive: (value: number | null | undefined) => number | null = (
37
+ value: number | null | undefined,
38
+ ): number | null => {
39
+ return typeof value === "number" && value > 0 ? value : null;
40
+ };
41
+
42
+ interface PillarOverride {
43
+ label: string;
44
+ days: number;
45
+ }
46
+
47
+ interface PillarRender {
48
+ title: string;
49
+ hint: string;
50
+ defaultDays: number | null;
51
+ overrides: Array<PillarOverride>;
52
+ }
53
+
54
+ const PillarCard: FunctionComponent<{ pillar: PillarRender }> = (props: {
55
+ pillar: PillarRender;
56
+ }): ReactElement => {
57
+ const { title, hint, defaultDays, overrides } = props.pillar;
58
+ const hasDefault: boolean = defaultDays !== null;
59
+ const hasOverrides: boolean = overrides.length > 0;
60
+ const isCustomized: boolean = hasDefault || hasOverrides;
61
+
62
+ return (
63
+ <div className="rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-900 p-4">
64
+ <div className="flex items-start justify-between gap-3">
65
+ <div className="min-w-0">
66
+ <div className="flex items-center gap-2">
67
+ <h4 className="text-sm font-semibold text-gray-900 dark:text-slate-100">
68
+ {title}
69
+ </h4>
70
+ <span
71
+ className={
72
+ isCustomized
73
+ ? "inline-flex items-center rounded-full bg-indigo-50 dark:bg-indigo-950/40 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-indigo-700 dark:text-indigo-300 border border-indigo-100 dark:border-indigo-900"
74
+ : "inline-flex items-center rounded-full bg-gray-100 dark:bg-slate-800 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-gray-500 dark:text-slate-400 border border-gray-200 dark:border-slate-700"
75
+ }
76
+ >
77
+ {isCustomized ? "Custom" : "Default"}
78
+ </span>
79
+ </div>
80
+ <p className="mt-1 text-xs text-gray-500 dark:text-slate-400">
81
+ {hint}
82
+ </p>
83
+ </div>
84
+ <div className="text-right whitespace-nowrap shrink-0">
85
+ {hasDefault ? (
86
+ <>
87
+ <div className="text-lg font-semibold text-gray-900 dark:text-slate-100 leading-tight">
88
+ {defaultDays}{" "}
89
+ <span className="text-sm font-normal text-gray-500 dark:text-slate-400">
90
+ {(defaultDays as number) === 1 ? "day" : "days"}
91
+ </span>
92
+ </div>
93
+ <div className="text-[10px] uppercase tracking-wide text-gray-400 dark:text-slate-500">
94
+ Default
95
+ </div>
96
+ </>
97
+ ) : (
98
+ <div className="text-xs italic text-gray-400 dark:text-slate-500">
99
+ Uses default
100
+ <br />
101
+ retention
102
+ </div>
103
+ )}
104
+ </div>
105
+ </div>
106
+ {hasOverrides ? (
107
+ <div className="mt-3 border-t border-gray-100 dark:border-slate-800 pt-3">
108
+ <p className="text-[10px] font-semibold uppercase tracking-wide text-gray-400 dark:text-slate-500 mb-2">
109
+ Specific overrides
110
+ </p>
111
+ <div className="flex flex-wrap gap-1.5">
112
+ {overrides.map((o: PillarOverride) => {
113
+ return (
114
+ <span
115
+ key={o.label}
116
+ className="inline-flex items-center gap-1.5 rounded-full border border-indigo-100 dark:border-indigo-900 bg-indigo-50 dark:bg-indigo-950/40 px-2.5 py-0.5 text-xs font-medium text-indigo-700 dark:text-indigo-300"
117
+ >
118
+ <span>{o.label}</span>
119
+ <span
120
+ aria-hidden="true"
121
+ className="text-indigo-300 dark:text-indigo-700"
122
+ >
123
+ ·
124
+ </span>
125
+ <span className="font-normal">{formatDays(o.days)}</span>
126
+ </span>
127
+ );
128
+ })}
129
+ </div>
130
+ </div>
131
+ ) : null}
132
+ </div>
133
+ );
134
+ };
135
+
136
+ const sortOverridesByDaysDesc: (
137
+ list: Array<PillarOverride>,
138
+ ) => Array<PillarOverride> = (
139
+ list: Array<PillarOverride>,
140
+ ): Array<PillarOverride> => {
141
+ return [...list].sort((a: PillarOverride, b: PillarOverride) => {
142
+ return b.days - a.days;
143
+ });
144
+ };
145
+
16
146
  const TelemetryRetentionConfigSummary: FunctionComponent<ComponentProps> = (
17
147
  props: ComponentProps,
18
148
  ): ReactElement => {
19
149
  const config: TelemetryRetentionConfig | null | undefined = props.config;
20
150
 
21
- if (
22
- !config ||
23
- (!config.logs && !config.traces && !config.metrics && !config.profiles)
24
- ) {
25
- return (
26
- <span className="text-sm text-gray-500 dark:text-slate-400">
27
- Using umbrella default for all pillars.
28
- </span>
29
- );
30
- }
31
-
32
- const renderPillar: (
33
- title: string,
34
- defaultDays: number | null | undefined,
35
- overrides: Array<{ label: string; days: number | null | undefined }>,
36
- ) => ReactElement | null = (
37
- title: string,
38
- defaultDays: number | null | undefined,
39
- overrides: Array<{ label: string; days: number | null | undefined }>,
40
- ): ReactElement | null => {
41
- const hasDefault: boolean =
42
- typeof defaultDays === "number" && defaultDays > 0;
43
- const visibleOverrides: Array<{ label: string; days: number }> = overrides
44
- .filter((o: { label: string; days: number | null | undefined }) => {
45
- return typeof o.days === "number" && o.days > 0;
46
- })
47
- .map((o: { label: string; days: number | null | undefined }) => {
48
- return { label: o.label, days: o.days as number };
49
- });
50
-
51
- if (!hasDefault && visibleOverrides.length === 0) {
52
- return null;
53
- }
54
-
55
- return (
56
- <div key={title} className="mb-2">
57
- <span className="text-sm font-medium text-gray-900 dark:text-slate-100">
58
- {title}:
59
- </span>{" "}
60
- <span className="text-sm text-gray-700 dark:text-slate-300">
61
- {hasDefault ? `${defaultDays} day(s)` : "inherits umbrella default"}
62
- </span>
63
- {visibleOverrides.length > 0 ? (
64
- <ul className="ml-4 text-xs text-gray-600 dark:text-slate-400 list-disc">
65
- {visibleOverrides.map((o: { label: string; days: number }) => {
66
- return (
67
- <li key={o.label}>
68
- {o.label}: {o.days} day(s)
69
- </li>
70
- );
71
- })}
72
- </ul>
73
- ) : null}
74
- </div>
75
- );
76
- };
151
+ const logsOverrides: Array<PillarOverride> = LOG_SEVERITY_ORDER.flatMap(
152
+ (severity: LogSeverity): Array<PillarOverride> => {
153
+ const days: number | null = pickPositive(
154
+ config?.logs?.bySeverity?.[severity],
155
+ );
156
+ return days === null ? [] : [{ label: severity, days: days }];
157
+ },
158
+ );
159
+
160
+ const tracesOverrides: Array<PillarOverride> = SPAN_STATUS_ORDER.flatMap(
161
+ (status: SpanStatus): Array<PillarOverride> => {
162
+ const days: number | null = pickPositive(
163
+ config?.traces?.byStatus?.[status],
164
+ );
165
+ return days === null
166
+ ? []
167
+ : [{ label: SPAN_STATUS_LABEL[status], days: days }];
168
+ },
169
+ );
170
+
171
+ const pillars: Array<PillarRender> = [
172
+ {
173
+ title: "Logs",
174
+ hint: "Log records, with optional per-severity overrides.",
175
+ defaultDays: pickPositive(config?.logs?.default),
176
+ overrides: sortOverridesByDaysDesc(logsOverrides),
177
+ },
178
+ {
179
+ title: "Traces",
180
+ hint: "Spans and exceptions, with optional per-status overrides.",
181
+ defaultDays: pickPositive(config?.traces?.default),
182
+ overrides: sortOverridesByDaysDesc(tracesOverrides),
183
+ },
184
+ {
185
+ title: "Metrics",
186
+ hint: "Metric data points and aggregates.",
187
+ defaultDays: pickPositive(config?.metrics?.default),
188
+ overrides: [],
189
+ },
190
+ {
191
+ title: "Profiles",
192
+ hint: "Profile samples and stack traces.",
193
+ defaultDays: pickPositive(config?.profiles?.default),
194
+ overrides: [],
195
+ },
196
+ ];
197
+
198
+ const anyConfigured: boolean = pillars.some((p: PillarRender) => {
199
+ return p.defaultDays !== null || p.overrides.length > 0;
200
+ });
77
201
 
78
202
  return (
79
- <div>
80
- {renderPillar(
81
- "Logs",
82
- config.logs?.default,
83
- Object.entries(config.logs?.bySeverity || {}).map(
84
- ([severity, days]: [string, number | null | undefined]) => {
85
- return { label: severity as LogSeverity, days: days };
86
- },
87
- ),
88
- )}
89
- {renderPillar(
90
- "Traces",
91
- config.traces?.default,
92
- Object.entries(config.traces?.byStatus || {}).map(
93
- ([statusKey, days]: [string, number | null | undefined]) => {
94
- const status: SpanStatus = Number(statusKey) as SpanStatus;
95
- return { label: SPAN_STATUS_LABEL[status] || statusKey, days };
96
- },
97
- ),
98
- )}
99
- {renderPillar("Metrics", config.metrics?.default, [])}
100
- {renderPillar("Profiles", config.profiles?.default, [])}
203
+ <div className="space-y-3">
204
+ {!anyConfigured ? (
205
+ <div className="rounded-md border border-dashed border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900/40 px-4 py-3">
206
+ <p className="text-sm font-medium text-gray-700 dark:text-slate-200">
207
+ No overrides set.
208
+ </p>
209
+ <p className="text-xs text-gray-500 dark:text-slate-400 mt-0.5">
210
+ Every telemetry type uses the default retention shown above.
211
+ </p>
212
+ </div>
213
+ ) : null}
214
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
215
+ {pillars.map((pillar: PillarRender) => {
216
+ return <PillarCard key={pillar.title} pillar={pillar} />;
217
+ })}
218
+ </div>
101
219
  </div>
102
220
  );
103
221
  };
@@ -21,6 +21,7 @@ export interface TelemetryTimeRangePickerProps {
21
21
  }
22
22
 
23
23
  const PRESET_OPTIONS: Array<{ range: TimeRange; label: string }> = [
24
+ { range: TimeRange.PAST_FIVE_MINS, label: "Past 5 Minutes" },
24
25
  { range: TimeRange.PAST_THIRTY_MINS, label: "Past 30 Minutes" },
25
26
  { range: TimeRange.PAST_ONE_HOUR, label: "Past 1 Hour" },
26
27
  { range: TimeRange.PAST_TWO_HOURS, label: "Past 2 Hours" },
@@ -9,6 +9,7 @@ import {
9
9
  EntityFilterModelType,
10
10
  } from "../../../Types/Dashboard/DashboardComponents/ComponentArgument";
11
11
  import DashboardComponentType from "../../../Types/Dashboard/DashboardComponentType";
12
+ import { getViewModeArgument } from "./DashboardListSharedArgs";
12
13
 
13
14
  const DisplaySection: ComponentArgumentSection = {
14
15
  name: "Display Options",
@@ -67,6 +68,10 @@ export default class DashboardAlertListComponentUtil extends DashboardBaseCompon
67
68
  section: DisplaySection,
68
69
  });
69
70
 
71
+ componentArguments.push(
72
+ getViewModeArgument<DashboardAlertListComponent>(DisplaySection),
73
+ );
74
+
70
75
  componentArguments.push({
71
76
  name: "Lifecycle State",
72
77
  description: "Quick filter by lifecycle state",
@@ -9,6 +9,7 @@ import {
9
9
  EntityFilterModelType,
10
10
  } from "../../../Types/Dashboard/DashboardComponents/ComponentArgument";
11
11
  import DashboardComponentType from "../../../Types/Dashboard/DashboardComponentType";
12
+ import { getViewModeArgument } from "./DashboardListSharedArgs";
12
13
 
13
14
  const DisplaySection: ComponentArgumentSection = {
14
15
  name: "Display Options",
@@ -67,6 +68,12 @@ export default class DashboardDockerContainerListComponentUtil extends Dashboard
67
68
  section: DisplaySection,
68
69
  });
69
70
 
71
+ args.push(
72
+ getViewModeArgument<DashboardDockerContainerListComponent>(
73
+ DisplaySection,
74
+ ),
75
+ );
76
+
70
77
  args.push({
71
78
  name: "Hosts",
72
79
  description: "Show only containers from the selected hosts",
@@ -8,6 +8,7 @@ import {
8
8
  ComponentInputType,
9
9
  } from "../../../Types/Dashboard/DashboardComponents/ComponentArgument";
10
10
  import DashboardComponentType from "../../../Types/Dashboard/DashboardComponentType";
11
+ import { getViewModeArgument } from "./DashboardListSharedArgs";
11
12
 
12
13
  const DisplaySection: ComponentArgumentSection = {
13
14
  name: "Display Options",
@@ -64,6 +65,10 @@ export default class DashboardDockerHostListComponentUtil extends DashboardBaseC
64
65
  section: DisplaySection,
65
66
  });
66
67
 
68
+ args.push(
69
+ getViewModeArgument<DashboardDockerHostListComponent>(DisplaySection),
70
+ );
71
+
67
72
  args.push({
68
73
  name: "Connection Status",
69
74
  description: "Quick filter by OTel collector status",
@@ -9,6 +9,7 @@ import {
9
9
  EntityFilterModelType,
10
10
  } from "../../../Types/Dashboard/DashboardComponents/ComponentArgument";
11
11
  import DashboardComponentType from "../../../Types/Dashboard/DashboardComponentType";
12
+ import { getViewModeArgument } from "./DashboardListSharedArgs";
12
13
 
13
14
  const DisplaySection: ComponentArgumentSection = {
14
15
  name: "Display Options",
@@ -66,6 +67,10 @@ export default class DashboardDockerImageListComponentUtil extends DashboardBase
66
67
  section: DisplaySection,
67
68
  });
68
69
 
70
+ args.push(
71
+ getViewModeArgument<DashboardDockerImageListComponent>(DisplaySection),
72
+ );
73
+
69
74
  args.push({
70
75
  name: "Hosts",
71
76
  description: "Show only images from the selected hosts",