@hot-updater/console 1.0.0-rc.9 → 1.0.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 (341) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-jB4Loglh.js +1 -0
  3. package/.output/public/assets/EventDetails-B6yUsyNA.js +1 -0
  4. package/.output/public/assets/HashValueDisplay-BFX_kY3-.js +1 -0
  5. package/.output/public/assets/InsightsInfo-BNO0WznZ.js +1 -0
  6. package/.output/public/assets/InsightsPageHeader-B892oVml.js +1 -0
  7. package/.output/public/assets/NotFoundPage-DIqCpWa3.js +1 -0
  8. package/.output/public/assets/alert-dialog-Bbk_4-WJ.js +1 -0
  9. package/.output/public/assets/api-DCM0wr6O.js +1 -0
  10. package/.output/public/assets/api-keys-CIO9L_wu.js +1 -0
  11. package/.output/public/assets/api-keys-DVTL_--f.js +1 -0
  12. package/.output/public/assets/arrow-left-CnMYpqB7.js +1 -0
  13. package/.output/public/assets/arrow-up-right-BtffZA6e.js +1 -0
  14. package/.output/public/assets/chevron-right-BqD5wtFb.js +1 -0
  15. package/.output/public/assets/clipboard-DS9ucUQL.js +1 -0
  16. package/.output/public/assets/console-features-api-BH5m_PtQ.js +1 -0
  17. package/.output/public/assets/dialog-Bqbb7B0N.js +1 -0
  18. package/.output/public/assets/dist-LxKJB-Vu.js +1 -0
  19. package/.output/public/assets/empty-CuT2UhFp.js +1 -0
  20. package/.output/public/assets/field-BBDpjIqu.js +1 -0
  21. package/.output/public/assets/index-6o51GkFe.js +11 -0
  22. package/.output/public/assets/input-group-ja_30qaF.js +1 -0
  23. package/.output/public/assets/insights-CmraDzQh.js +1 -0
  24. package/.output/public/assets/insights-CxZ2GCYV.js +2 -0
  25. package/.output/public/assets/insights-api-BigAU9sZ.js +1 -0
  26. package/.output/public/assets/insights-usage-rpc-DYEhsCKT.js +1 -0
  27. package/.output/public/assets/insights-vNRCrfnS.js +42 -0
  28. package/.output/public/assets/insights_.distribution-ChOerfJt.js +1 -0
  29. package/.output/public/assets/insights_.distribution-D-e4iga8.js +1 -0
  30. package/.output/public/assets/insights_.distribution-DZtkibX1.js +2 -0
  31. package/.output/public/assets/installations-BazSJXE0.js +2 -0
  32. package/.output/public/assets/installations-ClHTqdAO.js +1 -0
  33. package/.output/public/assets/installations-DpKsBuHi.js +1 -0
  34. package/.output/public/assets/key-round-wS_SIZ6b.js +1 -0
  35. package/.output/public/assets/list-CgrYiadM.js +1 -0
  36. package/.output/public/assets/refresh-cw-BYgoNCco.js +1 -0
  37. package/.output/public/assets/remote-config-BWwHOkCK.js +1 -0
  38. package/.output/public/assets/remote-config-Br56BODb.js +6 -0
  39. package/.output/public/assets/remote-config-CXOi0LRu.js +1 -0
  40. package/.output/public/assets/routes-B88Erkpk.js +2 -0
  41. package/.output/public/assets/routes-CfzsAleC.js +1 -0
  42. package/.output/public/assets/search-DaTDPW25.js +1 -0
  43. package/.output/public/assets/select-BZZBXMkr.js +1 -0
  44. package/.output/public/assets/sidebar-DNVyJPfX.js +3 -0
  45. package/.output/public/assets/smartphone-DpTFgtKf.js +1 -0
  46. package/.output/public/assets/styles-DTGEAJFv.css +2 -0
  47. package/.output/public/assets/switch-DZ1TPwx-.js +1 -0
  48. package/.output/public/assets/table-Gm4qRWwE.js +1 -0
  49. package/.output/public/assets/tabs-C7FOEjFC.js +1 -0
  50. package/.output/public/assets/undo-2-CmrHjwFc.js +1 -0
  51. package/.output/public/assets/useCompositeListItem-9E4pj_CH.js +1 -0
  52. package/.output/public/assets/valueToPercent-CH8GCHaL.js +1 -0
  53. package/.output/server/__23tanstack-start-server-fn-resolver-BpRlos5Q.mjs +170 -0
  54. package/.output/server/_libs/@base-ui/react+[...].mjs +4389 -4207
  55. package/.output/server/_libs/@tanstack/react-router+[...].mjs +3802 -4022
  56. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +140 -97
  57. package/.output/server/_libs/lucide-react.mjs +221 -9
  58. package/.output/server/_libs/next-themes.mjs +1 -1
  59. package/.output/server/_libs/react-redux+[...].mjs +1 -1
  60. package/.output/server/_libs/recharts+[...].mjs +483 -901
  61. package/.output/server/_libs/sonner.mjs +1 -1
  62. package/.output/server/_libs/tanstack__history.mjs +68 -41
  63. package/.output/server/_libs/tanstack__query-core.mjs +2010 -309
  64. package/.output/server/_libs/tanstack__react-query.mjs +448 -21
  65. package/.output/server/_libs/verkit.mjs +102 -1
  66. package/.output/server/_ssr/BundleIdDisplay-x94o9rlU.mjs +13 -0
  67. package/.output/server/_ssr/ConsoleFeatureUnavailable-ldW8v8-q.mjs +58 -0
  68. package/.output/server/_ssr/EventDetails-Q8kfj9Nr.mjs +344 -0
  69. package/.output/server/_ssr/{HashValueDisplay-CyMmr0O9.mjs → HashValueDisplay-n1GholGS.mjs} +3 -3
  70. package/.output/server/_ssr/InsightsInfo-DuJFatQO.mjs +75 -0
  71. package/.output/server/_ssr/InsightsPageHeader-BSUX3Ye9.mjs +145 -0
  72. package/.output/server/_ssr/NotFoundPage-DZzOOzS3.mjs +37 -0
  73. package/.output/server/_ssr/{dialog-CKvQ4VIE.mjs → alert-dialog-C43Doa0I.mjs} +6 -83
  74. package/.output/server/_ssr/{api-keys-CzZ7Ont4.mjs → api-keys-B4sYm8dM.mjs} +48 -9
  75. package/.output/server/_ssr/api-keys-CEnM9fqG.mjs +5 -0
  76. package/.output/server/_ssr/api-keys-rpc-BIFuUgMt.mjs +52 -0
  77. package/.output/server/_ssr/api-oZjRn9tP.mjs +186 -0
  78. package/.output/server/_ssr/api-rpc-BNiFRRK8.mjs +240 -0
  79. package/.output/server/_ssr/{auth-BF8mbAgF.mjs → auth-UP0wXs4i.mjs} +1 -1
  80. package/.output/server/_ssr/{auth-rpc-DH6Bhwcz.mjs → auth-rpc-DnZqYxyq.mjs} +6 -6
  81. package/.output/server/_ssr/auth.server-DFpyxRDd.mjs +5374 -0
  82. package/.output/server/_ssr/bundleActivity-M5gzBFNg.mjs +18 -0
  83. package/.output/server/_ssr/config-WVX4IxSR.mjs +15 -0
  84. package/.output/server/_ssr/config.server-DskXCAyI.mjs +35 -0
  85. package/.output/server/_ssr/console-access-BJfAXknd.mjs +17 -0
  86. package/.output/server/_ssr/console-features-Oz0q20jJ.mjs +61 -0
  87. package/.output/server/_ssr/console-features-api-C9kiKvaV.mjs +43 -0
  88. package/.output/server/_ssr/console-features-rpc-P0GL7Z-R.mjs +19 -0
  89. package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-Cm6Tn0_Y.mjs} +67 -56
  90. package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-RLR4H-Fn.mjs} +2 -2
  91. package/.output/server/_ssr/databaseConfig-C5SkAyUy.mjs +5 -0
  92. package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Csbuzc9Q.mjs} +28 -5
  93. package/.output/server/_ssr/databaseRows-CPphvbag.mjs +73 -0
  94. package/.output/server/_ssr/dialog-BbYDMjdC.mjs +84 -0
  95. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-CbQbBIw-.mjs} +140 -7
  96. package/.output/server/_ssr/{downloadBundle-CI7JUKvC.mjs → downloadBundle-CZysjmNW.mjs} +10 -10
  97. package/.output/server/_ssr/empty-CVLGfxuu.mjs +60 -0
  98. package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CPtwX8a7.mjs} +1 -1
  99. package/.output/server/_ssr/failureReports-DKDhVsXA.mjs +33 -0
  100. package/.output/server/_ssr/field-asw83X0v.mjs +189 -0
  101. package/.output/server/_ssr/getBundleChildren-Cv9iz1As.mjs +22 -0
  102. package/.output/server/_ssr/{input-group-BrGSjz4S.mjs → input-group-CuYJHosb.mjs} +2 -2
  103. package/.output/server/_ssr/insights-B55FG5fm.mjs +14 -0
  104. package/.output/server/_ssr/insights-CWnHIFUV.mjs +5 -0
  105. package/.output/server/_ssr/insights-CpLpedFZ.mjs +2315 -0
  106. package/.output/server/_ssr/insights-api-sjws23WF.mjs +122 -0
  107. package/.output/server/_ssr/insights-errors-BjnFUyZj.mjs +34 -0
  108. package/.output/server/_ssr/insights-failures-rpc-BvbWvqNf.mjs +36 -0
  109. package/.output/server/_ssr/insights-failures-xaa41Buf.mjs +46 -0
  110. package/.output/server/_ssr/insights-recovery-BWQhiZaF.mjs +28 -0
  111. package/.output/server/_ssr/insights-recovery-rpc-GiYXdkaY.mjs +32 -0
  112. package/.output/server/_ssr/{insights-rpc-DDKmIrpV.mjs → insights-rpc-DP_8Rmz4.mjs} +22 -14
  113. package/.output/server/_ssr/insights-usage-BpBM0U0c.mjs +29 -0
  114. package/.output/server/_ssr/insights-usage-rpc-BiolgB8e.mjs +29 -0
  115. package/.output/server/_ssr/insights-usage-rpc-KStL0zOx.mjs +41 -0
  116. package/.output/server/_ssr/insightsUsage-CzhVjsc8.mjs +56 -0
  117. package/.output/server/_ssr/insights_.distribution-CVrnbP3R.mjs +7 -0
  118. package/.output/server/_ssr/insights_.distribution-D2HaNTAA.mjs +16 -0
  119. package/.output/server/_ssr/{insights_.distribution-cIsU-kHG.mjs → insights_.distribution-DN9pR422.mjs} +50 -22
  120. package/.output/server/_ssr/installations-BHmbbnH8.mjs +5 -0
  121. package/.output/server/_ssr/{installations-C5anhyP0.mjs → installations-BSCmFq8Q.mjs} +279 -367
  122. package/.output/server/_ssr/installations-DD1g__Nc.mjs +69 -0
  123. package/.output/server/_ssr/listReleases-BfUBlCHo.mjs +79 -0
  124. package/.output/server/_ssr/plugins-BaD1slHx.mjs +2086 -0
  125. package/.output/server/_ssr/release-adoption-005tQRpo.mjs +55 -0
  126. package/.output/server/_ssr/release-adoption-rpc-BsstqxUp.mjs +41 -0
  127. package/.output/server/_ssr/releaseAdoption-CtOFkwli.mjs +114 -0
  128. package/.output/server/_ssr/releaseManagement-DFMUnDF5.mjs +11 -0
  129. package/.output/server/_ssr/remote-config-C2Ee8lXO.mjs +5 -0
  130. package/.output/server/_ssr/remote-config-Dv1ccMo-.mjs +17 -0
  131. package/.output/server/_ssr/remote-config-of0lGQH8.mjs +2648 -0
  132. package/.output/server/_ssr/remote-config-rpc-BzX31mUS.mjs +115 -0
  133. package/.output/server/_ssr/rolldown-runtime-rSIU-vHC.mjs +13 -0
  134. package/.output/server/_ssr/{router-BuanSsRI.mjs → router-D8NuoJ_I.mjs} +107 -134
  135. package/.output/server/_ssr/{routes-DY-vqU6R.mjs → routes-DgvnFqDy.mjs} +195 -429
  136. package/.output/server/_ssr/routes-V8sDybGf.mjs +409 -0
  137. package/.output/server/_ssr/runtime.server-De0QVWc9.mjs +104 -0
  138. package/.output/server/_ssr/schema-BiAVyEj2.mjs +361 -0
  139. package/.output/server/_ssr/{select-DD2szs3Y.mjs → select-t3iXRwc9.mjs} +3 -3
  140. package/.output/server/_ssr/server-h4VxnExB.mjs +8325 -0
  141. package/.output/server/_ssr/{createSsrRpc-CdlfejmI.mjs → sidebar-DJcErBMo.mjs} +107 -47
  142. package/.output/server/_ssr/ssr.mjs +1 -1
  143. package/.output/server/_ssr/start-BfFbHKMK.mjs +4 -0
  144. package/.output/server/_ssr/switch-E-btwMGt.mjs +43 -0
  145. package/.output/server/_ssr/{table-CTj6n-4F.mjs → table-37Dd23TN.mjs} +2 -2
  146. package/.output/server/_ssr/tabs-DEFnTm95.mjs +45 -0
  147. package/.output/server/_ssr/updateFailures-H6pnnYK4.mjs +40 -0
  148. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C88s9jWF.mjs} +2 -2
  149. package/.output/server/_tanstack-start-manifest_v-55AGsehh.mjs +139 -0
  150. package/.output/server/index.mjs +309 -211
  151. package/LICENSE +13 -14
  152. package/README.md +80 -71
  153. package/lib/index.d.mts +23 -11
  154. package/lib/vite-internal.mjs +13 -3
  155. package/lib/vite.d.mts +2 -0
  156. package/package.json +14 -14
  157. package/src/components/AppSidebar.tsx +69 -74
  158. package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
  159. package/src/components/NotFoundPage.tsx +7 -7
  160. package/src/components/features/FeatureSlots.tsx +103 -0
  161. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  162. package/src/components/features/bundles/BundleMetadata.tsx +8 -5
  163. package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
  164. package/src/components/features/channels/ChannelManagementDialog.tsx +8 -6
  165. package/src/components/features/insights/AppUsage.tsx +41 -14
  166. package/src/components/features/insights/BuiltinBundle.tsx +36 -0
  167. package/src/components/features/insights/ComparedBundlesChart.tsx +310 -0
  168. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  169. package/src/components/features/insights/EventDetails.tsx +390 -26
  170. package/src/components/features/insights/EventHistoryCard.tsx +111 -25
  171. package/src/components/features/insights/EventHistoryList.tsx +1 -1
  172. package/src/components/features/insights/FailureReports.tsx +407 -0
  173. package/src/components/features/insights/InsightsControls.tsx +240 -142
  174. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  175. package/src/components/features/insights/InsightsOverview.tsx +601 -403
  176. package/src/components/features/insights/InsightsPageHeader.tsx +19 -14
  177. package/src/components/features/insights/InsightsPeriodSelector.tsx +67 -12
  178. package/src/components/features/insights/InstallationHistoryCard.tsx +65 -10
  179. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  180. package/src/components/features/insights/ReleaseActivity.tsx +308 -0
  181. package/src/components/features/insights/ReportingDevicesSummary.tsx +13 -13
  182. package/src/components/features/insights/RollbackButton.tsx +118 -0
  183. package/src/components/features/insights/UpdateFailures.tsx +382 -0
  184. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  185. package/src/components/features/releases/ReleaseEditorSheet.tsx +27 -13
  186. package/src/components/features/remote-config/ConditionDialog.tsx +592 -0
  187. package/src/components/features/remote-config/ConditionsCard.tsx +247 -0
  188. package/src/components/features/remote-config/ParameterDialog.tsx +426 -0
  189. package/src/components/features/remote-config/ParametersCard.tsx +254 -0
  190. package/src/components/features/remote-config/PreviewCard.tsx +284 -0
  191. package/src/components/features/remote-config/PublishDialog.tsx +325 -0
  192. package/src/components/features/remote-config/RemoteConfigPage.tsx +353 -0
  193. package/src/components/features/remote-config/RuleSummary.tsx +21 -0
  194. package/src/components/features/remote-config/ValueInput.tsx +151 -0
  195. package/src/components/features/remote-config/VersionsCard.tsx +384 -0
  196. package/src/components/features/remote-config/remoteConfigFeatureUi.tsx +15 -0
  197. package/src/components/features/remote-config/useDateTimeText.ts +16 -0
  198. package/src/components/ui/sidebar.tsx +22 -29
  199. package/src/index.ts +26 -19
  200. package/src/lib/api-keys-api.ts +0 -28
  201. package/src/lib/api-keys-rpc.ts +27 -38
  202. package/src/lib/api-rpc.ts +125 -288
  203. package/src/lib/api.ts +15 -134
  204. package/src/lib/bundle-activity.ts +24 -4
  205. package/src/lib/console-access.ts +20 -0
  206. package/src/lib/console-features-api.ts +68 -0
  207. package/src/lib/console-features-rpc.ts +13 -0
  208. package/src/lib/console-features.ts +98 -0
  209. package/src/lib/insights-api.ts +56 -2
  210. package/src/lib/insights-errors.ts +64 -0
  211. package/src/lib/insights-failures-rpc.ts +36 -0
  212. package/src/lib/insights-failures.ts +130 -0
  213. package/src/lib/insights-recovery-rpc.ts +13 -17
  214. package/src/lib/insights-recovery.ts +12 -36
  215. package/src/lib/insights-retention.ts +21 -0
  216. package/src/lib/insights-rpc.ts +30 -12
  217. package/src/lib/insights-search.ts +50 -4
  218. package/src/lib/insights-usage-rpc.ts +15 -9
  219. package/src/lib/insights-usage.ts +41 -3
  220. package/src/lib/insights-view.ts +34 -5
  221. package/src/lib/release-adoption-api.ts +214 -0
  222. package/src/lib/release-adoption-rpc.ts +49 -0
  223. package/src/lib/release-adoption.ts +164 -0
  224. package/src/lib/remote-config-api.ts +119 -0
  225. package/src/lib/remote-config-draft.ts +666 -0
  226. package/src/lib/remote-config-rpc.ts +196 -0
  227. package/src/lib/scroll-restoration.ts +21 -0
  228. package/src/lib/server/auth.server.ts +13 -5
  229. package/src/lib/server/bundleActivity.ts +27 -28
  230. package/src/lib/server/config.server.ts +21 -76
  231. package/src/lib/server/console-feature-apis.ts +70 -0
  232. package/src/lib/server/console-runtime.server.ts +82 -38
  233. package/src/lib/server/downloadBundle.ts +14 -15
  234. package/src/lib/server/failureReports.ts +50 -0
  235. package/src/lib/server/getBundleChildren.ts +31 -122
  236. package/src/lib/server/insightsUsage.ts +52 -113
  237. package/src/lib/server/listReleases.ts +109 -44
  238. package/src/lib/server/releaseAdoption.ts +151 -0
  239. package/src/lib/server/releaseReachability.ts +8 -8
  240. package/src/lib/server/remoteConfig.ts +75 -0
  241. package/src/lib/server/runtime.server.ts +120 -31
  242. package/src/lib/server/updateFailures.ts +66 -0
  243. package/src/lib/use-dialog-target.ts +27 -0
  244. package/src/routeTree.gen.ts +44 -44
  245. package/src/router.tsx +3 -2
  246. package/src/routes/-installations-search.ts +32 -8
  247. package/src/routes/-releases-search.ts +76 -29
  248. package/src/routes/__root.tsx +7 -1
  249. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  250. package/src/routes/api-keys.tsx +5 -2
  251. package/src/routes/index.tsx +210 -152
  252. package/src/routes/insights.tsx +91 -23
  253. package/src/routes/insights_.distribution.tsx +50 -9
  254. package/src/routes/installations.tsx +97 -40
  255. package/src/routes/remote-config.tsx +42 -0
  256. package/src/styles.css +9 -0
  257. package/src/vite-internal.ts +15 -3
  258. package/src/vite.ts +2 -0
  259. package/.output/public/assets/HashValueDisplay-BpTc3Oyg.js +0 -1
  260. package/.output/public/assets/InsightsPageHeader-DC2eEst8.js +0 -1
  261. package/.output/public/assets/api-CdUyUqzR.js +0 -42
  262. package/.output/public/assets/api-keys-64PM9PwW.js +0 -1
  263. package/.output/public/assets/api-keys-api-BeaJnvY5.js +0 -1
  264. package/.output/public/assets/arrow-left-04Rh8Zgp.js +0 -1
  265. package/.output/public/assets/card-CYHs5FZv.js +0 -1
  266. package/.output/public/assets/chart-no-axes-combined-DwyMsVFW.js +0 -1
  267. package/.output/public/assets/createServerFn-DU71xucx.js +0 -3
  268. package/.output/public/assets/dialog-IVMkBRCq.js +0 -1
  269. package/.output/public/assets/dist-BR4GzeEu.js +0 -1
  270. package/.output/public/assets/field-B3CYDpjg.js +0 -1
  271. package/.output/public/assets/index-Cb_D1D6N.js +0 -10
  272. package/.output/public/assets/info-C3y9HNoU.js +0 -1
  273. package/.output/public/assets/input-group-nLZylhWF.js +0 -1
  274. package/.output/public/assets/insights-Bm9v8uO3.js +0 -2
  275. package/.output/public/assets/insights-DS3iO7Up.js +0 -1
  276. package/.output/public/assets/insights-search-Byj3g3Mp.js +0 -1
  277. package/.output/public/assets/insights-usage-rpc-BaGR3lgq.js +0 -1
  278. package/.output/public/assets/insights_.distribution-B1i6cjlQ.js +0 -1
  279. package/.output/public/assets/insights_.distribution-C1Wf27eB.js +0 -2
  280. package/.output/public/assets/installations-CkFW4fV9.js +0 -2
  281. package/.output/public/assets/installations-DxTaIcHD.js +0 -1
  282. package/.output/public/assets/lazyRouteComponent-BapzjYnb.js +0 -1
  283. package/.output/public/assets/list-Dqt53fWZ.js +0 -1
  284. package/.output/public/assets/loader-circle-CUKfUqow.js +0 -1
  285. package/.output/public/assets/preload-helper-ClBMtM1g.js +0 -1
  286. package/.output/public/assets/refresh-cw-CNE_2PnD.js +0 -1
  287. package/.output/public/assets/routes-Cgtvspjg.js +0 -2
  288. package/.output/public/assets/routes-O2r2UUN8.js +0 -1
  289. package/.output/public/assets/shield-check-BFcJ46kz.js +0 -1
  290. package/.output/public/assets/signing-BgspC-xP.js +0 -1
  291. package/.output/public/assets/styles-DnrWuBwd.css +0 -2
  292. package/.output/public/assets/table-BuWQgxJJ.js +0 -1
  293. package/.output/public/assets/useMutation-CSV9NCUl.js +0 -1
  294. package/.output/public/assets/useOpenInteractionType-D5kduCTD.js +0 -1
  295. package/.output/public/assets/valueToPercent-BAtcbSLk.js +0 -1
  296. package/.output/server/__23tanstack-start-server-fn-resolver-DBRojHaG.mjs +0 -150
  297. package/.output/server/_ssr/InsightsPageHeader-mtxVuZKK.mjs +0 -64
  298. package/.output/server/_ssr/api-BfCgYlj5.mjs +0 -332
  299. package/.output/server/_ssr/api-keys-api-C3oeq01R.mjs +0 -60
  300. package/.output/server/_ssr/api-keys-rpc-CdPFdGOx.mjs +0 -136
  301. package/.output/server/_ssr/api-rpc-B2E1ZIG5.mjs +0 -1399
  302. package/.output/server/_ssr/auth.server-CmicHh0J.mjs +0 -47
  303. package/.output/server/_ssr/bundle-signing-rpc-Di7-I6fE.mjs +0 -16
  304. package/.output/server/_ssr/bundle-signing.server-Uqt1OF49.mjs +0 -12
  305. package/.output/server/_ssr/bundleActivity-pBrPSG01.mjs +0 -28
  306. package/.output/server/_ssr/card-Bemj0dpY.mjs +0 -108
  307. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  308. package/.output/server/_ssr/config.server-B8WsT3s0.mjs +0 -368
  309. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  310. package/.output/server/_ssr/deleteBundle-8X-jq7Z7.mjs +0 -66
  311. package/.output/server/_ssr/field-BrQ2u0fT.mjs +0 -74
  312. package/.output/server/_ssr/getBundleChildren-DoL8l_AS.mjs +0 -50
  313. package/.output/server/_ssr/insights-BwQ-wF2a.mjs +0 -10
  314. package/.output/server/_ssr/insights-gr1I_dJU.mjs +0 -938
  315. package/.output/server/_ssr/insights-recovery-Cv6RSyeR.mjs +0 -21
  316. package/.output/server/_ssr/insights-recovery-rpc-DOzxfYfv.mjs +0 -37
  317. package/.output/server/_ssr/insights-search-1czJMZgk.mjs +0 -21
  318. package/.output/server/_ssr/insights-usage-rpc-4ImOq_0p.mjs +0 -184
  319. package/.output/server/_ssr/insights-usage-rpc-BUruODjE.mjs +0 -24
  320. package/.output/server/_ssr/insightsContract-B09K_Px1.mjs +0 -303
  321. package/.output/server/_ssr/insightsHistory-Ci4rYAgl.mjs +0 -41
  322. package/.output/server/_ssr/insightsRecovery-CaghWL2t.mjs +0 -113
  323. package/.output/server/_ssr/insightsUsage-DNIj6jxF.mjs +0 -71
  324. package/.output/server/_ssr/insights_.distribution-VmHuikRE.mjs +0 -10
  325. package/.output/server/_ssr/installations-HXMF_kKf.mjs +0 -29
  326. package/.output/server/_ssr/routes-Dru3IRj0.mjs +0 -37
  327. package/.output/server/_ssr/runtime.server-CsiKFp_Z.mjs +0 -436
  328. package/.output/server/_ssr/server-DJm72Kj-.mjs +0 -7468
  329. package/.output/server/_ssr/signing-H8Lg4lPe.mjs +0 -133
  330. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  331. package/.output/server/_tanstack-start-manifest_v-DsFcTLw_.mjs +0 -125
  332. package/src/components/features/bundles/BundleActivityChart.tsx +0 -97
  333. package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -126
  334. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  335. package/src/lib/bundle-signing-api.ts +0 -14
  336. package/src/lib/bundle-signing-rpc.ts +0 -16
  337. package/src/lib/server/bundle-signing.server.ts +0 -23
  338. package/src/lib/server/deleteBundle.ts +0 -145
  339. package/src/lib/server/insightsHistory.ts +0 -52
  340. package/src/lib/server/insightsRecovery.ts +0 -187
  341. package/src/routes/signing.tsx +0 -37
