mongoku 2.2.4 → 2.4.0

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 (398) hide show
  1. package/build/client/_app/immutable/assets/0.C-Y-CVnr.css +1 -0
  2. package/build/client/_app/immutable/assets/0.C-Y-CVnr.css.br +0 -0
  3. package/build/client/_app/immutable/assets/0.C-Y-CVnr.css.gz +0 -0
  4. package/build/client/_app/immutable/assets/10.BrydhPds.css +1 -0
  5. package/build/client/_app/immutable/assets/10.BrydhPds.css.br +0 -0
  6. package/build/client/_app/immutable/assets/10.BrydhPds.css.gz +0 -0
  7. package/build/client/_app/immutable/assets/12.I1sSDJ6i.css +1 -0
  8. package/build/client/_app/immutable/assets/12.I1sSDJ6i.css.br +0 -0
  9. package/build/client/_app/immutable/assets/12.I1sSDJ6i.css.gz +0 -0
  10. package/build/client/_app/immutable/assets/Modal.zD4dMMyk.css +1 -0
  11. package/build/client/_app/immutable/assets/Modal.zD4dMMyk.css.br +0 -0
  12. package/build/client/_app/immutable/assets/Modal.zD4dMMyk.css.gz +0 -0
  13. package/build/client/_app/immutable/chunks/0oUK0vJa.js +1 -0
  14. package/build/client/_app/immutable/chunks/0oUK0vJa.js.br +0 -0
  15. package/build/client/_app/immutable/chunks/0oUK0vJa.js.gz +0 -0
  16. package/build/client/_app/immutable/chunks/3xIOsPOb.js +16 -0
  17. package/build/client/_app/immutable/chunks/3xIOsPOb.js.br +0 -0
  18. package/build/client/_app/immutable/chunks/3xIOsPOb.js.gz +0 -0
  19. package/build/client/_app/immutable/chunks/B83FMsIQ.js +24 -0
  20. package/build/client/_app/immutable/chunks/B83FMsIQ.js.br +0 -0
  21. package/build/client/_app/immutable/chunks/B83FMsIQ.js.gz +0 -0
  22. package/build/client/_app/immutable/chunks/BGXqurs_.js +1 -0
  23. package/build/client/_app/immutable/chunks/BGXqurs_.js.br +0 -0
  24. package/build/client/_app/immutable/chunks/BGXqurs_.js.gz +0 -0
  25. package/build/client/_app/immutable/chunks/BPbCnJEo.js +1 -0
  26. package/build/client/_app/immutable/chunks/BPbCnJEo.js.br +0 -0
  27. package/build/client/_app/immutable/chunks/BPbCnJEo.js.gz +0 -0
  28. package/build/client/_app/immutable/chunks/BXUq66wC.js +1 -0
  29. package/build/client/_app/immutable/chunks/BXUq66wC.js.br +0 -0
  30. package/build/client/_app/immutable/chunks/BXUq66wC.js.gz +0 -0
  31. package/build/client/_app/immutable/chunks/BZY48V2X.js +14 -0
  32. package/build/client/_app/immutable/chunks/BZY48V2X.js.br +0 -0
  33. package/build/client/_app/immutable/chunks/BZY48V2X.js.gz +0 -0
  34. package/build/client/_app/immutable/chunks/Bq7qZpXP.js +1 -0
  35. package/build/client/_app/immutable/chunks/Bq7qZpXP.js.br +0 -0
  36. package/build/client/_app/immutable/chunks/Bq7qZpXP.js.gz +0 -0
  37. package/build/client/_app/immutable/chunks/BxoAxRg6.js +1 -0
  38. package/build/client/_app/immutable/chunks/BxoAxRg6.js.br +0 -0
  39. package/build/client/_app/immutable/chunks/BxoAxRg6.js.gz +0 -0
  40. package/build/client/_app/immutable/chunks/ByCRZ7zS.js +2 -0
  41. package/build/client/_app/immutable/chunks/ByCRZ7zS.js.br +0 -0
  42. package/build/client/_app/immutable/chunks/ByCRZ7zS.js.gz +0 -0
  43. package/build/client/_app/immutable/chunks/{D4iRRB8h.js → C1g0HmEj.js} +1 -1
  44. package/build/client/_app/immutable/chunks/C1g0HmEj.js.br +0 -0
  45. package/build/client/_app/immutable/chunks/C1g0HmEj.js.gz +0 -0
  46. package/build/client/_app/immutable/chunks/DGtnJf56.js +1 -0
  47. package/build/client/_app/immutable/chunks/DGtnJf56.js.br +0 -0
  48. package/build/client/_app/immutable/chunks/DGtnJf56.js.gz +0 -0
  49. package/build/client/_app/immutable/chunks/{DR9xdhsg.js → DIw4Wyr4.js} +1 -1
  50. package/build/client/_app/immutable/chunks/DIw4Wyr4.js.br +0 -0
  51. package/build/client/_app/immutable/chunks/DIw4Wyr4.js.gz +0 -0
  52. package/build/client/_app/immutable/chunks/Dn0_-jEe.js +1 -0
  53. package/build/client/_app/immutable/chunks/Dn0_-jEe.js.br +0 -0
  54. package/build/client/_app/immutable/chunks/Dn0_-jEe.js.gz +0 -0
  55. package/build/client/_app/immutable/chunks/UJuWD2W4.js +1 -0
  56. package/build/client/_app/immutable/chunks/UJuWD2W4.js.br +0 -0
  57. package/build/client/_app/immutable/chunks/UJuWD2W4.js.gz +0 -0
  58. package/build/client/_app/immutable/chunks/{Dx56OlI-.js → _Sxqiax7.js} +1 -1
  59. package/build/client/_app/immutable/chunks/_Sxqiax7.js.br +0 -0
  60. package/build/client/_app/immutable/chunks/_Sxqiax7.js.gz +0 -0
  61. package/build/client/_app/immutable/chunks/_kKBJjiB.js +1 -0
  62. package/build/client/_app/immutable/chunks/_kKBJjiB.js.br +0 -0
  63. package/build/client/_app/immutable/chunks/_kKBJjiB.js.gz +0 -0
  64. package/build/client/_app/immutable/chunks/pj4OZpZf.js +4 -0
  65. package/build/client/_app/immutable/chunks/pj4OZpZf.js.br +0 -0
  66. package/build/client/_app/immutable/chunks/pj4OZpZf.js.gz +0 -0
  67. package/build/client/_app/immutable/chunks/reNJcONQ.js +1 -0
  68. package/build/client/_app/immutable/chunks/reNJcONQ.js.br +0 -0
  69. package/build/client/_app/immutable/chunks/reNJcONQ.js.gz +0 -0
  70. package/build/client/_app/immutable/entry/app.DV8my7lq.js +2 -0
  71. package/build/client/_app/immutable/entry/app.DV8my7lq.js.br +0 -0
  72. package/build/client/_app/immutable/entry/app.DV8my7lq.js.gz +0 -0
  73. package/build/client/_app/immutable/entry/start.CHeMkIYN.js +1 -0
  74. package/build/client/_app/immutable/entry/start.CHeMkIYN.js.br +0 -0
  75. package/build/client/_app/immutable/entry/start.CHeMkIYN.js.gz +0 -0
  76. package/build/client/_app/immutable/nodes/0.CNNOoOHl.js +1 -0
  77. package/build/client/_app/immutable/nodes/0.CNNOoOHl.js.br +0 -0
  78. package/build/client/_app/immutable/nodes/0.CNNOoOHl.js.gz +0 -0
  79. package/build/client/_app/immutable/nodes/1.C-65KEEJ.js +1 -0
  80. package/build/client/_app/immutable/nodes/1.C-65KEEJ.js.br +0 -0
  81. package/build/client/_app/immutable/nodes/1.C-65KEEJ.js.gz +0 -0
  82. package/build/client/_app/immutable/nodes/10.BQ-b15Yh.js +1 -0
  83. package/build/client/_app/immutable/nodes/10.BQ-b15Yh.js.br +0 -0
  84. package/build/client/_app/immutable/nodes/10.BQ-b15Yh.js.gz +0 -0
  85. package/build/client/_app/immutable/nodes/11.iU-GIxdi.js +1 -0
  86. package/build/client/_app/immutable/nodes/11.iU-GIxdi.js.br +0 -0
  87. package/build/client/_app/immutable/nodes/11.iU-GIxdi.js.gz +0 -0
  88. package/build/client/_app/immutable/nodes/12.BneOFPb1.js +1 -0
  89. package/build/client/_app/immutable/nodes/12.BneOFPb1.js.br +0 -0
  90. package/build/client/_app/immutable/nodes/12.BneOFPb1.js.gz +0 -0
  91. package/build/client/_app/immutable/nodes/13.BsUY4XVc.js +66 -0
  92. package/build/client/_app/immutable/nodes/13.BsUY4XVc.js.br +0 -0
  93. package/build/client/_app/immutable/nodes/13.BsUY4XVc.js.gz +0 -0
  94. package/build/client/_app/immutable/nodes/{2.DwwrLLW4.js → 2.DqvBb3C7.js} +1 -1
  95. package/build/client/_app/immutable/nodes/2.DqvBb3C7.js.br +0 -0
  96. package/build/client/_app/immutable/nodes/2.DqvBb3C7.js.gz +0 -0
  97. package/build/client/_app/immutable/nodes/{3.t20GootO.js → 3.27swwcG4.js} +1 -1
  98. package/build/client/_app/immutable/nodes/3.27swwcG4.js.br +0 -0
  99. package/build/client/_app/immutable/nodes/3.27swwcG4.js.gz +0 -0
  100. package/build/client/_app/immutable/nodes/{4.CXHjkPtf.js → 4.CcholrFe.js} +1 -1
  101. package/build/client/_app/immutable/nodes/4.CcholrFe.js.br +0 -0
  102. package/build/client/_app/immutable/nodes/4.CcholrFe.js.gz +0 -0
  103. package/build/client/_app/immutable/nodes/{5.C9tcr5B0.js → 5.CzudN9q-.js} +1 -1
  104. package/build/client/_app/immutable/nodes/5.CzudN9q-.js.br +0 -0
  105. package/build/client/_app/immutable/nodes/5.CzudN9q-.js.gz +0 -0
  106. package/build/client/_app/immutable/nodes/7.S2O-z1Ox.js +1 -0
  107. package/build/client/_app/immutable/nodes/7.S2O-z1Ox.js.br +0 -0
  108. package/build/client/_app/immutable/nodes/7.S2O-z1Ox.js.gz +0 -0
  109. package/build/client/_app/immutable/nodes/8.aa4EbN0k.js +1 -0
  110. package/build/client/_app/immutable/nodes/8.aa4EbN0k.js.br +0 -0
  111. package/build/client/_app/immutable/nodes/8.aa4EbN0k.js.gz +0 -0
  112. package/build/client/_app/immutable/nodes/9.9dZgqvLb.js +2 -0
  113. package/build/client/_app/immutable/nodes/9.9dZgqvLb.js.br +0 -0
  114. package/build/client/_app/immutable/nodes/9.9dZgqvLb.js.gz +0 -0
  115. package/build/client/_app/version.json +1 -1
  116. package/build/client/_app/version.json.br +0 -0
  117. package/build/client/_app/version.json.gz +0 -0
  118. package/build/server/chunks/0-BHG1s0_H.js +22 -0
  119. package/build/server/chunks/0-BHG1s0_H.js.map +1 -0
  120. package/build/server/chunks/1-Xz1zN5FT.js +9 -0
  121. package/build/server/chunks/1-Xz1zN5FT.js.map +1 -0
  122. package/build/server/chunks/{10-DGUQMXHt.js → 10-CaYvpZVK.js} +6 -5
  123. package/build/server/chunks/10-CaYvpZVK.js.map +1 -0
  124. package/build/server/chunks/{11-CxBnyyqR.js → 11-uJK96PLt.js} +6 -5
  125. package/build/server/chunks/11-uJK96PLt.js.map +1 -0
  126. package/build/server/chunks/{12-K_JVUnsk.js → 12-CvEEILKx.js} +14 -13
  127. package/build/server/chunks/12-CvEEILKx.js.map +1 -0
  128. package/build/server/chunks/{13-Cay35aHK.js → 13-Cvk7ClFs.js} +6 -5
  129. package/build/server/chunks/{13-Cay35aHK.js.map → 13-Cvk7ClFs.js.map} +1 -1
  130. package/build/server/chunks/{2-ClkERMhY.js → 2-BheEgghx.js} +2 -2
  131. package/build/server/chunks/{2-ClkERMhY.js.map → 2-BheEgghx.js.map} +1 -1
  132. package/build/server/chunks/{3-CTpqmxdo.js → 3-BwrTscBH.js} +2 -2
  133. package/build/server/chunks/{3-CTpqmxdo.js.map → 3-BwrTscBH.js.map} +1 -1
  134. package/build/server/chunks/{4-CUH4fZn8.js → 4-D28kbpdN.js} +2 -2
  135. package/build/server/chunks/{4-CUH4fZn8.js.map → 4-D28kbpdN.js.map} +1 -1
  136. package/build/server/chunks/{5-BSEdxfut.js → 5--wY8V1OC.js} +2 -2
  137. package/build/server/chunks/{5-BSEdxfut.js.map → 5--wY8V1OC.js.map} +1 -1
  138. package/build/server/chunks/{7-DvU-_ADW.js → 7-DTmi6ASF.js} +6 -5
  139. package/build/server/chunks/7-DTmi6ASF.js.map +1 -0
  140. package/build/server/chunks/{8-CA3-CJtS.js → 8-CHIpUo4j.js} +6 -5
  141. package/build/server/chunks/8-CHIpUo4j.js.map +1 -0
  142. package/build/server/chunks/{9-jMOS3lPS.js → 9-C3nw_taL.js} +6 -5
  143. package/build/server/chunks/9-C3nw_taL.js.map +1 -0
  144. package/build/server/chunks/{JsonValue-WMdIUvRM.js → JsonValue-DiECF6Zd.js} +11 -4
  145. package/build/server/chunks/JsonValue-DiECF6Zd.js.map +1 -0
  146. package/build/server/chunks/{Modal-zVnoAfFx.js → Modal-BzZ9u6V6.js} +3 -3
  147. package/build/server/chunks/Modal-BzZ9u6V6.js.map +1 -0
  148. package/build/server/chunks/Panel-DEP08QXV.js +48 -0
  149. package/build/server/chunks/Panel-DEP08QXV.js.map +1 -0
  150. package/build/server/chunks/{PrettyJson-D3CylSVd.js → PrettyJson-ElzPxPF4.js} +55 -40
  151. package/build/server/chunks/PrettyJson-ElzPxPF4.js.map +1 -0
  152. package/build/server/chunks/Tooltip-DK6Aok7l.js +35 -0
  153. package/build/server/chunks/Tooltip-DK6Aok7l.js.map +1 -0
  154. package/build/server/chunks/{TooltipTable-DCu3GLqO.js → TooltipTable-DUYKVtrh.js} +3 -3
  155. package/build/server/chunks/{TooltipTable-DCu3GLqO.js.map → TooltipTable-DUYKVtrh.js.map} +1 -1
  156. package/build/server/chunks/_layout.svelte-CCi6mq1k.js +159 -0
  157. package/build/server/chunks/_layout.svelte-CCi6mq1k.js.map +1 -0
  158. package/build/server/chunks/{_page.svelte-C-13GpOe.js → _page.svelte-B4BL5PJR.js} +18 -16
  159. package/build/server/chunks/_page.svelte-B4BL5PJR.js.map +1 -0
  160. package/build/server/chunks/{_page.svelte-BRxX_vQR.js → _page.svelte-BUheieJ5.js} +26 -15
  161. package/build/server/chunks/_page.svelte-BUheieJ5.js.map +1 -0
  162. package/build/server/chunks/{_page.svelte-BBHqwhWF.js → _page.svelte-Bj297hp1.js} +16 -15
  163. package/build/server/chunks/_page.svelte-Bj297hp1.js.map +1 -0
  164. package/build/server/chunks/{_page.svelte-2w3fCax2.js → _page.svelte-BvjNfjgw.js} +14 -14
  165. package/build/server/chunks/{_page.svelte-2w3fCax2.js.map → _page.svelte-BvjNfjgw.js.map} +1 -1
  166. package/build/server/chunks/_page.svelte-DDe4hyAB.js +387 -0
  167. package/build/server/chunks/_page.svelte-DDe4hyAB.js.map +1 -0
  168. package/build/server/chunks/{_page.svelte-Ca9u_35e.js → _page.svelte-DEe2Kbbw.js} +72 -12
  169. package/build/server/chunks/_page.svelte-DEe2Kbbw.js.map +1 -0
  170. package/build/server/chunks/_page.svelte-DJFPwyij.js +323 -0
  171. package/build/server/chunks/_page.svelte-DJFPwyij.js.map +1 -0
  172. package/build/server/chunks/client-Cp5ag8tA.js +7 -0
  173. package/build/server/chunks/{client-BFlyU7ZU.js.map → client-Cp5ag8tA.js.map} +1 -1
  174. package/build/server/chunks/{client2-DYaMKWrc.js → client2-Dos6p_Xm.js} +3 -4
  175. package/build/server/chunks/{client2-DYaMKWrc.js.map → client2-Dos6p_Xm.js.map} +1 -1
  176. package/build/server/chunks/{error.svelte-BENdaVOT.js → error.svelte-BBEhCLUJ.js} +6 -7
  177. package/build/server/chunks/{error.svelte-BENdaVOT.js.map → error.svelte-BBEhCLUJ.js.map} +1 -1
  178. package/build/server/chunks/{index2-DBFlGzWV.js → index2-DzKnqQNM.js} +2 -2
  179. package/build/server/chunks/{index2-DBFlGzWV.js.map → index2-DzKnqQNM.js.map} +1 -1
  180. package/build/server/chunks/{index3-CoOO2o9t.js → index3-DANRthiW.js} +2 -2
  181. package/build/server/chunks/{index3-CoOO2o9t.js.map → index3-DANRthiW.js.map} +1 -1
  182. package/build/server/chunks/{mongo-Db8Vgimy.js → mongo-BHk5yAj9.js} +90 -19
  183. package/build/server/chunks/mongo-BHk5yAj9.js.map +1 -0
  184. package/build/server/chunks/{remote-xxtqbu-AjxdDZZ_.js → remote-xxtqbu-DBe3CqEJ.js} +6 -6
  185. package/build/server/chunks/remote-xxtqbu-DBe3CqEJ.js.map +1 -0
  186. package/build/server/chunks/{server2-_mGVuBoG.js → server2-YmCaJGWy.js} +2 -2
  187. package/build/server/chunks/{server2-_mGVuBoG.js.map → server2-YmCaJGWy.js.map} +1 -1
  188. package/build/server/chunks/{servers.remote-DrkuAWHP.js → servers.remote-HDmNH7n4.js} +245 -15
  189. package/build/server/chunks/servers.remote-HDmNH7n4.js.map +1 -0
  190. package/build/server/chunks/{shared-Buki-xt5.js → shared-CQd3A1I4.js} +2 -2
  191. package/build/server/chunks/{shared-Buki-xt5.js.map → shared-CQd3A1I4.js.map} +1 -1
  192. package/build/server/chunks/{state.svelte-C8IWmp_n.js → state.svelte-Dm0prnTp.js} +2 -2
  193. package/build/server/chunks/{state.svelte-C8IWmp_n.js.map → state.svelte-Dm0prnTp.js.map} +1 -1
  194. package/build/server/chunks/{event-DVH-6ISX.js → utils-D1ahxout.js} +39 -2
  195. package/build/server/chunks/utils-D1ahxout.js.map +1 -0
  196. package/build/server/index.js +5 -6
  197. package/build/server/index.js.map +1 -1
  198. package/build/server/manifest.js +15 -15
  199. package/build/server/manifest.js.map +1 -1
  200. package/package.json +3 -2
  201. package/src/api/servers.remote.ts +275 -11
  202. package/src/app.css +178 -135
  203. package/src/app.html +23 -10
  204. package/src/lib/actions/portal.ts +42 -0
  205. package/src/lib/components/Breadcrumbs.svelte +20 -60
  206. package/src/lib/components/JsonValue.svelte +30 -1
  207. package/src/lib/components/Modal.svelte +5 -5
  208. package/src/lib/components/Notifications.svelte +42 -21
  209. package/src/lib/components/PageSwitcher.svelte +16 -10
  210. package/src/lib/components/Panel.svelte +19 -15
  211. package/src/lib/components/PrettyJson.svelte +102 -90
  212. package/src/lib/components/ReplicaSetSelector.svelte +144 -0
  213. package/src/lib/components/SearchBox.svelte +117 -96
  214. package/src/lib/components/ThemeSwitcher.svelte +191 -36
  215. package/src/lib/components/Tooltip.svelte +23 -14
  216. package/src/lib/server/mongo.ts +129 -26
  217. package/src/lib/utils/formatSignificantDigits.ts +30 -0
  218. package/src/lib/utils/formatTimeAgo.ts +20 -0
  219. package/src/lib/utils/hostnames.ts +86 -0
  220. package/src/lib/utils/jsonParser.ts +50 -0
  221. package/src/lib/utils/longestCommonSuffix.ts +47 -0
  222. package/src/lib/utils/sum.ts +10 -0
  223. package/src/routes/+layout.svelte +36 -17
  224. package/src/routes/servers/+page.svelte +6 -7
  225. package/src/routes/servers/[server]/databases/+page.svelte +71 -2
  226. package/src/routes/servers/[server]/databases/[database]/collections/+page.svelte +13 -3
  227. package/src/routes/servers/[server]/databases/[database]/collections/[collection]/documents/+page.svelte +292 -57
  228. package/src/routes/servers/[server]/databases/[database]/collections/[collection]/indexes/+page.server.ts +8 -8
  229. package/src/routes/servers/[server]/databases/[database]/collections/[collection]/indexes/+page.svelte +628 -105
  230. package/src/routes/servers/[server]/databases/[database]/collections/[collection]/mappings/+page.svelte +26 -12
  231. package/build/client/_app/immutable/assets/0.COnAcXN4.css +0 -1
  232. package/build/client/_app/immutable/assets/0.COnAcXN4.css.br +0 -0
  233. package/build/client/_app/immutable/assets/0.COnAcXN4.css.gz +0 -0
  234. package/build/client/_app/immutable/assets/10.59aTjJn9.css +0 -1
  235. package/build/client/_app/immutable/assets/10.59aTjJn9.css.br +0 -0
  236. package/build/client/_app/immutable/assets/10.59aTjJn9.css.gz +0 -0
  237. package/build/client/_app/immutable/assets/12.BdCOhvz0.css +0 -1
  238. package/build/client/_app/immutable/assets/12.BdCOhvz0.css.br +0 -0
  239. package/build/client/_app/immutable/assets/12.BdCOhvz0.css.gz +0 -0
  240. package/build/client/_app/immutable/assets/Modal.DIIFkbGB.css +0 -1
  241. package/build/client/_app/immutable/assets/Modal.DIIFkbGB.css.br +0 -0
  242. package/build/client/_app/immutable/assets/Modal.DIIFkbGB.css.gz +0 -0
  243. package/build/client/_app/immutable/assets/Panel.B3aWupye.css +0 -1
  244. package/build/client/_app/immutable/assets/Panel.B3aWupye.css.br +0 -0
  245. package/build/client/_app/immutable/assets/Panel.B3aWupye.css.gz +0 -0
  246. package/build/client/_app/immutable/assets/cuprum-cyrillic-400-normal.Cnibl3-L.woff2 +0 -0
  247. package/build/client/_app/immutable/assets/cuprum-cyrillic-400-normal.RKIjpA76.woff +0 -0
  248. package/build/client/_app/immutable/assets/cuprum-cyrillic-ext-400-normal.CT5q4ZVh.woff2 +0 -0
  249. package/build/client/_app/immutable/assets/cuprum-cyrillic-ext-400-normal.iCCFJ4Gn.woff +0 -0
  250. package/build/client/_app/immutable/assets/cuprum-latin-400-normal.Cbwtr8a4.woff +0 -0
  251. package/build/client/_app/immutable/assets/cuprum-latin-400-normal.CjFvNwMJ.woff2 +0 -0
  252. package/build/client/_app/immutable/assets/cuprum-latin-ext-400-normal.BWTJtpjo.woff2 +0 -0
  253. package/build/client/_app/immutable/assets/cuprum-latin-ext-400-normal.BZXayy47.woff +0 -0
  254. package/build/client/_app/immutable/assets/cuprum-vietnamese-400-normal.uXRi1gw5.woff +0 -0
  255. package/build/client/_app/immutable/assets/rajdhani-devanagari-400-normal.BdIzgbsr.woff +0 -0
  256. package/build/client/_app/immutable/assets/rajdhani-devanagari-400-normal.CTuj2HZW.woff2 +0 -0
  257. package/build/client/_app/immutable/assets/rajdhani-latin-400-normal.C6_q4usG.woff +0 -0
  258. package/build/client/_app/immutable/assets/rajdhani-latin-400-normal.CurJOxDW.woff2 +0 -0
  259. package/build/client/_app/immutable/assets/rajdhani-latin-ext-400-normal.DACPYgMx.woff2 +0 -0
  260. package/build/client/_app/immutable/assets/rajdhani-latin-ext-400-normal.Der7ynDE.woff +0 -0
  261. package/build/client/_app/immutable/chunks/3iRu_dUj.js +0 -2
  262. package/build/client/_app/immutable/chunks/3iRu_dUj.js.br +0 -0
  263. package/build/client/_app/immutable/chunks/3iRu_dUj.js.gz +0 -0
  264. package/build/client/_app/immutable/chunks/5nAxkTTP.js +0 -1
  265. package/build/client/_app/immutable/chunks/5nAxkTTP.js.br +0 -5
  266. package/build/client/_app/immutable/chunks/5nAxkTTP.js.gz +0 -0
  267. package/build/client/_app/immutable/chunks/BBg-V8FE.js +0 -1
  268. package/build/client/_app/immutable/chunks/BBg-V8FE.js.br +0 -0
  269. package/build/client/_app/immutable/chunks/BBg-V8FE.js.gz +0 -0
  270. package/build/client/_app/immutable/chunks/BE8UzJuY.js +0 -1
  271. package/build/client/_app/immutable/chunks/BE8UzJuY.js.br +0 -0
  272. package/build/client/_app/immutable/chunks/BE8UzJuY.js.gz +0 -0
  273. package/build/client/_app/immutable/chunks/BQEBQyfZ.js +0 -1
  274. package/build/client/_app/immutable/chunks/BQEBQyfZ.js.br +0 -0
  275. package/build/client/_app/immutable/chunks/BQEBQyfZ.js.gz +0 -0
  276. package/build/client/_app/immutable/chunks/BUNgwIS0.js +0 -1
  277. package/build/client/_app/immutable/chunks/BUNgwIS0.js.br +0 -0
  278. package/build/client/_app/immutable/chunks/BUNgwIS0.js.gz +0 -0
  279. package/build/client/_app/immutable/chunks/BvKY81hz.js +0 -1
  280. package/build/client/_app/immutable/chunks/BvKY81hz.js.br +0 -0
  281. package/build/client/_app/immutable/chunks/BvKY81hz.js.gz +0 -0
  282. package/build/client/_app/immutable/chunks/C0PCwpqt.js +0 -23
  283. package/build/client/_app/immutable/chunks/C0PCwpqt.js.br +0 -0
  284. package/build/client/_app/immutable/chunks/C0PCwpqt.js.gz +0 -0
  285. package/build/client/_app/immutable/chunks/C0rm4mmq.js +0 -1
  286. package/build/client/_app/immutable/chunks/C0rm4mmq.js.br +0 -0
  287. package/build/client/_app/immutable/chunks/C0rm4mmq.js.gz +0 -0
  288. package/build/client/_app/immutable/chunks/C2sjtvBO.js +0 -1
  289. package/build/client/_app/immutable/chunks/C2sjtvBO.js.br +0 -0
  290. package/build/client/_app/immutable/chunks/C2sjtvBO.js.gz +0 -0
  291. package/build/client/_app/immutable/chunks/C82Rp5eQ.js +0 -1
  292. package/build/client/_app/immutable/chunks/C82Rp5eQ.js.br +0 -0
  293. package/build/client/_app/immutable/chunks/C82Rp5eQ.js.gz +0 -0
  294. package/build/client/_app/immutable/chunks/CGSOl_q_.js +0 -4
  295. package/build/client/_app/immutable/chunks/CGSOl_q_.js.br +0 -0
  296. package/build/client/_app/immutable/chunks/CGSOl_q_.js.gz +0 -0
  297. package/build/client/_app/immutable/chunks/CjNnAkTB.js +0 -1
  298. package/build/client/_app/immutable/chunks/CjNnAkTB.js.br +0 -0
  299. package/build/client/_app/immutable/chunks/CjNnAkTB.js.gz +0 -0
  300. package/build/client/_app/immutable/chunks/Cx2cA4gg.js +0 -1
  301. package/build/client/_app/immutable/chunks/Cx2cA4gg.js.br +0 -0
  302. package/build/client/_app/immutable/chunks/Cx2cA4gg.js.gz +0 -0
  303. package/build/client/_app/immutable/chunks/D4iRRB8h.js.br +0 -0
  304. package/build/client/_app/immutable/chunks/D4iRRB8h.js.gz +0 -0
  305. package/build/client/_app/immutable/chunks/D5ccpaXt.js +0 -1
  306. package/build/client/_app/immutable/chunks/D5ccpaXt.js.br +0 -0
  307. package/build/client/_app/immutable/chunks/D5ccpaXt.js.gz +0 -0
  308. package/build/client/_app/immutable/chunks/D7E46CWw.js +0 -1
  309. package/build/client/_app/immutable/chunks/D7E46CWw.js.br +0 -0
  310. package/build/client/_app/immutable/chunks/D7E46CWw.js.gz +0 -0
  311. package/build/client/_app/immutable/chunks/DR9xdhsg.js.br +0 -0
  312. package/build/client/_app/immutable/chunks/DR9xdhsg.js.gz +0 -0
  313. package/build/client/_app/immutable/chunks/DVRQRZnh.js +0 -1
  314. package/build/client/_app/immutable/chunks/DVRQRZnh.js.br +0 -0
  315. package/build/client/_app/immutable/chunks/DVRQRZnh.js.gz +0 -0
  316. package/build/client/_app/immutable/chunks/DcPEQ0_V.js +0 -29
  317. package/build/client/_app/immutable/chunks/DcPEQ0_V.js.br +0 -0
  318. package/build/client/_app/immutable/chunks/DcPEQ0_V.js.gz +0 -0
  319. package/build/client/_app/immutable/chunks/Dx56OlI-.js.br +0 -2
  320. package/build/client/_app/immutable/chunks/Dx56OlI-.js.gz +0 -0
  321. package/build/client/_app/immutable/chunks/PXh00dfp.js +0 -2
  322. package/build/client/_app/immutable/chunks/PXh00dfp.js.br +0 -0
  323. package/build/client/_app/immutable/chunks/PXh00dfp.js.gz +0 -0
  324. package/build/client/_app/immutable/entry/app.DrdSPfGo.js +0 -2
  325. package/build/client/_app/immutable/entry/app.DrdSPfGo.js.br +0 -0
  326. package/build/client/_app/immutable/entry/app.DrdSPfGo.js.gz +0 -0
  327. package/build/client/_app/immutable/entry/start.DGG3CpnK.js +0 -1
  328. package/build/client/_app/immutable/entry/start.DGG3CpnK.js.br +0 -2
  329. package/build/client/_app/immutable/entry/start.DGG3CpnK.js.gz +0 -0
  330. package/build/client/_app/immutable/nodes/0.kCLtccjl.js +0 -5
  331. package/build/client/_app/immutable/nodes/0.kCLtccjl.js.br +0 -0
  332. package/build/client/_app/immutable/nodes/0.kCLtccjl.js.gz +0 -0
  333. package/build/client/_app/immutable/nodes/1.Dr8p9JHT.js +0 -1
  334. package/build/client/_app/immutable/nodes/1.Dr8p9JHT.js.br +0 -3
  335. package/build/client/_app/immutable/nodes/1.Dr8p9JHT.js.gz +0 -0
  336. package/build/client/_app/immutable/nodes/10.B4uwvHd0.js +0 -2
  337. package/build/client/_app/immutable/nodes/10.B4uwvHd0.js.br +0 -0
  338. package/build/client/_app/immutable/nodes/10.B4uwvHd0.js.gz +0 -0
  339. package/build/client/_app/immutable/nodes/11.qfhS2VqU.js +0 -1
  340. package/build/client/_app/immutable/nodes/11.qfhS2VqU.js.br +0 -0
  341. package/build/client/_app/immutable/nodes/11.qfhS2VqU.js.gz +0 -0
  342. package/build/client/_app/immutable/nodes/12.DkZDCIfS.js +0 -1
  343. package/build/client/_app/immutable/nodes/12.DkZDCIfS.js.br +0 -0
  344. package/build/client/_app/immutable/nodes/12.DkZDCIfS.js.gz +0 -0
  345. package/build/client/_app/immutable/nodes/13.DFVzMgyY.js +0 -66
  346. package/build/client/_app/immutable/nodes/13.DFVzMgyY.js.br +0 -0
  347. package/build/client/_app/immutable/nodes/13.DFVzMgyY.js.gz +0 -0
  348. package/build/client/_app/immutable/nodes/2.DwwrLLW4.js.br +0 -1
  349. package/build/client/_app/immutable/nodes/2.DwwrLLW4.js.gz +0 -0
  350. package/build/client/_app/immutable/nodes/3.t20GootO.js.br +0 -0
  351. package/build/client/_app/immutable/nodes/3.t20GootO.js.gz +0 -0
  352. package/build/client/_app/immutable/nodes/4.CXHjkPtf.js.br +0 -0
  353. package/build/client/_app/immutable/nodes/4.CXHjkPtf.js.gz +0 -0
  354. package/build/client/_app/immutable/nodes/5.C9tcr5B0.js.br +0 -0
  355. package/build/client/_app/immutable/nodes/5.C9tcr5B0.js.gz +0 -0
  356. package/build/client/_app/immutable/nodes/7.D14iWySv.js +0 -1
  357. package/build/client/_app/immutable/nodes/7.D14iWySv.js.br +0 -0
  358. package/build/client/_app/immutable/nodes/7.D14iWySv.js.gz +0 -0
  359. package/build/client/_app/immutable/nodes/8.BSXe_VNA.js +0 -1
  360. package/build/client/_app/immutable/nodes/8.BSXe_VNA.js.br +0 -0
  361. package/build/client/_app/immutable/nodes/8.BSXe_VNA.js.gz +0 -0
  362. package/build/client/_app/immutable/nodes/9.BXMJq3Z_.js +0 -2
  363. package/build/client/_app/immutable/nodes/9.BXMJq3Z_.js.br +0 -0
  364. package/build/client/_app/immutable/nodes/9.BXMJq3Z_.js.gz +0 -0
  365. package/build/server/chunks/0-BozXjQlk.js +0 -22
  366. package/build/server/chunks/0-BozXjQlk.js.map +0 -1
  367. package/build/server/chunks/1-C_LPMO7A.js +0 -9
  368. package/build/server/chunks/1-C_LPMO7A.js.map +0 -1
  369. package/build/server/chunks/10-DGUQMXHt.js.map +0 -1
  370. package/build/server/chunks/11-CxBnyyqR.js.map +0 -1
  371. package/build/server/chunks/12-K_JVUnsk.js.map +0 -1
  372. package/build/server/chunks/7-DvU-_ADW.js.map +0 -1
  373. package/build/server/chunks/8-CA3-CJtS.js.map +0 -1
  374. package/build/server/chunks/9-jMOS3lPS.js.map +0 -1
  375. package/build/server/chunks/JsonValue-WMdIUvRM.js.map +0 -1
  376. package/build/server/chunks/Modal-zVnoAfFx.js.map +0 -1
  377. package/build/server/chunks/Panel-yg5YKL4d.js +0 -38
  378. package/build/server/chunks/Panel-yg5YKL4d.js.map +0 -1
  379. package/build/server/chunks/PrettyJson-D3CylSVd.js.map +0 -1
  380. package/build/server/chunks/Tooltip-ys6l541_.js +0 -28
  381. package/build/server/chunks/Tooltip-ys6l541_.js.map +0 -1
  382. package/build/server/chunks/_layout.svelte-DMm1Ill8.js +0 -168
  383. package/build/server/chunks/_layout.svelte-DMm1Ill8.js.map +0 -1
  384. package/build/server/chunks/_page.svelte-BBHqwhWF.js.map +0 -1
  385. package/build/server/chunks/_page.svelte-BRxX_vQR.js.map +0 -1
  386. package/build/server/chunks/_page.svelte-C-13GpOe.js.map +0 -1
  387. package/build/server/chunks/_page.svelte-Ca9u_35e.js.map +0 -1
  388. package/build/server/chunks/_page.svelte-Cm_vgRRR.js +0 -206
  389. package/build/server/chunks/_page.svelte-Cm_vgRRR.js.map +0 -1
  390. package/build/server/chunks/_page.svelte-DoOBFRSo.js +0 -299
  391. package/build/server/chunks/_page.svelte-DoOBFRSo.js.map +0 -1
  392. package/build/server/chunks/client-BFlyU7ZU.js +0 -7
  393. package/build/server/chunks/event-DVH-6ISX.js.map +0 -1
  394. package/build/server/chunks/mongo-Db8Vgimy.js.map +0 -1
  395. package/build/server/chunks/remote-xxtqbu-AjxdDZZ_.js.map +0 -1
  396. package/build/server/chunks/servers.remote-DrkuAWHP.js.map +0 -1
  397. package/build/server/chunks/utils-kjxf7BZO.js +0 -39
  398. package/build/server/chunks/utils-kjxf7BZO.js.map +0 -1
