@oneuptime/common 7.0.4674 → 7.0.4699

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 (55) hide show
  1. package/Server/Middleware/ProjectAuthorization.ts +31 -29
  2. package/Tests/UI/Components/NavBar.test.tsx +12 -3
  3. package/UI/Components/ActionButton/ActionButtonSchema.ts +1 -0
  4. package/UI/Components/Breadcrumbs/Breadcrumbs.tsx +1 -1
  5. package/UI/Components/Card/Card.tsx +57 -46
  6. package/UI/Components/Detail/Detail.tsx +22 -1
  7. package/UI/Components/Detail/Field.ts +1 -0
  8. package/UI/Components/EventHistoryList/EventHistoryDayList.tsx +32 -25
  9. package/UI/Components/Feed/FeedItem.tsx +1 -1
  10. package/UI/Components/Footer/Footer.tsx +1 -1
  11. package/UI/Components/Forms/BasicForm.tsx +4 -1
  12. package/UI/Components/Icon/Icon.tsx +5 -3
  13. package/UI/Components/List/ListRow.tsx +22 -1
  14. package/UI/Components/ModelTable/BaseModelTable.tsx +2 -0
  15. package/UI/Components/ModelTable/Column.ts +1 -0
  16. package/UI/Components/Navbar/NavBar.tsx +309 -7
  17. package/UI/Components/OrderedStatesList/Item.tsx +22 -1
  18. package/UI/Components/Pagination/Pagination.tsx +1 -1
  19. package/UI/Components/Table/TableHeader.tsx +73 -53
  20. package/UI/Components/Table/TableRow.tsx +174 -148
  21. package/UI/Components/Table/Types/Column.ts +1 -0
  22. package/build/dist/Server/Middleware/ProjectAuthorization.js +19 -17
  23. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  24. package/build/dist/Tests/UI/Components/NavBar.test.js +8 -1
  25. package/build/dist/Tests/UI/Components/NavBar.test.js.map +1 -1
  26. package/build/dist/UI/Components/Breadcrumbs/Breadcrumbs.js +1 -1
  27. package/build/dist/UI/Components/Breadcrumbs/Breadcrumbs.js.map +1 -1
  28. package/build/dist/UI/Components/Card/Card.js +9 -13
  29. package/build/dist/UI/Components/Card/Card.js.map +1 -1
  30. package/build/dist/UI/Components/Detail/Detail.js +17 -1
  31. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  32. package/build/dist/UI/Components/EventHistoryList/EventHistoryDayList.js +2 -2
  33. package/build/dist/UI/Components/EventHistoryList/EventHistoryDayList.js.map +1 -1
  34. package/build/dist/UI/Components/Feed/FeedItem.js +1 -1
  35. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  36. package/build/dist/UI/Components/Footer/Footer.js +1 -1
  37. package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
  38. package/build/dist/UI/Components/Forms/BasicForm.js +1 -1
  39. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  40. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  41. package/build/dist/UI/Components/List/ListRow.js +17 -1
  42. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  43. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -0
  44. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  45. package/build/dist/UI/Components/Navbar/NavBar.js +134 -4
  46. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  47. package/build/dist/UI/Components/OrderedStatesList/Item.js +17 -1
  48. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  49. package/build/dist/UI/Components/Pagination/Pagination.js +1 -1
  50. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  51. package/build/dist/UI/Components/Table/TableHeader.js +18 -2
  52. package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
  53. package/build/dist/UI/Components/Table/TableRow.js +24 -3
  54. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  55. package/package.json +1 -1
@@ -96,38 +96,40 @@ export default class ProjectMiddleware {
96
96
  props: { isRoot: true },
97
97
  });
98
98
 
