@oneuptime/common 9.3.8 → 9.3.10

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 (78) hide show
  1. package/Models/DatabaseModels/AIAgentTask.ts +18 -3
  2. package/Models/DatabaseModels/Index.ts +0 -5
  3. package/Models/DatabaseModels/MetricType.ts +7 -7
  4. package/Models/DatabaseModels/Service.ts +34 -8
  5. package/Models/DatabaseModels/TelemetryException.ts +11 -13
  6. package/Models/DatabaseModels/TelemetryUsageBilling.ts +11 -13
  7. package/Server/API/AIAgentDataAPI.ts +71 -108
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1767979055522-MigrationName.ts +743 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1767979448478-MigrationName.ts +224 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  11. package/Server/Services/Index.ts +0 -5
  12. package/Server/Services/OpenTelemetryIngestService.ts +26 -28
  13. package/Server/Services/ServiceService.ts +23 -0
  14. package/Server/Services/TelemetryExceptionService.ts +3 -4
  15. package/Server/Services/TelemetryUsageBillingService.ts +30 -32
  16. package/Server/Utils/Telemetry/Telemetry.ts +24 -23
  17. package/Types/AI/AIAgentTaskMetadata.ts +1 -1
  18. package/UI/Components/LogsViewer/LogsViewer.tsx +20 -23
  19. package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +3 -3
  20. package/UI/Components/LogsViewer/components/LogsTable.tsx +3 -4
  21. package/UI/Components/Navbar/NavBar.tsx +146 -52
  22. package/UI/Components/Navbar/NavBarMenu.tsx +87 -7
  23. package/UI/Components/Navbar/NavBarMenuItem.tsx +12 -0
  24. package/build/dist/Models/DatabaseModels/AIAgentTask.js +18 -3
  25. package/build/dist/Models/DatabaseModels/AIAgentTask.js.map +1 -1
  26. package/build/dist/Models/DatabaseModels/Index.js +0 -4
  27. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  28. package/build/dist/Models/DatabaseModels/MetricType.js +8 -8
  29. package/build/dist/Models/DatabaseModels/MetricType.js.map +1 -1
  30. package/build/dist/Models/DatabaseModels/Service.js +36 -8
  31. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  32. package/build/dist/Models/DatabaseModels/TelemetryException.js +14 -14
  33. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  34. package/build/dist/Models/DatabaseModels/TelemetryUsageBilling.js +14 -14
  35. package/build/dist/Models/DatabaseModels/TelemetryUsageBilling.js.map +1 -1
  36. package/build/dist/Server/API/AIAgentDataAPI.js +47 -76
  37. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  38. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1767979055522-MigrationName.js +254 -0
  39. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1767979055522-MigrationName.js.map +1 -0
  40. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1767979448478-MigrationName.js +140 -0
  41. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1767979448478-MigrationName.js.map +1 -0
  42. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  43. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  44. package/build/dist/Server/Services/Index.js +0 -4
  45. package/build/dist/Server/Services/Index.js.map +1 -1
  46. package/build/dist/Server/Services/OpenTelemetryIngestService.js +7 -7
  47. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  48. package/build/dist/Server/Services/ServiceService.js +23 -0
  49. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  50. package/build/dist/Server/Services/TelemetryExceptionService.js +3 -4
  51. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  52. package/build/dist/Server/Services/TelemetryUsageBillingService.js +15 -16
  53. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  54. package/build/dist/Server/Utils/Telemetry/Telemetry.js +20 -20
  55. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  56. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +5 -5
  57. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  58. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
  59. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
  60. package/build/dist/UI/Components/Navbar/NavBar.js +58 -13
  61. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  62. package/build/dist/UI/Components/Navbar/NavBarMenu.js +37 -5
  63. package/build/dist/UI/Components/Navbar/NavBarMenu.js.map +1 -1
  64. package/build/dist/UI/Components/Navbar/NavBarMenuItem.js +12 -0
  65. package/build/dist/UI/Components/Navbar/NavBarMenuItem.js.map +1 -1
  66. package/package.json +1 -1
  67. package/Models/DatabaseModels/ServiceTelemetryService.ts +0 -419
  68. package/Models/DatabaseModels/TelemetryService.ts +0 -529
  69. package/Server/Services/ServiceTelemetryServiceService.ts +0 -59
  70. package/Server/Services/TelemetryServiceService.ts +0 -53
  71. package/build/dist/Models/DatabaseModels/ServiceTelemetryService.js +0 -436
  72. package/build/dist/Models/DatabaseModels/ServiceTelemetryService.js.map +0 -1
  73. package/build/dist/Models/DatabaseModels/TelemetryService.js +0 -545
  74. package/build/dist/Models/DatabaseModels/TelemetryService.js.map +0 -1
  75. package/build/dist/Server/Services/ServiceTelemetryServiceService.js +0 -56
  76. package/build/dist/Server/Services/ServiceTelemetryServiceService.js.map +0 -1
  77. package/build/dist/Server/Services/TelemetryServiceService.js +0 -59
  78. package/build/dist/Server/Services/TelemetryServiceService.js.map +0 -1