@@ -1,16 +1,27 @@
1
1
  <script lang="ts">
2
2
  import {
3
+ createIndex as createIndexCommand,
4
+ detectPrimaryNode,
3
5
  dropIndex as dropIndexCommand,
6
+ getIndexStatsFromNodes,
7
+ getServerNodes,
4
8
  hideIndex as hideIndexCommand,
5
9
  unhideIndex as unhideIndexCommand,
6
10
  } from "$api/servers.remote";
11
+ import { invalidate, pushState, replaceState } from "$app/navigation";
7
12
  import { page } from "$app/state";
13
+ import { jsonTextarea } from "$lib/actions/jsonTextarea";
8
14
  import JsonValue from "$lib/components/JsonValue.svelte";
9
15
  import Modal from "$lib/components/Modal.svelte";
10
16
  import Panel from "$lib/components/Panel.svelte";
17
+ import ReplicaSetSelector from "$lib/components/ReplicaSetSelector.svelte";
11
18
  import { notificationStore } from "$lib/stores/notifications.svelte";
12
19
  import { formatBytes } from "$lib/utils/filters";
20
+ import { formatSignificantDigits } from "$lib/utils/formatSignificantDigits";
21
+ import { formatTimeAgo } from "$lib/utils/formatTimeAgo";
22
+ import { getShortenedHostnames } from "$lib/utils/hostnames";
13
23
  import { omit } from "$lib/utils/omit.js";
