rusty-replay 0.0.4
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/.eslintrc.js +10 -0
- package/.vscode/settings.json +3 -0
- package/README.md +92 -0
- package/apps/web/README.md +11 -0
- package/apps/web/api/auth/keys.ts +3 -0
- package/apps/web/api/auth/types.ts +25 -0
- package/apps/web/api/auth/use-query-profile.ts +19 -0
- package/apps/web/api/auth/use-sign-in.ts +24 -0
- package/apps/web/api/axios.ts +122 -0
- package/apps/web/api/error-code.ts +36 -0
- package/apps/web/api/event/keys.ts +14 -0
- package/apps/web/api/event/types.ts +91 -0
- package/apps/web/api/event/use-mutation-event-assignee.ts +103 -0
- package/apps/web/api/event/use-mutation-event-priority.ts +97 -0
- package/apps/web/api/event/use-mutation-event-status.ts +198 -0
- package/apps/web/api/event/use-query-event-detail.ts +25 -0
- package/apps/web/api/event/use-query-event-list.ts +42 -0
- package/apps/web/api/health-check/index.ts +21 -0
- package/apps/web/api/project/keys.ts +4 -0
- package/apps/web/api/project/types.ts +28 -0
- package/apps/web/api/project/use-create-project.ts +30 -0
- package/apps/web/api/project/use-query-project-list.ts +19 -0
- package/apps/web/api/project/use-query-project-users.ts +23 -0
- package/apps/web/api/types.ts +44 -0
- package/apps/web/app/(auth)/layout.tsx +5 -0
- package/apps/web/app/(auth)/sign-in/page.tsx +20 -0
- package/apps/web/app/(auth)/sign-up/page.tsx +5 -0
- package/apps/web/app/(project)/project/[project_id]/issues/[issue_id]/page.tsx +17 -0
- package/apps/web/app/(project)/project/[project_id]/issues/page.tsx +15 -0
- package/apps/web/app/(project)/project/[project_id]/page.tsx +10 -0
- package/apps/web/app/(project)/project/page.tsx +10 -0
- package/apps/web/app/(report)/error-list/page.tsx +7 -0
- package/apps/web/app/favicon.ico +0 -0
- package/apps/web/app/layout.tsx +35 -0
- package/apps/web/app/page.tsx +3 -0
- package/apps/web/components/.gitkeep +0 -0
- package/apps/web/components/event-list/event-detail.tsx +242 -0
- package/apps/web/components/event-list/event-list.tsx +376 -0
- package/apps/web/components/event-list/preview.tsx +573 -0
- package/apps/web/components/layouts/default-layout.tsx +59 -0
- package/apps/web/components/login-form.tsx +124 -0
- package/apps/web/components/project/create-project.tsx +130 -0
- package/apps/web/components/project/hooks/use-get-event-params.ts +9 -0
- package/apps/web/components/project/hooks/use-get-project-params.ts +10 -0
- package/apps/web/components/project/project-detail.tsx +240 -0
- package/apps/web/components/project/project-list.tsx +137 -0
- package/apps/web/components/providers.tsx +25 -0
- package/apps/web/components/ui/assignee-dropdown.tsx +176 -0
- package/apps/web/components/ui/event-status-dropdown.tsx +104 -0
- package/apps/web/components/ui/priority-dropdown.tsx +123 -0
- package/apps/web/components/widget/app-sidebar.tsx +225 -0
- package/apps/web/components/widget/nav-main.tsx +73 -0
- package/apps/web/components/widget/nav-projects.tsx +84 -0
- package/apps/web/components/widget/nav-user.tsx +113 -0
- package/apps/web/components.json +20 -0
- package/apps/web/constants/routes.ts +12 -0
- package/apps/web/eslint.config.js +4 -0
- package/apps/web/hooks/use-boolean-state.ts +13 -0
- package/apps/web/lib/.gitkeep +0 -0
- package/apps/web/next-env.d.ts +5 -0
- package/apps/web/next.config.mjs +6 -0
- package/apps/web/package.json +60 -0
- package/apps/web/postcss.config.mjs +1 -0
- package/apps/web/providers/flag-provider.tsx +35 -0
- package/apps/web/providers/query-client-provider.tsx +17 -0
- package/apps/web/providers/telemetry-provider.tsx +12 -0
- package/apps/web/tsconfig.json +24 -0
- package/apps/web/utils/avatar.ts +26 -0
- package/apps/web/utils/date.ts +26 -0
- package/apps/web/utils/front-end-tracer.ts +119 -0
- package/apps/web/utils/schema/project.schema.ts +12 -0
- package/apps/web/utils/span-processor.ts +36 -0
- package/package.json +21 -0
- package/packages/eslint-config/README.md +3 -0
- package/packages/eslint-config/base.js +32 -0
- package/packages/eslint-config/next.js +51 -0
- package/packages/eslint-config/package.json +25 -0
- package/packages/eslint-config/react-internal.js +41 -0
- package/packages/rusty-replay/README.md +165 -0
- package/packages/rusty-replay/package.json +67 -0
- package/packages/rusty-replay/src/environment.ts +27 -0
- package/packages/rusty-replay/src/error-batcher.ts +75 -0
- package/packages/rusty-replay/src/front-end-tracer.ts +86 -0
- package/packages/rusty-replay/src/handler.ts +37 -0
- package/packages/rusty-replay/src/index.ts +8 -0
- package/packages/rusty-replay/src/recorder.ts +71 -0
- package/packages/rusty-replay/src/reporter.ts +115 -0
- package/packages/rusty-replay/src/utils.ts +13 -0
- package/packages/rusty-replay/tsconfig.build.json +13 -0
- package/packages/rusty-replay/tsconfig.json +27 -0
- package/packages/rusty-replay/tsup.config.ts +39 -0
- package/packages/typescript-config/README.md +3 -0
- package/packages/typescript-config/base.json +20 -0
- package/packages/typescript-config/nextjs.json +13 -0
- package/packages/typescript-config/package.json +9 -0
- package/packages/typescript-config/react-library.json +8 -0
- package/packages/ui/components.json +20 -0
- package/packages/ui/eslint.config.js +4 -0
- package/packages/ui/package.json +60 -0
- package/packages/ui/postcss.config.mjs +6 -0
- package/packages/ui/src/components/.gitkeep +0 -0
- package/packages/ui/src/components/avatar.tsx +53 -0
- package/packages/ui/src/components/badge.tsx +46 -0
- package/packages/ui/src/components/breadcrumb.tsx +109 -0
- package/packages/ui/src/components/button.tsx +59 -0
- package/packages/ui/src/components/calendar.tsx +75 -0
- package/packages/ui/src/components/calendars/date-picker.tsx +43 -0
- package/packages/ui/src/components/calendars/date-range-picker.tsx +79 -0
- package/packages/ui/src/components/card.tsx +92 -0
- package/packages/ui/src/components/checkbox.tsx +32 -0
- package/packages/ui/src/components/collapsible.tsx +33 -0
- package/packages/ui/src/components/dialog.tsx +135 -0
- package/packages/ui/src/components/dialogs/confirmation-modal.tsx +216 -0
- package/packages/ui/src/components/dropdown-menu.tsx +261 -0
- package/packages/ui/src/components/input.tsx +30 -0
- package/packages/ui/src/components/label.tsx +24 -0
- package/packages/ui/src/components/login-form.tsx +68 -0
- package/packages/ui/src/components/mode-switcher.tsx +34 -0
- package/packages/ui/src/components/popover.tsx +48 -0
- package/packages/ui/src/components/scroll-area.tsx +58 -0
- package/packages/ui/src/components/select.tsx +185 -0
- package/packages/ui/src/components/separator.tsx +28 -0
- package/packages/ui/src/components/sheet.tsx +139 -0
- package/packages/ui/src/components/sidebar.tsx +726 -0
- package/packages/ui/src/components/skeleton.tsx +13 -0
- package/packages/ui/src/components/sonner.tsx +25 -0
- package/packages/ui/src/components/table.tsx +116 -0
- package/packages/ui/src/components/tabs.tsx +66 -0
- package/packages/ui/src/components/team-switcher.tsx +91 -0
- package/packages/ui/src/components/textarea.tsx +18 -0
- package/packages/ui/src/components/tooltip.tsx +61 -0
- package/packages/ui/src/hooks/.gitkeep +0 -0
- package/packages/ui/src/hooks/use-meta-color.ts +28 -0
- package/packages/ui/src/hooks/use-mobile.ts +19 -0
- package/packages/ui/src/lib/utils.ts +6 -0
- package/packages/ui/src/styles/globals.css +138 -0
- package/packages/ui/tsconfig.json +13 -0
- package/packages/ui/tsconfig.lint.json +8 -0
- package/pnpm-workspace.yaml +4 -0
- package/tsconfig.json +4 -0
- package/turbo.json +21 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import {
|
4
|
+
Folder,
|
5
|
+
Forward,
|
6
|
+
MoreHorizontal,
|
7
|
+
Trash2,
|
8
|
+
type LucideIcon,
|
9
|
+
} from 'lucide-react';
|
10
|
+
|
11
|
+
import {
|
12
|
+
DropdownMenu,
|
13
|
+
DropdownMenuContent,
|
14
|
+
DropdownMenuItem,
|
15
|
+
DropdownMenuSeparator,
|
16
|
+
DropdownMenuTrigger,
|
17
|
+
} from '@workspace/ui/components/dropdown-menu';
|
18
|
+
import {
|
19
|
+
SidebarGroup,
|
20
|
+
SidebarGroupLabel,
|
21
|
+
SidebarMenu,
|
22
|
+
SidebarMenuAction,
|
23
|
+
SidebarMenuButton,
|
24
|
+
SidebarMenuItem,
|
25
|
+
useSidebar,
|
26
|
+
} from '@workspace/ui/components/sidebar';
|
27
|
+
import Link from 'next/link';
|
28
|
+
|
29
|
+
export function NavProjects({
|
30
|
+
projects,
|
31
|
+
}: {
|
32
|
+
projects: {
|
33
|
+
name: string;
|
34
|
+
url: string;
|
35
|
+
icon: LucideIcon;
|
36
|
+
}[];
|
37
|
+
}) {
|
38
|
+
const { isMobile } = useSidebar();
|
39
|
+
|
40
|
+
return (
|
41
|
+
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
42
|
+
<SidebarGroupLabel>Projects</SidebarGroupLabel>
|
43
|
+
<SidebarMenu>
|
44
|
+
{projects.map((item) => (
|
45
|
+
<SidebarMenuItem key={item.name}>
|
46
|
+
<SidebarMenuButton asChild>
|
47
|
+
<Link href={item.url}>
|
48
|
+
<item.icon />
|
49
|
+
<span>{item.name}</span>
|
50
|
+
</Link>
|
51
|
+
</SidebarMenuButton>
|
52
|
+
<DropdownMenu>
|
53
|
+
<DropdownMenuTrigger asChild>
|
54
|
+
<SidebarMenuAction showOnHover>
|
55
|
+
<MoreHorizontal />
|
56
|
+
<span className="sr-only">More</span>
|
57
|
+
</SidebarMenuAction>
|
58
|
+
</DropdownMenuTrigger>
|
59
|
+
<DropdownMenuContent
|
60
|
+
className="w-48 rounded-lg"
|
61
|
+
side={isMobile ? 'bottom' : 'right'}
|
62
|
+
align={isMobile ? 'end' : 'start'}
|
63
|
+
>
|
64
|
+
<DropdownMenuItem>
|
65
|
+
<Folder className="text-muted-foreground" />
|
66
|
+
<span>View Project</span>
|
67
|
+
</DropdownMenuItem>
|
68
|
+
<DropdownMenuItem>
|
69
|
+
<Forward className="text-muted-foreground" />
|
70
|
+
<span>Share Project</span>
|
71
|
+
</DropdownMenuItem>
|
72
|
+
<DropdownMenuSeparator />
|
73
|
+
<DropdownMenuItem>
|
74
|
+
<Trash2 className="text-muted-foreground" />
|
75
|
+
<span>Delete Project</span>
|
76
|
+
</DropdownMenuItem>
|
77
|
+
</DropdownMenuContent>
|
78
|
+
</DropdownMenu>
|
79
|
+
</SidebarMenuItem>
|
80
|
+
))}
|
81
|
+
</SidebarMenu>
|
82
|
+
</SidebarGroup>
|
83
|
+
);
|
84
|
+
}
|
@@ -0,0 +1,113 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import {
|
4
|
+
BadgeCheck,
|
5
|
+
Bell,
|
6
|
+
ChevronsUpDown,
|
7
|
+
CreditCard,
|
8
|
+
LogOut,
|
9
|
+
Sparkles,
|
10
|
+
} from 'lucide-react';
|
11
|
+
|
12
|
+
import {
|
13
|
+
Avatar,
|
14
|
+
AvatarFallback,
|
15
|
+
AvatarImage,
|
16
|
+
} from '@workspace/ui/components/avatar';
|
17
|
+
import {
|
18
|
+
DropdownMenu,
|
19
|
+
DropdownMenuContent,
|
20
|
+
DropdownMenuGroup,
|
21
|
+
DropdownMenuItem,
|
22
|
+
DropdownMenuLabel,
|
23
|
+
DropdownMenuSeparator,
|
24
|
+
DropdownMenuTrigger,
|
25
|
+
} from '@workspace/ui/components/dropdown-menu';
|
26
|
+
import {
|
27
|
+
SidebarMenu,
|
28
|
+
SidebarMenuButton,
|
29
|
+
SidebarMenuItem,
|
30
|
+
useSidebar,
|
31
|
+
} from '@workspace/ui/components/sidebar';
|
32
|
+
import { UserResponse } from '@/api/auth/types';
|
33
|
+
|
34
|
+
export function NavUser({ profile }: { profile: UserResponse | undefined }) {
|
35
|
+
const { isMobile } = useSidebar();
|
36
|
+
|
37
|
+
return (
|
38
|
+
<SidebarMenu>
|
39
|
+
<SidebarMenuItem>
|
40
|
+
<DropdownMenu>
|
41
|
+
<DropdownMenuTrigger asChild>
|
42
|
+
<SidebarMenuButton
|
43
|
+
size="lg"
|
44
|
+
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
45
|
+
>
|
46
|
+
<Avatar className="h-8 w-8 rounded-lg">
|
47
|
+
{/* <AvatarImage src={profile?.} alt={profile?.username} /> */}
|
48
|
+
<AvatarFallback className="rounded-lg">
|
49
|
+
{profile?.username.substring(0, 2)}
|
50
|
+
</AvatarFallback>
|
51
|
+
</Avatar>
|
52
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
53
|
+
<span className="truncate font-medium">
|
54
|
+
{profile?.username}
|
55
|
+
</span>
|
56
|
+
<span className="truncate text-xs">{profile?.email}</span>
|
57
|
+
</div>
|
58
|
+
<ChevronsUpDown className="ml-auto size-4" />
|
59
|
+
</SidebarMenuButton>
|
60
|
+
</DropdownMenuTrigger>
|
61
|
+
<DropdownMenuContent
|
62
|
+
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
63
|
+
side={isMobile ? 'bottom' : 'right'}
|
64
|
+
align="end"
|
65
|
+
sideOffset={4}
|
66
|
+
>
|
67
|
+
<DropdownMenuLabel className="p-0 font-normal">
|
68
|
+
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
69
|
+
<Avatar className="h-8 w-8 rounded-lg">
|
70
|
+
{/* <AvatarImage src={profile.avatar} alt={profile?.username} /> */}
|
71
|
+
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
72
|
+
</Avatar>
|
73
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
74
|
+
<span className="truncate font-medium">
|
75
|
+
{profile?.username}
|
76
|
+
</span>
|
77
|
+
<span className="truncate text-xs">{profile?.email}</span>
|
78
|
+
</div>
|
79
|
+
</div>
|
80
|
+
</DropdownMenuLabel>
|
81
|
+
<DropdownMenuSeparator />
|
82
|
+
<DropdownMenuGroup>
|
83
|
+
<DropdownMenuItem>
|
84
|
+
<Sparkles />
|
85
|
+
Upgrade to Pro
|
86
|
+
</DropdownMenuItem>
|
87
|
+
</DropdownMenuGroup>
|
88
|
+
<DropdownMenuSeparator />
|
89
|
+
<DropdownMenuGroup>
|
90
|
+
<DropdownMenuItem>
|
91
|
+
<BadgeCheck />
|
92
|
+
Account
|
93
|
+
</DropdownMenuItem>
|
94
|
+
<DropdownMenuItem>
|
95
|
+
<CreditCard />
|
96
|
+
Billing
|
97
|
+
</DropdownMenuItem>
|
98
|
+
<DropdownMenuItem>
|
99
|
+
<Bell />
|
100
|
+
Notifications
|
101
|
+
</DropdownMenuItem>
|
102
|
+
</DropdownMenuGroup>
|
103
|
+
<DropdownMenuSeparator />
|
104
|
+
<DropdownMenuItem>
|
105
|
+
<LogOut />
|
106
|
+
Log out
|
107
|
+
</DropdownMenuItem>
|
108
|
+
</DropdownMenuContent>
|
109
|
+
</DropdownMenu>
|
110
|
+
</SidebarMenuItem>
|
111
|
+
</SidebarMenu>
|
112
|
+
);
|
113
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
3
|
+
"style": "new-york",
|
4
|
+
"rsc": true,
|
5
|
+
"tsx": true,
|
6
|
+
"tailwind": {
|
7
|
+
"config": "",
|
8
|
+
"css": "../../packages/ui/src/styles/globals.css",
|
9
|
+
"baseColor": "neutral",
|
10
|
+
"cssVariables": true
|
11
|
+
},
|
12
|
+
"iconLibrary": "lucide",
|
13
|
+
"aliases": {
|
14
|
+
"components": "@/components",
|
15
|
+
"hooks": "@/hooks",
|
16
|
+
"lib": "@/lib",
|
17
|
+
"utils": "@workspace/ui/lib/utils",
|
18
|
+
"ui": "@workspace/ui/components"
|
19
|
+
}
|
20
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
export const routes = {
|
2
|
+
home: () => `/`,
|
3
|
+
event: {
|
4
|
+
detail: (projectId: number, issueId: number) =>
|
5
|
+
`/project/${projectId}/issues/${issueId}`,
|
6
|
+
list: (projectId: number) => `/project/${projectId}/issues`,
|
7
|
+
},
|
8
|
+
project: {
|
9
|
+
list: () => `/projects`,
|
10
|
+
detail: (projectId: number) => `/project/${projectId}`,
|
11
|
+
},
|
12
|
+
};
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { useState, useCallback } from 'react';
|
2
|
+
|
3
|
+
export function useBooleanState() {
|
4
|
+
const [isOpen, setIsOpen] = useState<boolean>(false);
|
5
|
+
|
6
|
+
const open = useCallback(() => setIsOpen(true), [setIsOpen]);
|
7
|
+
|
8
|
+
const close = useCallback(() => setIsOpen(false), [setIsOpen]);
|
9
|
+
|
10
|
+
const toggle = useCallback(() => setIsOpen((prev) => !prev), [setIsOpen]);
|
11
|
+
|
12
|
+
return { isOpen, open, close, toggle };
|
13
|
+
}
|
File without changes
|
@@ -0,0 +1,60 @@
|
|
1
|
+
{
|
2
|
+
"name": "web",
|
3
|
+
"version": "0.0.1",
|
4
|
+
"type": "module",
|
5
|
+
"private": true,
|
6
|
+
"scripts": {
|
7
|
+
"dev": "next dev --turbopack",
|
8
|
+
"build": "next build",
|
9
|
+
"start": "next start",
|
10
|
+
"lint": "next lint",
|
11
|
+
"lint:fix": "next lint --fix",
|
12
|
+
"typecheck": "tsc --noEmit"
|
13
|
+
},
|
14
|
+
"dependencies": {
|
15
|
+
"@hookform/resolvers": "^5.0.1",
|
16
|
+
"@openfeature/flagd-web-provider": "^0.7.3",
|
17
|
+
"@openfeature/react-sdk": "^1.0.0",
|
18
|
+
"@opentelemetry/auto-instrumentations-web": "^0.46.0",
|
19
|
+
"@opentelemetry/context-zone": "^2.0.0",
|
20
|
+
"@opentelemetry/core": "^2.0.0",
|
21
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
|
22
|
+
"@opentelemetry/exporter-trace-otlp-proto": "^0.200.0",
|
23
|
+
"@opentelemetry/instrumentation": "^0.200.0",
|
24
|
+
"@opentelemetry/instrumentation-fetch": "^0.200.0",
|
25
|
+
"@opentelemetry/instrumentation-xml-http-request": "^0.200.0",
|
26
|
+
"@opentelemetry/resources": "^2.0.0",
|
27
|
+
"@opentelemetry/sdk-trace-base": "^2.0.0",
|
28
|
+
"@opentelemetry/sdk-trace-web": "^2.0.0",
|
29
|
+
"@opentelemetry/semantic-conventions": "^1.32.0",
|
30
|
+
"@rrweb/packer": "2.0.0-alpha.18",
|
31
|
+
"@tanstack/react-query": "^5.74.4",
|
32
|
+
"@workspace/ui": "workspace:*",
|
33
|
+
"axios": "^1.8.4",
|
34
|
+
"dayjs": "^1.11.13",
|
35
|
+
"framer-motion": "^12.9.2",
|
36
|
+
"import-in-the-middle": "^1.13.1",
|
37
|
+
"lucide-react": "^0.475.0",
|
38
|
+
"next": "^15.2.3",
|
39
|
+
"next-themes": "^0.4.4",
|
40
|
+
"react": "^19.0.0",
|
41
|
+
"react-day-picker": "8.10.1",
|
42
|
+
"react-dom": "^19.0.0",
|
43
|
+
"react-hook-form": "^7.56.1",
|
44
|
+
"require-in-the-middle": "^7.5.2",
|
45
|
+
"rrweb": "2.0.0-alpha.4",
|
46
|
+
"rrweb-player": "1.0.0-alpha.4",
|
47
|
+
"rusty-replay": "workspace:*",
|
48
|
+
"zod": "^3.24.2"
|
49
|
+
},
|
50
|
+
"devDependencies": {
|
51
|
+
"@rrweb/types": "2.0.0-alpha.18",
|
52
|
+
"@tanstack/eslint-plugin-query": "^5.73.3",
|
53
|
+
"@types/node": "^20",
|
54
|
+
"@types/react": "^19",
|
55
|
+
"@types/react-dom": "^19",
|
56
|
+
"@workspace/eslint-config": "workspace:^",
|
57
|
+
"@workspace/typescript-config": "workspace:*",
|
58
|
+
"typescript": "^5.7.3"
|
59
|
+
}
|
60
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default } from "@workspace/ui/postcss.config";
|
@@ -0,0 +1,35 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import React, { PropsWithChildren, useEffect } from 'react';
|
4
|
+
import { OpenFeature } from '@openfeature/react-sdk';
|
5
|
+
import { FlagdWebProvider } from '@openfeature/flagd-web-provider';
|
6
|
+
|
7
|
+
export default function FlagProvider({ children }: PropsWithChildren) {
|
8
|
+
useEffect(() => {
|
9
|
+
// const session = SessionGateway.getSession();
|
10
|
+
// OpenFeature.setContext({ targetingKey: session.userId, ...session });
|
11
|
+
OpenFeature.setContext({ targetingKey: '1' })
|
12
|
+
.then(() => {
|
13
|
+
const useTLS = window.location.protocol === 'https:';
|
14
|
+
const port = window.location.port
|
15
|
+
? parseInt(window.location.port, 10)
|
16
|
+
: useTLS
|
17
|
+
? 443
|
18
|
+
: 80;
|
19
|
+
|
20
|
+
OpenFeature.setProvider(
|
21
|
+
new FlagdWebProvider({
|
22
|
+
host: window.location.hostname,
|
23
|
+
pathPrefix: 'flagservice',
|
24
|
+
port,
|
25
|
+
tls: useTLS,
|
26
|
+
maxRetries: 3,
|
27
|
+
maxDelay: 10000,
|
28
|
+
})
|
29
|
+
);
|
30
|
+
})
|
31
|
+
.catch(console.error);
|
32
|
+
}, []);
|
33
|
+
|
34
|
+
return <>{children}</>;
|
35
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import React, { PropsWithChildren, useEffect } from 'react';
|
4
|
+
import {
|
5
|
+
QueryClient,
|
6
|
+
QueryClientProvider as ReactQueryClientProvider,
|
7
|
+
} from '@tanstack/react-query';
|
8
|
+
|
9
|
+
const queryClient = new QueryClient({});
|
10
|
+
|
11
|
+
export default function QueryClientProvider({ children }: PropsWithChildren) {
|
12
|
+
return (
|
13
|
+
<ReactQueryClientProvider client={queryClient}>
|
14
|
+
{children}
|
15
|
+
</ReactQueryClientProvider>
|
16
|
+
);
|
17
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { FrontendTracer } from '@/utils/front-end-tracer';
|
4
|
+
import React, { PropsWithChildren, useEffect } from 'react';
|
5
|
+
|
6
|
+
export default function TelemetryProvider({ children }: PropsWithChildren) {
|
7
|
+
useEffect(() => {
|
8
|
+
FrontendTracer();
|
9
|
+
}, []);
|
10
|
+
|
11
|
+
return <>{children}</>;
|
12
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
{
|
2
|
+
"extends": "@workspace/typescript-config/nextjs.json",
|
3
|
+
"compilerOptions": {
|
4
|
+
"baseUrl": ".",
|
5
|
+
"paths": {
|
6
|
+
"@/*": ["./*"],
|
7
|
+
"@workspace/ui/*": ["../../packages/ui/src/*"],
|
8
|
+
"@workspace/rusty-replay/*": ["../../packages/rusty-replay/src/*"]
|
9
|
+
},
|
10
|
+
"plugins": [
|
11
|
+
{
|
12
|
+
"name": "next"
|
13
|
+
}
|
14
|
+
]
|
15
|
+
},
|
16
|
+
"include": [
|
17
|
+
"next-env.d.ts",
|
18
|
+
"next.config.ts",
|
19
|
+
"**/*.ts",
|
20
|
+
"**/*.tsx",
|
21
|
+
".next/types/**/*.ts"
|
22
|
+
],
|
23
|
+
"exclude": ["node_modules"]
|
24
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
export function getInitials(name: string) {
|
2
|
+
return name
|
3
|
+
.split(' ')
|
4
|
+
.map((word) => word[0])
|
5
|
+
.join('')
|
6
|
+
.toUpperCase()
|
7
|
+
.slice(0, 2);
|
8
|
+
}
|
9
|
+
|
10
|
+
export function getAvatarColor(name: string) {
|
11
|
+
const colors = [
|
12
|
+
'bg-blue-500',
|
13
|
+
'bg-purple-500',
|
14
|
+
'bg-orange-500',
|
15
|
+
'bg-green-500',
|
16
|
+
'bg-indigo-500',
|
17
|
+
'bg-teal-500',
|
18
|
+
'bg-pink-500',
|
19
|
+
];
|
20
|
+
|
21
|
+
let hash = 0;
|
22
|
+
for (let i = 0; i < name.length; i++) {
|
23
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
24
|
+
}
|
25
|
+
return colors[Math.abs(hash) % colors.length];
|
26
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import dayjs from 'dayjs';
|
2
|
+
|
3
|
+
export function formatDateFromNow(dateString: string | null) {
|
4
|
+
if (!dateString) {
|
5
|
+
return '';
|
6
|
+
}
|
7
|
+
|
8
|
+
try {
|
9
|
+
return dayjs(dateString).fromNow();
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
11
|
+
} catch (e) {
|
12
|
+
return dateString;
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
export function formatDate(
|
17
|
+
dateString: string,
|
18
|
+
format: string = 'YYYY-MM-DD HH:mm:ss'
|
19
|
+
) {
|
20
|
+
try {
|
21
|
+
return dayjs(dateString).format(format);
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
23
|
+
} catch (e) {
|
24
|
+
return dateString;
|
25
|
+
}
|
26
|
+
}
|
@@ -0,0 +1,119 @@
|
|
1
|
+
import {
|
2
|
+
CompositePropagator,
|
3
|
+
W3CBaggagePropagator,
|
4
|
+
W3CTraceContextPropagator,
|
5
|
+
} from '@opentelemetry/core';
|
6
|
+
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
|
7
|
+
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
8
|
+
import { registerInstrumentations } from '@opentelemetry/instrumentation';
|
9
|
+
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';
|
10
|
+
import { resourceFromAttributes, osDetector } from '@opentelemetry/resources';
|
11
|
+
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
|
12
|
+
// import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
13
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
|
14
|
+
// import { SessionIdProcessor } from './SessionIdProcessor';
|
15
|
+
import { detectResources } from '@opentelemetry/resources/build/src/detect-resources';
|
16
|
+
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
|
17
|
+
import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';
|
18
|
+
|
19
|
+
declare global {
|
20
|
+
interface Window {
|
21
|
+
ENV: {
|
22
|
+
NEXT_PUBLIC_PLATFORM?: string;
|
23
|
+
NEXT_PUBLIC_OTEL_SERVICE_NAME?: string;
|
24
|
+
NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?: string;
|
25
|
+
IS_SYNTHETIC_REQUEST?: string;
|
26
|
+
};
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
const {
|
31
|
+
NEXT_PUBLIC_OTEL_SERVICE_NAME = 'replay',
|
32
|
+
NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = '',
|
33
|
+
IS_SYNTHETIC_REQUEST = '',
|
34
|
+
} = typeof window !== 'undefined' && window.ENV
|
35
|
+
? window.ENV
|
36
|
+
: {
|
37
|
+
NEXT_PUBLIC_OTEL_SERVICE_NAME:
|
38
|
+
process.env.NEXT_PUBLIC_OTEL_SERVICE_NAME || 'replay',
|
39
|
+
NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:
|
40
|
+
process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || '',
|
41
|
+
IS_SYNTHETIC_REQUEST: process.env.IS_SYNTHETIC_REQUEST || '',
|
42
|
+
};
|
43
|
+
|
44
|
+
const endpoint =
|
45
|
+
NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||
|
46
|
+
'http://localhost:8081/traces';
|
47
|
+
|
48
|
+
export const FrontendTracer = async () => {
|
49
|
+
const { ZoneContextManager } = await import('@opentelemetry/context-zone');
|
50
|
+
|
51
|
+
let resource = resourceFromAttributes({
|
52
|
+
[ATTR_SERVICE_NAME]: NEXT_PUBLIC_OTEL_SERVICE_NAME,
|
53
|
+
});
|
54
|
+
const detectedResources = detectResources({ detectors: [osDetector] });
|
55
|
+
resource = resource.merge(detectedResources);
|
56
|
+
|
57
|
+
const provider = new WebTracerProvider({
|
58
|
+
resource,
|
59
|
+
spanProcessors: [
|
60
|
+
// new SessionIdProcessor(),
|
61
|
+
new BatchSpanProcessor(
|
62
|
+
new OTLPTraceExporter({
|
63
|
+
url:
|
64
|
+
NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||
|
65
|
+
'http://localhost:8081/traces',
|
66
|
+
|
67
|
+
headers: {
|
68
|
+
'Content-Type': 'application/x-protobuf',
|
69
|
+
},
|
70
|
+
}),
|
71
|
+
{
|
72
|
+
scheduledDelayMillis: 500,
|
73
|
+
}
|
74
|
+
),
|
75
|
+
],
|
76
|
+
});
|
77
|
+
|
78
|
+
const contextManager = new ZoneContextManager();
|
79
|
+
|
80
|
+
provider.register({
|
81
|
+
contextManager,
|
82
|
+
// fetch, xhr을 제외한 모든 event
|
83
|
+
// propagator: new CompositePropagator({
|
84
|
+
// propagators: [
|
85
|
+
// new W3CBaggagePropagator(),
|
86
|
+
// new W3CTraceContextPropagator(),
|
87
|
+
// ],
|
88
|
+
// }),
|
89
|
+
});
|
90
|
+
|
91
|
+
const backendOrigin = new URL(endpoint).origin; // "http://localhost:8081"
|
92
|
+
|
93
|
+
const fetchInstrumentation = new FetchInstrumentation({
|
94
|
+
propagateTraceHeaderCorsUrls: [backendOrigin],
|
95
|
+
clearTimingResources: true,
|
96
|
+
});
|
97
|
+
|
98
|
+
const xhrInstrumentation = new XMLHttpRequestInstrumentation({
|
99
|
+
propagateTraceHeaderCorsUrls: [backendOrigin],
|
100
|
+
clearTimingResources: true,
|
101
|
+
});
|
102
|
+
|
103
|
+
registerInstrumentations({
|
104
|
+
tracerProvider: provider,
|
105
|
+
instrumentations: [
|
106
|
+
fetchInstrumentation,
|
107
|
+
xhrInstrumentation,
|
108
|
+
// getWebAutoInstrumentations({
|
109
|
+
// '@opentelemetry/instrumentation-fetch': {
|
110
|
+
// propagateTraceHeaderCorsUrls: /.*/,
|
111
|
+
// clearTimingResources: true,
|
112
|
+
// applyCustomAttributesOnSpan(span) {
|
113
|
+
// span.setAttribute('app.synthetic_request', IS_SYNTHETIC_REQUEST);
|
114
|
+
// },
|
115
|
+
// },
|
116
|
+
// }),
|
117
|
+
],
|
118
|
+
});
|
119
|
+
};
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { z } from 'zod';
|
2
|
+
|
3
|
+
export const CreateProjectSchema = z.object({
|
4
|
+
name: z
|
5
|
+
.string({
|
6
|
+
required_error: '프로젝트 이름을 입력하세요',
|
7
|
+
})
|
8
|
+
.min(1, { message: '프로젝트 이름을 입력하세요' }),
|
9
|
+
description: z.string().optional().nullable(),
|
10
|
+
});
|
11
|
+
|
12
|
+
export type CreateProjectSchemaType = z.infer<typeof CreateProjectSchema>;
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import {
|
2
|
+
SimpleSpanProcessor,
|
3
|
+
ReadableSpan,
|
4
|
+
SpanProcessor,
|
5
|
+
} from '@opentelemetry/sdk-trace-base';
|
6
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
|
7
|
+
|
8
|
+
class EndpointOnlySpanProcessor implements SpanProcessor {
|
9
|
+
private delegate = new SimpleSpanProcessor(
|
10
|
+
new OTLPTraceExporter({
|
11
|
+
url: process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
|
12
|
+
headers: { 'Content-Type': 'application/x-protobuf' },
|
13
|
+
})
|
14
|
+
);
|
15
|
+
|
16
|
+
onStart(_span: ReadableSpan) {}
|
17
|
+
|
18
|
+
onEnd(span: ReadableSpan) {
|
19
|
+
const url = span.attributes['http.url'] as string | undefined;
|
20
|
+
if (
|
21
|
+
url &&
|
22
|
+
url.startsWith(
|
23
|
+
process.env.NEXT_PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT!
|
24
|
+
)
|
25
|
+
) {
|
26
|
+
this.delegate.onEnd(span);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
shutdown() {
|
31
|
+
return this.delegate.shutdown();
|
32
|
+
}
|
33
|
+
forceFlush() {
|
34
|
+
return this.delegate.forceFlush();
|
35
|
+
}
|
36
|
+
}
|
package/package.json
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
{
|
2
|
+
"name": "rusty-replay",
|
3
|
+
"version": "0.0.4",
|
4
|
+
"scripts": {
|
5
|
+
"build": "turbo build",
|
6
|
+
"dev": "turbo dev",
|
7
|
+
"lint": "turbo lint",
|
8
|
+
"format": "prettier --write \"**/*.{ts,tsx,md}\""
|
9
|
+
},
|
10
|
+
"devDependencies": {
|
11
|
+
"@workspace/eslint-config": "workspace:*",
|
12
|
+
"@workspace/typescript-config": "workspace:*",
|
13
|
+
"prettier": "^3.5.1",
|
14
|
+
"turbo": "^2.4.2",
|
15
|
+
"typescript": "5.7.3"
|
16
|
+
},
|
17
|
+
"packageManager": "pnpm@10.4.1",
|
18
|
+
"engines": {
|
19
|
+
"node": ">=20"
|
20
|
+
}
|
21
|
+
}
|