@oneuptime/common 10.5.29 → 10.5.31

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.
@@ -1838,7 +1838,7 @@ export class Service extends DatabaseService<Model> {
1838
1838
  to: to,
1839
1839
  data: [
1840
1840
  {
1841
- sayMessage: "This is a call from OneUptime",
1841
+ sayMessage: "This is a call from One Uptime",
1842
1842
  },
1843
1843
  {
1844
1844
  sayMessage: "A new alert has been created",
@@ -1894,7 +1894,7 @@ export class Service extends DatabaseService<Model> {
1894
1894
  to: to,
1895
1895
  data: [
1896
1896
  {
1897
- sayMessage: "This is a call from OneUptime",
1897
+ sayMessage: "This is a call from One Uptime",
1898
1898
  },
1899
1899
  {
1900
1900
  sayMessage: "A new incident has been created",
@@ -1951,7 +1951,7 @@ export class Service extends DatabaseService<Model> {
1951
1951
  to: to,
1952
1952
  data: [
1953
1953
  {
1954
- sayMessage: "This is a call from OneUptime",
1954
+ sayMessage: "This is a call from One Uptime",
1955
1955
  },
1956
1956
  {
1957
1957
  sayMessage: "A new alert episode has been created",
@@ -933,12 +933,13 @@ function createKubernetesDashboardConfig(): DashboardViewConfig {
933
933
  * first-class percent metric we replace it with a Value widget that
934
934
  * renders the absolute usage via ValueFormatter (e.g. "8.3 GB").
935
935
  *
936
- * - CPU widgets use OTel's k8s.*.cpu.utilization, which the collector
937
- * emits as a [0, 1] ratio with unit "1". DashboardValueComponent /
938
- * DashboardGaugeComponent now scale that to a percent at render time
939
- * when the metric name carries the `.utilization` suffix, so "0.05"
940
- * reads as "5.00%" and gauge thresholds in the natural 0-100 scale work
941
- * as expected.
936
+ * - CPU widgets show cores, not a percent. OTel's k8s.*.cpu.utilization
937
+ * is a misnamed cores gauge (cores in use, NOT a [0, 1] ratio), and
938
+ * this templated dashboard's renderer can't divide by per-node
939
+ * allocatable CPU to form a true percentage. So we use the `.usage`
940
+ * metrics and label them in cores ("2.3 cores"). The Kubernetes
941
+ * cluster overview page — which fetches `k8s.node.allocatable_cpu` —
942
+ * is where CPU is shown as a real "% of capacity".
942
943
  */
943
944
  const components: Array<DashboardBaseComponent> = [
944
945
  // Row 0: Title
@@ -953,16 +954,16 @@ function createKubernetesDashboardConfig(): DashboardViewConfig {
953
954
 
954
955
  /*
955
956
  * Row 1: Key cluster metrics — averages render with proper units via
956
- * ValueFormatter (CPU utilization"%", memory.usage → "MB"/"GB").
957
+ * ValueFormatter (CPU usagecores, memory.usage → "MB"/"GB").
957
958
  * All four are "higher = worse" (closer to capacity = bad).
958
959
  */
959
960
  createValueComponent({
960
- title: "Pod CPU (avg)",
961
+ title: "Pod CPU (cores, avg)",
961
962
  top: 1,
962
963
  left: 0,
963
964
  width: 3,
964
965
  metricConfig: {
965
- metricName: "k8s.pod.cpu.utilization",
966
+ metricName: "k8s.pod.cpu.usage",
966
967
  aggregationType: MetricsAggregationType.Avg,
967
968
  },
968
969
  trendDirection: DashboardValueTrendDirection.HigherIsWorse,
@@ -979,12 +980,12 @@ function createKubernetesDashboardConfig(): DashboardViewConfig {
979
980
  trendDirection: DashboardValueTrendDirection.HigherIsWorse,
980
981
  }),
981
982
  createValueComponent({
982
- title: "Node CPU (avg)",
983
+ title: "Node CPU (cores, avg)",
983
984
  top: 1,
984
985
  left: 6,
985
986
  width: 3,
986
987
  metricConfig: {
987
- metricName: "k8s.node.cpu.utilization",
988
+ metricName: "k8s.node.cpu.usage",
988
989
  aggregationType: MetricsAggregationType.Avg,
989
990
  },
990
991
  trendDirection: DashboardValueTrendDirection.HigherIsWorse,
@@ -1003,16 +1004,16 @@ function createKubernetesDashboardConfig(): DashboardViewConfig {
1003
1004
 
1004
1005
  // Row 2-4: Resource usage charts
1005
1006
  createChartComponent({
1006
- title: "CPU Usage Over Time",
1007
+ title: "Pod CPU Cores Over Time",
1007
1008
  chartType: DashboardChartType.Line,
1008
1009
  top: 2,
1009
1010
  left: 0,
1010
1011
  width: 6,
1011
1012
  height: 3,
1012
1013
  metricConfig: {
1013
- metricName: "k8s.pod.cpu.utilization",
1014
+ metricName: "k8s.pod.cpu.usage",
1014
1015
  aggregationType: MetricsAggregationType.Avg,
1015
- legend: "CPU Utilization",
1016
+ legend: "CPU Cores",
1016
1017
  },
1017
1018
  }),
1018
1019
  createChartComponent({
@@ -1072,24 +1073,24 @@ function createKubernetesDashboardConfig(): DashboardViewConfig {
1072
1073
  }),
1073
1074
 
1074
1075
  /*
1075
- * Row 11-13: CPU gauge (auto-scaled from [0,1] to percent), and the
1076
- * network throughput chart. The old "Memory Utilization" gauge over
1077
- * raw bytes is gone see top-of-function comment.
1076
+ * Row 11-13: cluster CPU cores tile and the network throughput
1077
+ * chart. This was a 0-100 CPU gauge, but the cores-valued
1078
+ * `k8s.node.cpu.utilization` pinned it to nonsense (e.g. 711%) and
1079
+ * the templated renderer can't divide by allocatable CPU to make a
1080
+ * real percentage — so we show total cores in use instead. The old
1081
+ * "Memory Utilization" gauge over raw bytes is gone — see
1082
+ * top-of-function comment.
1078
1083
  */
1079
- createGaugeComponent({
1080
- title: "Cluster CPU Utilization",
1084
+ createValueComponent({
1085
+ title: "Cluster CPU (cores in use)",
1081
1086
  top: 11,
1082
1087
  left: 0,
1083
1088
  width: 4,
1084
- height: 3,
1085
- minValue: 0,
1086
- maxValue: 100,
1087
- warningThreshold: 70,
1088
- criticalThreshold: 90,
1089
1089
  metricConfig: {
1090
- metricName: "k8s.node.cpu.utilization",
1091
- aggregationType: MetricsAggregationType.Avg,
1090
+ metricName: "k8s.node.cpu.usage",
1091
+ aggregationType: MetricsAggregationType.Sum,
1092
1092
  },
1093
+ trendDirection: DashboardValueTrendDirection.HigherIsWorse,
1093
1094
  }),
1094
1095
  createChartComponent({
1095
1096
  title: "Network I/O",
@@ -18,6 +18,17 @@ export default interface MetricQueryConfigData {
18
18
  getSeries?: ((data: AggregatedModel) => ChartSeries) | undefined;
19
19
  chartType?: MetricChartType | undefined;
20
20
  yAxisValueFormatter?: ((value: number) => string) | undefined;
21
+ /*
22
+ * Optional post-aggregation transform of each datapoint's plotted
23
+ * value. Runs before `transformAsRate`. Receives the raw aggregated
24
+ * value plus the full datapoint (so the transform can read grouped
25
+ * attributes like `resource.k8s.node.name`). Used, e.g., to turn a
26
+ * Kubernetes CPU *cores* value into "% of its node's allocatable CPU"
27
+ * by dividing each point by the node's capacity. Unset = no change.
28
+ */
29
+ transformValue?:
30
+ | ((value: number, dataPoint: AggregatedModel) => number)
31
+ | undefined;
21
32
  warningThreshold?: number | undefined;
22
33
  criticalThreshold?: number | undefined;
23
34
  /*
@@ -12,6 +12,7 @@ import Sort from "../../../Types/BaseDatabase/Sort";
12
12
  import Select from "../../../Types/BaseDatabase/Select";
13
13
  import { Logger } from "../../Utils/Logger";
14
14
  import Navigation from "../../Utils/Navigation";
15
+ import TableFilterUrlState from "../../Utils/TableFilterUrlState";
15
16
  import PermissionUtil from "../../Utils/Permission";
16
17
  import ProjectUtil from "../../Utils/Project";
17
18
  import User from "../../Utils/User";
@@ -1862,6 +1863,43 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
1862
1863
  setQuery({ ...newQuery });
1863
1864
  };
1864
1865
 
1866
+ /*
1867
+ * URL persistence for classic (column) filters, keyed by the table's
1868
+ * `saveFilterProps.tableId`. Mirrors the facet persistence in
1869
+ * `useResourceOwners` so filters survive navigating to a detail page and
1870
+ * back, and so a filtered view is shareable. `hasRestoredUrlFilters` is
1871
+ * state (not a ref) so the persist effect only runs after the restore has
1872
+ * been applied — never clobbering the snapshot with the empty default.
1873
+ */
1874
+ const [hasRestoredUrlFilters, setHasRestoredUrlFilters] =
1875
+ useState<boolean>(false);
1876
+
1877
+ useEffect(() => {
1878
+ const restored: JSONObject | null = TableFilterUrlState.read(
1879
+ props.saveFilterProps?.tableId,
1880
+ "filter",
1881
+ );
1882
+ if (restored) {
1883
+ /*
1884
+ * Re-run through onFilterChanged so the derived query is rebuilt and the
1885
+ * first fetch uses the restored filters.
1886
+ */
1887
+ onFilterChanged(restored as unknown as FilterData<TBaseModel>);
1888
+ }
1889
+ setHasRestoredUrlFilters(true);
1890
+ }, []);
1891
+
1892
+ useEffect(() => {
1893
+ if (!hasRestoredUrlFilters) {
1894
+ return;
1895
+ }
1896
+ TableFilterUrlState.write(
1897
+ props.saveFilterProps?.tableId,
1898
+ "filter",
1899
+ filterData as unknown as JSONObject,
1900
+ );
1901
+ }, [hasRestoredUrlFilters, filterData]);
1902
+
1865
1903
  type GetDeleteBulkActionFunction = () => BulkActionButtonSchema<TBaseModel>;
1866
1904
 
1867
1905
  const getDeleteBulkAction: GetDeleteBulkActionFunction =
@@ -66,6 +66,45 @@ abstract class Navigation {
66
66
  return null;
67
67
  }
68
68
 
69
+ /**
70
+ * Update (or remove) query-string params on the current URL *in place*,
71
+ * without pushing a new browser-history entry and without triggering a
72
+ * react-router navigation/re-render. Pass `null` (or "") as a value to
73
+ * delete that param.
74
+ *
75
+ * This is used to keep table filter/facet state in the URL so it survives
76
+ * navigating to a detail page and back, and so a filtered view is
77
+ * shareable/bookmarkable. We use `replaceState` (not push) so changing a
78
+ * filter doesn't flood the back-button history.
79
+ */
80
+ public static setQueryString(params: Dictionary<string | null>): void {
81
+ const urlSearchParams: URLSearchParams = new URLSearchParams(
82
+ window.location.search,
83
+ );
84
+
85
+ for (const paramName in params) {
86
+ const value: string | null =
87
+ params[paramName] === undefined
88
+ ? null
89
+ : (params[paramName] as string | null);
90
+
91
+ if (value === null || value === "") {
92
+ urlSearchParams.delete(paramName);
93
+ } else {
94
+ urlSearchParams.set(paramName, value);
95
+ }
96
+ }
97
+
98
+ const queryString: string = urlSearchParams.toString();
99
+
100
+ const newRelativeUrl: string =
101
+ window.location.pathname +
102
+ (queryString ? `?${queryString}` : "") +
103
+ window.location.hash;
104
+
105
+ window.history.replaceState(window.history.state, "", newRelativeUrl);
106
+ }
107
+
69
108
  public static getParamByName(
70
109
  paramName: string,
71
110
  routeTemplate: Route,
@@ -0,0 +1,92 @@
1
+ import Navigation from "./Navigation";
2
+ import { JSONObject } from "../../Types/JSON";
3
+ import JSONFunctions from "../../Types/JSONFunctions";
4
+
5
+ export type TableFilterUrlStateKind = "facets" | "filter";
6
+
7
+ /**
8
+ * Persists a table's filter/facet selections in the URL query string so they:
9
+ * - survive navigating to a detail page and clicking the browser "Back" button
10
+ * (the list page remounts with the params still on the URL), and
11
+ * - are shareable/bookmarkable (the URL fully describes the filtered view).
12
+ *
13
+ * The snapshot is run through {@link JSONFunctions} so OneUptime's typed query
14
+ * values (Search, Includes, InBetween, ...) round-trip as real class instances
15
+ * rather than plain objects. Params are namespaced by `tableId` so two tables on
16
+ * the same route don't clobber each other.
17
+ */
18
+ export default class TableFilterUrlState {
19
+ private static getParamName(
20
+ tableId: string,
21
+ kind: TableFilterUrlStateKind,
22
+ ): string {
23
+ return `${tableId}-${kind}`;
24
+ }
25
+
26
+ /*
27
+ * Read a previously-persisted snapshot from the URL. Returns null when absent,
28
+ * empty, or unparseable (e.g. a hand-edited param) so callers fall back to
29
+ * their defaults.
30
+ */
31
+ public static read(
32
+ tableId: string | undefined,
33
+ kind: TableFilterUrlStateKind,
34
+ ): JSONObject | null {
35
+ if (!tableId) {
36
+ return null;
37
+ }
38
+
39
+ try {
40
+ const raw: string | null = Navigation.getQueryStringByName(
41
+ this.getParamName(tableId, kind),
42
+ );
43
+
44
+ if (!raw) {
45
+ return null;
46
+ }
47
+
48
+ const deserialized: JSONObject = JSONFunctions.deserialize(
49
+ JSONFunctions.parseJSONObject(raw),
50
+ );
51
+
52
+ if (deserialized && Object.keys(deserialized).length > 0) {
53
+ return deserialized;
54
+ }
55
+
56
+ return null;
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ /*
63
+ * Persist a snapshot to the URL, or remove the param when the state is empty
64
+ * (`null`, or an object with no keys). Does not add a browser-history entry.
65
+ */
66
+ public static write(
67
+ tableId: string | undefined,
68
+ kind: TableFilterUrlStateKind,
69
+ state: JSONObject | null,
70
+ ): void {
71
+ if (!tableId) {
72
+ return;
73
+ }
74
+
75
+ try {
76
+ const hasState: boolean = Boolean(state && Object.keys(state).length > 0);
77
+
78
+ const value: string | null = hasState
79
+ ? JSON.stringify(JSONFunctions.serialize(state as JSONObject))
80
+ : null;
81
+
82
+ Navigation.setQueryString({
83
+ [this.getParamName(tableId, kind)]: value,
84
+ });
85
+ } catch {
86
+ /*
87
+ * Serialization failure (shouldn't happen for filter data) — skip the
88
+ * URL sync rather than break the table.
89
+ */
90
+ }
91
+ }
92
+ }
@@ -1339,7 +1339,7 @@ export class Service extends DatabaseService {
1339
1339
  to: to,
1340
1340
  data: [
1341
1341
  {
1342
- sayMessage: "This is a call from OneUptime",
1342
+ sayMessage: "This is a call from One Uptime",
1343
1343
  },
1344
1344
  {
1345
1345
  sayMessage: "A new alert has been created",
@@ -1378,7 +1378,7 @@ export class Service extends DatabaseService {
1378
1378
  to: to,
1379
1379
  data: [
1380
1380
  {
1381
- sayMessage: "This is a call from OneUptime",
1381
+ sayMessage: "This is a call from One Uptime",
1382
1382
  },
1383
1383
  {
1384
1384
  sayMessage: "A new incident has been created",
@@ -1419,7 +1419,7 @@ export class Service extends DatabaseService {
1419
1419
  to: to,
1420
1420
  data: [
1421
1421
  {
1422
- sayMessage: "This is a call from OneUptime",
1422
+ sayMessage: "This is a call from One Uptime",
1423
1423
  },
1424
1424
  {
1425
1425
  sayMessage: "A new alert episode has been created",