24
+ import { sum } from "$lib/utils/sum";
14
25
  import type { PageData } from "./$types.js";
15
26
 
16
27
  const { data } = $props();
@@ -20,6 +31,61 @@
20
31
  let loadingIndexes = $state<Set<string>>(new Set());
21
32
  let showDropModal = $state(false);
22
33
  let indexToDrop = $state<string | null>(null);
34
+ let showCreateModal = $state(false);
35
+ let showReplicaSetSelector = $state(false);
36
+ let availableNodes = $state<string[]>([]);
37
+ let selectedNodes = $state<string[]>([]);
38
+ let loadingNodes = $state(false);
39
+ let primaryNode = $state<string | null>(null);
40
+ let multiNodeStats = $state<Record<string, { ops: number; since: Date; host: string }>>({});
41
+ let baselineMultiNodeStats = $state<Record<string, { ops: number; since: Date; host: string }>>({});
42
+ let baselineTimestamp = $state<Date | null>(null);
43
+ let loadingMultiNodeStats = $state(false);
44
+ let creatingIndex = $state(false);
45
+ let aggregateUsage = $state(false);
46
+ let hasLoadedFromUrl = $state(false);
47
+ let lastFetchedNodes = $state<string[]>([]);
48
+ let now = $state(new Date());
49
+
50
+ // Calculate shortened hostnames for display
51
+ const shortenedHostnames = $derived.by(() => {
52
+ const hosts = [...new Set(Object.values(multiNodeStats).map((stat) => stat.host))];
53
+ return getShortenedHostnames(hosts);
54
+ });
55
+
56
+ // Check if selected nodes have changed since last fetch
57
+ const selectedNodesChanged = $derived(
58
+ selectedNodes.length !== lastFetchedNodes.length || !selectedNodes.every((node) => lastFetchedNodes.includes(node)),
59
+ );
60
+
61
+ // Check if we have a baseline
62
+ const hasBaseline = $derived(Object.keys(baselineMultiNodeStats).length > 0 && !selectedNodesChanged);
63
+
64
+ // Format time elapsed since baseline (updates every second)
65
+ const timeElapsed = $derived.by(() => {
66
+ // This depends on 'now' which updates every second
67
+ /* eslint-disable-next-line @typescript-eslint/no-unused-expressions */
68
+ now;
69
+ return formatTimeAgo(baselineTimestamp);
70
+ });
71
+
72
+ // Update 'now' every second to trigger reactivity
73
+ $effect(() => {
74
+ const interval = setInterval(() => {
75
+ now = new Date();
76
+ }, 1000);
77
+
78
+ return () => clearInterval(interval);
79
+ });
80
+
81
+ // Create index form state
82
+ let indexKeys = $state('{ "fieldName": 1 }');
83
+ let indexName = $state("");
84
+ let indexUnique = $state(false);
85
+ let indexSparse = $state(false);
86
+ let indexPartialFilterExpression = $state("");
87
+ let indexExpireAfterSeconds = $state<number | undefined>(undefined);
88
+ let indexBackground = $state(false);
23
89
 