99
- tenantId = apiKeyModel?.projectId || null;
100
-
101
- if (!tenantId) {
102
- throw new BadDataException("Invalid API Key");
103
- }
104
-
105
- (req as OneUptimeRequest).tenantId = tenantId;
106
-
107
99
  if (apiKeyModel) {
108
- (req as OneUptimeRequest).userType = UserType.API;
109
- // TODO: Add API key permissions.
110
- // (req as OneUptimeRequest).permissions =
111
- // apiKeyModel.permissions || [];
112
- (req as OneUptimeRequest).userGlobalAccessPermission =
113
- await APIKeyAccessPermission.getDefaultApiGlobalPermission(
114
- tenantId,
115
- );
116
-
117
- const userTenantAccessPermission: UserTenantAccessPermission | null =
118
- await APIKeyAccessPermission.getApiTenantAccessPermission(
119
- tenantId,
120
- apiKeyModel.id!,
121
- );
100
+ tenantId = apiKeyModel?.projectId || null;
122
101
 
123
- if (userTenantAccessPermission) {
124
- (req as OneUptimeRequest).userTenantAccessPermission = {};
125
- (
126
- (req as OneUptimeRequest)
127
- .userTenantAccessPermission as Dictionary<UserTenantAccessPermission>
128
- )[tenantId.toString()] = userTenantAccessPermission;
102
+ if (!tenantId) {
103
+ throw new BadDataException("Invalid API Key");
104
+ }
129
105
 
130
- return next();
106
+ (req as OneUptimeRequest).tenantId = tenantId;
107
+
108
+ if (apiKeyModel) {
109
+ (req as OneUptimeRequest).userType = UserType.API;
110
+ // TODO: Add API key permissions.
111
+ // (req as OneUptimeRequest).permissions =
112
+ // apiKeyModel.permissions || [];
113
+ (req as OneUptimeRequest).userGlobalAccessPermission =
114
+ await APIKeyAccessPermission.getDefaultApiGlobalPermission(
115
+ tenantId,
116
+ );
117
+
118
+ const userTenantAccessPermission: UserTenantAccessPermission | null =
119
+ await APIKeyAccessPermission.getApiTenantAccessPermission(
120
+ tenantId,
121
+ apiKeyModel.id!,
122
+ );
123
+
124
+ if (userTenantAccessPermission) {
125
+ (req as OneUptimeRequest).userTenantAccessPermission = {};
126
+ (
127
+ (req as OneUptimeRequest)
128
+ .userTenantAccessPermission as Dictionary<UserTenantAccessPermission>
129
+ )[tenantId.toString()] = userTenantAccessPermission;
130
+
131
+ return next();
132
+ }
131
133
  }
132
134
  }
133
135
  }
@@ -1,9 +1,13 @@
1
- import Navbar, { ComponentProps } from "../../../UI/Components/Navbar/NavBar";
1
+ import Navbar, {
2
+ ComponentProps,
3
+ NavItem,
4
+ } from "../../../UI/Components/Navbar/NavBar";
2
5
  import { describe, expect, it } from "@jest/globals";
3
6
  import "@testing-library/jest-dom/extend-expect";
4
7
  import { render, screen } from "@testing-library/react";
5
8
  import React from "react";
6
- import { ReactElement } from "react-markdown/lib/react-markdown";
9
+ import Route from "../../../Types/API/Route";
10
+ import IconProp from "../../../Types/Icon/IconProp";
7
11
 