@@ -0,0 +1,592 @@
1
+ import { Plus, RefreshCw, Trash2, X } from "lucide-react";
2
+ import { type FormEvent, useId, useState } from "react";
3
+
4
+ import { PlatformIcon } from "@/components/PlatformIcon";
5
+ import { Button } from "@/components/ui/button";
6
+ import {
7
+ Dialog,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ } from "@/components/ui/dialog";
14
+ import {
15
+ Field,
16
+ FieldDescription,
17
+ FieldError,
18
+ FieldGroup,
19
+ FieldLabel,
20
+ FieldLegend,
21
+ FieldSet,
22
+ } from "@/components/ui/field";
23
+ import { Input } from "@/components/ui/input";
24
+ import {
25
+ InputGroup,
26
+ InputGroupAddon,
27
+ InputGroupButton,
28
+ InputGroupInput,
29
+ } from "@/components/ui/input-group";
30
+ import {
31
+ Select,
32
+ SelectContent,
33
+ SelectGroup,
34
+ SelectItem,
35
+ SelectTrigger,
36
+ SelectValue,
37
+ } from "@/components/ui/select";
38
+ import { Textarea } from "@/components/ui/textarea";
39
+ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
40
+ import { useChannelsQuery } from "@/lib/api";
41
+ import {
42
+ conditionNameError,
43
+ createRule,
44
+ createSeed,
45
+ endPresets,
46
+ fromLocalDateTimeInput,
47
+ RULE_TYPE_LABELS,
48
+ type RemoteConfigCondition,
49
+ type RemoteConfigRule,
50
+ type RemoteConfigRuleType,
51
+ ruleError,
52
+ splitList,
53
+ startPresets,
54
+ suggestConditionName,
55
+ toLocalDateTimeInput,
56
+ } from "@/lib/remote-config-draft";
57
+ import { useOpenSession } from "@/lib/use-dialog-target";
58
+
59
+ import { useDateTimeText } from "./useDateTimeText";
60
+
61
+ const RULE_TYPES = Object.keys(RULE_TYPE_LABELS) as RemoteConfigRuleType[];
62
+
63
+ /** A list typed as text, kept as typed until it is saved. */
64
+ function ListInput({
65
+ id,
66
+ values,
67
+ onChange,
68
+ placeholder,
69
+ multiline = false,
70
+ }: {
71
+ readonly id: string;
72
+ readonly values: readonly string[];
73
+ readonly onChange: (values: string[]) => void;
74
+ readonly placeholder: string;
75
+ readonly multiline?: boolean;
76
+ }) {
77
+ const [text, setText] = useState(values.join(", "));
78
+ // A value added outside the input, such as a known channel, shows in it.
79
+ const [shown, setShown] = useState(values);
80
+ if (shown !== values) {
81
+ setShown(values);
82
+ if (splitList(text).join("\n") !== values.join("\n")) {
83
+ setText(values.join(", "));
84
+ }
85
+ }
86
+ const update = (next: string) => {
87
+ setText(next);
88
+ onChange(splitList(next));
89
+ };
90
+ return multiline ? (
91
+ <Textarea
92
+ className="min-h-20 font-mono text-xs"
93
+ id={id}
94
+ onChange={(event) => update(event.target.value)}
95
+ placeholder={placeholder}
96
+ spellCheck={false}
97
+ value={text}
98
+ />
99
+ ) : (
100
+ <Input
101
+ autoComplete="off"
102
+ className="min-h-11 sm:min-h-9"
103
+ id={id}
104
+ onChange={(event) => update(event.target.value)}
105
+ placeholder={placeholder}
106
+ value={text}
107
+ />
108
+ );
109
+ }
110
+
111
+ /** One end of a date and time rule: a picker, a clear button, and quick choices. */
112
+ function DateTimeField({
113
+ id,
114
+ label,
115
+ value,
116
+ presets,
117
+ onChange,
118
+ }: {
119
+ readonly id: string;
120
+ readonly label: string;
121
+ readonly value: string | undefined;
122
+ readonly presets: readonly { readonly label: string; readonly iso: string }[];
123
+ readonly onChange: (value: string | undefined) => void;
124
+ }) {
125
+ return (
126
+ <Field>
127
+ <FieldLabel htmlFor={id}>{label}</FieldLabel>
128
+ <InputGroup className="h-11 sm:h-9">
129
+ <InputGroupInput
130
+ className="h-11 tabular-nums sm:h-9"
131
+ id={id}
132
+ onChange={(event) =>
133
+ onChange(fromLocalDateTimeInput(event.target.value))
134
+ }
135
+ type="datetime-local"
136
+ value={toLocalDateTimeInput(value)}
137
+ />
138
+ {value === undefined ? null : (
139
+ <InputGroupAddon align="inline-end">
140
+ <InputGroupButton
141
+ aria-label={`Clear ${label.toLowerCase()}`}
142
+ className="size-11 sm:size-5"
143
+ onClick={() => onChange(undefined)}
144
+ size="icon-xs"
145
+ >
146
+ <X />
147
+ </InputGroupButton>
148
+ </InputGroupAddon>
149
+ )}
150
+ </InputGroup>
151
+ <div className="flex flex-wrap gap-1.5">
152
+ {presets.map((preset) => (
153
+ <Button
154
+ aria-pressed={preset.iso === value}
155
+ className="min-h-11 sm:min-h-6"
156
+ key={preset.label}
157
+ onClick={() => onChange(preset.iso)}
158
+ size="xs"
159
+ type="button"
160
+ variant={preset.iso === value ? "secondary" : "outline"}
161
+ >
162
+ {preset.label}
163
+ </Button>
164
+ ))}
165
+ </div>
166
+ </Field>
167
+ );
168
+ }
169
+
170
+ function RuleFields({
171
+ rule,
172
+ onChange,
173
+ }: {
174
+ readonly rule: RemoteConfigRule;
175
+ readonly onChange: (rule: RemoteConfigRule) => void;
176
+ }) {
177
+ const id = useId();
178
+ const channels = useChannelsQuery();
179
+ switch (rule.type) {
180
+ case "platform":
181
+ return (
182
+ <ToggleGroup
183
+ aria-label="Platforms"
184
+ multiple
185
+ onValueChange={(values) =>
186
+ onChange({
187
+ ...rule,
188
+ platforms: (values as string[]).filter(
189
+ (value): value is "ios" | "android" =>
190
+ value === "ios" || value === "android",
191
+ ),
192
+ })
193
+ }
194
+ value={[...rule.platforms]}
195
+ variant="outline"
196
+ >
197
+ {(["ios", "android"] as const).map((platform) => (
198
+ <ToggleGroupItem
199
+ className="min-h-11 px-3 sm:min-h-8"
200
+ key={platform}
201
+ value={platform}
202
+ >
203
+ <PlatformIcon className="size-3.5" platform={platform} />
204
+ {platform === "ios" ? "iOS" : "Android"}
205
+ </ToggleGroupItem>
206
+ ))}
207
+ </ToggleGroup>
208
+ );
209
+ case "channel": {
210
+ const known = (channels.data ?? [])
211
+ .map(({ name }) => name)
212
+ .filter((channel) => !rule.channels.includes(channel));
213
+ return (
214
+ <div className="flex flex-col gap-2">
215
+ <ListInput
216
+ id={`${id}-channels`}
217
+ onChange={(next) => onChange({ ...rule, channels: next })}
218
+ placeholder="production, beta"
219
+ values={rule.channels}
220
+ />
221
+ {known.length > 0 ? (
222
+ <div className="flex flex-wrap items-center gap-1.5">
223
+ <span className="text-xs text-muted-foreground">Add:</span>
224
+ {known.slice(0, 8).map((channel) => (
225
+ <Button
226
+ className="min-h-11 sm:min-h-6"
227
+ key={channel}
228
+ onClick={() =>
229
+ onChange({ ...rule, channels: [...rule.channels, channel] })
230
+ }
231
+ size="xs"
232
+ type="button"
233
+ variant="outline"
234
+ >
235
+ <Plus data-icon="inline-start" />
236
+ {channel}
237
+ </Button>
238
+ ))}
239
+ </div>
240
+ ) : null}
241
+ </div>
242
+ );
243
+ }
244
+ case "appVersion":
245
+ return (
246
+ <Input
247
+ aria-label="App version range"
248
+ autoComplete="off"
249
+ className="min-h-11 font-mono sm:min-h-9"
250
+ onChange={(event) => onChange({ ...rule, range: event.target.value })}
251
+ placeholder=">=1.4.0"
252
+ value={rule.range}
253
+ />
254
+ );
255
+ case "cohort":
256
+ return (
257
+ <ListInput
258
+ id={`${id}-cohorts`}
259
+ onChange={(next) => onChange({ ...rule, cohorts: next })}
260
+ placeholder="1, 2, beta-testers"
261
+ values={rule.cohorts}
262
+ />
263
+ );
264
+ case "percent":
265
+ return (
266
+ <div className="grid gap-2 sm:grid-cols-[repeat(2,minmax(0,8rem))_minmax(0,1fr)]">
267
+ <Field>
268
+ <FieldLabel htmlFor={`${id}-from`}>From %</FieldLabel>
269
+ <Input
270
+ className="min-h-11 tabular-nums sm:min-h-9"
271
+ id={`${id}-from`}
272
+ inputMode="decimal"
273
+ max={100}
274
+ min={0}
275
+ onChange={(event) =>
276
+ onChange({ ...rule, from: Number(event.target.value) })
277
+ }
278
+ step={0.1}
279
+ type="number"
280
+ value={rule.from}
281
+ />
282
+ </Field>
283
+ <Field>
284
+ <FieldLabel htmlFor={`${id}-to`}>To %</FieldLabel>
285
+ <Input
286
+ className="min-h-11 tabular-nums sm:min-h-9"
287
+ id={`${id}-to`}
288
+ inputMode="decimal"
289
+ max={100}
290
+ min={0}
291
+ onChange={(event) =>
292
+ onChange({ ...rule, to: Number(event.target.value) })
293
+ }
294
+ step={0.1}
295
+ type="number"
296
+ value={rule.to}
297
+ />
298
+ </Field>
299
+ <Field>
300
+ <FieldLabel htmlFor={`${id}-seed`}>Seed</FieldLabel>
301
+ <div className="flex gap-2">
302
+ <Input
303
+ autoComplete="off"
304
+ className="min-h-11 font-mono sm:min-h-9"
305
+ id={`${id}-seed`}
306
+ onChange={(event) =>
307
+ onChange({ ...rule, seed: event.target.value })
308
+ }
309
+ value={rule.seed}
310
+ />
311
+ <Button
312
+ aria-label="New seed"
313
+ className="size-11 sm:size-9"
314
+ onClick={() => onChange({ ...rule, seed: createSeed() })}
315
+ size="icon"
316
+ type="button"
317
+ variant="outline"
318
+ >
319
+ <RefreshCw />
320
+ </Button>
321
+ </div>
322
+ </Field>
323
+ </div>
324
+ );
325
+ case "fingerprint":
326
+ return (
327
+ <ListInput
328
+ id={`${id}-hashes`}
329
+ multiline
330
+ onChange={(next) => onChange({ ...rule, hashes: next })}
331
+ placeholder="One fingerprint hash per line"
332
+ values={rule.hashes}
333
+ />
334
+ );
335
+ case "dateTime": {
336
+ const change = (field: "from" | "to", instant: string | undefined) => {
337
+ const { [field]: _previous, ...rest } = rule;
338
+ onChange(instant === undefined ? rest : { ...rest, [field]: instant });
339
+ };
340
+ return (
341
+ <div className="grid gap-3 sm:grid-cols-2">
342
+ <DateTimeField
343
+ id={`${id}-from`}
344
+ label="From"
345
+ onChange={(instant) => change("from", instant)}
346
+ presets={startPresets()}
347
+ value={rule.from}
348
+ />
349
+ <DateTimeField
350
+ id={`${id}-until`}
351
+ label="Until"
352
+ onChange={(instant) => change("to", instant)}
353
+ presets={endPresets(rule.from)}
354
+ value={rule.to}
355
+ />
356
+ </div>
357
+ );
358
+ }
359
+ }
360
+ }
361
+
362
+ const RULE_HINTS: Readonly<Record<RemoteConfigRuleType, string>> = {
363
+ platform: "Devices on any of these platforms.",
364
+ channel: "Devices on any of these channels, separated by commas.",
365
+ appVersion:
366
+ "A semver range of native app versions, as release targets use: 2.x, >=1.4.0 <2.0.0.",
367
+ percent:
368
+ "Installs whose numeric cohort lands in this share. Rules with one seed split the same order, so 0–10 and 10–20 never overlap.",
369
+ cohort: "Numeric cohorts from 1 to 1000, or custom cohort names.",
370
+ fingerprint: "Native builds with any of these fingerprints.",
371
+ dateTime:
372
+ "Leave either end empty to keep it open. Devices get the change at their next fetch.",
373
+ };
374
+
375
+ /** Adds or edits a condition: a name and the rules a device must all match. */
376
+ export function ConditionDialog({
377
+ open,
378
+ onOpenChange,
379
+ condition,
380
+ takenNames,
381
+ onSave,
382
+ }: {
383
+ readonly open: boolean;
384
+ readonly onOpenChange: (open: boolean) => void;
385
+ /** The condition to edit, or null for a new one. */
386
+ readonly condition: RemoteConfigCondition | null;
387
+ readonly takenNames: readonly string[];
388
+ readonly onSave: (condition: RemoteConfigCondition) => void;
389
+ }) {
390
+ // The form stays while the dialog animates closed, and starts over on
391
+ // each opening.
392
+ const session = useOpenSession(open);
393
+ return (
394
+ <Dialog onOpenChange={onOpenChange} open={open}>
395
+ <DialogContent className="max-h-[calc(100svh-2rem)] overflow-y-auto sm:max-w-xl">
396
+ <ConditionForm
397
+ condition={condition}
398
+ key={session}
399
+ onCancel={() => onOpenChange(false)}
400
+ onSave={(next) => {
401
+ onSave(next);
402
+ onOpenChange(false);
403
+ }}
404
+ takenNames={takenNames}
405
+ />
406
+ </DialogContent>
407
+ </Dialog>
408
+ );
409
+ }
410
+
411
+ function ConditionForm({
412
+ condition,
413
+ takenNames,
414
+ onSave,
415
+ onCancel,
416
+ }: {
417
+ readonly condition: RemoteConfigCondition | null;
418
+ readonly takenNames: readonly string[];
419
+ readonly onSave: (condition: RemoteConfigCondition) => void;
420
+ readonly onCancel: () => void;
421
+ }) {
422
+ const id = useId();
423
+ const { dateTimeText, timeZone } = useDateTimeText();
424
+ // A condition named after its rules keeps following them: its name starts
425
+ // empty, so it saves as the rules after an edit too.
426
+ const [name, setName] = useState(() =>
427
+ condition === null ||
428
+ condition.name ===
429
+ suggestConditionName(condition.rules, takenNames, dateTimeText)
430
+ ? ""
431
+ : condition.name,
432
+ );
433
+ const [rules, setRules] = useState<RemoteConfigRule[]>(
434
+ condition === null ? [createRule("platform")] : [...condition.rules],
435
+ );
436
+ const [submitted, setSubmitted] = useState(false);
437
+ // An empty name saves as the rules in a few words.
438
+ const suggestedName = suggestConditionName(rules, takenNames, dateTimeText);
439
+ const savedName = name.trim().length === 0 ? suggestedName : name;
440
+ const nameError = conditionNameError(savedName, takenNames);
441
+ const ruleErrors = rules.map(ruleError);
442
+ const unused = RULE_TYPES.filter(
443
+ (type) => !rules.some((rule) => rule.type === type),
444
+ );
445
+ const invalid =
446
+ nameError !== null ||
447
+ rules.length === 0 ||
448
+ ruleErrors.some((error) => error !== null);
449
+
450
+ const submit = (event: FormEvent<HTMLFormElement>) => {
451
+ event.preventDefault();
452
+ // Opened from the parameter dialog, it must not submit that form too.
453
+ event.stopPropagation();
454
+ setSubmitted(true);
455
+ if (invalid) return;
456
+ onSave({
457
+ name: savedName.trim(),
458
+ rules: rules.map((rule) =>
459
+ rule.type === "appVersion"
460
+ ? { ...rule, range: rule.range.trim() }
461
+ : rule,
462
+ ),
463
+ });
464
+ };
465
+
466
+ return (
467
+ <form className="flex flex-col gap-4" noValidate onSubmit={submit}>
468
+ <DialogHeader>
469
+ <DialogTitle>
470
+ {condition === null ? "Add condition" : "Edit condition"}
471
+ </DialogTitle>
472
+ <DialogDescription>
473
+ A device matches when it matches every rule.
474
+ </DialogDescription>
475
+ </DialogHeader>
476
+ <FieldGroup>
477
+ <Field data-invalid={(submitted && nameError !== null) || undefined}>
478
+ <FieldLabel htmlFor={`${id}-name`}>Name</FieldLabel>
479
+ <Input
480
+ autoComplete="off"
481
+ className="min-h-11 sm:min-h-9"
482
+ id={`${id}-name`}
483
+ maxLength={101}
484
+ onChange={(event) => setName(event.target.value)}
485
+ placeholder={suggestedName || "Beta testers on iOS"}
486
+ value={name}
487
+ />
488
+ <FieldDescription>
489
+ Optional: without one, it is named after its rules.
490
+ </FieldDescription>
491
+ <FieldError>{submitted ? nameError : null}</FieldError>
492
+ </Field>
493
+ <FieldSet>
494
+ <FieldLegend variant="label">Rules</FieldLegend>
495
+ {rules.length === 0 ? (
496
+ <FieldError>
497
+ {submitted ? "Add at least one rule." : null}
498
+ </FieldError>
499
+ ) : null}
500
+ <ol className="flex flex-col gap-3">
501
+ {rules.map((rule, index) => (
502
+ <li className="rounded-lg border p-3" key={rule.type}>
503
+ <Field
504
+ data-invalid={
505
+ (submitted && ruleErrors[index] !== null) || undefined
506
+ }
507
+ >
508
+ <div className="flex items-center justify-between gap-2">
509
+ <span className="text-sm font-medium">
510
+ {RULE_TYPE_LABELS[rule.type]}
511
+ </span>
512
+ <Button
513
+ aria-label={`Remove the ${RULE_TYPE_LABELS[rule.type].toLowerCase()} rule`}
514
+ className="size-11 sm:size-7"
515
+ onClick={() =>
516
+ setRules(rules.filter((_, at) => at !== index))
517
+ }
518
+ size="icon-sm"
519
+ type="button"
520
+ variant="ghost"
521
+ >
522
+ <Trash2 />
523
+ </Button>
524
+ </div>
525
+ <RuleFields
526
+ onChange={(next) =>
527
+ setRules(
528
+ rules.map((existing, at) =>
529
+ at === index ? next : existing,
530
+ ),
531
+ )
532
+ }
533
+ rule={rule}
534
+ />
535
+ <FieldDescription>
536
+ {rule.type === "dateTime"
537
+ ? `In your time zone, ${timeZone}. ${RULE_HINTS.dateTime}`
538
+ : RULE_HINTS[rule.type]}
539
+ </FieldDescription>
540
+ <FieldError>
541
+ {submitted ? ruleErrors[index] : null}
542
+ </FieldError>
543
+ </Field>
544
+ </li>
545
+ ))}
546
+ </ol>
547
+ {unused.length > 0 ? (
548
+ <Select
549
+ items={Object.fromEntries(
550
+ unused.map((type) => [type, RULE_TYPE_LABELS[type]]),
551
+ )}
552
+ onValueChange={(value) => {
553
+ if (value !== null) {
554
+ setRules([
555
+ ...rules,
556
+ createRule(value as RemoteConfigRuleType),
557
+ ]);
558
+ }
559
+ }}
560
+ value={null}
561
+ >
562
+ <SelectTrigger
563
+ aria-label="Add a rule"
564
+ className="min-h-11 w-full sm:min-h-9 sm:w-56"
565
+ >
566
+ <Plus aria-hidden="true" className="size-3.5" />
567
+ <SelectValue placeholder="Add a rule" />
568
+ </SelectTrigger>
569
+ <SelectContent alignItemWithTrigger={false}>
570
+ <SelectGroup>
571
+ {unused.map((type) => (
572
+ <SelectItem key={type} value={type}>
573
+ {RULE_TYPE_LABELS[type]}
574
+ </SelectItem>
575
+ ))}
576
+ </SelectGroup>
577
+ </SelectContent>
578
+ </Select>
579
+ ) : null}
580
+ </FieldSet>
581
+ </FieldGroup>
582
+ <DialogFooter>
583
+ <Button onClick={onCancel} type="button" variant="outline">
584
+ Cancel
585
+ </Button>
586
+ <Button type="submit">
587
+ {condition === null ? "Add condition" : "Save condition"}
588
+ </Button>
589
+ </DialogFooter>
590
+ </form>
591
+ );
592
+ }