@@ -3,7 +3,7 @@ import ObjectID from "../../../Types/ObjectID";
3
3
  import CaptureSpan from "./CaptureSpan";
4
4
  import MetricType from "../../../Models/DatabaseModels/MetricType";
5
5
  import MetricTypeService from "../../Services/MetricTypeService";
6
- import TelemetryService from "../../../Models/DatabaseModels/TelemetryService";
6
+ import Service from "../../../Models/DatabaseModels/Service";
7
7
  import Dictionary from "../../../Types/Dictionary";
8
8
 
9
9
  export type AttributeType = string | number | boolean | null;
@@ -22,7 +22,7 @@ export default class TelemetryUtil {
22
22
  name: metricName,
23
23
  },
24
24
  select: {
25
- telemetryServices: true,
25
+ services: true,
26
26
  name: true,
27
27
  description: true,
28
28
  unit: true,
@@ -35,21 +35,22 @@ export default class TelemetryUtil {
35
35
  const metricTypeInMap: MetricType =
36
36
  data.metricNameServiceNameMap[metricName]!;
37
37
 
38
- // check if telemetry services are same as the ones in the map
39
- const telemetryServicesInMap: Array<ObjectID> =
40
- metricTypeInMap?.telemetryServices?.map((service: TelemetryService) => {
38
+ // check if services are same as the ones in the map
39
+ const servicesInMap: Array<ObjectID> =
40
+ metricTypeInMap?.services?.map((service: Service) => {
41
41
  return service.id!;
42
42
  }) || [];
43
43
 
44
44
  if (metricType) {
45
- if (!metricType.telemetryServices) {
46
- metricType.telemetryServices = [];
45
+ if (!metricType.services) {
46
+ metricType.services = [];
47
47
  }
48
48
 
49
- const telemetryServiceIds: Array<ObjectID> =
50
- metricType.telemetryServices!.map((service: TelemetryService) => {
49
+ const serviceIds: Array<ObjectID> = metricType.services!.map(
50
+ (service: Service) => {
51
51
  return service.id!;
52
- });
52
+ },
53
+ );
53
54
 
54
55
  let isSame: boolean = true;
55
56
 
@@ -65,19 +66,19 @@ export default class TelemetryUtil {
65
66
  metricType.unit = metricTypeInMap.unit || "";
66
67
  }
67
68
 
68
- // check if telemetry services are same
69
+ // check if services are same
69
70
 
70
- for (const telemetryServiceId of telemetryServicesInMap) {
71
+ for (const serviceId of servicesInMap) {
71
72
  if (
72
- telemetryServiceIds.filter((serviceId: ObjectID) => {
73
- return serviceId.toString() === telemetryServiceId.toString();
73
+ serviceIds.filter((existingServiceId: ObjectID) => {
74
+ return existingServiceId.toString() === serviceId.toString();
74
75
  }).length === 0
75
76
  ) {
76
77
  isSame = false;
77
78
  // add the service id to the list
78
- const telemetryService: TelemetryService = new TelemetryService();
79
- telemetryService.id = telemetryServiceId;
80
- metricType.telemetryServices!.push(telemetryService);
79
+ const service: Service = new Service();
80
+ service.id = serviceId;
81
+ metricType.services!.push(service);
81
82
  }
82
83
  }
83
84
 
@@ -88,7 +89,7 @@ export default class TelemetryUtil {
88
89
  await MetricTypeService.updateOneById({
89
90
  id: metricType.id!,
90
91
  data: {
91
- telemetryServices: metricType.telemetryServices || [],
92
+ services: metricType.services || [],
92
93
  description: metricTypeInMap.description || "",
93
94
  unit: metricTypeInMap.unit || "",
94
95
  },
@@ -104,12 +105,12 @@ export default class TelemetryUtil {
104
105
  metricType.description = metricTypeInMap.description || "";
105
106
  metricType.unit = metricTypeInMap.unit || "";
106
107
  metricType.projectId = data.projectId;
107
- metricType.telemetryServices = [];
108
+ metricType.services = [];
108
109
 
109
- for (const telemetryServiceId of telemetryServicesInMap) {
110
- const telemetryService: TelemetryService = new TelemetryService();
111
- telemetryService.id = telemetryServiceId;
112
- metricType.telemetryServices!.push(telemetryService);
110
+ for (const serviceId of servicesInMap) {
111
+ const service: Service = new Service();
112
+ service.id = serviceId;
113
+ metricType.services!.push(service);
113
114
  }
114
115
 
115
116
  // save metric type
@@ -9,7 +9,7 @@ export interface AIAgentTaskMetadataBase {
9
9
  export interface FixExceptionTaskMetadata extends AIAgentTaskMetadataBase {
10
10
  taskType: AIAgentTaskType.FixException;
11
11
  exceptionId: string;
12
- telemetryServiceId?: string;
12
+ serviceId?: string;
13
13
  stackTrace?: string;
14
14
  errorMessage?: string;
15
15
  }
@@ -19,7 +19,7 @@ import API from "../../Utils/API/API";
19
19
  import { APP_API_URL } from "../../Config";
20
20
  import PageLoader from "../Loader/PageLoader";
21
21
  import ErrorMessage from "../ErrorMessage/ErrorMessage";
22
- import TelemetryService from "../../../Models/DatabaseModels/TelemetryService";
22
+ import Service from "../../../Models/DatabaseModels/Service";
23
23
  import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
24
24
  import SortOrder from "../../../Types/BaseDatabase/SortOrder";
25
25
  import ListResult from "../../../Types/BaseDatabase/ListResult";
@@ -99,9 +99,7 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
99
99
  const [isPageLoading, setIsPageLoading] = useState<boolean>(true);
100
100
  const [pageError, setPageError] = useState<string>("");
101
101
 
102
- const [serviceMap, setServiceMap] = useState<Dictionary<TelemetryService>>(
103
- {},
104
- );
102
+ const [serviceMap, setServiceMap] = useState<Dictionary<Service>>({});
105
103
 
106
104
  const [selectedLogId, setSelectedLogId] = useState<string | null>(null);
107
105
 
@@ -237,29 +235,28 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
237
235
  }
238
236
  }, [displayedLogs, selectedLogId]);
239
237
 
240
- const loadTelemetryServices: PromiseVoidFunction =
238
+ const loadServices: PromiseVoidFunction =
241
239
  useCallback(async (): Promise<void> => {
242
240
  try {
243
241
  setIsPageLoading(true);
244
242
  setPageError("");
245
243
 
246
- const telemetryServices: ListResult<TelemetryService> =
247
- await ModelAPI.getList({
248
- modelType: TelemetryService,
249
- query: {},
250
- select: {
251
- name: true,
252
- serviceColor: true,
253
- },
254
- limit: LIMIT_PER_PROJECT,
255
- skip: 0,
256
- sort: {
257
- name: SortOrder.Ascending,
258
- },
259
- });
260
- const services: Dictionary<TelemetryService> = {};
244
+ const telemetryServices: ListResult<Service> = await ModelAPI.getList({
245
+ modelType: Service,
246
+ query: {},
247
+ select: {
248
+ name: true,
249
+ serviceColor: true,
250
+ },
251
+ limit: LIMIT_PER_PROJECT,
252
+ skip: 0,
253
+ sort: {
254
+ name: SortOrder.Ascending,
255
+ },
256
+ });
257
+ const services: Dictionary<Service> = {};
261
258
 
262
- telemetryServices.data.forEach((service: TelemetryService) => {
259
+ telemetryServices.data.forEach((service: Service) => {
263
260
  if (!service.id) {
264
261
  return;
265
262
  }
@@ -315,8 +312,8 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
315
312
  }, []);
316
313
 
317
314
  useEffect(() => {
318
- void loadTelemetryServices();
319
- }, [loadTelemetryServices]);
315
+ void loadServices();
316
+ }, [loadServices]);
320
317
 
321
318
  const resetPage: () => void = (): void => {
322
319
  if (props.onPageChange) {
@@ -1,6 +1,6 @@
1
1
  import React, { FunctionComponent, ReactElement, useMemo } from "react";
2
2
  import Log from "../../../../Models/AnalyticsModels/Log";
3
- import TelemetryService from "../../../../Models/DatabaseModels/TelemetryService";
3
+ import Service from "../../../../Models/DatabaseModels/Service";
4
4
  import Dictionary from "../../../../Types/Dictionary";
5
5
  import Route from "../../../../Types/API/Route";
6
6
  import URL from "../../../../Types/API/URL";
@@ -14,7 +14,7 @@ import SeverityBadge from "./SeverityBadge";
14
14
 
15
15
  export interface LogDetailsPanelProps {
16
16
  log: Log;
17
- serviceMap: Dictionary<TelemetryService>;
17
+ serviceMap: Dictionary<Service>;
18
18
  onClose?: () => void;
19
19
  getTraceRoute?:
20
20
  | ((traceId: string, log: Log) => Route | URL | undefined)
@@ -69,7 +69,7 @@ const LogDetailsPanel: FunctionComponent<LogDetailsPanelProps> = (
69
69
  ): ReactElement => {
70
70
  const variant: "floating" | "embedded" = props.variant || "floating";
71
71
  const serviceId: string = props.log.serviceId?.toString() || "";
72
- const service: TelemetryService | undefined = props.serviceMap[serviceId];
72
+ const service: Service | undefined = props.serviceMap[serviceId];
73
73
  const serviceName: string = service?.name || serviceId || "Unknown service";
74
74
  const serviceColor: string =
75
75
  (service?.serviceColor && service?.serviceColor.toString()) || "#64748b";
@@ -1,6 +1,6 @@
1
1
  import React, { Fragment, FunctionComponent, ReactElement } from "react";
2
2
  import Log from "../../../../Models/AnalyticsModels/Log";
3
- import TelemetryService from "../../../../Models/DatabaseModels/TelemetryService";
3
+ import Service from "../../../../Models/DatabaseModels/Service";
4
4
  import Dictionary from "../../../../Types/Dictionary";
5
5
  import OneUptimeDate from "../../../../Types/Date";
6
6
  import CopyTextButton from "../../CopyTextButton/CopyTextButton";
@@ -13,7 +13,7 @@ import IconProp from "../../../../Types/Icon/IconProp";
13
13
 
14
14
  export interface LogsTableProps {
15
15
  logs: Array<Log>;
16
- serviceMap: Dictionary<TelemetryService>;
16
+ serviceMap: Dictionary<Service>;
17
17
  isLoading: boolean;
18
18
  emptyMessage?: string | undefined;
19
19
  onRowClick: (log: Log, rowId: string) => void;
@@ -151,8 +151,7 @@ const LogsTable: FunctionComponent<LogsTableProps> = (
151
151
  {props.logs.map((log: Log, index: number) => {
152
152
  const rowId: string = resolveLogIdentifier(log, index);
153
153
  const serviceId: string = log.serviceId?.toString() || "";
154
- const service: TelemetryService | undefined =
155
- props.serviceMap[serviceId];
154
+ const service: Service | undefined = props.serviceMap[serviceId];
156
155
  const serviceName: string =
157
156
  service?.name || serviceId || "Unknown";
158
157
  const serviceColor: string =
@@ -13,6 +13,7 @@ import NavBarMenuItem from "./NavBarMenuItem";
13
13
  import Button, { ButtonStyleType } from "../Button/Button";
14
14
  import Navigation from "../../Utils/Navigation";
15
15
  import useComponentOutsideClick from "../../Types/UseComponentOutsideClick";
16
+ import Icon, { ThickProp } from "../Icon/Icon";
16
17
 
17
18
  export interface NavItem {
18
19
  id: string;
@@ -29,13 +30,16 @@ export interface MoreMenuItem {
29
30
  description: string;
30
31
  route: Route;
31
32
  icon: IconProp;
32
- iconColor?: string; // Tailwind color class like "bg-blue-500"
33
+ iconColor?: string; // Tailwind color name like "blue", "purple", "amber"
34
+ category?: string; // Category for grouping items (e.g., "Essentials", "Observability")
35
+ activeRoute?: Route | undefined; // Route to check for active state
33
36
  }
34
37
 
35
38
  export interface ComponentProps {
36
39
  items?: NavItem[];
37
40
  rightElement?: NavItem;
38
41
  moreMenuItems?: MoreMenuItem[];
42
+ moreMenuTitle?: string; // Title for the more menu (default: "More")
39
43
  moreMenuFooter?: {
40
44
  title: string;
41
45
  description: string;
@@ -246,12 +250,150 @@ const Navbar: FunctionComponent<ComponentProps> = (
246
250
  // Desktop view
247
251
  const className: string =
248
252
  props.className ||
249
- "bg-white flex text-center lg:space-x-8 lg:py-2 hidden md:flex";
253
+ "bg-white flex text-center items-center lg:py-2 hidden md:flex";
254
+
255
+ // Find active item in more menu items (needed for breadcrumb)
256
+ const activeMoreItem: MoreMenuItem | undefined = props.moreMenuItems?.find(
257
+ (item: MoreMenuItem) => {
258
+ const routeToCheck: Route = item.activeRoute || item.route;
259
+ return Navigation.isStartWith(routeToCheck);
260
+ },
261
+ );
262
+
263
+ // Group items by category for the menu
264
+ const categories: Map<string, MoreMenuItem[]> = new Map();
265
+ props.moreMenuItems?.forEach((item: MoreMenuItem) => {
266
+ const cat: string = item.category || "Other";
267
+ if (!categories.has(cat)) {
268
+ categories.set(cat, []);
269
+ }
270
+ categories.get(cat)!.push(item);
271
+ });
272
+
273
+ // Convert to sections array for NavBarMenu
274
+ const sections: Array<{ title: string; items: Array<ReactElement> }> = [];
275
+ categories.forEach((items: MoreMenuItem[], category: string) => {
276
+ sections.push({
277
+ title: category,
278
+ items: items.map((item: MoreMenuItem) => {
279
+ return (
280
+ <NavBarMenuItem
281
+ key={item.title}
282
+ title={item.title}
283
+ description={item.description}
284
+ route={item.route}
285
+ icon={item.icon}
286
+ iconColor={item.iconColor}
287
+ onClick={forceHideMoreMenu}
288
+ />
289
+ );
290
+ }),
291
+ });
292
+ });
293
+
294
+ // Find Home item from navItems
295
+ const homeItem: NavItem | undefined = props.items.find((item: NavItem) => {
296
+ return item.title === "Home";
297
+ });
298
+ const otherNavItems: NavItem[] = props.items.filter((item: NavItem) => {
299
+ return item.title !== "Home";
300
+ });
250
301
 
251
302
  return (
252
- <nav className={props.rightElement ? `flex justify-between` : ""}>
303
+ <nav
304
+ className={props.rightElement ? `flex justify-between items-center` : ""}
305
+ >
253
306
  <div data-testid="nav-children" className={className}>
254
- {props.items.map((item: any) => {
307
+ {/* Combined Home > Product breadcrumb */}
308
+ <div className="flex items-center">
309
+ {/* Home link */}
310
+ {homeItem && (
311
+ <NavBarItem
312
+ key={homeItem.id}
313
+ id={homeItem.id}
314
+ title={homeItem.title}
315
+ icon={homeItem.icon}
316
+ activeRoute={homeItem.activeRoute}
317
+ route={homeItem.route}
318
+ exact={true}
319
+ />
320
+ )}
321
+
322
+ {/* Separator and active product */}
323
+ {activeMoreItem && (
324
+ <>
325
+ <span className="text-gray-400 mx-1">/</span>
326
+ <div
327
+ onMouseOver={showMoreMenu}
328
+ onMouseLeave={hideMoreMenu}
329
+ className="relative"
330
+ >
331
+ <button
332
+ onClick={showMoreMenu}
333
+ onMouseOver={showMoreMenu}
334
+ className="bg-gray-100 text-gray-900 hover:bg-gray-200 rounded-md py-2 px-3 inline-flex items-center text-sm font-medium transition-colors cursor-pointer"
335
+ >
336
+ <Icon
337
+ icon={activeMoreItem.icon}
338
+ className="mr-1.5 h-4 w-4"
339
+ thick={ThickProp.Thick}
340
+ />
341
+ <span>{activeMoreItem.title}</span>
342
+ <Icon
343
+ icon={IconProp.ChevronDown}
344
+ className="ml-1.5 h-3 w-3 text-gray-500"
345
+ />
346
+ </button>
347
+ {isMoreMenuVisible && (
348
+ <NavBarMenu
349
+ sections={sections}
350
+ footer={props.moreMenuFooter}
351
+ />
352
+ )}
353
+ </div>
354
+ </>
355
+ )}
356
+
357
+ {/* Show Products button when no product is selected */}
358
+ {!activeMoreItem &&
359
+ props.moreMenuItems &&
360
+ props.moreMenuItems.length > 0 && (
361
+ <>
362
+ <span className="text-gray-400 mx-1">/</span>
363
+ <div
364
+ onMouseOver={showMoreMenu}
365
+ onMouseLeave={hideMoreMenu}
366
+ className="relative"
367
+ >
368
+ <button
369
+ onClick={showMoreMenu}
370
+ onMouseOver={showMoreMenu}
371
+ className="text-gray-500 hover:bg-gray-50 hover:text-gray-900 rounded-md py-2 px-3 inline-flex items-center text-sm font-medium transition-colors cursor-pointer"
372
+ >
373
+ <Icon
374
+ icon={IconProp.Squares}
375
+ className="mr-1.5 h-4 w-4"
376
+ thick={ThickProp.Thick}
377
+ />
378
+ <span>{props.moreMenuTitle || "Products"}</span>
379
+ <Icon
380
+ icon={IconProp.ChevronDown}
381
+ className="ml-1.5 h-3 w-3 text-gray-400"
382
+ />
383
+ </button>
384
+ {isMoreMenuVisible && (
385
+ <NavBarMenu
386
+ sections={sections}
387
+ footer={props.moreMenuFooter}
388
+ />
389
+ )}
390
+ </div>
391
+ </>
392
+ )}
393
+ </div>
394
+
395
+ {/* Other nav items */}
396
+ {otherNavItems.map((item: NavItem) => {
255
397
  return (
256
398
  <NavBarItem
257
399
  key={item.id}
@@ -264,54 +406,6 @@ const Navbar: FunctionComponent<ComponentProps> = (
264
406
  />
265
407
  );
266
408
  })}
267
-
268
- {/* More menu for desktop */}
269
- {props.moreMenuItems && props.moreMenuItems.length > 0 && (
270
- <NavBarItem
271
- title="More"
272
- icon={IconProp.More}
273
- onMouseLeave={hideMoreMenu}
274
- onMouseOver={showMoreMenu}
275
- onClick={showMoreMenu}
276
- >
277
- <div onMouseOver={showMoreMenu} onMouseLeave={hideMoreMenu}>
278
- {isMoreMenuVisible &&
279
- (props.moreMenuFooter ? (
280
- <NavBarMenu footer={props.moreMenuFooter}>
281
- {props.moreMenuItems.map((item: MoreMenuItem) => {
282
- return (
283
- <NavBarMenuItem
284
- key={item.title}
285
- title={item.title}
286
- description={item.description}
287
- route={item.route}
288
- icon={item.icon}
289
- iconColor={item.iconColor}
290
- onClick={forceHideMoreMenu}
291
- />
292
- );
293
- })}
294
- </NavBarMenu>
295
- ) : (
296
- <NavBarMenu>
297
- {props.moreMenuItems.map((item: MoreMenuItem) => {
298
- return (
299
- <NavBarMenuItem
300
- key={item.title}
301
- title={item.title}
302
- description={item.description}
303
- route={item.route}
304
- icon={item.icon}
305
- iconColor={item.iconColor}
306
- onClick={forceHideMoreMenu}
307
- />
308
- );
309
- })}
310
- </NavBarMenu>
311
- ))}
312
- </div>
313
- </NavBarItem>
314
- )}
315
409
  </div>
316
410
 
317
411
  {props.rightElement && (
@@ -6,11 +6,12 @@ import React, { FunctionComponent, ReactElement } from "react";
6
6
 
7
7
  export interface MenuSection {
8
8
  title: string;
9
- children: ReactElement | Array<ReactElement>;
9
+ items: Array<ReactElement>;
10
10
  }
11
11
 
12
12
  export interface ComponentProps {
13
- children: ReactElement | Array<ReactElement>;
13
+ children?: ReactElement | Array<ReactElement>;
14
+ sections?: MenuSection[];
14
15
  footer?: {
15
16
  title: string;
16
17
  description: string;
@@ -21,11 +22,90 @@ export interface ComponentProps {
21
22
  const NavBarMenu: FunctionComponent<ComponentProps> = (
22
23
  props: ComponentProps,
23
24
  ): ReactElement => {
24
- let children: Array<ReactElement>;
25
- if (!Array.isArray(props.children) && props.children) {
26
- children = [props.children];
27
- } else {
28
- children = props.children;
25
+ // If sections are provided, render categorized menu
26
+ if (props.sections && props.sections.length > 0) {
27
+ // Separate Settings section from other sections
28
+ const mainSections: MenuSection[] = props.sections.filter(
29
+ (section: MenuSection) => {
30
+ return section.title !== "Settings";
31
+ },
32
+ );
33
+ const settingsSection: MenuSection | undefined = props.sections.find(
34
+ (section: MenuSection) => {
35
+ return section.title === "Settings";
36
+ },
37
+ );
38
+
39
+ return (
40
+ <div className="absolute left-0 z-10 mt-8 w-screen max-w-5xl transform px-2 sm:px-0">
41
+ <div className="overflow-hidden rounded-2xl shadow-xl ring-1 ring-black ring-opacity-5 bg-white">
42
+ {/* Sections */}
43
+ <div className="p-6">
44
+ <div className="flex gap-6">
45
+ {/* Main sections grid */}
46
+ <div className="flex-1 grid grid-cols-1 md:grid-cols-3 gap-6">
47
+ {mainSections.map((section: MenuSection, index: number) => {
48
+ return (
49
+ <div key={index} className="space-y-3">
50
+ <h3 className="text-xs font-semibold uppercase tracking-wider text-gray-500 px-2.5 text-left">
51
+ {section.title}
52
+ </h3>
53
+ <div className="space-y-1">{section.items}</div>
54
+ </div>
55
+ );
56
+ })}
57
+ </div>
58
+
59
+ {/* Settings section on the side */}
60
+ {settingsSection && (
61
+ <div className="border-l border-gray-100 pl-6 space-y-3 min-w-[200px]">
62
+ <h3 className="text-xs font-semibold uppercase tracking-wider text-gray-500 px-2.5 text-left">
63
+ {settingsSection.title}
64
+ </h3>
65
+ <div className="space-y-1">{settingsSection.items}</div>
66
+ </div>
67
+ )}
68
+ </div>
69
+ </div>
70
+
71
+ {/* Footer */}
72
+ {props.footer && (
73
+ <div className="border-t border-gray-100 bg-gray-50 px-4 py-4">
74
+ <Link
75
+ to={props.footer.link}
76
+ openInNewTab={true}
77
+ className="group flex items-center gap-3 rounded-lg p-2.5 -m-2 transition-colors hover:bg-gray-100"
78
+ >
79
+ <div className="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-gray-100 ring-1 ring-gray-200 group-hover:bg-gray-200 group-hover:ring-gray-300 transition-all">
80
+ <Icon
81
+ icon={IconProp.GitHub}
82
+ className="h-5 w-5 text-gray-700"
83
+ />
84
+ </div>
85
+ <div className="flex-1 min-w-0 text-left">
86
+ <p className="text-sm font-medium text-gray-900">
87
+ {props.footer.title}
88
+ </p>
89
+ <p className="text-xs text-gray-500">
90
+ {props.footer.description}
91
+ </p>
92
+ </div>
93
+ </Link>
94
+ </div>
95
+ )}
96
+ </div>
97
+ </div>
98
+ );
99
+ }
100
+
101
+ // Legacy: render children directly
102
+ let children: Array<ReactElement> = [];
103
+ if (props.children) {
104
+ if (!Array.isArray(props.children)) {
105
+ children = [props.children];
106
+ } else {
107
+ children = props.children;
108
+ }
29
109
  }
30
110
 
31
111
  // Calculate number of columns based on items count
@@ -102,6 +102,18 @@ const NavBarMenuItem: FunctionComponent<ComponentProps> = (
102
102
  hoverBg: "hover:bg-sky-50",
103
103
  hoverRing: "group-hover:ring-sky-300",
104
104
  },
105
+ emerald: {
106
+ bg: "bg-emerald-50",
107
+ ring: "ring-emerald-200",
108
+ hoverBg: "hover:bg-emerald-50",
109
+ hoverRing: "group-hover:ring-emerald-300",
110
+ },
111
+ cyan: {
112
+ bg: "bg-cyan-50",
113
+ ring: "ring-cyan-200",
114
+ hoverBg: "hover:bg-cyan-50",
115
+ hoverRing: "group-hover:ring-cyan-300",
116
+ },
105
117
  };
106
118
 
107
119
  const colors: {
@@ -189,7 +189,12 @@ __decorate([
189
189
  ], AIAgentTask.prototype, "description", void 0);
190
190
  __decorate([
191
191
  ColumnAccessControl({
192
- create: [],
192
+ create: [
193
+ Permission.ProjectOwner,
194
+ Permission.ProjectAdmin,
195
+ Permission.ProjectMember,
196
+ Permission.CreateProjectAIAgentTask,
197
+ ],
193
198
  read: [
194
199
  Permission.ProjectOwner,
195
200
  Permission.ProjectAdmin,
@@ -224,7 +229,12 @@ __decorate([
224
229
  ], AIAgentTask.prototype, "aiAgent", void 0);
225
230
  __decorate([
226
231
  ColumnAccessControl({
227
- create: [],
232
+ create: [
233
+ Permission.ProjectOwner,
234
+ Permission.ProjectAdmin,
235
+ Permission.ProjectMember,
236
+ Permission.CreateProjectAIAgentTask,
237
+ ],
228
238
  read: [
229
239
  Permission.ProjectOwner,
230
240
  Permission.ProjectAdmin,
@@ -505,7 +515,12 @@ __decorate([
505
515
  ], AIAgentTask.prototype, "createdByUserId", void 0);
506
516
  __decorate([
507
517
  ColumnAccessControl({
508
- create: [],
518
+ create: [
519
+ Permission.ProjectOwner,
520
+ Permission.ProjectAdmin,
521
+ Permission.ProjectMember,
522
+ Permission.CreateProjectAIAgentTask,
523
+ ],
509
524
  read: [
510
525
  Permission.ProjectOwner,
511
526
  Permission.ProjectAdmin,