@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.
- package/Models/DatabaseModels/AIAgentTask.ts +18 -3
- package/Models/DatabaseModels/Index.ts +0 -5
- package/Models/DatabaseModels/MetricType.ts +7 -7
- package/Models/DatabaseModels/Service.ts +34 -8
- package/Models/DatabaseModels/TelemetryException.ts +11 -13
- package/Models/DatabaseModels/TelemetryUsageBilling.ts +11 -13
- package/Server/API/AIAgentDataAPI.ts +71 -108
- package/Server/Infrastructure/Postgres/SchemaMigrations/1767979055522-MigrationName.ts +743 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1767979448478-MigrationName.ts +224 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/Index.ts +0 -5
- package/Server/Services/OpenTelemetryIngestService.ts +26 -28
- package/Server/Services/ServiceService.ts +23 -0
- package/Server/Services/TelemetryExceptionService.ts +3 -4
- package/Server/Services/TelemetryUsageBillingService.ts +30 -32
- package/Server/Utils/Telemetry/Telemetry.ts +24 -23
- package/Types/AI/AIAgentTaskMetadata.ts +1 -1
- package/UI/Components/LogsViewer/LogsViewer.tsx +20 -23
- package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +3 -3
- package/UI/Components/LogsViewer/components/LogsTable.tsx +3 -4
- package/UI/Components/Navbar/NavBar.tsx +146 -52
- package/UI/Components/Navbar/NavBarMenu.tsx +87 -7
- package/UI/Components/Navbar/NavBarMenuItem.tsx +12 -0
- package/build/dist/Models/DatabaseModels/AIAgentTask.js +18 -3
- package/build/dist/Models/DatabaseModels/AIAgentTask.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +0 -4
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MetricType.js +8 -8
- package/build/dist/Models/DatabaseModels/MetricType.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +36 -8
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +14 -14
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryUsageBilling.js +14 -14
- package/build/dist/Models/DatabaseModels/TelemetryUsageBilling.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +47 -76
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1767979055522-MigrationName.js +254 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1767979055522-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1767979448478-MigrationName.js +140 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1767979448478-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/Index.js +0 -4
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +7 -7
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +23 -0
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryExceptionService.js +3 -4
- package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +15 -16
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +20 -20
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +5 -5
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBar.js +58 -13
- package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenu.js +37 -5
- package/build/dist/UI/Components/Navbar/NavBarMenu.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuItem.js +12 -0
- package/build/dist/UI/Components/Navbar/NavBarMenuItem.js.map +1 -1
- package/package.json +1 -1
- package/Models/DatabaseModels/ServiceTelemetryService.ts +0 -419
- package/Models/DatabaseModels/TelemetryService.ts +0 -529
- package/Server/Services/ServiceTelemetryServiceService.ts +0 -59
- package/Server/Services/TelemetryServiceService.ts +0 -53
- package/build/dist/Models/DatabaseModels/ServiceTelemetryService.js +0 -436
- package/build/dist/Models/DatabaseModels/ServiceTelemetryService.js.map +0 -1
- package/build/dist/Models/DatabaseModels/TelemetryService.js +0 -545
- package/build/dist/Models/DatabaseModels/TelemetryService.js.map +0 -1
- package/build/dist/Server/Services/ServiceTelemetryServiceService.js +0 -56
- package/build/dist/Server/Services/ServiceTelemetryServiceService.js.map +0 -1
- package/build/dist/Server/Services/TelemetryServiceService.js +0 -59
- 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
|
|
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
|
-
|
|
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
|
|
39
|
-
const
|
|
40
|
-
metricTypeInMap?.
|
|
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.
|
|
46
|
-
metricType.
|
|
45
|
+
if (!metricType.services) {
|
|
46
|
+
metricType.services = [];
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const
|
|
50
|
-
|
|
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
|
|
69
|
+
// check if services are same
|
|
69
70
|
|
|
70
|
-
for (const
|
|
71
|
+
for (const serviceId of servicesInMap) {
|
|
71
72
|
if (
|
|
72
|
-
|
|
73
|
-
return
|
|
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
|
|
79
|
-
|
|
80
|
-
metricType.
|
|
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
|
-
|
|
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.
|
|
108
|
+
metricType.services = [];
|
|
108
109
|
|
|
109
|
-
for (const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
metricType.
|
|
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
|
-
|
|
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
|
|
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<
|
|
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
|
|
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<
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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:
|
|
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
|
|
319
|
-
}, [
|
|
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
|
|
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<
|
|
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:
|
|
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
|
|
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<
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
303
|
+
<nav
|
|
304
|
+
className={props.rightElement ? `flex justify-between items-center` : ""}
|
|
305
|
+
>
|
|
253
306
|
<div data-testid="nav-children" className={className}>
|
|
254
|
-
{
|
|
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
|
-
|
|
9
|
+
items: Array<ReactElement>;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export interface ComponentProps {
|
|
13
|
-
children
|
|
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
|
-
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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,
|