24
90
  // Resolve the promise once and store the result
25
91
  $effect(() => {
@@ -70,6 +136,54 @@
70
136
  }
71
137
  }
72
138
 
139
+ function openCreateModal() {
140
+ // Reset form
141
+ indexKeys = '{ "fieldName": 1 }';
142
+ indexName = "";
143
+ indexUnique = false;
144
+ indexSparse = false;
145
+ indexPartialFilterExpression = "";
146
+ indexExpireAfterSeconds = undefined;
147
+ indexBackground = false;
148
+ showCreateModal = true;
149
+ }
150
+
151
+ function closeCreateModal() {
152
+ showCreateModal = false;
153
+ }
154
+
155
+ async function confirmCreate() {
156
+ const { server, database, collection } = page.params;
157
+ if (!server || !database || !collection) return;
158
+
159
+ creatingIndex = true;
160
+
161
+ try {
162
+ await createIndexCommand({
163
+ server,
164
+ database,
165
+ collection,
166
+ keys: indexKeys,
167
+ name: indexName || undefined,
168
+ unique: indexUnique || undefined,
169
+ sparse: indexSparse || undefined,
170
+ partialFilterExpression: indexPartialFilterExpression || undefined,
171
+ expireAfterSeconds: indexExpireAfterSeconds,
172
+ background: indexBackground || undefined,
173
+ });
174
+
175
+ notificationStore.notifySuccess("Index created successfully");
176
+ closeCreateModal();
177
+
178
+ // Refresh the indexes list
179
+ await invalidate("app:indexes");
180
+ } catch (error) {
181
+ notificationStore.notifyError(error, "Failed to create index");
182
+ } finally {
183
+ creatingIndex = false;
184
+ }
185
+ }
186
+
73
187
  function openDropModal(indexName: string) {
74
188
  indexToDrop = indexName;
75
189
  showDropModal = true;
@@ -113,6 +227,152 @@
113
227
  loadingIndexes = loadingIndexes;
114
228
  }
