@lensjs/core 1.0.1
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/README.md +1 -0
- package/copy-front-build.cjs +16 -0
- package/package.json +40 -0
- package/src/abstracts/adapter.ts +41 -0
- package/src/abstracts/store.ts +36 -0
- package/src/context/container.ts +67 -0
- package/src/context/context.ts +9 -0
- package/src/core/api_controller.ts +116 -0
- package/src/core/lens.ts +147 -0
- package/src/core/watcher.ts +6 -0
- package/src/index.ts +11 -0
- package/src/stores/better_sqlite.ts +176 -0
- package/src/stores/index.ts +1 -0
- package/src/types/index.ts +103 -0
- package/src/ui/README.md +69 -0
- package/src/ui/bun.lock +750 -0
- package/src/ui/eslint.config.js +27 -0
- package/src/ui/index.html +13 -0
- package/src/ui/package-lock.json +2953 -0
- package/src/ui/package.json +40 -0
- package/src/ui/public/favicon.ico +0 -0
- package/src/ui/src/App.tsx +40 -0
- package/src/ui/src/components/DetailPanel.tsx +70 -0
- package/src/ui/src/components/JsonViewer.tsx +232 -0
- package/src/ui/src/components/LoadMore.tsx +25 -0
- package/src/ui/src/components/MethodBadge.tsx +19 -0
- package/src/ui/src/components/Modal.tsx +48 -0
- package/src/ui/src/components/StatusCode.tsx +20 -0
- package/src/ui/src/components/Table.tsx +127 -0
- package/src/ui/src/components/layout/DeleteButton.tsx +60 -0
- package/src/ui/src/components/layout/Footer.tsx +12 -0
- package/src/ui/src/components/layout/Header.tsx +40 -0
- package/src/ui/src/components/layout/Layout.tsx +49 -0
- package/src/ui/src/components/layout/LoadingScreen.tsx +14 -0
- package/src/ui/src/components/layout/Sidebar.tsx +67 -0
- package/src/ui/src/components/queryFormatters/MongoViewer.tsx +92 -0
- package/src/ui/src/components/queryFormatters/QueryViewer.tsx +18 -0
- package/src/ui/src/components/queryFormatters/SqlViewer.tsx +105 -0
- package/src/ui/src/components/table/NoData.tsx +26 -0
- package/src/ui/src/components/tabs/TabbedDataViewer.tsx +77 -0
- package/src/ui/src/containers/queries/QueriesContainer.tsx +21 -0
- package/src/ui/src/containers/queries/QueryDetailsContainer.tsx +15 -0
- package/src/ui/src/containers/requests/RequestDetailsContainer.tsx +16 -0
- package/src/ui/src/containers/requests/RequestsContainer.tsx +22 -0
- package/src/ui/src/hooks/useLensApi.ts +92 -0
- package/src/ui/src/hooks/useLoadMore.ts +48 -0
- package/src/ui/src/hooks/useQueries.ts +58 -0
- package/src/ui/src/hooks/useRequests.ts +79 -0
- package/src/ui/src/hooks/useTanstackApi.ts +126 -0
- package/src/ui/src/index.css +78 -0
- package/src/ui/src/interfaces/index.ts +10 -0
- package/src/ui/src/main.tsx +33 -0
- package/src/ui/src/router/Router.ts +11 -0
- package/src/ui/src/router/routes/Loading.tsx +5 -0
- package/src/ui/src/router/routes/index.tsx +85 -0
- package/src/ui/src/types/index.ts +95 -0
- package/src/ui/src/utils/api.ts +7 -0
- package/src/ui/src/utils/context.ts +24 -0
- package/src/ui/src/utils/date.ts +36 -0
- package/src/ui/src/views/queries/QueryDetails.tsx +58 -0
- package/src/ui/src/views/queries/QueryTable.tsx +21 -0
- package/src/ui/src/views/queries/columns.tsx +83 -0
- package/src/ui/src/views/requests/BasicRequestDetails.tsx +82 -0
- package/src/ui/src/views/requests/RequestDetails.tsx +70 -0
- package/src/ui/src/views/requests/RequetsTable.tsx +19 -0
- package/src/ui/src/views/requests/columns.tsx +62 -0
- package/src/ui/src/vite-env.d.ts +1 -0
- package/src/ui/tsconfig.app.json +27 -0
- package/src/ui/tsconfig.json +7 -0
- package/src/ui/tsconfig.node.json +25 -0
- package/src/ui/vite.config.ts +9 -0
- package/src/utils/event_emitter.ts +13 -0
- package/src/utils/index.ts +176 -0
- package/src/watchers/index.ts +2 -0
- package/src/watchers/query_watcher.ts +15 -0
- package/src/watchers/request_watcher.ts +27 -0
- package/tests/core/lens.test.ts +89 -0
- package/tests/stores/better_sqlite.test.ts +168 -0
- package/tests/utils/index.test.ts +182 -0
- package/tests/watchers/query_watcher.test.ts +35 -0
- package/tests/watchers/request_watcher.test.ts +59 -0
- package/tsconfig.json +3 -0
- package/tsup.config.ts +15 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import type { HasMoreType } from "../types";
|
|
3
|
+
import type { UseLoadMoreOptions } from "../interfaces";
|
|
4
|
+
|
|
5
|
+
export function useLoadMore<T>({
|
|
6
|
+
paginatedPage,
|
|
7
|
+
}: UseLoadMoreOptions<T>): HasMoreType<T> {
|
|
8
|
+
const [data, setData] = useState<T[]>([]);
|
|
9
|
+
const [page, setPage] = useState(1);
|
|
10
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
11
|
+
const [hasMore, setHasMore] = useState<boolean>(false);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
setLoading(paginatedPage.loading);
|
|
15
|
+
setHasMore(paginatedPage.meta.currentPage < paginatedPage.meta.lastPage);
|
|
16
|
+
setPage(1);
|
|
17
|
+
|
|
18
|
+
if (!paginatedPage.loading) {
|
|
19
|
+
setData(paginatedPage.initialData);
|
|
20
|
+
}
|
|
21
|
+
}, [
|
|
22
|
+
paginatedPage.initialData,
|
|
23
|
+
paginatedPage.loading,
|
|
24
|
+
paginatedPage.meta.currentPage,
|
|
25
|
+
paginatedPage.meta.lastPage
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const loadMore = async () => {
|
|
29
|
+
if (loading || !hasMore) return;
|
|
30
|
+
|
|
31
|
+
setLoading(true);
|
|
32
|
+
const newPage = page + 1;
|
|
33
|
+
const newData = await paginatedPage.fetchRawPage(newPage);
|
|
34
|
+
|
|
35
|
+
setData((prev) => [...prev, ...newData!.data!]);
|
|
36
|
+
setPage(newPage);
|
|
37
|
+
|
|
38
|
+
if (!newData?.meta) {
|
|
39
|
+
setHasMore(false);
|
|
40
|
+
} else {
|
|
41
|
+
setHasMore(newData.meta.currentPage < newData.meta.lastPage);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setLoading(false);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return { data, loading, hasMore, loadMore };
|
|
48
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import type { PaginatorMeta, QueryEntry, QueryTableRow } from "../types";
|
|
3
|
+
import useLensApi, { DEFAULT_META } from "./useLensApi";
|
|
4
|
+
|
|
5
|
+
export default function useQueries() {
|
|
6
|
+
const [queries, setQueries] = useState<QueryTableRow[]>([]);
|
|
7
|
+
const [query, setQuery] = useState<QueryEntry>();
|
|
8
|
+
const [loading, setLoading] = useState(false);
|
|
9
|
+
const [meta, setMeta] = useState<PaginatorMeta>(DEFAULT_META);
|
|
10
|
+
|
|
11
|
+
const { getQueries, getQueryById } = useLensApi();
|
|
12
|
+
const fetchQuery = useCallback(
|
|
13
|
+
async (id: string) => {
|
|
14
|
+
setLoading(true);
|
|
15
|
+
getQueryById(id)
|
|
16
|
+
.then((res) => {
|
|
17
|
+
console.log(res, "das2s");
|
|
18
|
+
setQuery(res.data!);
|
|
19
|
+
})
|
|
20
|
+
.finally(() => {
|
|
21
|
+
setLoading(false);
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
[getQueries]
|
|
25
|
+
);
|
|
26
|
+
const fetchQueries = useCallback(
|
|
27
|
+
async (page?: number) => {
|
|
28
|
+
setLoading(true);
|
|
29
|
+
await getQueries(page ?? 1)
|
|
30
|
+
.then((res) => {
|
|
31
|
+
setQueries(res.data!);
|
|
32
|
+
setMeta(res.meta!);
|
|
33
|
+
})
|
|
34
|
+
.finally(() => {
|
|
35
|
+
setLoading(false);
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
[getQueries]
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const loadMoreRequests = useMemo(
|
|
42
|
+
() => ({
|
|
43
|
+
initialData: queries,
|
|
44
|
+
meta,
|
|
45
|
+
loading,
|
|
46
|
+
fetchRawPage: getQueries,
|
|
47
|
+
}),
|
|
48
|
+
[queries, meta, loading, getQueries]
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
loadMoreRequests,
|
|
53
|
+
fetchQueries,
|
|
54
|
+
fetchQuery,
|
|
55
|
+
queries,
|
|
56
|
+
query,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import type { OneRequest, PaginatorMeta, RequestTableRow } from "../types";
|
|
3
|
+
import useLensApi, { DEFAULT_META } from "./useLensApi";
|
|
4
|
+
|
|
5
|
+
const defaultRequest: OneRequest = {
|
|
6
|
+
created_at: "",
|
|
7
|
+
data: {
|
|
8
|
+
body: {},
|
|
9
|
+
createdAt: "",
|
|
10
|
+
duration: "",
|
|
11
|
+
headers: {},
|
|
12
|
+
id: "",
|
|
13
|
+
ip: "",
|
|
14
|
+
method: "GET",
|
|
15
|
+
path: "",
|
|
16
|
+
response: {
|
|
17
|
+
headers: {},
|
|
18
|
+
json: {},
|
|
19
|
+
},
|
|
20
|
+
status: 0,
|
|
21
|
+
user: null,
|
|
22
|
+
},
|
|
23
|
+
id: "",
|
|
24
|
+
lens_entry_id: null,
|
|
25
|
+
type: "request",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default function useRequests() {
|
|
29
|
+
const [requests, setRequests] = useState<RequestTableRow[]>([]);
|
|
30
|
+
const [request, setRequest] = useState<OneRequest>(defaultRequest);
|
|
31
|
+
const [loading, setLoading] = useState(false);
|
|
32
|
+
const [meta, setMeta] = useState<PaginatorMeta>(DEFAULT_META);
|
|
33
|
+
|
|
34
|
+
const { getAllRequests, getRequestById } = useLensApi();
|
|
35
|
+
const fetchRequest = useCallback(
|
|
36
|
+
async (id: string) => {
|
|
37
|
+
setLoading(true);
|
|
38
|
+
getRequestById(id)
|
|
39
|
+
.then((res) => {
|
|
40
|
+
setRequest(res.data!);
|
|
41
|
+
})
|
|
42
|
+
.finally(() => {
|
|
43
|
+
setLoading(false);
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
[getRequestById],
|
|
47
|
+
);
|
|
48
|
+
const fetchRequests = useCallback(
|
|
49
|
+
async (page?: number) => {
|
|
50
|
+
setLoading(true);
|
|
51
|
+
getAllRequests(page)
|
|
52
|
+
.then((res) => {
|
|
53
|
+
setRequests(res.data!);
|
|
54
|
+
setMeta(res.meta!);
|
|
55
|
+
})
|
|
56
|
+
.finally(() => {
|
|
57
|
+
setLoading(false);
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
[getAllRequests],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const loadMoreRequests = useMemo(
|
|
64
|
+
() => ({
|
|
65
|
+
initialData: requests,
|
|
66
|
+
meta,
|
|
67
|
+
loading,
|
|
68
|
+
fetchRawPage: getAllRequests,
|
|
69
|
+
}),
|
|
70
|
+
[requests, meta, loading, getAllRequests],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
loadMoreRequests,
|
|
75
|
+
fetchRequests,
|
|
76
|
+
fetchRequest,
|
|
77
|
+
request,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { useQuery, type UseQueryOptions } from "@tanstack/react-query";
|
|
2
|
+
import type {
|
|
3
|
+
ApiResponse,
|
|
4
|
+
GenericLensEntry,
|
|
5
|
+
PaginatorMeta,
|
|
6
|
+
QueryEntry,
|
|
7
|
+
QueryTableRow,
|
|
8
|
+
RequestEntry,
|
|
9
|
+
RequestTableRow,
|
|
10
|
+
} from "../types";
|
|
11
|
+
import { prepareApiUrl } from "../utils/api";
|
|
12
|
+
import { useConfig } from "../utils/context";
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_META: PaginatorMeta = {
|
|
15
|
+
currentPage: 1,
|
|
16
|
+
lastPage: 1,
|
|
17
|
+
total: 0,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
async function fetchJson<TData>(
|
|
21
|
+
url: string,
|
|
22
|
+
options?: RequestInit
|
|
23
|
+
): Promise<ApiResponse<TData>> {
|
|
24
|
+
const res = await fetch(url, {
|
|
25
|
+
headers: { "Content-Type": "application/json" },
|
|
26
|
+
...options,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
throw new Error(`Failed to fetch: ${url}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return res.json();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const withQueryParams = (
|
|
37
|
+
endpoint: string,
|
|
38
|
+
params?: Record<string, unknown>
|
|
39
|
+
) => {
|
|
40
|
+
const searchParams = new URLSearchParams(
|
|
41
|
+
Object.entries(params || {}).reduce(
|
|
42
|
+
(acc, [key, value]) => {
|
|
43
|
+
if (value !== undefined && value !== null) {
|
|
44
|
+
acc[key] = String(value);
|
|
45
|
+
}
|
|
46
|
+
return acc;
|
|
47
|
+
},
|
|
48
|
+
{} as Record<string, string>
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return `${endpoint}${searchParams.toString() ? `?${searchParams}` : ""}`;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function useAllRequests(
|
|
56
|
+
page?: number,
|
|
57
|
+
options?: UseQueryOptions<ApiResponse<RequestTableRow[]>>
|
|
58
|
+
) {
|
|
59
|
+
const config = useConfig();
|
|
60
|
+
return useQuery<ApiResponse<RequestTableRow[]>>({
|
|
61
|
+
queryKey: ["requests", page],
|
|
62
|
+
queryFn: () =>
|
|
63
|
+
fetchJson<RequestTableRow[]>(
|
|
64
|
+
prepareApiUrl(
|
|
65
|
+
withQueryParams(config.api.requests, {
|
|
66
|
+
page,
|
|
67
|
+
})
|
|
68
|
+
)
|
|
69
|
+
),
|
|
70
|
+
...options,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function useRequestById(
|
|
75
|
+
id: string,
|
|
76
|
+
options?: UseQueryOptions<ApiResponse<GenericLensEntry<RequestEntry>>>
|
|
77
|
+
) {
|
|
78
|
+
const config = useConfig();
|
|
79
|
+
return useQuery<ApiResponse<GenericLensEntry<RequestEntry>>>({
|
|
80
|
+
queryKey: ["request", id],
|
|
81
|
+
queryFn: async () =>
|
|
82
|
+
await fetchJson<GenericLensEntry<RequestEntry>>(
|
|
83
|
+
prepareApiUrl(`${config.api.requests}/${id}`)
|
|
84
|
+
),
|
|
85
|
+
...{
|
|
86
|
+
enabled: !!id,
|
|
87
|
+
...options,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function useQueries(
|
|
93
|
+
page: number,
|
|
94
|
+
options?: UseQueryOptions<ApiResponse<QueryTableRow[]>>
|
|
95
|
+
) {
|
|
96
|
+
const config = useConfig();
|
|
97
|
+
return useQuery<ApiResponse<QueryTableRow[]>>({
|
|
98
|
+
queryKey: ["queries", page],
|
|
99
|
+
queryFn: () =>
|
|
100
|
+
fetchJson<QueryTableRow[]>(
|
|
101
|
+
prepareApiUrl(
|
|
102
|
+
withQueryParams(config.api.queries, {
|
|
103
|
+
page,
|
|
104
|
+
})
|
|
105
|
+
)
|
|
106
|
+
),
|
|
107
|
+
...options,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function useQueryById(
|
|
112
|
+
id: string,
|
|
113
|
+
options?: UseQueryOptions<ApiResponse<QueryEntry>>
|
|
114
|
+
) {
|
|
115
|
+
const config = useConfig();
|
|
116
|
+
return useQuery<ApiResponse<QueryEntry>>({
|
|
117
|
+
queryKey: ["query", id],
|
|
118
|
+
|
|
119
|
+
queryFn: () =>
|
|
120
|
+
fetchJson<QueryEntry>(prepareApiUrl(`${config.api.queries}/${id}`)),
|
|
121
|
+
...{
|
|
122
|
+
enabled: !!id,
|
|
123
|
+
...options,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@theme inline {
|
|
4
|
+
--color-background: var(--background);
|
|
5
|
+
--color-foreground: var(--foreground);
|
|
6
|
+
--font-sans: var(--font-geist-sans);
|
|
7
|
+
--font-mono: var(--font-geist-mono);
|
|
8
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
9
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
10
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
11
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
12
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
13
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
14
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
15
|
+
--color-sidebar: var(--sidebar);
|
|
16
|
+
--color-chart-5: var(--chart-5);
|
|
17
|
+
--color-chart-4: var(--chart-4);
|
|
18
|
+
--color-chart-3: var(--chart-3);
|
|
19
|
+
--color-chart-2: var(--chart-2);
|
|
20
|
+
--color-chart-1: var(--chart-1);
|
|
21
|
+
--color-ring: var(--ring);
|
|
22
|
+
--color-input: var(--input);
|
|
23
|
+
--color-border: var(--border);
|
|
24
|
+
--color-destructive: var(--destructive);
|
|
25
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
26
|
+
--color-accent: var(--accent);
|
|
27
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
28
|
+
--color-muted: var(--muted);
|
|
29
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
30
|
+
--color-secondary: var(--secondary);
|
|
31
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
32
|
+
--color-primary: var(--primary);
|
|
33
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
34
|
+
--color-popover: var(--popover);
|
|
35
|
+
--color-card-foreground: var(--card-foreground);
|
|
36
|
+
--color-card: var(--card);
|
|
37
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
38
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
39
|
+
--radius-lg: var(--radius);
|
|
40
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
41
|
+
--color-secondary-bg: light-dark(#f8f8f8, #191a2e);
|
|
42
|
+
--color-Gray: light-dark(#f8f8f8, #2d2d2d);
|
|
43
|
+
--color-dark-Gray: #2d2d2d;
|
|
44
|
+
--color-LightGray: #bcbcbc;
|
|
45
|
+
--color-DarkBlue: #7ea4c4;
|
|
46
|
+
--color-green-check: #26bc67;
|
|
47
|
+
--color-yellow-cart: #ffd000;
|
|
48
|
+
--color-DarkerGray: #bcbcbc;
|
|
49
|
+
--color-2ndLightGray: #e7e7e7;
|
|
50
|
+
--color-sku-blue: #3591e1;
|
|
51
|
+
}
|
|
52
|
+
@layer base {
|
|
53
|
+
:root {
|
|
54
|
+
--bg-primary: 0 0 0;
|
|
55
|
+
--bg-secondary: 15 15 15;
|
|
56
|
+
--text-primary: 248 250 252;
|
|
57
|
+
--text-secondary: 148 163 184;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
body {
|
|
61
|
+
background-color: rgb(var(--bg-primary));
|
|
62
|
+
color: rgb(var(--text-primary));
|
|
63
|
+
transition:
|
|
64
|
+
background-color 0.3s ease,
|
|
65
|
+
color 0.3s ease;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
button {
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@layer utilities {
|
|
74
|
+
.container {
|
|
75
|
+
margin-inline: auto;
|
|
76
|
+
padding-inline: 10px;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
2
|
+
import dayjs from "dayjs";
|
|
3
|
+
import relativeTime from "dayjs/plugin/relativeTime";
|
|
4
|
+
import utc from "dayjs/plugin/utc";
|
|
5
|
+
import timezone from "dayjs/plugin/timezone";
|
|
6
|
+
import { StrictMode } from "react";
|
|
7
|
+
import { createRoot } from "react-dom/client";
|
|
8
|
+
import { BrowserRouter } from "react-router-dom";
|
|
9
|
+
import App from "./App.tsx";
|
|
10
|
+
import "./index.css";
|
|
11
|
+
|
|
12
|
+
dayjs.extend(relativeTime);
|
|
13
|
+
dayjs.extend(utc);
|
|
14
|
+
dayjs.extend(timezone);
|
|
15
|
+
|
|
16
|
+
const queryClient = new QueryClient({
|
|
17
|
+
defaultOptions: {
|
|
18
|
+
queries: {
|
|
19
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
20
|
+
gcTime: 10 * 60 * 1000, // 10 minutes
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
createRoot(document.getElementById("root")!).render(
|
|
26
|
+
<StrictMode>
|
|
27
|
+
<QueryClientProvider client={queryClient}>
|
|
28
|
+
<BrowserRouter>
|
|
29
|
+
<App />
|
|
30
|
+
</BrowserRouter>
|
|
31
|
+
</QueryClientProvider>
|
|
32
|
+
</StrictMode>
|
|
33
|
+
);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useRoutes, type RouteObject } from "react-router-dom";
|
|
2
|
+
import { getRoutes } from "./routes";
|
|
3
|
+
import type { LensConfig } from "../types";
|
|
4
|
+
|
|
5
|
+
const Router = ({config}: {config: LensConfig}) => {
|
|
6
|
+
const allRoutes: RouteObject[] = getRoutes(config);
|
|
7
|
+
|
|
8
|
+
return useRoutes([...allRoutes]);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default Router;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ArrowRightLeft, Database } from "lucide-react";
|
|
2
|
+
import { lazy} from "react";
|
|
3
|
+
import { Navigate, type RouteObject } from "react-router-dom";
|
|
4
|
+
import Layout from "../../components/layout/Layout";
|
|
5
|
+
import type { LensConfig } from "../../types";
|
|
6
|
+
|
|
7
|
+
const RequestsContainer = lazy(
|
|
8
|
+
() => import("../../containers/requests/RequestsContainer"),
|
|
9
|
+
);
|
|
10
|
+
const QueriesContainer = lazy(
|
|
11
|
+
() => import("../../containers/queries/QueriesContainer"),
|
|
12
|
+
);
|
|
13
|
+
const RequestDetailsContainer = lazy(
|
|
14
|
+
() => import("../../containers/requests/RequestDetailsContainer"),
|
|
15
|
+
);
|
|
16
|
+
const QueryDetailsContainer = lazy(
|
|
17
|
+
() => import("../../containers/queries/QueryDetailsContainer"),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export function getRoutesPaths(config: LensConfig) {
|
|
21
|
+
return {
|
|
22
|
+
REQUESTS: `${config.path}/requests`,
|
|
23
|
+
QUERIES: `${config.path}/queries`,
|
|
24
|
+
REQUEST_DETAILS: `${config.path}/requests/:requestId`,
|
|
25
|
+
QUERY_DETAILS: `${config.path}/queries/:queryId`,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getSidebarRoutes(config: LensConfig) {
|
|
30
|
+
const paths = getRoutesPaths(config);
|
|
31
|
+
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
path: paths.REQUESTS,
|
|
35
|
+
label: "Requests",
|
|
36
|
+
icon: ArrowRightLeft,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
path: paths.QUERIES,
|
|
40
|
+
label: "Queries",
|
|
41
|
+
icon: Database,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getRoutes(config: LensConfig): RouteObject[] {
|
|
47
|
+
const paths = getRoutesPaths(config);
|
|
48
|
+
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
path: "/",
|
|
52
|
+
element: <Navigate to={paths.REQUESTS} replace />,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
path: config.path,
|
|
56
|
+
element: <Layout />,
|
|
57
|
+
children: [
|
|
58
|
+
{
|
|
59
|
+
index: true,
|
|
60
|
+
element: <Navigate to="requests" replace />,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
path: "requests",
|
|
64
|
+
element: <RequestsContainer />,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
path: "requests/:id",
|
|
68
|
+
element: <RequestDetailsContainer />,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
path: "queries",
|
|
72
|
+
element: <QueriesContainer />,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
path: "queries/:id",
|
|
76
|
+
element: <QueryDetailsContainer />,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
path: "*",
|
|
80
|
+
element: <h1>Error</h1>,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export type LensConfig = {
|
|
2
|
+
appName: string;
|
|
3
|
+
path: string;
|
|
4
|
+
api: {
|
|
5
|
+
requests: string;
|
|
6
|
+
queries: string;
|
|
7
|
+
truncate: string
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type LensEntryType = "request" | "query";
|
|
12
|
+
|
|
13
|
+
export type PaginationParams = {
|
|
14
|
+
page: number;
|
|
15
|
+
perPage: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type PaginatorMeta = {
|
|
19
|
+
total: number;
|
|
20
|
+
lastPage: number;
|
|
21
|
+
currentPage: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type Paginator<T> = {
|
|
25
|
+
meta: PaginatorMeta;
|
|
26
|
+
data: PaginatorData<T>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type PaginatorData<T> = T[];
|
|
30
|
+
|
|
31
|
+
export type ApiResponse<T> = {
|
|
32
|
+
status: number;
|
|
33
|
+
message: string;
|
|
34
|
+
data: T | null;
|
|
35
|
+
meta?: Paginator<T>["meta"];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type QueryEntry = {
|
|
39
|
+
id: string;
|
|
40
|
+
type: LensEntryType;
|
|
41
|
+
created_at: string;
|
|
42
|
+
lens_entry_id: string | null;
|
|
43
|
+
data: {
|
|
44
|
+
query: string;
|
|
45
|
+
duration: string;
|
|
46
|
+
createdAt: string;
|
|
47
|
+
type: QueryType;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type UserEntry = {
|
|
52
|
+
id: number | string;
|
|
53
|
+
name: string;
|
|
54
|
+
email: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type RequestEntry = {
|
|
58
|
+
id: string;
|
|
59
|
+
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
|
|
60
|
+
duration: string;
|
|
61
|
+
path: string;
|
|
62
|
+
headers: Record<string, string>;
|
|
63
|
+
body: Record<string, any>;
|
|
64
|
+
status: number;
|
|
65
|
+
ip: string;
|
|
66
|
+
createdAt: string;
|
|
67
|
+
response: {
|
|
68
|
+
json: Record<string, any>;
|
|
69
|
+
headers: Record<string, string>;
|
|
70
|
+
};
|
|
71
|
+
user?: UserEntry | null;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type RequestTableEntry = Omit<RequestEntry, "ip" | "headers" | "body">;
|
|
75
|
+
export type Queries = QueryEntry;
|
|
76
|
+
export type GenericLensEntry<T> = {
|
|
77
|
+
id: string;
|
|
78
|
+
type: LensEntryType;
|
|
79
|
+
created_at: string;
|
|
80
|
+
lens_entry_id: string | null;
|
|
81
|
+
data: T;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type HasMoreType<T> = {
|
|
85
|
+
data: T[];
|
|
86
|
+
hasMore: boolean;
|
|
87
|
+
loading: boolean;
|
|
88
|
+
loadMore: () => Promise<void>;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type RequestTableRow = GenericLensEntry<RequestTableEntry>;
|
|
92
|
+
export type OneRequest = GenericLensEntry<RequestEntry>;
|
|
93
|
+
export type QueryTableRow = QueryEntry;
|
|
94
|
+
export type OneQuery = GenericLensEntry<QueryEntry>;
|
|
95
|
+
export type QueryType = "sql" | "mongodb";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import type { LensConfig } from "../types";
|
|
3
|
+
|
|
4
|
+
interface ConfigContextType {
|
|
5
|
+
config: LensConfig;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const ConfigContext = createContext<ConfigContextType | undefined>(undefined);
|
|
9
|
+
|
|
10
|
+
export function useConfig() {
|
|
11
|
+
const context = useContext(ConfigContext);
|
|
12
|
+
if (!context) {
|
|
13
|
+
throw new Error("useConfig must be used inside a ConfigProvider");
|
|
14
|
+
}
|
|
15
|
+
return context.config;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getBasePath() {
|
|
19
|
+
const config = useConfig();
|
|
20
|
+
|
|
21
|
+
return config.path;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default ConfigContext;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
|
|
3
|
+
export function getCurrentTimezone(): string {
|
|
4
|
+
return dayjs.tz.guess();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function parseBackendUTC(date: string | Date | number) {
|
|
8
|
+
return dayjs.utc(date).tz(getCurrentTimezone());
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const humanDifferentDate = (date: string | Date) => {
|
|
12
|
+
const d = parseBackendUTC(date);
|
|
13
|
+
return {
|
|
14
|
+
label: d.fromNow(),
|
|
15
|
+
exact: d.format("YYYY-MM-DD HH:mm:ss"),
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function formatTimeAgo(dateInput: string | number | Date): string {
|
|
20
|
+
const d = parseBackendUTC(dateInput);
|
|
21
|
+
return d.isValid() ? d.fromNow() : "Unknown";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function formatDateWithTimeAgo(
|
|
25
|
+
dateInput: string | number | Date | null | undefined,
|
|
26
|
+
locale: string = "en"
|
|
27
|
+
): string {
|
|
28
|
+
if (!dateInput) return "N/A";
|
|
29
|
+
|
|
30
|
+
const d = parseBackendUTC(dateInput);
|
|
31
|
+
if (!d.isValid()) return "N/A";
|
|
32
|
+
|
|
33
|
+
const formatted = d.locale(locale).format("MMMM D, YYYY h:mm A");
|
|
34
|
+
return `${formatted} (${d.fromNow()})`;
|
|
35
|
+
}
|
|
36
|
+
|