mongoku 2.0.2 → 2.0.3
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/Dockerfile +27 -0
- package/build/client/_app/immutable/chunks/{MHUppGzk.js → BAM9w9EL.js} +1 -1
- package/build/client/_app/immutable/chunks/BAM9w9EL.js.br +0 -0
- package/build/client/_app/immutable/chunks/BAM9w9EL.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{CN-ecO3-.js → BMa204Dm.js} +1 -1
- package/build/client/_app/immutable/chunks/BMa204Dm.js.br +0 -0
- package/build/client/_app/immutable/chunks/BMa204Dm.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{_2kcttvK.js → BdR-m9Ad.js} +1 -1
- package/build/client/_app/immutable/chunks/BdR-m9Ad.js.br +0 -0
- package/build/client/_app/immutable/chunks/BdR-m9Ad.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BzAcxkRZ.js +4 -0
- package/build/client/_app/immutable/chunks/BzAcxkRZ.js.br +0 -0
- package/build/client/_app/immutable/chunks/BzAcxkRZ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BFhvhM4X.js → CyQLXPZI.js} +1 -1
- package/build/client/_app/immutable/chunks/CyQLXPZI.js.br +0 -0
- package/build/client/_app/immutable/chunks/CyQLXPZI.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BdWVCPGW.js → D4VhtiDg.js} +1 -1
- package/build/client/_app/immutable/chunks/D4VhtiDg.js.br +0 -0
- package/build/client/_app/immutable/chunks/D4VhtiDg.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{zXvB9_Mi.js → XYFbSe2V.js} +1 -1
- package/build/client/_app/immutable/chunks/XYFbSe2V.js.br +0 -0
- package/build/client/_app/immutable/chunks/XYFbSe2V.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{CGIdus8b.js → uMNMODvc.js} +1 -1
- package/build/client/_app/immutable/chunks/uMNMODvc.js.br +0 -0
- package/build/client/_app/immutable/chunks/uMNMODvc.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.hGE78f-O.js → app.9nC_873E.js} +2 -2
- package/build/client/_app/immutable/entry/app.9nC_873E.js.br +0 -0
- package/build/client/_app/immutable/entry/app.9nC_873E.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.Bn88Alw2.js +1 -0
- package/build/client/_app/immutable/entry/start.Bn88Alw2.js.br +2 -0
- package/build/client/_app/immutable/entry/start.Bn88Alw2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{0.DyBXVtnT.js → 0.COxTCtn2.js} +1 -1
- package/build/client/_app/immutable/nodes/0.COxTCtn2.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.COxTCtn2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.FqB0jq88.js → 1.Bc8yPK_D.js} +1 -1
- package/build/client/_app/immutable/nodes/1.Bc8yPK_D.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.Bc8yPK_D.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{3.Bekn_8hM.js → 3.CI2GcqTf.js} +1 -1
- package/build/client/_app/immutable/nodes/3.CI2GcqTf.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.CI2GcqTf.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{4.DQfaAvJi.js → 4.ChSdW7ac.js} +1 -1
- package/build/client/_app/immutable/nodes/4.ChSdW7ac.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.ChSdW7ac.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{5.B1E6iW2R.js → 5.DaMML2go.js} +1 -1
- package/build/client/_app/immutable/nodes/5.DaMML2go.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.DaMML2go.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{6.28eZQkvz.js → 6.Dcq0qwvO.js} +1 -1
- package/build/client/_app/immutable/nodes/6.Dcq0qwvO.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.Dcq0qwvO.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{7.qpcLWZb7.js → 7.CU-ncPes.js} +1 -1
- package/build/client/_app/immutable/nodes/7.CU-ncPes.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.CU-ncPes.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/server/chunks/{0-m42kIUxj.js → 0-C1NyHW8A.js} +2 -2
- package/build/server/chunks/{0-m42kIUxj.js.map → 0-C1NyHW8A.js.map} +1 -1
- package/build/server/chunks/{1-uc74UVG3.js → 1-CThf4W5r.js} +2 -2
- package/build/server/chunks/{1-uc74UVG3.js.map → 1-CThf4W5r.js.map} +1 -1
- package/build/server/chunks/{3-Bi8teWON.js → 3-CJf0NbiV.js} +2 -2
- package/build/server/chunks/{3-Bi8teWON.js.map → 3-CJf0NbiV.js.map} +1 -1
- package/build/server/chunks/{4-u1WGAtFU.js → 4-Dfbpsagm.js} +2 -2
- package/build/server/chunks/{4-u1WGAtFU.js.map → 4-Dfbpsagm.js.map} +1 -1
- package/build/server/chunks/{5-BlGdcdjs.js → 5-DLB6GOjf.js} +2 -2
- package/build/server/chunks/{5-BlGdcdjs.js.map → 5-DLB6GOjf.js.map} +1 -1
- package/build/server/chunks/{6-YCp6xyCU.js → 6-DfCARDKO.js} +2 -2
- package/build/server/chunks/{6-YCp6xyCU.js.map → 6-DfCARDKO.js.map} +1 -1
- package/build/server/chunks/{7-ieA4k9K_.js → 7-B5o4OymX.js} +2 -2
- package/build/server/chunks/{7-ieA4k9K_.js.map → 7-B5o4OymX.js.map} +1 -1
- package/build/server/index.js +1 -1
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +8 -8
- package/build/server/manifest.js.map +1 -1
- package/cli.ts +148 -0
- package/dist/cli.js +2 -3
- package/ecosystem.config.js +9 -0
- package/package.json +10 -2
- package/src/api/servers.remote.ts +98 -0
- package/src/app.css +228 -0
- package/src/app.d.ts +16 -0
- package/src/app.html +11 -0
- package/src/hooks.server.ts +34 -0
- package/src/lib/components/Breadcrumbs.svelte +133 -0
- package/src/lib/components/JsonValue.svelte +248 -0
- package/src/lib/components/Notifications.svelte +81 -0
- package/src/lib/components/Panel.svelte +37 -0
- package/src/lib/components/PrettyJson.svelte +187 -0
- package/src/lib/components/SearchBox.svelte +160 -0
- package/src/lib/components/TooltipTable.svelte +137 -0
- package/src/lib/server/HostsManager.ts +105 -0
- package/src/lib/server/JsonEncoder.ts +62 -0
- package/src/lib/server/mongo.ts +199 -0
- package/src/lib/stores/notifications.svelte.ts +45 -0
- package/src/lib/types.ts +56 -0
- package/src/lib/utils/filters.ts +25 -0
- package/src/lib/utils/jsonParser.ts +125 -0
- package/src/routes/+layout.server.ts +7 -0
- package/src/routes/+layout.svelte +27 -0
- package/src/routes/+page.server.ts +6 -0
- package/src/routes/servers/+page.server.ts +53 -0
- package/src/routes/servers/+page.svelte +196 -0
- package/src/routes/servers/[server]/databases/+page.server.ts +47 -0
- package/src/routes/servers/[server]/databases/+page.svelte +88 -0
- package/src/routes/servers/[server]/databases/[database]/collections/+page.server.ts +21 -0
- package/src/routes/servers/[server]/databases/[database]/collections/+page.svelte +110 -0
- package/src/routes/servers/[server]/databases/[database]/collections/[collection]/+page.server.ts +106 -0
- package/src/routes/servers/[server]/databases/[database]/collections/[collection]/+page.svelte +174 -0
- package/src/routes/servers/[server]/databases/[database]/collections/[collection]/documents/[document]/+page.server.ts +25 -0
- package/src/routes/servers/[server]/databases/[database]/collections/[collection]/documents/[document]/+page.svelte +90 -0
- package/src/tests/api/readonly.test.ts +89 -0
- package/src/tests/setup.ts +19 -0
- package/svelte.config.js +28 -0
- package/tsconfig.cli.json +15 -0
- package/tsconfig.json +19 -0
- package/vite.config.ts +7 -0
- package/build/client/_app/immutable/chunks/BFhvhM4X.js.br +0 -0
- package/build/client/_app/immutable/chunks/BFhvhM4X.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BdWVCPGW.js.br +0 -0
- package/build/client/_app/immutable/chunks/BdWVCPGW.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CGIdus8b.js.br +0 -0
- package/build/client/_app/immutable/chunks/CGIdus8b.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CN-ecO3-.js.br +0 -0
- package/build/client/_app/immutable/chunks/CN-ecO3-.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DB3PPjLu.js +0 -4
- package/build/client/_app/immutable/chunks/DB3PPjLu.js.br +0 -0
- package/build/client/_app/immutable/chunks/DB3PPjLu.js.gz +0 -0
- package/build/client/_app/immutable/chunks/MHUppGzk.js.br +0 -0
- package/build/client/_app/immutable/chunks/MHUppGzk.js.gz +0 -0
- package/build/client/_app/immutable/chunks/_2kcttvK.js.br +0 -0
- package/build/client/_app/immutable/chunks/_2kcttvK.js.gz +0 -0
- package/build/client/_app/immutable/chunks/zXvB9_Mi.js.br +0 -0
- package/build/client/_app/immutable/chunks/zXvB9_Mi.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.hGE78f-O.js.br +0 -0
- package/build/client/_app/immutable/entry/app.hGE78f-O.js.gz +0 -0
- package/build/client/_app/immutable/entry/start._GE1Zd3d.js +0 -1
- package/build/client/_app/immutable/entry/start._GE1Zd3d.js.br +0 -2
- package/build/client/_app/immutable/entry/start._GE1Zd3d.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.DyBXVtnT.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.DyBXVtnT.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.FqB0jq88.js.br +0 -2
- package/build/client/_app/immutable/nodes/1.FqB0jq88.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.Bekn_8hM.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.Bekn_8hM.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.DQfaAvJi.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.DQfaAvJi.js.gz +0 -0
- package/build/client/_app/immutable/nodes/5.B1E6iW2R.js.br +0 -0
- package/build/client/_app/immutable/nodes/5.B1E6iW2R.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.28eZQkvz.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.28eZQkvz.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.qpcLWZb7.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.qpcLWZb7.js.gz +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { resolve } from "$app/paths";
|
|
3
|
+
import { page } from "$app/state";
|
|
4
|
+
import { serverName } from "$lib/utils/filters";
|
|
5
|
+
|
|
6
|
+
interface BreadcrumbItem {
|
|
7
|
+
label: string;
|
|
8
|
+
href?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getBreadcrumbs(pathname: string, params: Record<string, string>): BreadcrumbItem[] {
|
|
12
|
+
const breadcrumbs: BreadcrumbItem[] = [];
|
|
13
|
+
|
|
14
|
+
// Parse the path segments
|
|
15
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
16
|
+
|
|
17
|
+
if (segments.length === 0) {
|
|
18
|
+
// We're at home, no breadcrumbs needed
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (segments[0] === "servers" && segments.length >= 3) {
|
|
23
|
+
const server = decodeURIComponent(params.server || "");
|
|
24
|
+
const serverDisplayName = serverName(server);
|
|
25
|
+
|
|
26
|
+
if (segments[2] === "databases") {
|
|
27
|
+
// /servers/{server}/databases
|
|
28
|
+
breadcrumbs.push({
|
|
29
|
+
label: serverDisplayName,
|
|
30
|
+
href: `/servers/${encodeURIComponent(server)}/databases`,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (segments.length >= 5 && segments[3] && segments[4] === "collections") {
|
|
34
|
+
const database = decodeURIComponent(params.database || "");
|
|
35
|
+
// /servers/{server}/databases/{database}/collections
|
|
36
|
+
breadcrumbs.push({
|
|
37
|
+
label: database,
|
|
38
|
+
href: `/servers/${encodeURIComponent(server)}/databases/${encodeURIComponent(database)}/collections`,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (segments.length >= 6 && segments[5]) {
|
|
42
|
+
const collection = decodeURIComponent(params.collection || "");
|
|
43
|
+
|
|
44
|
+
if (segments.length === 6) {
|
|
45
|
+
// /servers/{server}/databases/{database}/collections/{collection}
|
|
46
|
+
breadcrumbs.push({
|
|
47
|
+
label: collection,
|
|
48
|
+
});
|
|
49
|
+
} else if (segments.length >= 7 && segments[6] === "documents" && segments[7]) {
|
|
50
|
+
// /servers/{server}/databases/{database}/collections/{collection}/documents/{document}
|
|
51
|
+
breadcrumbs.push({
|
|
52
|
+
label: collection,
|
|
53
|
+
href: `/servers/${encodeURIComponent(server)}/databases/${encodeURIComponent(database)}/collections/${encodeURIComponent(collection)}`,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
breadcrumbs.push({
|
|
57
|
+
label: decodeURIComponent(params.document || "[Document]"),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return breadcrumbs;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const breadcrumbs = $derived(getBreadcrumbs(page.url.pathname, page.params));
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
{#if breadcrumbs.length > 0}
|
|
72
|
+
<nav class="breadcrumbs" aria-label="Breadcrumb">
|
|
73
|
+
<ol class="breadcrumb-list">
|
|
74
|
+
{#each breadcrumbs as crumb, index (index)}
|
|
75
|
+
<li class="breadcrumb-item">
|
|
76
|
+
{#if crumb.href && index < breadcrumbs.length - 1}
|
|
77
|
+
<!-- eslint-disable-next-line @typescript-eslint/no-explicit-any -->
|
|
78
|
+
<a href={resolve(crumb.href as any)} class="breadcrumb-link">{crumb.label}</a>
|
|
79
|
+
{:else}
|
|
80
|
+
<span class="breadcrumb-current">{crumb.label}</span>
|
|
81
|
+
{/if}
|
|
82
|
+
{#if index < breadcrumbs.length - 1}
|
|
83
|
+
<span class="breadcrumb-separator">/</span>
|
|
84
|
+
{/if}
|
|
85
|
+
</li>
|
|
86
|
+
{/each}
|
|
87
|
+
</ol>
|
|
88
|
+
</nav>
|
|
89
|
+
{/if}
|
|
90
|
+
|
|
91
|
+
<style lang="postcss">
|
|
92
|
+
.breadcrumbs {
|
|
93
|
+
margin-left: 20px;
|
|
94
|
+
font-size: 16px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.breadcrumb-list {
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
list-style: none;
|
|
101
|
+
margin: 0;
|
|
102
|
+
padding: 0;
|
|
103
|
+
gap: 8px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.breadcrumb-item {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
gap: 8px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.breadcrumb-link {
|
|
113
|
+
color: var(--text);
|
|
114
|
+
text-decoration: none;
|
|
115
|
+
transition: color 0.2s;
|
|
116
|
+
|
|
117
|
+
&:hover {
|
|
118
|
+
color: var(--text);
|
|
119
|
+
text-decoration: underline;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.breadcrumb-current {
|
|
124
|
+
color: var(--text-darker);
|
|
125
|
+
font-weight: 500;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.breadcrumb-separator {
|
|
129
|
+
color: var(--text-darker);
|
|
130
|
+
font-weight: 300;
|
|
131
|
+
user-select: none;
|
|
132
|
+
}
|
|
133
|
+
</style>
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import JsonValue from "./JsonValue.svelte";
|
|
3
|
+
|
|
4
|
+
const INDENT = " ";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
value: any;
|
|
9
|
+
key?: string;
|
|
10
|
+
/** are sub items collapsed by default*/
|
|
11
|
+
autoCollapse?: boolean;
|
|
12
|
+
/** is it collapsed at top level */
|
|
13
|
+
collapsed?: boolean;
|
|
14
|
+
depth?: number;
|
|
15
|
+
localTopLevel?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let { value, key, autoCollapse = false, collapsed = false, localTopLevel = true, depth = 0 }: Props = $props();
|
|
19
|
+
|
|
20
|
+
function toggleCollapse() {
|
|
21
|
+
collapsed = !collapsed;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getIndent(level: number): string {
|
|
25
|
+
return INDENT.repeat(level);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isUrl(str: string): boolean {
|
|
29
|
+
return /^https?:\/\/[^\s]+$/.test(str);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
function getValueType(val: any): string {
|
|
34
|
+
if (val === null) return "null";
|
|
35
|
+
if (Array.isArray(val)) return "array";
|
|
36
|
+
|
|
37
|
+
switch (typeof val) {
|
|
38
|
+
case "boolean":
|
|
39
|
+
return "boolean";
|
|
40
|
+
case "number":
|
|
41
|
+
return "number";
|
|
42
|
+
case "string":
|
|
43
|
+
return "string";
|
|
44
|
+
case "object":
|
|
45
|
+
switch (val.$type) {
|
|
46
|
+
case "ObjectId":
|
|
47
|
+
return "objectid";
|
|
48
|
+
case "Date":
|
|
49
|
+
return "date";
|
|
50
|
+
case "RegExp":
|
|
51
|
+
return "regexp";
|
|
52
|
+
default:
|
|
53
|
+
return "object";
|
|
54
|
+
}
|
|
55
|
+
default:
|
|
56
|
+
return "unknown";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let valueType = $derived(getValueType(value));
|
|
61
|
+
let isCollapsible = $derived(valueType === "array" || valueType === "object");
|
|
62
|
+
let isEmpty = $derived(
|
|
63
|
+
(valueType === "array" && value.length === 0) || (valueType === "object" && Object.keys(value).length === 0),
|
|
64
|
+
);
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
{#if key !== undefined}
|
|
68
|
+
<span class="prop" class:collapsible={isCollapsible && !isEmpty} class:collapsed>
|
|
69
|
+
{getIndent(depth + 1)}<var>{key}</var>: <JsonValue
|
|
70
|
+
{value}
|
|
71
|
+
{autoCollapse}
|
|
72
|
+
collapsed={autoCollapse}
|
|
73
|
+
depth={depth + 1}
|
|
74
|
+
localTopLevel={false}
|
|
75
|
+
/>
|
|
76
|
+
</span>
|
|
77
|
+
{:else}
|
|
78
|
+
<!-- Root value rendering -->
|
|
79
|
+
{#if valueType === "string"}
|
|
80
|
+
<span class="value quoted">
|
|
81
|
+
"<span class="string" class:url={isUrl(value)}>
|
|
82
|
+
{#if isUrl(value)}
|
|
83
|
+
<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
|
|
84
|
+
<a href={value} target="_blank">{value}</a>
|
|
85
|
+
{:else}
|
|
86
|
+
{value}
|
|
87
|
+
{/if}
|
|
88
|
+
</span>"
|
|
89
|
+
</span>
|
|
90
|
+
{:else if valueType === "number"}
|
|
91
|
+
<span class="value number">{value}</span>
|
|
92
|
+
{:else if valueType === "boolean"}
|
|
93
|
+
<span class="value boolean">{value}</span>
|
|
94
|
+
{:else if valueType === "null"}
|
|
95
|
+
<span class="value null">null</span>
|
|
96
|
+
{:else if valueType === "objectid"}
|
|
97
|
+
<span class="call function">ObjectId("<span class="string">{value.$value}</span>")</span>
|
|
98
|
+
{:else if valueType === "date"}
|
|
99
|
+
<span class="call function">Date("<span class="string">{value.$value}</span>")</span>
|
|
100
|
+
{:else if valueType === "regexp"}
|
|
101
|
+
<span class="value regexp">/{value.$value.$pattern}/{value.$value.$flags}</span>
|
|
102
|
+
{:else if valueType === "array"}
|
|
103
|
+
{#if isEmpty}
|
|
104
|
+
<span class="value array">[]</span>
|
|
105
|
+
{:else}
|
|
106
|
+
<span class="collapsible-wrapper" class:collapsed class:expanded={!collapsed}>
|
|
107
|
+
<button class="collapse-toggle" onclick={toggleCollapse}>
|
|
108
|
+
{collapsed ? "▶" : "▼"}
|
|
109
|
+
</button><span class="value array">
|
|
110
|
+
[<span class="collapsible-content" class:hidden={collapsed}>
|
|
111
|
+
{#each value as item, i (i)}
|
|
112
|
+
<br />{getIndent(depth + 1)}<JsonValue
|
|
113
|
+
value={item}
|
|
114
|
+
{autoCollapse}
|
|
115
|
+
collapsed={false}
|
|
116
|
+
depth={depth + 1}
|
|
117
|
+
/>{/each}
|
|
118
|
+
<br />{getIndent(depth)}
|
|
119
|
+
</span>{#if collapsed}
|
|
120
|
+
<span class="collapsed-summary">... {value.length} item{value.length !== 1 ? "s" : ""}</span>
|
|
121
|
+
{/if}]
|
|
122
|
+
</span>
|
|
123
|
+
</span>
|
|
124
|
+
{/if}
|
|
125
|
+
{:else if valueType === "object"}
|
|
126
|
+
{#if isEmpty}
|
|
127
|
+
<span class="value object">{"{}"}</span>
|
|
128
|
+
{:else}
|
|
129
|
+
<span class="collapsible-wrapper" class:collapsed class:expanded={!collapsed}>
|
|
130
|
+
{#if depth !== 0}<button class="collapse-toggle" class:local-top-level={localTopLevel} onclick={toggleCollapse}>
|
|
131
|
+
{collapsed ? "▶" : "▼"}
|
|
132
|
+
</button>{/if}<span class="value object">
|
|
133
|
+
{"{"}<span class="collapsible-content" class:hidden={collapsed}
|
|
134
|
+
>{#each Object.keys(value) as objKey (objKey)}
|
|
135
|
+
<JsonValue value={value[objKey]} key={objKey} {autoCollapse} collapsed={autoCollapse} {depth} />{/each}
|
|
136
|
+
</span>{#if collapsed}
|
|
137
|
+
<span class="collapsed-summary"
|
|
138
|
+
>... {Object.keys(value).length} key{Object.keys(value).length !== 1 ? "s" : ""}</span
|
|
139
|
+
>
|
|
140
|
+
{:else}
|
|
141
|
+
{getIndent(depth)}
|
|
142
|
+
{/if}}
|
|
143
|
+
</span>
|
|
144
|
+
</span>
|
|
145
|
+
{/if}
|
|
146
|
+
{:else}
|
|
147
|
+
<span>{String(value)}</span>
|
|
148
|
+
{/if}{#if depth !== 0},{/if}
|
|
149
|
+
{/if}
|
|
150
|
+
|
|
151
|
+
<style lang="postcss">
|
|
152
|
+
.prop {
|
|
153
|
+
position: relative;
|
|
154
|
+
display: block;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.collapsible-wrapper {
|
|
158
|
+
display: inline;
|
|
159
|
+
position: relative;
|
|
160
|
+
|
|
161
|
+
.collapse-toggle {
|
|
162
|
+
display: inline-block;
|
|
163
|
+
width: 16px;
|
|
164
|
+
cursor: pointer;
|
|
165
|
+
user-select: none;
|
|
166
|
+
color: var(--text-secondary, #888);
|
|
167
|
+
font-size: 12px;
|
|
168
|
+
margin-right: 4px;
|
|
169
|
+
transition: color 0.2s;
|
|
170
|
+
background: none;
|
|
171
|
+
border: none;
|
|
172
|
+
padding: 0;
|
|
173
|
+
font-family: inherit;
|
|
174
|
+
|
|
175
|
+
&.local-top-level {
|
|
176
|
+
position: absolute;
|
|
177
|
+
left: -18px;
|
|
178
|
+
margin-top: 4px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
&:hover {
|
|
182
|
+
color: var(--text, #fff);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
&.collapsed .collapsible-content {
|
|
187
|
+
display: none;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.collapsible-content {
|
|
192
|
+
white-space: pre;
|
|
193
|
+
|
|
194
|
+
&.hidden {
|
|
195
|
+
display: none;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.collapsed-summary {
|
|
200
|
+
color: var(--text-secondary, #888);
|
|
201
|
+
font-style: italic;
|
|
202
|
+
margin: 0 4px;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.value {
|
|
206
|
+
&.number {
|
|
207
|
+
color: var(--code-numbers);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
&.boolean {
|
|
211
|
+
color: var(--code-boolean);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
&.null {
|
|
215
|
+
color: var(--code-null);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
&.quoted {
|
|
219
|
+
.string {
|
|
220
|
+
color: var(--code-string);
|
|
221
|
+
|
|
222
|
+
&.url a {
|
|
223
|
+
color: var(--code-string);
|
|
224
|
+
text-decoration: underline;
|
|
225
|
+
|
|
226
|
+
&:hover {
|
|
227
|
+
color: var(--code-links);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
&.regexp {
|
|
234
|
+
color: var(--code-regexp);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.call {
|
|
239
|
+
&.function {
|
|
240
|
+
color: var(--code-function);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
var {
|
|
245
|
+
color: var(--code-namespace);
|
|
246
|
+
font-style: normal;
|
|
247
|
+
}
|
|
248
|
+
</style>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { notificationStore } from "$lib/stores/notifications.svelte";
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div class="notifications">
|
|
6
|
+
{#each notificationStore.items as notification (notification.id)}
|
|
7
|
+
<div class="notification notification-{notification.type}">
|
|
8
|
+
<span>{notification.message}</span>
|
|
9
|
+
<button onclick={() => notificationStore.remove(notification.id)}>×</button>
|
|
10
|
+
</div>
|
|
11
|
+
{/each}
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<style lang="postcss">
|
|
15
|
+
.notifications {
|
|
16
|
+
position: fixed;
|
|
17
|
+
top: 80px;
|
|
18
|
+
right: 20px;
|
|
19
|
+
z-index: 1000;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
gap: 10px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.notification {
|
|
26
|
+
padding: 15px 20px;
|
|
27
|
+
border-radius: 4px;
|
|
28
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: 15px;
|
|
32
|
+
min-width: 300px;
|
|
33
|
+
max-width: 500px;
|
|
34
|
+
animation: slideIn 0.3s ease-out;
|
|
35
|
+
|
|
36
|
+
button {
|
|
37
|
+
background: none;
|
|
38
|
+
border: none;
|
|
39
|
+
font-size: 24px;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
opacity: 0.7;
|
|
42
|
+
margin-left: auto;
|
|
43
|
+
padding: 0;
|
|
44
|
+
width: 24px;
|
|
45
|
+
height: 24px;
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: center;
|
|
49
|
+
|
|
50
|
+
&:hover {
|
|
51
|
+
opacity: 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.notification-error {
|
|
57
|
+
background-color: #dc3545;
|
|
58
|
+
color: white;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.notification-success {
|
|
62
|
+
background-color: #28a745;
|
|
63
|
+
color: white;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.notification-info {
|
|
67
|
+
background-color: #17a2b8;
|
|
68
|
+
color: white;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@keyframes slideIn {
|
|
72
|
+
from {
|
|
73
|
+
transform: translateX(400px);
|
|
74
|
+
opacity: 0;
|
|
75
|
+
}
|
|
76
|
+
to {
|
|
77
|
+
transform: translateX(0);
|
|
78
|
+
opacity: 1;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
title?: string | Snippet;
|
|
7
|
+
actions?: Snippet;
|
|
8
|
+
class?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { children, title, actions, class: className }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div class="panel rounded-md {className}">
|
|
15
|
+
{#if title}
|
|
16
|
+
<div class="px-4 py-2 flex justify-between items-center text-lg uppercase font-medium">
|
|
17
|
+
{#if typeof title === "string"}
|
|
18
|
+
<span>{title}</span>
|
|
19
|
+
{:else}
|
|
20
|
+
{@render title()}
|
|
21
|
+
{/if}
|
|
22
|
+
{#if actions}
|
|
23
|
+
<div class="flex gap-2 items-center">
|
|
24
|
+
{@render actions()}
|
|
25
|
+
</div>
|
|
26
|
+
{/if}
|
|
27
|
+
</div>
|
|
28
|
+
{/if}
|
|
29
|
+
{@render children?.()}
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<style lang="postcss">
|
|
33
|
+
.panel {
|
|
34
|
+
background-color: var(--color-2);
|
|
35
|
+
border: var(--border);
|
|
36
|
+
}
|
|
37
|
+
</style>
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { resolve } from "$app/paths";
|
|
3
|
+
import { notificationStore } from "$lib/stores/notifications.svelte";
|
|
4
|
+
import type { MongoDocument } from "$lib/types";
|
|
5
|
+
import { parseJSON } from "$lib/utils/jsonParser";
|
|
6
|
+
import JsonValue from "./JsonValue.svelte";
|
|
7
|
+
import Panel from "./Panel.svelte";
|
|
8
|
+
|
|
9
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
json: MongoDocument;
|
|
13
|
+
autoCollapse?: boolean;
|
|
14
|
+
readOnly?: boolean;
|
|
15
|
+
onedit?: (json: any) => void;
|
|
16
|
+
onremove?: () => void;
|
|
17
|
+
server: string;
|
|
18
|
+
database: string;
|
|
19
|
+
collection: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
json,
|
|
24
|
+
autoCollapse = false,
|
|
25
|
+
readOnly = false,
|
|
26
|
+
onedit,
|
|
27
|
+
onremove,
|
|
28
|
+
server,
|
|
29
|
+
database,
|
|
30
|
+
collection,
|
|
31
|
+
}: Props = $props();
|
|
32
|
+
|
|
33
|
+
let editorVisible = $state(false);
|
|
34
|
+
let editJson = $state("");
|
|
35
|
+
let removing = $state(false);
|
|
36
|
+
let editorRef = $state<HTMLTextAreaElement>();
|
|
37
|
+
|
|
38
|
+
// Extract timestamp from ObjectId
|
|
39
|
+
function getTimestampFromObjectId(objectId: string): Date | null {
|
|
40
|
+
try {
|
|
41
|
+
// ObjectId is 24 hex characters, first 8 represent timestamp
|
|
42
|
+
const timestamp = parseInt(objectId.substring(0, 8), 16);
|
|
43
|
+
return new Date(timestamp * 1000);
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Custom serializer that converts ObjectId objects back to new ObjectId() format
|
|
50
|
+
function serializeForEditing(obj: any, depth = 0): string {
|
|
51
|
+
const indent = " ".repeat(depth);
|
|
52
|
+
const nextIndent = " ".repeat(depth + 1);
|
|
53
|
+
|
|
54
|
+
if (obj === null) return "null";
|
|
55
|
+
if (obj === undefined) return "undefined";
|
|
56
|
+
if (typeof obj === "string") return JSON.stringify(obj);
|
|
57
|
+
if (typeof obj === "number") return obj.toString();
|
|
58
|
+
if (typeof obj === "boolean") return obj.toString();
|
|
59
|
+
|
|
60
|
+
if (Array.isArray(obj)) {
|
|
61
|
+
if (obj.length === 0) return "[]";
|
|
62
|
+
const items = obj.map((item) => `${nextIndent}${serializeForEditing(item, depth + 1)}`).join(",\n");
|
|
63
|
+
return `[\n${items}\n${indent}]`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof obj === "object") {
|
|
67
|
+
// Handle special MongoDB types
|
|
68
|
+
if (obj.$type === "ObjectId") {
|
|
69
|
+
return `new ObjectId("${obj.$value}")`;
|
|
70
|
+
}
|
|
71
|
+
if (obj.$type === "Date") {
|
|
72
|
+
return `new Date("${obj.$value}")`;
|
|
73
|
+
}
|
|
74
|
+
if (obj.$type === "RegExp") {
|
|
75
|
+
return `new RegExp("${obj.$value.$pattern}", "${obj.$value.$flags}")`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Handle regular objects
|
|
79
|
+
const keys = Object.keys(obj);
|
|
80
|
+
if (keys.length === 0) return "{}";
|
|
81
|
+
|
|
82
|
+
const pairs = keys
|
|
83
|
+
.map((key) => {
|
|
84
|
+
const value = serializeForEditing(obj[key], depth + 1);
|
|
85
|
+
return `${nextIndent}${JSON.stringify(key)}: ${value}`;
|
|
86
|
+
})
|
|
87
|
+
.join(",\n");
|
|
88
|
+
|
|
89
|
+
return `{\n${pairs}\n${indent}}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return JSON.stringify(obj);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function enableEditor() {
|
|
96
|
+
editJson = serializeForEditing(json);
|
|
97
|
+
editorVisible = true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function disableEditor() {
|
|
101
|
+
editorVisible = false;
|
|
102
|
+
editJson = "";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function save() {
|
|
106
|
+
try {
|
|
107
|
+
const updatedJson = parseJSON(editJson);
|
|
108
|
+
disableEditor();
|
|
109
|
+
onedit?.(updatedJson);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
notificationStore.notifyError(err, "Invalid JSON");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function showRemove() {
|
|
116
|
+
removing = true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function cancelRemove() {
|
|
120
|
+
removing = false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function confirmRemove() {
|
|
124
|
+
onremove?.();
|
|
125
|
+
}
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<Panel class="group">
|
|
129
|
+
{#snippet title()}
|
|
130
|
+
{#if json._id}
|
|
131
|
+
<div class="">
|
|
132
|
+
<a
|
|
133
|
+
type="button"
|
|
134
|
+
class="bg-transparent border-none text-[var(--text)] no-underline cursor-pointer text-xl font-inherit p-0 hover:underline"
|
|
135
|
+
href={resolve(
|
|
136
|
+
`/servers/${encodeURIComponent(server)}/databases/${encodeURIComponent(database)}/collections/${encodeURIComponent(collection)}/documents/${json._id?.$value}`,
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
{json._id?.$value}
|
|
140
|
+
</a>
|
|
141
|
+
{#if json._id?.$value}
|
|
142
|
+
{@const timestamp = getTimestampFromObjectId(json._id.$value)}
|
|
143
|
+
{#if timestamp}
|
|
144
|
+
<span class="ml-2 text-md text-[var(--text-secondary,#888)]">{timestamp.toLocaleString()}</span>
|
|
145
|
+
{/if}
|
|
146
|
+
{/if}
|
|
147
|
+
</div>
|
|
148
|
+
{/if}
|
|
149
|
+
{/snippet}
|
|
150
|
+
|
|
151
|
+
{#snippet actions()}
|
|
152
|
+
{#if !readOnly}
|
|
153
|
+
<div class="hidden group-hover:block">
|
|
154
|
+
<button class="btn btn-outline-light btn-sm ml-2 -my-2" onclick={enableEditor}>Edit</button>
|
|
155
|
+
<button class="btn btn-outline-danger btn-sm ml-2 -my-2" onclick={showRemove}>Remove</button>
|
|
156
|
+
</div>
|
|
157
|
+
{/if}
|
|
158
|
+
{/snippet}
|
|
159
|
+
|
|
160
|
+
<div class="p-4 relative border-t border-[var(--border-color)]">
|
|
161
|
+
<div class="font-mono text-sm leading-tight whitespace-pre-wrap break-words relative">
|
|
162
|
+
<JsonValue value={json} {autoCollapse} collapsed={false} />
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<div class="absolute h-full z-[100] w-full top-0 left-0" class:hidden={!editorVisible} class:block={editorVisible}>
|
|
166
|
+
<div class="absolute z-10 right-5 top-4">
|
|
167
|
+
<button class="btn btn-success ml-2" onclick={save}>Save</button>
|
|
168
|
+
<button class="btn btn-default ml-2" onclick={disableEditor}>Cancel</button>
|
|
169
|
+
</div>
|
|
170
|
+
<textarea
|
|
171
|
+
bind:this={editorRef}
|
|
172
|
+
bind:value={editJson}
|
|
173
|
+
class="w-full min-h-[300px] font-mono text-sm leading-relaxed p-2.5 bg-[var(--color-1)] text-[var(--text)] border border-[var(--border-color)] rounded resize-y"
|
|
174
|
+
></textarea>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
{#if removing}
|
|
178
|
+
<div class="absolute z-10 top-0 left-0 w-full h-full rounded bg-[var(--text-inverse)] opacity-70">
|
|
179
|
+
<p class="text-[var(--text)] text-center mt-5 text-2xl">Are you sure?</p>
|
|
180
|
+
<div class="absolute w-full h-full top-0 flex justify-center items-center">
|
|
181
|
+
<button class="btn btn-danger m-5" onclick={confirmRemove}>Yes - Remove</button>
|
|
182
|
+
<button class="btn btn-success m-5" onclick={cancelRemove}>No - Cancel</button>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
{/if}
|
|
186
|
+
</div>
|
|
187
|
+
</Panel>
|