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.
Files changed (151) hide show
  1. package/Dockerfile +27 -0
  2. package/build/client/_app/immutable/chunks/{MHUppGzk.js → BAM9w9EL.js} +1 -1
  3. package/build/client/_app/immutable/chunks/BAM9w9EL.js.br +0 -0
  4. package/build/client/_app/immutable/chunks/BAM9w9EL.js.gz +0 -0
  5. package/build/client/_app/immutable/chunks/{CN-ecO3-.js → BMa204Dm.js} +1 -1
  6. package/build/client/_app/immutable/chunks/BMa204Dm.js.br +0 -0
  7. package/build/client/_app/immutable/chunks/BMa204Dm.js.gz +0 -0
  8. package/build/client/_app/immutable/chunks/{_2kcttvK.js → BdR-m9Ad.js} +1 -1
  9. package/build/client/_app/immutable/chunks/BdR-m9Ad.js.br +0 -0
  10. package/build/client/_app/immutable/chunks/BdR-m9Ad.js.gz +0 -0
  11. package/build/client/_app/immutable/chunks/BzAcxkRZ.js +4 -0
  12. package/build/client/_app/immutable/chunks/BzAcxkRZ.js.br +0 -0
  13. package/build/client/_app/immutable/chunks/BzAcxkRZ.js.gz +0 -0
  14. package/build/client/_app/immutable/chunks/{BFhvhM4X.js → CyQLXPZI.js} +1 -1
  15. package/build/client/_app/immutable/chunks/CyQLXPZI.js.br +0 -0
  16. package/build/client/_app/immutable/chunks/CyQLXPZI.js.gz +0 -0
  17. package/build/client/_app/immutable/chunks/{BdWVCPGW.js → D4VhtiDg.js} +1 -1
  18. package/build/client/_app/immutable/chunks/D4VhtiDg.js.br +0 -0
  19. package/build/client/_app/immutable/chunks/D4VhtiDg.js.gz +0 -0
  20. package/build/client/_app/immutable/chunks/{zXvB9_Mi.js → XYFbSe2V.js} +1 -1
  21. package/build/client/_app/immutable/chunks/XYFbSe2V.js.br +0 -0
  22. package/build/client/_app/immutable/chunks/XYFbSe2V.js.gz +0 -0
  23. package/build/client/_app/immutable/chunks/{CGIdus8b.js → uMNMODvc.js} +1 -1
  24. package/build/client/_app/immutable/chunks/uMNMODvc.js.br +0 -0
  25. package/build/client/_app/immutable/chunks/uMNMODvc.js.gz +0 -0
  26. package/build/client/_app/immutable/entry/{app.hGE78f-O.js → app.9nC_873E.js} +2 -2
  27. package/build/client/_app/immutable/entry/app.9nC_873E.js.br +0 -0
  28. package/build/client/_app/immutable/entry/app.9nC_873E.js.gz +0 -0
  29. package/build/client/_app/immutable/entry/start.Bn88Alw2.js +1 -0
  30. package/build/client/_app/immutable/entry/start.Bn88Alw2.js.br +2 -0
  31. package/build/client/_app/immutable/entry/start.Bn88Alw2.js.gz +0 -0
  32. package/build/client/_app/immutable/nodes/{0.DyBXVtnT.js → 0.COxTCtn2.js} +1 -1
  33. package/build/client/_app/immutable/nodes/0.COxTCtn2.js.br +0 -0
  34. package/build/client/_app/immutable/nodes/0.COxTCtn2.js.gz +0 -0
  35. package/build/client/_app/immutable/nodes/{1.FqB0jq88.js → 1.Bc8yPK_D.js} +1 -1
  36. package/build/client/_app/immutable/nodes/1.Bc8yPK_D.js.br +0 -0
  37. package/build/client/_app/immutable/nodes/1.Bc8yPK_D.js.gz +0 -0
  38. package/build/client/_app/immutable/nodes/{3.Bekn_8hM.js → 3.CI2GcqTf.js} +1 -1
  39. package/build/client/_app/immutable/nodes/3.CI2GcqTf.js.br +0 -0
  40. package/build/client/_app/immutable/nodes/3.CI2GcqTf.js.gz +0 -0
  41. package/build/client/_app/immutable/nodes/{4.DQfaAvJi.js → 4.ChSdW7ac.js} +1 -1
  42. package/build/client/_app/immutable/nodes/4.ChSdW7ac.js.br +0 -0
  43. package/build/client/_app/immutable/nodes/4.ChSdW7ac.js.gz +0 -0
  44. package/build/client/_app/immutable/nodes/{5.B1E6iW2R.js → 5.DaMML2go.js} +1 -1
  45. package/build/client/_app/immutable/nodes/5.DaMML2go.js.br +0 -0
  46. package/build/client/_app/immutable/nodes/5.DaMML2go.js.gz +0 -0
  47. package/build/client/_app/immutable/nodes/{6.28eZQkvz.js → 6.Dcq0qwvO.js} +1 -1
  48. package/build/client/_app/immutable/nodes/6.Dcq0qwvO.js.br +0 -0
  49. package/build/client/_app/immutable/nodes/6.Dcq0qwvO.js.gz +0 -0
  50. package/build/client/_app/immutable/nodes/{7.qpcLWZb7.js → 7.CU-ncPes.js} +1 -1
  51. package/build/client/_app/immutable/nodes/7.CU-ncPes.js.br +0 -0
  52. package/build/client/_app/immutable/nodes/7.CU-ncPes.js.gz +0 -0
  53. package/build/client/_app/version.json +1 -1
  54. package/build/client/_app/version.json.br +0 -0
  55. package/build/client/_app/version.json.gz +0 -0
  56. package/build/server/chunks/{0-m42kIUxj.js → 0-C1NyHW8A.js} +2 -2
  57. package/build/server/chunks/{0-m42kIUxj.js.map → 0-C1NyHW8A.js.map} +1 -1
  58. package/build/server/chunks/{1-uc74UVG3.js → 1-CThf4W5r.js} +2 -2
  59. package/build/server/chunks/{1-uc74UVG3.js.map → 1-CThf4W5r.js.map} +1 -1
  60. package/build/server/chunks/{3-Bi8teWON.js → 3-CJf0NbiV.js} +2 -2
  61. package/build/server/chunks/{3-Bi8teWON.js.map → 3-CJf0NbiV.js.map} +1 -1
  62. package/build/server/chunks/{4-u1WGAtFU.js → 4-Dfbpsagm.js} +2 -2
  63. package/build/server/chunks/{4-u1WGAtFU.js.map → 4-Dfbpsagm.js.map} +1 -1
  64. package/build/server/chunks/{5-BlGdcdjs.js → 5-DLB6GOjf.js} +2 -2
  65. package/build/server/chunks/{5-BlGdcdjs.js.map → 5-DLB6GOjf.js.map} +1 -1
  66. package/build/server/chunks/{6-YCp6xyCU.js → 6-DfCARDKO.js} +2 -2
  67. package/build/server/chunks/{6-YCp6xyCU.js.map → 6-DfCARDKO.js.map} +1 -1
  68. package/build/server/chunks/{7-ieA4k9K_.js → 7-B5o4OymX.js} +2 -2
  69. package/build/server/chunks/{7-ieA4k9K_.js.map → 7-B5o4OymX.js.map} +1 -1
  70. package/build/server/index.js +1 -1
  71. package/build/server/index.js.map +1 -1
  72. package/build/server/manifest.js +8 -8
  73. package/build/server/manifest.js.map +1 -1
  74. package/cli.ts +148 -0
  75. package/dist/cli.js +2 -3
  76. package/ecosystem.config.js +9 -0
  77. package/package.json +10 -2
  78. package/src/api/servers.remote.ts +98 -0
  79. package/src/app.css +228 -0
  80. package/src/app.d.ts +16 -0
  81. package/src/app.html +11 -0
  82. package/src/hooks.server.ts +34 -0
  83. package/src/lib/components/Breadcrumbs.svelte +133 -0
  84. package/src/lib/components/JsonValue.svelte +248 -0
  85. package/src/lib/components/Notifications.svelte +81 -0
  86. package/src/lib/components/Panel.svelte +37 -0
  87. package/src/lib/components/PrettyJson.svelte +187 -0
  88. package/src/lib/components/SearchBox.svelte +160 -0
  89. package/src/lib/components/TooltipTable.svelte +137 -0
  90. package/src/lib/server/HostsManager.ts +105 -0
  91. package/src/lib/server/JsonEncoder.ts +62 -0
  92. package/src/lib/server/mongo.ts +199 -0
  93. package/src/lib/stores/notifications.svelte.ts +45 -0
  94. package/src/lib/types.ts +56 -0
  95. package/src/lib/utils/filters.ts +25 -0
  96. package/src/lib/utils/jsonParser.ts +125 -0
  97. package/src/routes/+layout.server.ts +7 -0
  98. package/src/routes/+layout.svelte +27 -0
  99. package/src/routes/+page.server.ts +6 -0
  100. package/src/routes/servers/+page.server.ts +53 -0
  101. package/src/routes/servers/+page.svelte +196 -0
  102. package/src/routes/servers/[server]/databases/+page.server.ts +47 -0
  103. package/src/routes/servers/[server]/databases/+page.svelte +88 -0
  104. package/src/routes/servers/[server]/databases/[database]/collections/+page.server.ts +21 -0
  105. package/src/routes/servers/[server]/databases/[database]/collections/+page.svelte +110 -0
  106. package/src/routes/servers/[server]/databases/[database]/collections/[collection]/+page.server.ts +106 -0
  107. package/src/routes/servers/[server]/databases/[database]/collections/[collection]/+page.svelte +174 -0
  108. package/src/routes/servers/[server]/databases/[database]/collections/[collection]/documents/[document]/+page.server.ts +25 -0
  109. package/src/routes/servers/[server]/databases/[database]/collections/[collection]/documents/[document]/+page.svelte +90 -0
  110. package/src/tests/api/readonly.test.ts +89 -0
  111. package/src/tests/setup.ts +19 -0
  112. package/svelte.config.js +28 -0
  113. package/tsconfig.cli.json +15 -0
  114. package/tsconfig.json +19 -0
  115. package/vite.config.ts +7 -0
  116. package/build/client/_app/immutable/chunks/BFhvhM4X.js.br +0 -0
  117. package/build/client/_app/immutable/chunks/BFhvhM4X.js.gz +0 -0
  118. package/build/client/_app/immutable/chunks/BdWVCPGW.js.br +0 -0
  119. package/build/client/_app/immutable/chunks/BdWVCPGW.js.gz +0 -0
  120. package/build/client/_app/immutable/chunks/CGIdus8b.js.br +0 -0
  121. package/build/client/_app/immutable/chunks/CGIdus8b.js.gz +0 -0
  122. package/build/client/_app/immutable/chunks/CN-ecO3-.js.br +0 -0
  123. package/build/client/_app/immutable/chunks/CN-ecO3-.js.gz +0 -0
  124. package/build/client/_app/immutable/chunks/DB3PPjLu.js +0 -4
  125. package/build/client/_app/immutable/chunks/DB3PPjLu.js.br +0 -0
  126. package/build/client/_app/immutable/chunks/DB3PPjLu.js.gz +0 -0
  127. package/build/client/_app/immutable/chunks/MHUppGzk.js.br +0 -0
  128. package/build/client/_app/immutable/chunks/MHUppGzk.js.gz +0 -0
  129. package/build/client/_app/immutable/chunks/_2kcttvK.js.br +0 -0
  130. package/build/client/_app/immutable/chunks/_2kcttvK.js.gz +0 -0
  131. package/build/client/_app/immutable/chunks/zXvB9_Mi.js.br +0 -0
  132. package/build/client/_app/immutable/chunks/zXvB9_Mi.js.gz +0 -0
  133. package/build/client/_app/immutable/entry/app.hGE78f-O.js.br +0 -0
  134. package/build/client/_app/immutable/entry/app.hGE78f-O.js.gz +0 -0
  135. package/build/client/_app/immutable/entry/start._GE1Zd3d.js +0 -1
  136. package/build/client/_app/immutable/entry/start._GE1Zd3d.js.br +0 -2
  137. package/build/client/_app/immutable/entry/start._GE1Zd3d.js.gz +0 -0
  138. package/build/client/_app/immutable/nodes/0.DyBXVtnT.js.br +0 -0
  139. package/build/client/_app/immutable/nodes/0.DyBXVtnT.js.gz +0 -0
  140. package/build/client/_app/immutable/nodes/1.FqB0jq88.js.br +0 -2
  141. package/build/client/_app/immutable/nodes/1.FqB0jq88.js.gz +0 -0
  142. package/build/client/_app/immutable/nodes/3.Bekn_8hM.js.br +0 -0
  143. package/build/client/_app/immutable/nodes/3.Bekn_8hM.js.gz +0 -0
  144. package/build/client/_app/immutable/nodes/4.DQfaAvJi.js.br +0 -0
  145. package/build/client/_app/immutable/nodes/4.DQfaAvJi.js.gz +0 -0
  146. package/build/client/_app/immutable/nodes/5.B1E6iW2R.js.br +0 -0
  147. package/build/client/_app/immutable/nodes/5.B1E6iW2R.js.gz +0 -0
  148. package/build/client/_app/immutable/nodes/6.28eZQkvz.js.br +0 -0
  149. package/build/client/_app/immutable/nodes/6.28eZQkvz.js.gz +0 -0
  150. package/build/client/_app/immutable/nodes/7.qpcLWZb7.js.br +0 -0
  151. 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)}>&times;</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>