115
229
  }
230
+
231
+ async function loadNodes() {
232
+ const { server, database, collection } = page.params;
233
+ if (!server || !database || !collection) return;
234
+
235
+ loadingNodes = true;
236
+ try {
237
+ const result = await getServerNodes({ server });
238
+
239
+ if (result.error) {
240
+ notificationStore.notifyError(result.error, "Failed to load nodes");
241
+ availableNodes = [];
242
+ } else {
243
+ availableNodes = result.data;
244
+ if (availableNodes.length > 0) {
245
+ notificationStore.notifySuccess(`Found ${availableNodes.length} node(s)`);
246
+
247
+ // Detect which node is the primary
248
+ const primaryResult = await detectPrimaryNode({ server, database, collection });
249
+ if (!primaryResult.error && primaryResult.data) {
250
+ primaryNode = primaryResult.data;
251
+ }
252
+ }
253
+ }
254
+ } catch (error) {
255
+ notificationStore.notifyError(error, "Failed to load nodes");
256
+ availableNodes = [];
257
+ } finally {
258
+ loadingNodes = false;
259
+ }
260
+ }
261
+
262
+ async function fetchMultiNodeStats(updateUrl = true) {
263
+ const { server, database, collection } = page.params;
264
+ if (!server || !database || !collection) return;
265
+
266
+ if (selectedNodes.length === 0) {
267
+ notificationStore.notifyError("No nodes selected", "Please select at least one node");
268
+ return;
269
+ }
270
+
271
+ loadingMultiNodeStats = true;
272
+
273
+ // Reset baseline if nodes changed or no baseline exists
274
+ const shouldResetBaseline = selectedNodesChanged || !hasBaseline;
275
+
276
+ try {
277
+ const result = await getIndexStatsFromNodes({
278
+ server,
279
+ database,
280
+ collection,
281
+ nodes: selectedNodes,
282
+ });
283
+
284
+ if (result.error) {
285
+ notificationStore.notifyError(result.error, "Failed to fetch stats from some nodes");
286
+ }
287
+
288
+ multiNodeStats = result.data as Record<string, { ops: number; since: Date; host: string }>;
289
+ lastFetchedNodes = [...selectedNodes];
290
+
291
+ // Set baseline on first fetch or when nodes change
292
+ if (shouldResetBaseline) {
293
+ baselineMultiNodeStats = { ...multiNodeStats };
294
+ baselineTimestamp = new Date();
295
+ }
296
+
297
+ const statsCount = Object.keys(multiNodeStats).length;
298
+ if (statsCount === 0) {
299
+ notificationStore.notifyError("No stats found", "No index stats found for selected nodes");
300
+ } else {
301
+ const message = shouldResetBaseline
302
+ ? `Fetched stats from ${statsCount} index/host combination(s)`
303
+ : `Updated stats from ${statsCount} index/host combination(s)`;
304
+ notificationStore.notifySuccess(message);
305
+ }
306
+
307
+ // Update URL with pushState
308
+ if (updateUrl) {
309
+ const url = new URL(window.location.href);
310
+ url.searchParams.set("nodes", selectedNodes.join(","));
311
+ if (aggregateUsage) {
312
+ url.searchParams.set("aggregate", "true");
313
+ }
314
+ /* eslint-disable-next-line svelte/no-navigation-without-resolve */
315
+ pushState(url.toString(), {});
316
+ }
317
+ } catch (error) {
318
+ notificationStore.notifyError(error, "Failed to fetch multi-node usage");
319
+ } finally {
320
+ loadingMultiNodeStats = false;
321
+ }
322
+ }
323
+
324
+ // Load nodes when the selector is shown
325
+ $effect(() => {
326
+ if (showReplicaSetSelector && availableNodes.length === 0 && !loadingNodes) {
327
+ loadNodes();
328
+ }
329
+ });
330
+
331
+ // Restore state from URL on mount (only once)
332
+ $effect(() => {
333
+ if (!hasLoadedFromUrl) {
334
+ const url = new URL(page.url);
335
+ const nodesParam = url.searchParams.get("nodes");
336
+ const aggregateParam = url.searchParams.get("aggregate");
337
+
338
+ if (nodesParam) {
339
+ const nodes = nodesParam.split(",").filter((n) => n.length > 0);
340
+ if (nodes.length > 0) {
341
+ showReplicaSetSelector = true;
342
+ selectedNodes = nodes;
343
+
344
+ // Auto-fetch if we have nodes in URL
345
+ if (availableNodes.length === 0) {
346
+ loadNodes().then(() => {
347
+ if (nodes.length > 0) {
348
+ fetchMultiNodeStats(false); // Don't update URL since we're loading from it
349
+ }
350
+ });
351
+ }
352
+ }
353
+ }
354
+
355
+ if (aggregateParam !== null) {
356
+ aggregateUsage = aggregateParam === "true";
357
+ }
358
+
359
+ hasLoadedFromUrl = true;
360
+ }
361
+ });
362
+
363
+ // Update URL when aggregate mode changes (replaceState)
364
+ $effect(() => {
365
+ if (hasLoadedFromUrl && Object.keys(multiNodeStats).length > 0) {
366
+ const url = new URL(window.location.href);
367
+ if (aggregateUsage) {
368
+ url.searchParams.set("aggregate", "true");
369
+ } else {
370
+ url.searchParams.delete("aggregate");
371
+ }
372
+ /* eslint-disable-next-line svelte/no-navigation-without-resolve */
373
+ replaceState(url.toString(), {});
374
+ }
375
+ });
116
376
  </script>