8
12
  describe("Navbar", () => {
9
13
  const defaultProps: ComponentProps = {
@@ -26,7 +30,12 @@ describe("Navbar", () => {
26
30
  });
27
31
 
28
32
  it("renders with a rightElement", () => {
29
- const rightElement: ReactElement = <div>Right Element</div>;
33
+ const rightElement: NavItem = {
34
+ id: "test-right-element",
35
+ title: "Right Element",
36
+ icon: IconProp.User,
37
+ route: new Route("/test"),
38
+ };
30
39
  const customProps: ComponentProps = { ...defaultProps, rightElement };
31
40
  render(<Navbar {...customProps} />);
32
41
  expect(screen.getByText("Right Element")).toBeInTheDocument();
@@ -9,6 +9,7 @@ interface ActionButtonSchema<T extends GenericObject> {
9
9
  buttonStyleType: ButtonStyleType;
10
10
  isLoading?: boolean | undefined;
11
11
  isVisible?: (item: T) => boolean | undefined;
12
+ hideOnMobile?: boolean | undefined;
12
13
  onClick: (
13
14
  item: T,
14
15
  onCompleteAction: VoidFunction,
@@ -14,7 +14,7 @@ const Breadcrumbs: FunctionComponent<ComponentProps> = ({
14
14
  links,
15
15
  }: ComponentProps): ReactElement => {
16
16
  return (
17
- <nav className="flex" aria-label="Breadcrumb">
17
+ <nav className="flex hidden md:block" aria-label="Breadcrumb">
18
18
  <ol role="list" className="flex items-center space-x-1">
19
19
  {links &&
20
20
  links.length > 0 &&
@@ -33,9 +33,9 @@ const Card: FunctionComponent<ComponentProps> = (
33
33
  return (
34
34
  <React.Fragment>
35
35
  <div data-testid="card" className={props.className}>
36
- <div className="shadow sm:rounded-md">
37
- <div className="bg-white py-6 px-4 sm:p-6">
38
- <div className="flex justify-between">
36
+ <div className="shadow md:rounded-md">
37
+ <div className="bg-white py-6 px-4 md:p-6">
38
+ <div className="flex flex-col md:flex-row md:justify-between">
39
39
  <div className={`${noRightElementsOrButtons ? "w-full" : ""}`}>
40
40
  {props.title && (
41
41
  <h2
@@ -55,49 +55,60 @@ const Card: FunctionComponent<ComponentProps> = (
55
55
  </p>
56
56
  )}
57
57
  </div>
58
- <div className="flex w-fit">
59
- {props.rightElement}
60
- {props.buttons?.map(
61
- (button: CardButtonSchema | ReactElement, i: number) => {
62
- return (
63
- <div
64
- style={
65
- i > 0
66
- ? {
67
- marginLeft: "10px",
68
- }
69
- : {}
70
- }
71
- key={i}
72
- >
73
- {React.isValidElement(button) ? button : null}
74
- {React.isValidElement(button) ? null : (
75
- <Button
76
- key={i}
77
- title={(button as CardButtonSchema).title}
78
- buttonStyle={
79
- (button as CardButtonSchema).buttonStyle
80
- }
81
- className={(button as CardButtonSchema).className}
82
- onClick={() => {
83
- if ((button as CardButtonSchema).onClick) {
84
- (button as CardButtonSchema).onClick();
85
- }
86
- }}
87
- disabled={(button as CardButtonSchema).disabled}
88
- icon={(button as CardButtonSchema).icon}
89
- shortcutKey={
90
- (button as CardButtonSchema).shortcutKey
91
- }
92
- dataTestId="card-button"
93
- isLoading={(button as CardButtonSchema).isLoading}
94
- />
95
- )}
96
- </div>
97
- );
98
- },
99
- )}
100
- </div>
58
+ {(props.rightElement ||
59
+ (props.buttons && props.buttons.length > 0)) && (
60
+ <div className="flex flex-col md:flex-row md:w-fit mt-4 md:mt-0 gap-2 md:gap-0">
61
+ {props.rightElement && (
62
+ <div className="mb-2 md:mb-0 md:mr-2">
63
+ {props.rightElement}
64
+ </div>
65
+ )}
66
+ {props.buttons && props.buttons.length > 0 && (
67
+ <div className="flex flex-wrap gap-2 md:gap-0">
68
+ {props.buttons.map(
69
+ (
70
+ button: CardButtonSchema | ReactElement,
71
+ i: number,
72
+ ) => {
73
+ return (
74
+ <div className="md:ml-2 first:md:ml-0" key={i}>
75
+ {React.isValidElement(button) ? button : null}
76
+ {React.isValidElement(button) ? null : (
77
+ <Button
78
+ key={i}
79
+ title={(button as CardButtonSchema).title}
80
+ buttonStyle={
81
+ (button as CardButtonSchema).buttonStyle
82
+ }
83
+ className={
84
+ (button as CardButtonSchema).className
85
+ }
86
+ onClick={() => {
87
+ if ((button as CardButtonSchema).onClick) {
88
+ (button as CardButtonSchema).onClick();
89
+ }
90
+ }}
91
+ disabled={
92
+ (button as CardButtonSchema).disabled
93
+ }
94
+ icon={(button as CardButtonSchema).icon}
95
+ shortcutKey={
96
+ (button as CardButtonSchema).shortcutKey
97
+ }
98
+ dataTestId="card-button"
99
+ isLoading={
100
+ (button as CardButtonSchema).isLoading
101
+ }
102
+ />
103
+ )}
104
+ </div>
105
+ );
106
+ },
107
+ )}
108
+ </div>
109
+ )}
110
+ </div>
111
+ )}
101
112
  </div>
102
113
 
103
114
  {props.children && (
@@ -20,7 +20,7 @@ import Dictionary from "../../../Types/Dictionary";
20
20
  import BadDataException from "../../../Types/Exception/BadDataException";
21
21
  import GenericObject from "../../../Types/GenericObject";
22
22
  import get from "lodash/get";
23
- import React, { ReactElement } from "react";
23
+ import React, { ReactElement, useEffect, useState } from "react";
24
24
 
25
25
  export interface ComponentProps<T extends GenericObject> {
26
26
  item: T;
@@ -36,6 +36,22 @@ type DetailFunction = <T extends GenericObject>(
36
36
  const Detail: DetailFunction = <T extends GenericObject>(
37
37
  props: ComponentProps<T>,
38
38
  ): ReactElement => {
39
+ // Track mobile view for responsive behavior
40
+ const [isMobile, setIsMobile] = useState<boolean>(false);
41
+
42
+ useEffect(() => {
43
+ const checkMobile: () => void = (): void => {
44
+ setIsMobile(window.innerWidth < 768); // md breakpoint
45
+ };
46
+
47
+ checkMobile();
48
+ window.addEventListener("resize", checkMobile);
49
+
50
+ return () => {
51
+ window.removeEventListener("resize", checkMobile);
52
+ };
53
+ }, []);
54
+
39
55
  type GetMarkdownViewerFunction = (text: string) => ReactElement;
40
56
 
41
57
  const getMarkdownViewer: GetMarkdownViewerFunction = (
@@ -410,6 +426,11 @@ const Detail: DetailFunction = <T extends GenericObject>(
410
426
  props.fields.length > 0 &&
411
427
  props.fields
412
428
  .filter((field: Field<T>) => {
429
+ // Filter out fields with hideOnMobile on mobile devices
430
+ if (field.hideOnMobile && isMobile) {
431
+ return false;
432
+ }
433
+
413
434
  // check if showIf exists.
414
435
  if (field.showIf) {
415
436
  return field.showIf(props.item);
@@ -23,6 +23,7 @@ export interface FieldBase<T> {
23
23
  alignItem?: AlignItem | undefined;
24
24
  contentClassName?: string | undefined;
25
25
  showIf?: ((item: T) => boolean) | undefined;
26
+ hideOnMobile?: boolean | undefined; // Hide field on mobile devices
26
27
  getElement?:
27
28
  | ((
28
29
  item: T,
@@ -2,7 +2,12 @@ import EventHistoryItem, {
2
2
  ComponentProps as ItemComponentProps,
3
3
  } from "../EventItem/EventItem";
4
4
  import OneUptimeDate from "../../../Types/Date";
5
- import React, { FunctionComponent, ReactElement, useState, useEffect } from "react";
5
+ import React, {
6
+ FunctionComponent,
7
+ ReactElement,
8
+ useState,
9
+ useEffect,
10
+ } from "react";
6
11
 
7
12
  export interface ComponentProps {
8
13
  date: Date;
@@ -14,52 +19,54 @@ const EventHistoryDayList: FunctionComponent<ComponentProps> = (
14
19
  props: ComponentProps,
15
20
  ): ReactElement => {
16
21
  const [windowWidth, setWindowWidth] = useState<number>(
17
- typeof window !== "undefined" ? window.innerWidth : 1024
22
+ typeof window !== "undefined" ? window.innerWidth : 1024,
18
23
  );
19
24
 
20
25
  useEffect(() => {
21
- const handleResize = () => {
26
+ const handleResize: () => void = (): void => {
22
27
  setWindowWidth(window.innerWidth);
23
28
  };
24
29
 
25
30
  window.addEventListener("resize", handleResize);
26
-
31
+
27
32
  // Cleanup event listener on component unmount
28
33
  return () => {
29
34
  window.removeEventListener("resize", handleResize);
30
35
  };
31
36
  }, []);
32
37
 
33
- const isMobile = windowWidth <= 768;
38
+ const isMobile: boolean = windowWidth <= 768;
34
39
  return (
35
40
  <div
36
41
  className="md:flex bottom-Gray500-border"
37
42
  style={{
38
- marginLeft: "-10px",
39
- marginRight: "-10px",
40
- marginBottom: props.isLastItem ? "0px" : "20px",
41
- borderBottomWidth: props.isLastItem ? "0px" : "1px",
43
+ marginLeft: "-10px",
44
+ marginRight: "-10px",
45
+ marginBottom: props.isLastItem ? "0px" : "20px",
46
+ borderBottomWidth: props.isLastItem ? "0px" : "1px",
42
47
  }}
43
48
  >
44
49
  <div
45
- className="text-gray-400 mt-2 text-sm"
46
- style={{
47
- padding: "20px",
48
- paddingLeft: "10px",
49
- paddingRight: "0px",
50
- width: isMobile ? "100%" : "15%",
51
- }}
50
+ className="text-gray-400 mt-2 text-sm"
51
+ style={{
52
+ padding: "20px",
53
+ paddingLeft: "10px",
54
+ paddingRight: "0px",
55
+ width: isMobile ? "100%" : "15%",
56
+ }}
52
57
  >
53
- {OneUptimeDate.getDateAsLocalFormattedString(props.date, true)}
58
+ {OneUptimeDate.getDateAsLocalFormattedString(props.date, true)}
54
59
  </div>
55
- <div style={{
56
- padding: "10px",
57
- paddingTop: "0px",
58
- width: isMobile ? "100%" : "85%"
59
- }}>
60
- {props.items.map((item: ItemComponentProps, i: number) => {
61
- return <EventHistoryItem key={i} {...item} />;
62
- })}
60
+ <div
61
+ style={{
62
+ padding: "10px",
63
+ paddingTop: "0px",
64
+ width: isMobile ? "100%" : "85%",
65
+ }}
66
+ >
67
+ {props.items.map((item: ItemComponentProps, i: number) => {
68
+ return <EventHistoryItem key={i} {...item} />;
69
+ })}
63
70
  </div>
64
71
  </div>
65
72
  );
@@ -135,7 +135,7 @@ const FeedItem: FunctionComponent<ComponentProps> = (
135
135
  )}
136
136
  {props.element && <div>{props.element}</div>}
137
137
  {props.moreTextInMarkdown && (
138
- <div className="-ml-3">
138
+ <div className="-ml-3 w-fit">
139
139
  <Button
140
140
  onClick={() => {
141
141
  return setShowMoreInformationModal(true);
@@ -23,7 +23,7 @@ const Footer: FunctionComponent<ComponentProps> = (
23
23
  return (
24
24
  <React.Fragment>
25
25
  <footer
26
- className={props.className || "bg-white h-16 inset-x-0 bottom-0"}
26
+ className={props.className || "bg-white min-h-16"}
27
27
  style={props.style}
28
28
  >
29
29
  <div className="mx-auto w-full py-5 px-6 md:flex md:items-center md:justify-between lg:px-0">
@@ -592,7 +592,10 @@ const BasicForm: ForwardRefExoticComponent<any> = forwardRef(
592
592
 
593
593
  <div className="flex">
594
594
  {formSteps && currentFormStepId && (
595
- <div style={{ flex: "0 1 auto" }} className="mr-10">
595
+ <div
596
+ style={{ flex: "0 1 auto" }}
597
+ className="mr-10 hidden lg:block"
598
+ >
596
599
  {/* Form Steps */}
597
600
 
598
601
  <Steps
@@ -187,9 +187,11 @@ const Icon: FunctionComponent<ComponentProps> = ({
187
187
  });
188
188
  } else if (icon === IconProp.Bars3) {
189
189
  return getSvgWrapper(
190
-
191
- <path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
192
-
190
+ <path
191
+ strokeLinecap="round"
192
+ strokeLinejoin="round"
193
+ d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
194
+ />,
193
195
  );
194
196
  } else if (icon === IconProp.ShieldCheck) {
195
197
  return getSvgWrapper(
@@ -6,7 +6,7 @@ import Icon, { ThickProp } from "../Icon/Icon";
6
6
  import ConfirmModal from "../Modal/ConfirmModal";
7
7
  import GenericObject from "../../../Types/GenericObject";
8
8
  import IconProp from "../../../Types/Icon/IconProp";
9
- import React, { ReactElement, useState } from "react";
9
+ import React, { ReactElement, useState, useEffect } from "react";
10
10
  import { Draggable, DraggableProvided } from "react-beautiful-dnd";
11
11
 
12
12
  export interface ListDetailProps {
@@ -39,6 +39,22 @@ const ListRow: ListRowFunction = <T extends GenericObject>(
39
39
 
40
40
  const [error, setError] = useState<string>("");
41
41
 
42
+ // Track mobile view for responsive behavior
43
+ const [isMobile, setIsMobile] = useState<boolean>(false);
44
+
45
+ useEffect(() => {
46
+ const checkMobile: () => void = (): void => {
47
+ setIsMobile(window.innerWidth < 768); // md breakpoint
48
+ };
49
+
50
+ checkMobile();
51
+ window.addEventListener("resize", checkMobile);
52
+
53
+ return () => {
54
+ window.removeEventListener("resize", checkMobile);
55
+ };
56
+ }, []);
57
+
42
58
  type GetRowFunction = (provided?: DraggableProvided) => ReactElement;
43
59
 
44
60
  const getRow: GetRowFunction = (
@@ -91,6 +107,11 @@ const ListRow: ListRowFunction = <T extends GenericObject>(
91
107
  return <></>;
92
108
  }
93
109
 
110
+ // Hide button on mobile if hideOnMobile is true
111
+ if (button.hideOnMobile && isMobile) {
112
+ return <></>;
113
+ }
114
+
94
115
  return (
95
116
  <div key={i}>
96
117
  <Button
@@ -398,6 +398,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
398
398
  colSpan: column.colSpan,
399
399
  contentClassName: column.contentClassName,
400
400
  alignItem: column.alignItem,
401
+ hideOnMobile: column.hideOnMobile, // Propagate hideOnMobile property
401
402
  getElement: column.getElement
402
403
  ? (item: TBaseModel): ReactElement => {
403
404
  return column.getElement!(item, onBeforeFetchData);
@@ -1130,6 +1131,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
1130
1131
  actionsSchema.push({
1131
1132
  title: "Show ID",
1132
1133
  buttonStyleType: ButtonStyleType.OUTLINE,
1134
+ hideOnMobile: true,
1133
1135
  onClick: async (
1134
1136
  item: TBaseModel,
1135
1137
  onCompleteAction: VoidFunction,
@@ -28,6 +28,7 @@ export default interface Columns<
28
28
  actionButtons?: Array<ActionButton>;
29
29
  alignItem?: AlignItem | undefined;
30
30
  noValueMessage?: string | undefined;
31
+ hideOnMobile?: boolean | undefined; // Hide column on mobile devices
31
32
  getElement?:
32
33
  | ((item: TEntity, onBeforeFetchData?: TEntity | undefined) => ReactElement)
33
34
  | undefined;