117
377
 
118
378
  {#await indexesData}
@@ -126,116 +386,214 @@
126
386
  <p class="error">{result.error}</p>
127
387
  </div>
128
388
  </Panel>
129
- {:else if result.data.length === 0}
130
- <Panel title="Indexes">
131
- <div class="text-center p-4" style="color: var(--text-darker)">No indexes found</div>
132
- </Panel>
133
389
  {:else}
134
- <Panel title="Indexes">
135
- <table class="table">
136
- <thead>
137
- <tr>
138
- <th class="shrink-column">Name</th>
139
- <th>Keys</th>
140
- <th>Properties</th>
141
- <th class="shrink-column">Size</th>
142
- <th class="shrink-column">Usage</th>
143
- <th style="min-width: 300px">Details</th>
144
- {#if !readOnly}
145
- <th style="width: 120px">Actions</th>
390
+ <!-- Action buttons at the top -->
391
+ <div class="mb-4 flex gap-3">
392
+ {#if !readOnly}
393
+ <button class="btn btn-success btn-sm flex items-center" type="button" onclick={openCreateModal}>
394
+ <svg
395
+ xmlns="http://www.w3.org/2000/svg"
396
+ viewBox="0 0 24 24"
397
+ fill="none"
398
+ stroke="currentColor"
399
+ stroke-width="2"
400
+ stroke-linecap="round"
401
+ stroke-linejoin="round"
402
+ class="w-4 h-4 inline mr-1"
403
+ >
404
+ <path d="M12 5v14M5 12h14" />
405
+ </svg>
406
+ Create Index
407
+ </button>
408
+ {/if}
409
+ <button class="btn btn-primary btn-sm" onclick={() => (showReplicaSetSelector = !showReplicaSetSelector)}>
410
+ {showReplicaSetSelector ? "Hide" : "Show"} Multi-Node Usage
411
+ </button>
412
+ </div>
413
+
414
+ {#if result.data.length === 0}
415
+ <Panel title="Indexes">
416
+ <div class="text-center p-4" style="color: var(--text-darker)">No indexes found</div>
417
+ </Panel>
418
+ {:else}
419
+ <!-- Replica Set Selector Section -->
420
+ {#if showReplicaSetSelector}
421
+ <div class="mb-4">
422
+ <ReplicaSetSelector bind:availableNodes bind:selectedNodes loading={loadingNodes} {primaryNode} />
423
+ <div class="mt-3 flex gap-3 items-center flex-wrap">
424
+ <button
425
+ class="btn btn-sm"
426
+ class:btn-primary={!hasBaseline}
427
+ class:btn-outline-primary={hasBaseline}
428
+ onclick={() => fetchMultiNodeStats()}
429
+ disabled={loadingMultiNodeStats || selectedNodes.length === 0}
430
+ title={hasBaseline ? `Baseline from ${timeElapsed}` : "Fetch index usage statistics"}
431
+ >
432
+ {#if loadingMultiNodeStats}
433
+ {hasBaseline ? "Checking Δ..." : "Fetching..."}
434
+ {:else}
435
+ {hasBaseline ? "Δ Usage" : "Fetch Usage"}
436
+ {/if}
437
+ </button>
438
+
439
+ {#if Object.keys(multiNodeStats).length > 0}
440
+ <label class="flex items-center gap-2 cursor-pointer">
441
+ <input type="checkbox" bind:checked={aggregateUsage} class="cursor-pointer" />
442
+ <span class="text-sm" style="color: var(--text);">Aggregate usage</span>
443
+ </label>
146
444
  {/if}
147
- </tr>
148
- </thead>
149
- <tbody>
150
- {#each result.data as index (index.name)}
151
- {@const isLoading = loadingIndexes.has(index.name)}
152
- <tr class="group" class:opacity-50={isLoading}>
153
- <td>
154
- <span class="font-mono text-sm">{index.name}</span>
155
- </td>
156
- <td>
157
- {#if index.key}
158
- <div class="flex flex-wrap gap-2">
159
- {#each Object.entries(index.key) as [field, direction], i (i)}
160
- <span class="index-key">
161
- {field}: {direction === 1 ? "↑" : direction === -1 ? "↓" : direction}
162
- </span>
163
- {/each}
164
- </div>
165
- {:else}
166
- <span style="color: var(--text-darker)">-</span>
167
- {/if}
168
- </td>
169
- <td>
170
- <div class="flex flex-wrap gap-2">
171
- {#if index.hidden}
172
- <span class="badge badge-hidden">hidden</span>
173
- {/if}
174
- {#if index.unique}
175
- <span class="badge badge-unique">unique</span>
176
- {/if}
177
- {#if index.sparse}
178
- <span class="badge badge-sparse">sparse</span>
179
- {/if}
180
- {#if index.partialFilterExpression}
181
- <span class="badge badge-partial">partial</span>
182
- {/if}
183
- {#if !index.hidden && !index.unique && !index.sparse && !index.partialFilterExpression}
184
- <span style="color: var(--text-darker)">-</span>
185
- {/if}
186
- </div>
187
- </td>
188
- <td>
189
- {#if index.size}
190
- <span class="text-sm whitespace-nowrap">{formatBytes(index.size)}</span>
191
- {:else}
192
- <span style="color: var(--text-darker)">-</span>
445
+ </div>
446
+ </div>
447
+ {/if}
448
+
449
+ <Panel title="Indexes">
450
+ <div class="table-wrapper">
451
+ <table class="table">
452
+ <thead>
453
+ <tr>
454
+ <th class="name-column">Name</th>
455
+ <th>Keys</th>
456
+ <th>Properties</th>
457
+ <th class="shrink-column">Size</th>
458
+ <th class="shrink-column">Usage</th>
459
+ <th style="min-width: 300px">Details</th>
460
+ {#if !readOnly}
461
+ <th style="width: 150px">Actions</th>
193
462
  {/if}
194
- </td>
195
- <td>
196
- {#if index.stats}
197
- <div class="text-sm mt-1">
198
- <div>{index.stats.ops.toLocaleString()}</div>
199
- </div>
200
- {:else}
201
- <span style="color: var(--text-darker)">-</span>
202
- {/if}
203
- </td>
204
- <td>
205
- <details class="cursor-pointer">
206
- <summary class="details-link">View full</summary>
207
- <div class="details-content">
208
- <JsonValue value={omit(index, ["stats"])} collapsed={false} />
209
- </div>
210
- </details>
211
- </td>
212
- {#if !readOnly}
213
- <td>
214
- {#if index.name !== "_id_"}
215
- <div class="flex gap-2 hidden group-hover:flex -my-2">
216
- <button
217
- class="btn btn-outline-light btn-sm"
218
- onclick={() => toggleHidden(index.name, index.hidden)}
219
- disabled={isLoading}
220
- >
221
- {index.hidden ? "Unhide" : "Hide"}
222
- </button>
223
- <button
224
- class="btn btn-outline-danger btn-sm"
225
- onclick={() => openDropModal(index.name)}
226
- disabled={isLoading}
227
- >
228
- Drop
229
- </button>
463
+ </tr>
464
+ </thead>
465
+ <tbody>
466
+ {#each result.data as index (index.name)}
467
+ {@const isLoading = loadingIndexes.has(index.name)}
468
+ <tr class="group" class:opacity-50={isLoading}>
469
+ <td class="name-cell">
470
+ <span class="font-mono text-sm index-name" title={index.name}>{index.name}</span>
471
+ </td>
472
+ <td>
473
+ {#if index.key}
474
+ <div class="flex flex-wrap gap-2">
475
+ {#each Object.entries(index.key) as [field, direction], i (i)}
476
+ <span class="index-key">
477
+ {field}: {direction === 1 ? "" : direction === -1 ? "↓" : direction}
478
+ </span>
479
+ {/each}
480
+ </div>
481
+ {:else}
482
+ <span style="color: var(--text-darker)">-</span>
483
+ {/if}
484
+ </td>
485
+ <td>
486
+ <div class="flex flex-wrap gap-2">
487
+ {#if index.stats?.building}
488
+ <span class="badge badge-building">building</span>
489
+ {/if}
490
+ {#if index.hidden}
491
+ <span class="badge badge-hidden">hidden</span>
492
+ {/if}
493
+ {#if index.unique}
494
+ <span class="badge badge-unique">unique</span>
495
+ {/if}
496
+ {#if index.sparse}
497
+ <span class="badge badge-sparse">sparse</span>
498
+ {/if}
499
+ {#if index.partialFilterExpression}
500
+ <span class="badge badge-partial">partial</span>
501
+ {/if}
502
+ {#if !index.stats?.building && !index.hidden && !index.unique && !index.sparse && !index.partialFilterExpression}
503
+ <span style="color: var(--text-darker)">-</span>
504
+ {/if}
230
505
  </div>
506
+ </td>
507
+ <td>
508
+ {#if index.size}
509
+ <span class="text-sm whitespace-nowrap">{formatBytes(index.size)}</span>
510
+ {:else}
511
+ <span style="color: var(--text-darker)">-</span>
512
+ {/if}
513
+ </td>
514
+ <td>
515
+ {#if index.stats}
516
+ {@const indexMultiNodeStats = Object.entries(multiNodeStats)
517
+ .filter(([key]) => key.startsWith(`${index.name}::`))
518
+ .map(([key, stats]) => ({ key, stats }))}
519
+ {@const indexBaselineStats = Object.fromEntries(
520
+ Object.entries(baselineMultiNodeStats).filter(([key]) => key.startsWith(`${index.name}::`)),
521
+ )}
522
+ <div class="text-sm mt-1">
523
+ {#if indexMultiNodeStats.length > 0}
524
+ {#if aggregateUsage}
525
+ {@const totalOps = sum(indexMultiNodeStats.map((item) => item.stats.ops))}
526
+ {@const baselineTotalOps = sum(Object.values(indexBaselineStats).map((stats) => stats.ops))}
527
+ {@const increment = baselineTotalOps > 0 ? totalOps - baselineTotalOps : 0}
528
+ <div title="Aggregated across {indexMultiNodeStats.length} node(s)">
529
+ {increment > 0 ? formatSignificantDigits(totalOps) : totalOps.toLocaleString()}
530
+ {#if increment > 0}
531
+ <span class="increment-badge">+{formatSignificantDigits(increment)}</span>
532
+ {/if}
533
+ <span class="text-xs" style="color: var(--text-darker)">
534
+ ({indexMultiNodeStats.length} node{indexMultiNodeStats.length > 1 ? "s" : ""})
535
+ </span>
536
+ </div>
537
+ {:else}
538
+ {#each indexMultiNodeStats as { key, stats }, i (i)}
539
+ {@const baselineOps = indexBaselineStats[key]?.ops || 0}
540
+ {@const increment = baselineOps > 0 ? stats.ops - baselineOps : 0}
541
+ <div class="separate-stat" title={stats.host}>
542
+ <span class="stat-host">{shortenedHostnames[stats.host] || stats.host}:</span>
543
+ <span class="stat-value">
544
+ {increment > 0 ? formatSignificantDigits(stats.ops) : stats.ops.toLocaleString()}
545
+ {#if increment > 0}
546
+ <span class="increment-badge">+{formatSignificantDigits(increment)}</span>
547
+ {/if}
548
+ </span>
549
+ </div>
550
+ {/each}
551
+ {/if}
552
+ {:else}
553
+ <div>{index.stats.ops.toLocaleString()}</div>
554
+ {/if}
555
+ </div>
556
+ {:else}
557
+ <span style="color: var(--text-darker)">-</span>
558
+ {/if}
559
+ </td>
560
+ <td>
561
+ <details class="cursor-pointer">
562
+ <summary class="details-link">View full</summary>
563
+ <div class="details-content">
564
+ <JsonValue value={omit(index, ["stats"])} collapsed={false} />
565
+ </div>
566
+ </details>
567
+ </td>
568
+ {#if !readOnly}
569
+ <td>
570
+ {#if index.name !== "_id_"}
571
+ <div class="flex gap-2 hidden group-hover:flex -my-2">
572
+ <button
573
+ class="btn btn-outline-light btn-sm"
574
+ onclick={() => toggleHidden(index.name, index.hidden)}
575
+ disabled={isLoading}
576
+ >
577
+ {index.hidden ? "Unhide" : "Hide"}
578
+ </button>
579
+ <button
580
+ class="btn btn-outline-danger btn-sm"
581
+ onclick={() => openDropModal(index.name)}
582
+ disabled={isLoading}
583
+ >
584
+ Drop
585
+ </button>
586
+ </div>
587
+ {/if}
588
+ </td>
231
589
  {/if}
232
- </td>
233
- {/if}
234
- </tr>
235
- {/each}
236
- </tbody>
237
- </table>
238
- </Panel>
590
+ </tr>
591
+ {/each}
592
+ </tbody>
593
+ </table>
594
+ </div>
595
+ </Panel>
596
+ {/if}
239
597
  {/if}
240
598
  {:catch err}
241
599
  <Panel title="Indexes">
@@ -258,7 +616,106 @@
258
616
  {/snippet}
259
617
  </Modal>
260
618
 
619
+ <Modal show={showCreateModal} onclose={closeCreateModal} title="Create Index">
620
+ <div class="space-y-4">
621
+ <div>
622
+ <label for="index-keys" class="block text-sm font-semibold mb-2" style="color: var(--text);">
623
+ Index Keys (JSON) <span style="color: var(--error);">*</span>
624
+ </label>
625
+ <p class="text-xs mb-2" style="color: var(--text-darker);">
626
+ Define the fields to index. Use 1 for ascending, -1 for descending. Example: <code>{"{"} "fieldName": 1 }</code>
627
+ </p>
628
+ <textarea
629
+ id="index-keys"
630
+ bind:value={indexKeys}
631
+ rows="3"
632
+ use:jsonTextarea
633
+ placeholder={'{"fieldName": 1}'}
634
+ class="w-full p-3 rounded border border-[var(--border-color)] bg-[var(--color-1)] font-mono text-sm focus:outline-none focus:ring-2"
635
+ style="color: var(--text); --tw-ring-color: var(--link);"
636
+ ></textarea>
637
+ </div>
638
+
639
+ <div>
640
+ <label for="index-name" class="block text-sm font-semibold mb-2" style="color: var(--text);">
641
+ Index Name (Optional)
642
+ </label>
643
+ <p class="text-xs mb-2" style="color: var(--text-darker);">Leave empty to auto-generate based on fields</p>
644
+ <input
645
+ id="index-name"
646
+ type="text"
647
+ bind:value={indexName}
648
+ placeholder="e.g., user_email_idx"
649
+ class="w-full p-2 rounded border border-[var(--border-color)] bg-[var(--color-1)] text-sm focus:outline-none focus:ring-2"
650
+ style="color: var(--text); --tw-ring-color: var(--link);"
651
+ />
652
+ </div>
653
+
654
+ <div class="grid grid-cols-2 gap-3">
655
+ <label class="flex items-center gap-2 cursor-pointer">
656
+ <input type="checkbox" bind:checked={indexUnique} class="cursor-pointer" />
657
+ <span class="text-sm" style="color: var(--text);">Unique</span>
658
+ </label>
659
+ <label class="flex items-center gap-2 cursor-pointer">
660
+ <input type="checkbox" bind:checked={indexSparse} class="cursor-pointer" />
661
+ <span class="text-sm" style="color: var(--text);">Sparse</span>
662
+ </label>
663
+ <label class="flex items-center gap-2 cursor-pointer">
664
+ <input type="checkbox" bind:checked={indexBackground} class="cursor-pointer" />
665
+ <span class="text-sm" style="color: var(--text);">Background</span>
666
+ </label>
667
+ </div>
668
+
669
+ <div>
670
+ <label for="index-partial" class="block text-sm font-semibold mb-2" style="color: var(--text);">
671
+ Partial Filter Expression (Optional)
672
+ </label>
673
+ <p class="text-xs mb-2" style="color: var(--text-darker);">
674
+ Only index documents that match this filter. Example: <code>{"{"} "status": "active" }</code>
675
+ </p>
676
+ <textarea
677
+ id="index-partial"
678
+ bind:value={indexPartialFilterExpression}
679
+ rows="2"
680
+ use:jsonTextarea
681
+ placeholder={'{"status": "active"}'}
682
+ class="w-full p-3 rounded border border-[var(--border-color)] bg-[var(--color-1)] font-mono text-sm focus:outline-none focus:ring-2"
683
+ style="color: var(--text); --tw-ring-color: var(--link);"
684
+ ></textarea>
685
+ </div>
686
+
687
+ <div>
688
+ <label for="index-ttl" class="block text-sm font-semibold mb-2" style="color: var(--text);">
689
+ TTL - Expire After Seconds (Optional)
690
+ </label>
691
+ <p class="text-xs mb-2" style="color: var(--text-darker);">
692
+ Automatically delete documents after specified seconds (for TTL indexes)
693
+ </p>
694
+ <input
695
+ id="index-ttl"
696
+ type="number"
697
+ bind:value={indexExpireAfterSeconds}
698
+ placeholder="e.g., 3600"
699
+ min="0"
700
+ class="w-full p-2 rounded border border-[var(--border-color)] bg-[var(--color-1)] text-sm focus:outline-none focus:ring-2"
701
+ style="color: var(--text); --tw-ring-color: var(--link);"
702
+ />
703
+ </div>
704
+ </div>
705
+
706
+ {#snippet footer()}
707
+ <button class="btn btn-default btn-sm" onclick={closeCreateModal} disabled={creatingIndex}>Cancel</button>
708
+ <button class="btn btn-success btn-sm" onclick={confirmCreate} disabled={creatingIndex}>
709
+ {creatingIndex ? "Creating..." : "Create Index"}
710
+ </button>
711
+ {/snippet}
712
+ </Modal>
713
+
261
714
  <style lang="postcss">
715
+ .table-wrapper {
716
+ overflow-x: auto;
717
+ }
718
+
262
719
  .table tbody td {
263
720
  vertical-align: top;
264
721
  }
@@ -268,6 +725,23 @@
268
725
  white-space: nowrap;
269
726
  }
270
727
 
728
+ .name-column {
729
+ width: 20%;
730
+ min-width: 150px;
731
+ max-width: 300px;
732
+ }
733
+
734
+ .name-cell {
735
+ max-width: 300px;
736
+ }
737
+
738
+ .index-name {
739
+ display: block;
740
+ overflow: hidden;
741
+ text-overflow: ellipsis;
742
+ white-space: nowrap;
743
+ }
744
+
271
745
  .index-key {
272
746
  display: inline-flex;
273
747
  align-items: center;
@@ -280,6 +754,22 @@
280
754
  border: 1px solid var(--color-4);
281
755
  }
282
756
 
757
+ .badge-building {
758
+ background-color: hsl(45, 100%, 50%);
759
+ color: var(--text-inverse);
760
+ animation: pulse 2s ease-in-out infinite;
761
+ }
762
+
763
+ @keyframes pulse {
764
+ 0%,
765
+ 100% {
766
+ opacity: 1;
767
+ }
768
+ 50% {
769
+ opacity: 0.6;
770
+ }
771
+ }
772
+
283
773
  .badge-unique {
284
774
  background-color: var(--button-success);
285
775
  color: var(--text-lighter);
@@ -330,4 +820,37 @@
330
820
  border: 1px solid var(--border-color);
331
821
  max-width: 600px;
332
822
  }
823
+
824
+ .stat-value {
825
+ font-weight: 500;
826
+ white-space: nowrap;
827
+ }
828
+
829
+ .separate-stat {
830
+ display: flex;
831
+ gap: 6px;
832
+ align-items: baseline;
833
+ padding: 2px 0;
834
+ }
835
+
836
+ .stat-host {
837
+ font-family: monospace;
838
+ font-size: 11px;
839
+ color: var(--text-darker);
840
+ white-space: nowrap;
841
+ overflow: hidden;
842
+ text-overflow: ellipsis;
843
+ max-width: 150px;
844
+ }
845
+
846
+ .increment-badge {
847
+ display: inline-block;
848
+ margin-left: 6px;
849
+ padding: 2px 6px;
850
+ background-color: hsl(150, 60%, 45%);
851
+ color: white;
852
+ border-radius: 4px;
853
+ font-size: 11px;
854
+ font-weight: 600;
855
+ }
333
856
  </style>