queryapigate 0.32.0__tar.gz → 0.34.0__tar.gz

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 (218) hide show
  1. {queryapigate-0.32.0/queryapigate.egg-info → queryapigate-0.34.0}/PKG-INFO +4 -3
  2. {queryapigate-0.32.0 → queryapigate-0.34.0}/README.md +2 -1
  3. {queryapigate-0.32.0 → queryapigate-0.34.0}/pyproject.toml +1 -1
  4. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/__init__.py +1 -1
  5. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/app.py +35 -29
  6. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/cli.py +8 -8
  7. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/config.py +17 -4
  8. queryapigate-0.32.0/queryapigate/console_dist/assets/AccessMapPage-CFAKvNHS.js → queryapigate-0.34.0/queryapigate/console_dist/assets/AccessMapPage-BVQ6wRwp.js +3 -3
  9. queryapigate-0.32.0/queryapigate/console_dist/assets/AdministratorsPage-O9o_8T8Q.js → queryapigate-0.34.0/queryapigate/console_dist/assets/AdministratorsPage-BiUGhISO.js +1 -1
  10. queryapigate-0.32.0/queryapigate/console_dist/assets/AgentsPage-CJL7R9ML.js → queryapigate-0.34.0/queryapigate/console_dist/assets/AgentsPage-CSA-qCUv.js +1 -1
  11. queryapigate-0.32.0/queryapigate/console_dist/assets/AlertsPage-Rhnx0WE5.js → queryapigate-0.34.0/queryapigate/console_dist/assets/AlertsPage-MYZoziNZ.js +1 -1
  12. queryapigate-0.32.0/queryapigate/console_dist/assets/ApiKeysPage-LSfT7OFf.js → queryapigate-0.34.0/queryapigate/console_dist/assets/ApiKeysPage-BZISE5oJ.js +1 -1
  13. queryapigate-0.32.0/queryapigate/console_dist/assets/ApprovalsPage-CxyRU_om.js → queryapigate-0.34.0/queryapigate/console_dist/assets/ApprovalsPage-CUiEkHq3.js +1 -1
  14. queryapigate-0.32.0/queryapigate/console_dist/assets/AuditLogPage-CfozUklt.js → queryapigate-0.34.0/queryapigate/console_dist/assets/AuditLogPage-D9vrSHU7.js +1 -1
  15. queryapigate-0.32.0/queryapigate/console_dist/assets/CachingPage-8TY7yUvl.js → queryapigate-0.34.0/queryapigate/console_dist/assets/CachingPage-2hjur6KQ.js +1 -1
  16. queryapigate-0.32.0/queryapigate/console_dist/assets/CodeBox-B8iq2Oop.js → queryapigate-0.34.0/queryapigate/console_dist/assets/CodeBox-BZY9RBv5.js +1 -1
  17. queryapigate-0.32.0/queryapigate/console_dist/assets/ConnectionsPage-CrgOBe8i.js → queryapigate-0.34.0/queryapigate/console_dist/assets/ConnectionsPage-D26k-98S.js +1 -1
  18. queryapigate-0.32.0/queryapigate/console_dist/assets/DesignerPage-C7m2WgjH.js → queryapigate-0.34.0/queryapigate/console_dist/assets/DesignerPage-B_os91xI.js +1 -1
  19. queryapigate-0.32.0/queryapigate/console_dist/assets/DestinationsPage-hjd4ejdt.js → queryapigate-0.34.0/queryapigate/console_dist/assets/DestinationsPage--bjoNE0y.js +1 -1
  20. queryapigate-0.32.0/queryapigate/console_dist/assets/ExportsPage-D70vuQNl.js → queryapigate-0.34.0/queryapigate/console_dist/assets/ExportsPage-5ai6Ojbl.js +1 -1
  21. queryapigate-0.32.0/queryapigate/console_dist/assets/HelpPage-C7vakDSS.js → queryapigate-0.34.0/queryapigate/console_dist/assets/HelpPage-DNaGeh-8.js +1 -1
  22. queryapigate-0.34.0/queryapigate/console_dist/assets/HomePage-DINN_eBD.js +4 -0
  23. queryapigate-0.32.0/queryapigate/console_dist/assets/Managed-DjB6h9Kc.js → queryapigate-0.34.0/queryapigate/console_dist/assets/Managed-CVE0layM.js +1 -1
  24. queryapigate-0.32.0/queryapigate/console_dist/assets/MetricsPage-BAQxP5-4.js → queryapigate-0.34.0/queryapigate/console_dist/assets/MetricsPage-D6UAGhYP.js +1 -1
  25. queryapigate-0.32.0/queryapigate/console_dist/assets/RepositoryPage-8CBmviv-.js → queryapigate-0.34.0/queryapigate/console_dist/assets/RepositoryPage-BvsZyKvp.js +1 -1
  26. queryapigate-0.32.0/queryapigate/console_dist/assets/Results-DofTMPkj.js → queryapigate-0.34.0/queryapigate/console_dist/assets/Results-D8fDmPdY.js +1 -1
  27. queryapigate-0.32.0/queryapigate/console_dist/assets/RolesPage-CH_Dr1DP.js → queryapigate-0.34.0/queryapigate/console_dist/assets/RolesPage-BdEQdLFD.js +1 -1
  28. queryapigate-0.32.0/queryapigate/console_dist/assets/SchemaBrowser-D57iE2Kg.js → queryapigate-0.34.0/queryapigate/console_dist/assets/SchemaBrowser-Bu0NvoUy.js +1 -1
  29. queryapigate-0.32.0/queryapigate/console_dist/assets/SettingsPage-CHmKfYHu.js → queryapigate-0.34.0/queryapigate/console_dist/assets/SettingsPage-BQhT4xlX.js +1 -1
  30. queryapigate-0.32.0/queryapigate/console_dist/assets/StatTile-BBd2i5mC.js → queryapigate-0.34.0/queryapigate/console_dist/assets/StatTile-DEmSFOkX.js +1 -1
  31. queryapigate-0.32.0/queryapigate/console_dist/assets/Time-CR17Ozn9.js → queryapigate-0.34.0/queryapigate/console_dist/assets/Time-MA05YtZP.js +1 -1
  32. queryapigate-0.32.0/queryapigate/console_dist/assets/forms-DR5CXITG.js → queryapigate-0.34.0/queryapigate/console_dist/assets/forms-BSqwVHNJ.js +1 -1
  33. queryapigate-0.32.0/queryapigate/console_dist/assets/forms-C3dw2wG6.js → queryapigate-0.34.0/queryapigate/console_dist/assets/forms-C6RLe1F3.js +1 -1
  34. queryapigate-0.32.0/queryapigate/console_dist/assets/icons-C-i_90Gs.js → queryapigate-0.34.0/queryapigate/console_dist/assets/icons-DQ8_pv9M.js +1 -1
  35. queryapigate-0.32.0/queryapigate/console_dist/assets/index-BNMV1syF.js → queryapigate-0.34.0/queryapigate/console_dist/assets/index-sn5NUET1.js +2 -2
  36. queryapigate-0.32.0/queryapigate/console_dist/assets/maskRules-DyD2pxp7.js → queryapigate-0.34.0/queryapigate/console_dist/assets/maskRules-BGQ7mS0j.js +1 -1
  37. queryapigate-0.32.0/queryapigate/console_dist/assets/reach-0Yvn3NAt.js → queryapigate-0.34.0/queryapigate/console_dist/assets/reach-2gDU11-l.js +1 -1
  38. queryapigate-0.32.0/queryapigate/console_dist/assets/shared-BebZOZXs.js → queryapigate-0.34.0/queryapigate/console_dist/assets/shared-D5ownoD-.js +1 -1
  39. queryapigate-0.32.0/queryapigate/console_dist/assets/useMutation-DCzNFnov.js → queryapigate-0.34.0/queryapigate/console_dist/assets/useMutation-BF7AWw3p.js +1 -1
  40. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/console_dist/index.html +1 -1
  41. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/db.py +86 -4
  42. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/events.py +235 -56
  43. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/examples.py +1 -1
  44. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/experimental.py +10 -22
  45. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/history.py +41 -23
  46. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/openapi.py +20 -6
  47. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/secretrefs.py +33 -3
  48. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/store.py +6 -5
  49. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/v1_spec.py +2 -1
  50. {queryapigate-0.32.0 → queryapigate-0.34.0/queryapigate.egg-info}/PKG-INFO +4 -3
  51. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate.egg-info/SOURCES.txt +32 -32
  52. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate.egg-info/requires.txt +1 -1
  53. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_adhoc_history.py +5 -4
  54. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_aws.py +2 -2
  55. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_data_protection.py +6 -5
  56. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_db.py +39 -1
  57. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_dependency_floors.py +2 -1
  58. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_events.py +16 -3
  59. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_events_server.py +145 -9
  60. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_experimental.py +10 -7
  61. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_history.py +5 -2
  62. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_mcp.py +2 -2
  63. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_multinode.py +1 -1
  64. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_secretrefs.py +54 -2
  65. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_settings.py +1 -1
  66. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_upgrades.py +31 -1
  67. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_vault.py +2 -2
  68. queryapigate-0.32.0/queryapigate/console_dist/assets/HomePage-BuExjOpj.js +0 -4
  69. {queryapigate-0.32.0 → queryapigate-0.34.0}/LICENSE +0 -0
  70. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/__main__.py +0 -0
  71. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/accesslog.py +0 -0
  72. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/adminroles.py +0 -0
  73. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/admins.py +0 -0
  74. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/agentwrites.py +0 -0
  75. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/alerts.py +0 -0
  76. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/apikeys.py +0 -0
  77. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/auditchain.py +0 -0
  78. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/auditstream.py +0 -0
  79. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/aws.py +0 -0
  80. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/broadcast.py +0 -0
  81. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/bundle.py +0 -0
  82. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/cache.py +0 -0
  83. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/collection_admin.py +0 -0
  84. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/configcode.py +0 -0
  85. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/console.py +0 -0
  86. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/console_dist/assets/index-BLd0t6vZ.css +0 -0
  87. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/console_dist/assets/state-O13cl7cO.js +0 -0
  88. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/console_dist/favicon.svg +0 -0
  89. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/cors.py +0 -0
  90. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/databases.py +0 -0
  91. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/definitions.py +0 -0
  92. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/deprecations.py +0 -0
  93. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/descriptions.py +0 -0
  94. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/destinations.py +0 -0
  95. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/duckfiles.py +0 -0
  96. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/engine.py +0 -0
  97. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/errors.py +0 -0
  98. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/exporting.py +0 -0
  99. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/exports.py +0 -0
  100. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/formats.py +0 -0
  101. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/governance.py +0 -0
  102. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/gunicorn_conf.py +0 -0
  103. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/instances.py +0 -0
  104. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/jwtauth.py +0 -0
  105. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/lib/h2-2.2.224.jar +0 -0
  106. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/limits.py +0 -0
  107. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/logging_setup.py +0 -0
  108. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/masking.py +0 -0
  109. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/mcp_server.py +0 -0
  110. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/metrics.py +0 -0
  111. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/mongotools.py +0 -0
  112. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/oidc.py +0 -0
  113. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/params.py +0 -0
  114. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/parquet.py +0 -0
  115. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/pool.py +0 -0
  116. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/postman.py +0 -0
  117. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/quotas.py +0 -0
  118. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/ratelimit.py +0 -0
  119. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/rediscache.py +0 -0
  120. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/rekey.py +0 -0
  121. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/runners.py +0 -0
  122. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/schema.py +0 -0
  123. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/secheaders.py +0 -0
  124. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/services/__init__.py +0 -0
  125. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/services/access.py +0 -0
  126. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/services/administrators.py +0 -0
  127. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/services/audit.py +0 -0
  128. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/services/collections.py +0 -0
  129. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/services/connections.py +0 -0
  130. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/services/descriptions.py +0 -0
  131. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/services/destinations.py +0 -0
  132. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/services/exports.py +0 -0
  133. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/services/mcp.py +0 -0
  134. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/services/queries.py +0 -0
  135. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/sqlflow.py +0 -0
  136. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/sqltools.py +0 -0
  137. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/sso.py +0 -0
  138. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/tableguard.py +0 -0
  139. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/tracing.py +0 -0
  140. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/usage.py +0 -0
  141. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/v1.py +0 -0
  142. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate/vault.py +0 -0
  143. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate.egg-info/dependency_links.txt +0 -0
  144. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate.egg-info/entry_points.txt +0 -0
  145. {queryapigate-0.32.0 → queryapigate-0.34.0}/queryapigate.egg-info/top_level.txt +0 -0
  146. {queryapigate-0.32.0 → queryapigate-0.34.0}/setup.cfg +0 -0
  147. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_adminroles.py +0 -0
  148. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_admins.py +0 -0
  149. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_agent_writes.py +0 -0
  150. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_alerts.py +0 -0
  151. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_api_administrators.py +0 -0
  152. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_api_keys.py +0 -0
  153. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_api_v1.py +0 -0
  154. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_audit_chain.py +0 -0
  155. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_audit_log.py +0 -0
  156. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_audit_siem.py +0 -0
  157. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_audit_stream.py +0 -0
  158. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_backup_restore.py +0 -0
  159. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_broadcast.py +0 -0
  160. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_cli.py +0 -0
  161. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_collection_bundle.py +0 -0
  162. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_collection_postman.py +0 -0
  163. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_collections.py +0 -0
  164. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_configcode.py +0 -0
  165. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_connection_encryption.py +0 -0
  166. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_console.py +0 -0
  167. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_cors_and_rate_limit.py +0 -0
  168. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_databases.py +0 -0
  169. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_deprecations.py +0 -0
  170. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_descriptions.py +0 -0
  171. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_destinations.py +0 -0
  172. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_docs_site.py +0 -0
  173. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_duckdb.py +0 -0
  174. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_duckdb_files.py +0 -0
  175. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_errors.py +0 -0
  176. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_examples.py +0 -0
  177. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_export_security.py +0 -0
  178. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_exporting.py +0 -0
  179. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_exports.py +0 -0
  180. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_fips.py +0 -0
  181. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_helm.py +0 -0
  182. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_howto.py +0 -0
  183. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_instances.py +0 -0
  184. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_integration.py +0 -0
  185. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_jdbc.py +0 -0
  186. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_jwt.py +0 -0
  187. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_key_rotation.py +0 -0
  188. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_limits.py +0 -0
  189. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_masking.py +0 -0
  190. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_masking_requests.py +0 -0
  191. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_mcp_keycloak.py +0 -0
  192. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_mcp_oauth.py +0 -0
  193. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_mongotools.py +0 -0
  194. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_observability.py +0 -0
  195. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_oidc.py +0 -0
  196. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_params.py +0 -0
  197. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_parquet.py +0 -0
  198. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_pool.py +0 -0
  199. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_publishing.py +0 -0
  200. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_query_window.py +0 -0
  201. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_queryapigate.py +0 -0
  202. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_quotas.py +0 -0
  203. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_rekey.py +0 -0
  204. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_release_check.py +0 -0
  205. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_response_cache.py +0 -0
  206. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_roles.py +0 -0
  207. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_rolling_upgrade.py +0 -0
  208. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_security_headers.py +0 -0
  209. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_shared_rate_limit.py +0 -0
  210. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_sql_guard_fuzz.py +0 -0
  211. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_sql_guard_writes.py +0 -0
  212. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_sqlflow.py +0 -0
  213. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_sso.py +0 -0
  214. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_sso_keycloak.py +0 -0
  215. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_supply_chain.py +0 -0
  216. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_tableguard.py +0 -0
  217. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_tracing.py +0 -0
  218. {queryapigate-0.32.0 → queryapigate-0.34.0}/tests/test_usage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: queryapigate
3
- Version: 0.32.0
3
+ Version: 0.34.0
4
4
  Summary: Turn SQL into governed REST APIs over MySQL, PostgreSQL, ClickHouse, SQLite, DuckDB, H2, MongoDB, and Parquet/CSV/JSON files - JSON, CSV, Parquet or Excel out, with scoped keys, an admin Console and MCP for AI agents.
5
5
  Author-email: Anantha Raju C <arcswdev@gmail.com>
6
6
  License-Expression: FSL-1.1-MIT
@@ -32,7 +32,7 @@ Requires-Dist: openpyxl>=3.1
32
32
  Provides-Extra: mysql
33
33
  Requires-Dist: mysql-connector-python>=8.0; extra == "mysql"
34
34
  Provides-Extra: postgres
35
- Requires-Dist: psycopg2-binary>=2.9; extra == "postgres"
35
+ Requires-Dist: psycopg2-binary>=2.9.5; extra == "postgres"
36
36
  Provides-Extra: clickhouse
37
37
  Requires-Dist: clickhouse-driver>=0.2; extra == "clickhouse"
38
38
  Provides-Extra: h2
@@ -694,6 +694,7 @@ Everything is configured through environment variables (all optional):
694
694
  | `QUERYAPIGATE_EVENTS_PORT` | `5002` | Port of `queryapigate events`, the separate live-events server for many clients - see [Live events](documentation/API.md#live-events-server-sent-events). |
695
695
  | `QUERYAPIGATE_EVENTS_MAX_CONNECTIONS` | `10000` | Open streams one `queryapigate events` process accepts. |
696
696
  | `QUERYAPIGATE_EVENTS_POLL_INTERVAL` | `1` | Seconds between `queryapigate events` checks for new runs (on PostgreSQL a notification usually arrives first). |
697
+ | `QUERYAPIGATE_EVENTS_RETENTION_HOURS` | `24` | Hours the event log keeps an event: how far back a `queryapigate events` client can resume with `Last-Event-ID`. A whole number, at least 1. |
697
698
  | `QUERYAPIGATE_MCP_PORT` | `5001` | Bind port for `queryapigate mcp` - see [documentation/MCP.md](documentation/MCP.md). |
698
699
  | `QUERYAPIGATE_MCP_MAX_ROWS` | `200` | Row cap for an MCP tool call's result. Always a positive count; a malformed value stops startup. |
699
700
  | `QUERYAPIGATE_SSO_ISSUER` / `QUERYAPIGATE_SSO_CLIENT_ID` / `QUERYAPIGATE_SSO_CLIENT_SECRET` / `QUERYAPIGATE_SSO_REDIRECT_URL` | unset | Administrators sign in to the Console through your identity provider, their role from their groups (`QUERYAPIGATE_SSO_ROLES`). Experimental - see [Console sign-in through SSO](documentation/API.md#console-sign-in-through-sso). |
@@ -803,7 +804,7 @@ through the Management API, the same calls the Console makes. Clean up with `doc
803
804
  | `/q/<name>` | GET, POST | Run a saved query; query-string or body values become parameters. |
804
805
  | `/connections/<name>/schema` | GET | List a connection's tables/views and their columns. |
805
806
  | `/catalog` | GET | The saved queries this caller can reach, and the terms they're offered under. |
806
- | `/events` | GET | Live saved-query runs (Server-Sent Events). |
807
+ | `/events` | GET | Live events: every run, as it happens (Server-Sent Events). |
807
808
  | `/api/v1/...` | GET, POST, PATCH, DELETE | The Management API (admin only): saved queries and their versions, drafts and publishing, connections, API keys, roles, collections, history, the audit log, settings, the response cache and the example APIs - see [Management API (v1)](documentation/API.md#management-api-v1). |
808
809
  | `/health`, `/docs`, `/openapi.json` | GET | Liveness, Swagger UI, OpenAPI spec. |
809
810
  | `/metrics` | GET | Prometheus text-format metrics: request/query counts and latencies, pool occupancy, rate-limit rejections. |
@@ -616,6 +616,7 @@ Everything is configured through environment variables (all optional):
616
616
  | `QUERYAPIGATE_EVENTS_PORT` | `5002` | Port of `queryapigate events`, the separate live-events server for many clients - see [Live events](documentation/API.md#live-events-server-sent-events). |
617
617
  | `QUERYAPIGATE_EVENTS_MAX_CONNECTIONS` | `10000` | Open streams one `queryapigate events` process accepts. |
618
618
  | `QUERYAPIGATE_EVENTS_POLL_INTERVAL` | `1` | Seconds between `queryapigate events` checks for new runs (on PostgreSQL a notification usually arrives first). |
619
+ | `QUERYAPIGATE_EVENTS_RETENTION_HOURS` | `24` | Hours the event log keeps an event: how far back a `queryapigate events` client can resume with `Last-Event-ID`. A whole number, at least 1. |
619
620
  | `QUERYAPIGATE_MCP_PORT` | `5001` | Bind port for `queryapigate mcp` - see [documentation/MCP.md](documentation/MCP.md). |
620
621
  | `QUERYAPIGATE_MCP_MAX_ROWS` | `200` | Row cap for an MCP tool call's result. Always a positive count; a malformed value stops startup. |
621
622
  | `QUERYAPIGATE_SSO_ISSUER` / `QUERYAPIGATE_SSO_CLIENT_ID` / `QUERYAPIGATE_SSO_CLIENT_SECRET` / `QUERYAPIGATE_SSO_REDIRECT_URL` | unset | Administrators sign in to the Console through your identity provider, their role from their groups (`QUERYAPIGATE_SSO_ROLES`). Experimental - see [Console sign-in through SSO](documentation/API.md#console-sign-in-through-sso). |
@@ -725,7 +726,7 @@ through the Management API, the same calls the Console makes. Clean up with `doc
725
726
  | `/q/<name>` | GET, POST | Run a saved query; query-string or body values become parameters. |
726
727
  | `/connections/<name>/schema` | GET | List a connection's tables/views and their columns. |
727
728
  | `/catalog` | GET | The saved queries this caller can reach, and the terms they're offered under. |
728
- | `/events` | GET | Live saved-query runs (Server-Sent Events). |
729
+ | `/events` | GET | Live events: every run, as it happens (Server-Sent Events). |
729
730
  | `/api/v1/...` | GET, POST, PATCH, DELETE | The Management API (admin only): saved queries and their versions, drafts and publishing, connections, API keys, roles, collections, history, the audit log, settings, the response cache and the example APIs - see [Management API (v1)](documentation/API.md#management-api-v1). |
730
731
  | `/health`, `/docs`, `/openapi.json` | GET | Liveness, Swagger UI, OpenAPI spec. |
731
732
  | `/metrics` | GET | Prometheus text-format metrics: request/query counts and latencies, pool occupancy, rate-limit rejections. |
@@ -32,7 +32,7 @@ dependencies = ["Flask>=3.0", "Werkzeug>=3.0", "PyYAML>=6.0.1", "openpyxl>=3.1"]
32
32
 
33
33
  [project.optional-dependencies]
34
34
  mysql = ["mysql-connector-python>=8.0"]
35
- postgres = ["psycopg2-binary>=2.9"]
35
+ postgres = ["psycopg2-binary>=2.9.5"] # 2.9.5 is the first with wheels for Python 3.11, the oldest supported
36
36
  clickhouse = ["clickhouse-driver>=0.2"]
37
37
  h2 = ["JayDeBeApi>=1.2", "JPype1>=1.4"] # also needs a Java runtime
38
38
  duckdb = ["duckdb>=1.2"] # allowed_directories/allowed_paths, which lock every connection (duckfiles.py), arrived in 1.2
@@ -1,5 +1,5 @@
1
1
  """QueryAPIGate - expose SQL databases as a REST API."""
2
- __version__ = "0.32.0"
2
+ __version__ = "0.34.0"
3
3
 
4
4
  from .app import create_app # noqa: E402 (app imports __version__)
5
5
 
@@ -28,6 +28,7 @@ from . import (
28
28
  deprecations,
29
29
  descriptions,
30
30
  engine,
31
+ events,
31
32
  examples,
32
33
  experimental,
33
34
  governance,
@@ -571,13 +572,13 @@ def stream_sql_response(sql, connection_name, params, timeout, output_format, fi
571
572
  path, number = saved
572
573
 
573
574
  def record(finished):
574
- _record_and_broadcast(path, number, connection_name, finished, broadcaster)
575
+ _record_and_broadcast(path, number, finished, broadcaster)
575
576
  else:
576
577
  if database:
577
578
  entry['database'] = database
578
579
 
579
580
  def record(finished):
580
- _record_adhoc_and_broadcast(sql, params, connection_name, finished, broadcaster)
581
+ _record_adhoc_and_broadcast(sql, params, finished, broadcaster)
581
582
  opened = rows
582
583
  response = stream_response(output_format, columns,
583
584
  _record_stream_history(rows, entry, record, g.get('permission')), filename)
@@ -602,17 +603,16 @@ def run_entry(connection_name):
602
603
  **({'trace_id': trace_id} if trace_id else {})}
603
604
 
604
605
 
605
- def _record_adhoc_and_broadcast(sql, params, connection_name, entry, broadcaster=None):
606
+ def _record_adhoc_and_broadcast(sql, params, entry, broadcaster=None):
606
607
  """An ad-hoc run's _record_and_broadcast(): into run history (history.record_adhoc()) and the live feed, as an
607
- `adhoc_execution` event, under the same per-key rule as a saved query's run."""
608
+ `query.run` event of kind `adhoc`, under the same per-key rule as a saved query's run."""
608
609
  recorded = history.record_adhoc(entry, sql, params)
609
610
  if broadcaster is None:
610
611
  broadcaster = current_app.extensions['queryapigate_broadcaster']
611
- broadcaster.publish({'type': 'adhoc_execution', 'connection_name': connection_name, 'entry': recorded},
612
- entry['key_name'])
612
+ broadcaster.publish(events.run_payload(None, None, recorded), entry['key_name'])
613
613
 
614
614
 
615
- def _record_and_broadcast(path, number, connection_name, entry, broadcaster=None):
615
+ def _record_and_broadcast(path, number, entry, broadcaster=None):
616
616
  """store.record_execution(), plus a live event for the admin UI's Home tab and any subscribed API key's
617
617
  own personal feed (BACKLOG #43) - every call site that used to call record_execution() directly calls
618
618
  this instead. ``broadcaster`` defaults to the current request's extension (the normal case); a caller
@@ -623,8 +623,7 @@ def _record_and_broadcast(path, number, connection_name, entry, broadcaster=None
623
623
  store.record_execution(path, number, entry)
624
624
  if broadcaster is None:
625
625
  broadcaster = current_app.extensions['queryapigate_broadcaster']
626
- broadcaster.publish({'type': 'execution', 'filename': path, 'version': number,
627
- 'connection_name': connection_name, 'entry': entry}, entry['key_name'])
626
+ broadcaster.publish(events.run_payload(path, number, entry), entry['key_name'])
628
627
 
629
628
 
630
629
  def _record_stream_history(rows, entry, record, permission=None):
@@ -688,10 +687,10 @@ def execute_sql_endpoint():
688
687
  database=database, allowed_tables=g.permission.allowed_tables,
689
688
  mask=g.permission.mask)
690
689
  except ApiError as error:
691
- _record_adhoc_and_broadcast(data['sql'], params, data['connection_name'],
690
+ _record_adhoc_and_broadcast(data['sql'], params,
692
691
  {**entry, 'status': 'error', 'error': error.message, 'code': error.code})
693
692
  raise
694
- _record_adhoc_and_broadcast(data['sql'], params, data['connection_name'],
693
+ _record_adhoc_and_broadcast(data['sql'], params,
695
694
  {**entry, 'status': 'success', 'rows': len(result.rows), 'duration_ms': elapsed_ms,
696
695
  **masked_entry(result.masked)})
697
696
  return render(result, output_format, page, limit)
@@ -721,10 +720,10 @@ def execute_mongo_endpoint():
721
720
  projection=data.get('projection'), sort=data.get('sort'),
722
721
  key_name=metrics_key_name(), mask=g.permission.mask)
723
722
  except ApiError as error:
724
- _record_adhoc_and_broadcast(statement, params, data['connection_name'],
723
+ _record_adhoc_and_broadcast(statement, params,
725
724
  {**entry, 'status': 'error', 'error': error.message, 'code': error.code})
726
725
  raise
727
- _record_adhoc_and_broadcast(statement, params, data['connection_name'],
726
+ _record_adhoc_and_broadcast(statement, params,
728
727
  {**entry, 'status': 'success', 'rows': len(result.rows), 'duration_ms': elapsed_ms})
729
728
  return render(result, output_format, page, limit)
730
729
 
@@ -915,15 +914,15 @@ def _run_saved(ref, body, url_params):
915
914
  allowed_write_ops=g.permission.allowed_write_ops,
916
915
  allowed_tables=g.permission.allowed_tables, mask=g.permission.mask)
917
916
  except ApiError as error:
918
- _record_and_broadcast(path, number, connection_name,
917
+ _record_and_broadcast(path, number,
919
918
  {**entry, 'status': 'error', 'error': error.message, 'code': error.code})
920
919
  raise
921
920
  tracing.set_attributes(query_span, queryapigate__rows=len(result.rows), queryapigate__masked=bool(result.masked))
922
921
  response = render(result, output_format, page, limit) # sets g.serialization_ms - see render()
923
- _record_and_broadcast(path, number, connection_name, {**entry, 'status': 'success', 'rows': len(result.rows),
924
- 'duration_ms': elapsed_ms,
925
- 'serialization_ms': g.serialization_ms,
926
- **masked_entry(result.masked)})
922
+ _record_and_broadcast(path, number, {**entry, 'status': 'success', 'rows': len(result.rows),
923
+ 'duration_ms': elapsed_ms,
924
+ 'serialization_ms': g.serialization_ms,
925
+ **masked_entry(result.masked)})
927
926
  if cache_key is not None:
928
927
  response = cache_store(cache_key, response, ttl, cache_meta)
929
928
  return response
@@ -974,15 +973,15 @@ def _execute_agent_write(request, saved, path, number, connection_name, values,
974
973
  mask=g.permission.mask)
975
974
  except ApiError as error:
976
975
  agentwrites.finish(request['id'], error={'error': error.message, 'code': error.code or 'internal_error'})
977
- _record_and_broadcast(path, number, connection_name,
976
+ _record_and_broadcast(path, number,
978
977
  {**entry, 'status': 'error', 'error': error.message, 'code': error.code})
979
978
  raise
980
979
  limit = config.mcp_max_rows()
981
980
  rows = [dict(zip(result.columns, row, strict=False)) for row in result.rows[:limit]]
982
981
  done = agentwrites.finish(request['id'], result={'rows_affected': affected, 'rows': rows,
983
982
  'truncated': len(result.rows) > limit})
984
- _record_and_broadcast(path, number, connection_name, {**entry, 'status': 'success', 'rows': affected,
985
- **masked_entry(result.masked)})
983
+ _record_and_broadcast(path, number, {**entry, 'status': 'success', 'rows': affected,
984
+ **masked_entry(result.masked)})
986
985
  return _agent_write_answer(done)
987
986
 
988
987
 
@@ -1021,13 +1020,13 @@ def run_saved_mongo(saved, path, number, ref, connection_name, raw, output_forma
1021
1020
  projection=saved.get('mongo_projection'), sort=saved.get('mongo_sort'),
1022
1021
  key_name=metrics_key_name(), mask=g.permission.mask)
1023
1022
  except ApiError as error:
1024
- _record_and_broadcast(path, number, connection_name,
1023
+ _record_and_broadcast(path, number,
1025
1024
  {**entry, 'status': 'error', 'error': error.message, 'code': error.code})
1026
1025
  raise
1027
1026
  response = render(result, output_format, page, limit) # sets g.serialization_ms - see render()
1028
- _record_and_broadcast(path, number, connection_name, {**entry, 'status': 'success', 'rows': len(result.rows),
1029
- 'duration_ms': elapsed_ms,
1030
- 'serialization_ms': g.serialization_ms})
1027
+ _record_and_broadcast(path, number, {**entry, 'status': 'success', 'rows': len(result.rows),
1028
+ 'duration_ms': elapsed_ms,
1029
+ 'serialization_ms': g.serialization_ms})
1031
1030
  return response
1032
1031
 
1033
1032
 
@@ -1049,8 +1048,9 @@ _open_streams_lock = threading.Lock()
1049
1048
 
1050
1049
  @bp.route('/events', methods=['GET'])
1051
1050
  def stream_events():
1052
- """Server-Sent Events: one `data: {...}` line per live event (today: a saved-query execution, as
1053
- it's recorded - see _record_and_broadcast()). Originally admin-only, feeding just the admin UI's Home
1051
+ """Server-Sent Events: one `data: {...}` line per live event (today: a run, `query.run` - events.run_payload() -
1052
+ as it's recorded, see _record_and_broadcast()), narrowed by the same subscription filters as `queryapigate
1053
+ events` (events.parse_filters()). Originally admin-only, feeding just the admin UI's Home
1054
1054
  tab; open to any authenticated key now, each getting its own personal activity feed - a scoped key sees
1055
1055
  only executions triggered by that same key (broadcast.Broadcaster's key_name filter), the admin key
1056
1056
  keeps its original unfiltered view of everything, matching the admin-sees-all/scoped-sees-its-own
@@ -1069,6 +1069,10 @@ def stream_events():
1069
1069
  `queryapigate events` (events.py) instead: a separate asyncio process with no such limit, every instance's
1070
1070
  runs, and Last-Event-ID resume."""
1071
1071
  global _open_streams
1072
+ try:
1073
+ filters = events.parse_filters(request.args.get)
1074
+ except ValueError as error:
1075
+ raise ApiError(str(error), code='invalid_filter') from None
1072
1076
  with _open_streams_lock:
1073
1077
  if _open_streams >= config.events_max_streams():
1074
1078
  raise ApiError('Too many open event streams on this server - try again shortly, or connect to '
@@ -1090,7 +1094,7 @@ def stream_events():
1090
1094
  _open_streams -= 1
1091
1095
  broadcaster.unsubscribe(subscriber)
1092
1096
 
1093
- def events():
1097
+ def stream():
1094
1098
  try:
1095
1099
  while True:
1096
1100
  try:
@@ -1098,11 +1102,13 @@ def stream_events():
1098
1102
  except queue.Empty:
1099
1103
  yield ': keepalive\n\n'
1100
1104
  continue
1105
+ if not events.matches(filters, events.fields(event)):
1106
+ continue
1101
1107
  yield f'data: {json.dumps(event, default=json_default)}\n\n'
1102
1108
  finally:
1103
1109
  release()
1104
1110
 
1105
- response = Response(ClosingIterator(events(), release), mimetype='text/event-stream')
1111
+ response = Response(ClosingIterator(stream(), release), mimetype='text/event-stream')
1106
1112
  response.headers['Cache-Control'] = 'no-cache'
1107
1113
  response.headers['X-Accel-Buffering'] = 'no' # disable reverse-proxy response buffering (nginx, etc.)
1108
1114
  return response
@@ -108,8 +108,7 @@ def _events(args):
108
108
  print(f'queryapigate: {error}', file=sys.stderr)
109
109
  return 2
110
110
  logging_setup.configure(logging.getLogger('queryapigate'))
111
- experimental.warn_in_use(logging.getLogger('queryapigate'), os.environ, store.read_connections(),
112
- also=('live_events',))
111
+ experimental.warn_in_use(logging.getLogger('queryapigate'), os.environ, store.read_connections())
113
112
  db.init_schema()
114
113
  instances.register('events')
115
114
  args.port = args.port or config.events_port()
@@ -678,7 +677,7 @@ def _handler():
678
677
 
679
678
 
680
679
  def _history_forget(args):
681
- """`queryapigate history forget CALLER`: delete a person's runs and usage counts (ADR 0016)."""
680
+ """`queryapigate history forget CALLER`: delete a person's runs, events and usage counts (ADR 0016)."""
682
681
  from . import history
683
682
  try:
684
683
  config.check_settings()
@@ -687,10 +686,11 @@ def _history_forget(args):
687
686
  print(f'queryapigate history forget: {getattr(error, "message", error)}', file=sys.stderr)
688
687
  return 1
689
688
  if args.dry_run:
690
- print(f"Would delete {found['runs']} run(s) and {found['usage_periods']} usage period(s) of {args.caller}.")
689
+ print(f"Would delete {found['runs']} run(s), {found['events']} event(s) and {found['usage_periods']} usage "
690
+ f"period(s) of {args.caller}.")
691
691
  else:
692
- print(f"Deleted {found['runs']} run(s) and {found['usage_periods']} usage period(s) of {args.caller}; recorded "
693
- 'in the audit log by its hash, not its name.')
692
+ print(f"Deleted {found['runs']} run(s), {found['events']} event(s) and {found['usage_periods']} usage "
693
+ f'period(s) of {args.caller}; recorded in the audit log by its hash, not its name.')
694
694
  return 0
695
695
 
696
696
 
@@ -929,8 +929,8 @@ def build_parser():
929
929
  history_parser.set_defaults(func=lambda a: history_parser.print_help() or 2)
930
930
  history_actions = history_parser.add_subparsers(dest='action')
931
931
  history_forget = history_actions.add_parser(
932
- 'forget', help="delete every run by CALLER and its usage counts - a person's data, on request; recorded in the "
933
- 'audit log by its hash, not its name')
932
+ 'forget', help="delete every run by CALLER, its events and its usage counts - a person's data, on request; "
933
+ 'recorded in the audit log by its hash, not its name')
934
934
  history_forget.add_argument('caller', help='a key name, or jwt:<subject> for a signed-in person')
935
935
  history_forget.add_argument('--dry-run', action='store_true', help='say how much would go; delete nothing')
936
936
  history_forget.set_defaults(func=_history_forget)
@@ -21,6 +21,7 @@ DEFAULT_EVENTS_PORT = 5002 # `queryapigate events`
21
21
  DEFAULT_EVENTS_MAX_CONNECTIONS = 10_000
22
22
  DEFAULT_EVENTS_POLL_INTERVAL = 1.0 # seconds between `queryapigate events` checks for new runs
23
23
  DEFAULT_EVENTS_MAX_STREAMS = 4 # concurrent GET /events streams the main server itself will hold open
24
+ DEFAULT_EVENTS_RETENTION_HOURS = 24 # how long the event log keeps an event (ADR 0020)
24
25
  AUDIT_LOG_LIMIT = 500 # administrative-action entries remembered across the whole server
25
26
  DEFAULT_QUERY_TIMEOUT = 30.0 # seconds
26
27
  DEFAULT_POOL_SIZE = 5 # idle connections kept per distinct connection
@@ -430,6 +431,9 @@ def check_settings():
430
431
  raw = os.environ.get('QUERYAPIGATE_EVENTS_POLL_INTERVAL', '').strip()
431
432
  if raw and not _number_in(raw, lambda v: 0.05 <= v <= 60):
432
433
  raise ValueError('QUERYAPIGATE_EVENTS_POLL_INTERVAL must be a number of seconds from 0.05 to 60')
434
+ raw = os.environ.get('QUERYAPIGATE_EVENTS_RETENTION_HOURS', '').strip()
435
+ if raw and (not raw.isdigit() or int(raw) < 1):
436
+ raise ValueError('QUERYAPIGATE_EVENTS_RETENTION_HOURS must be a whole number of hours, at least 1')
433
437
  raw = os.environ.get('QUERYAPIGATE_HISTORY_SAMPLE_RATE', '').strip()
434
438
  if raw and not _number_in(raw, lambda v: 0 < v <= 1):
435
439
  raise ValueError('QUERYAPIGATE_HISTORY_SAMPLE_RATE must be a number above 0 and at most 1, e.g. 0.1 '
@@ -1116,6 +1120,13 @@ def events_poll_interval():
1116
1120
  return float(raw) if raw else DEFAULT_EVENTS_POLL_INTERVAL
1117
1121
 
1118
1122
 
1123
+ def events_retention_hours():
1124
+ """Hours the event log keeps an event (QUERYAPIGATE_EVENTS_RETENTION_HOURS), default 24 - how far back a client of
1125
+ `queryapigate events` can resume with Last-Event-ID (ADR 0020)."""
1126
+ raw = os.environ.get('QUERYAPIGATE_EVENTS_RETENTION_HOURS', '').strip()
1127
+ return int(raw) if raw else DEFAULT_EVENTS_RETENTION_HOURS
1128
+
1129
+
1119
1130
  def events_max_streams():
1120
1131
  """Concurrent GET /events streams the main (WSGI) server holds open (QUERYAPIGATE_EVENTS_MAX_STREAMS). Each
1121
1132
  one occupies a request thread for as long as it is open, so the default leaves most of the Docker image's 8
@@ -1229,13 +1240,13 @@ def describe_settings():
1229
1240
  and "default" when the built-in default applies, and ``env_value`` is the raw value for a "copy as .env"
1230
1241
  export, or None for a secret so that export can never leak one. Read-only: settings are environment
1231
1242
  variables, changed by restarting the server, not through the API."""
1232
- def row(label, description, env, value, secret=False, experimental_row=False):
1243
+ def row(label, description, env, value, secret=False):
1233
1244
  raw = os.environ.get(env, '').strip()
1234
1245
  if secret and secret_source(env):
1235
1246
  value = f'{value} (from {env}_FILE)' # never the file's content: where it came from
1236
1247
  return {'label': label, 'description': description, 'env': env, 'value': value,
1237
1248
  'source': 'env' if raw else 'default', 'env_value': None if secret or not raw else raw,
1238
- 'experimental': experimental_row or env in experimental.SETTINGS}
1249
+ 'experimental': env in experimental.SETTINGS}
1239
1250
 
1240
1251
  def on_off(flag):
1241
1252
  return 'on' if flag else 'off'
@@ -1422,7 +1433,7 @@ def describe_settings():
1422
1433
  'in-process' if redis_val is None else f'Redis ({redact_redis_url(redis_val)})',
1423
1434
  secret=True),
1424
1435
  row('Live updates', 'GET /events on this server: live runs for the admin UI and light use, from '
1425
- 'this process only.', '', 'in-process', experimental_row=True), # live events, no variable
1436
+ 'this process only.', '', 'in-process'),
1426
1437
  row('Streams on this server', 'Concurrent GET /events streams this server holds open - each takes '
1427
1438
  'a request thread. Beyond it, /events answers 503. 0 turns it off.',
1428
1439
  'QUERYAPIGATE_EVENTS_MAX_STREAMS', str(events_max_streams())),
@@ -1432,7 +1443,9 @@ def describe_settings():
1432
1443
  row('Events server connections', 'Open streams one `queryapigate events` process accepts.',
1433
1444
  'QUERYAPIGATE_EVENTS_MAX_CONNECTIONS', str(events_max_connections())),
1434
1445
  row('Events poll interval', 'Seconds between checks for new runs (on PostgreSQL a notification '
1435
- 'usually arrives first).', 'QUERYAPIGATE_EVENTS_POLL_INTERVAL', _seconds(events_poll_interval()))]},
1446
+ 'usually arrives first).', 'QUERYAPIGATE_EVENTS_POLL_INTERVAL', _seconds(events_poll_interval())),
1447
+ row('Event retention', 'Hours the event log keeps an event: how far back a client can resume with '
1448
+ 'Last-Event-ID.', 'QUERYAPIGATE_EVENTS_RETENTION_HOURS', f'{events_retention_hours()} h')]},
1436
1449
  {'id': 'alerts', 'title': 'Alerts',
1437
1450
  'description': 'When GET /api/v1/alerts (the Console\'s Alerts screen) calls something out. A query is '
1438
1451
  'slow when its typical run is over the slow-query threshold (Query execution).', 'rows': [
@@ -1,3 +1,3 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/forms-DR5CXITG.js","assets/index-BNMV1syF.js","assets/index-BLd0t6vZ.css","assets/SchemaBrowser-D57iE2Kg.js","assets/useMutation-DCzNFnov.js","assets/reach-0Yvn3NAt.js","assets/CodeBox-B8iq2Oop.js"])))=>i.map(i=>d[i]);
2
- import{$ as e,C as t,D as n,F as r,H as i,I as a,L as o,Q as s,V as c,Z as l,h as u,o as d,s as f,u as p,z as m}from"./index-BNMV1syF.js";import{a as h}from"./reach-0Yvn3NAt.js";import{t as g}from"./Time-CR17Ozn9.js";import{f as _,i as v,n as y,r as b,u as x}from"./SchemaBrowser-D57iE2Kg.js";import{t as S}from"./CodeBox-B8iq2Oop.js";var C=e(s(),1),w=l();function T({term:e,children:t}){return(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`dt`,{children:e}),(0,w.jsx)(`dd`,{title:typeof t==`string`?t:void 0,children:t})]})}function E({name:e,dbType:s}){let c=m(),{closeDrawer:l,toast:u}=o(),d=i({queryKey:[`queries`,`detail`,e,`info`],queryFn:async()=>n(await t.GET(`/api/v1/queries/{name}`,{params:{path:{name:e}}})),retry:!1}),f=d.data,p=f?.versions[f.versions.length-1],h=p?.query_type===`mongo`?JSON.stringify(p.mongo??{},null,2):p?.sql??``,_=i({queryKey:[`queries`,`flow`,e,p?.version],queryFn:()=>v(e,p.version),enabled:!(!p||p.query_type!==`sql`||h.includes(`
3
- `)),staleTime:1/0});if(d.isPending)return(0,w.jsx)(r,{});if(!f||!p)return(0,w.jsx)(`div`,{className:`hint`,children:`Could not load the SQL.`});let y=_.data?.formatted||h,b=f.created_at,x=f.last_used_at;return(0,w.jsxs)(`div`,{className:`form`,children:[p.description?(0,w.jsx)(`p`,{className:`d-desc`,children:p.description}):null,(0,w.jsxs)(`dl`,{className:`meta`,children:[(0,w.jsx)(T,{term:`connection`,children:p.connection_name||`—`}),(0,w.jsx)(T,{term:`database`,children:s?(0,w.jsx)(`span`,{className:`tag`,children:s}):(0,w.jsx)(`span`,{className:`dim`,children:`—`})}),(0,w.jsx)(T,{term:`collection`,children:f.collection||`—`}),(0,w.jsx)(T,{term:`status`,children:p.status||`—`}),(0,w.jsx)(T,{term:`author`,children:p.author||`—`}),(0,w.jsx)(T,{term:`created`,children:(0,w.jsx)(g,{value:b})}),(0,w.jsx)(T,{term:`last modified`,children:(0,w.jsx)(g,{value:p.last_modified_at||p.created_at})}),(0,w.jsx)(T,{term:`last used`,children:(0,w.jsx)(g,{value:x,fallback:`never`})}),p.tags.length?(0,w.jsx)(T,{term:`tags`,children:p.tags.join(`, `)}):null]}),(0,w.jsx)(`p`,{className:`sub-h`,style:{marginTop:6},children:`SQL · v${p.version}${f.versions.length>1?` (latest)`:``}`}),(0,w.jsx)(`div`,{children:_.isFetching?(0,w.jsx)(r,{text:`Loading SQL…`}):(0,w.jsxs)(w.Fragment,{children:[(0,w.jsx)(S,{sql:y}),(0,w.jsx)(`div`,{style:{marginTop:6},children:(0,w.jsx)(`button`,{type:`button`,className:`btn sm ghost`,onClick:()=>a(y,u),children:`Copy SQL`})})]})}),(0,w.jsx)(`p`,{className:`sub-h`,style:{marginTop:6},children:`Versions`}),(0,w.jsx)(`div`,{className:`panel`,style:{overflow:`auto`},children:(0,w.jsxs)(`table`,{className:`grid qi-versions`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsx)(`tr`,{children:[`Version`,`Created`,`Last modified`,`Runs`].map(e=>(0,w.jsx)(`th`,{children:e},e))})}),(0,w.jsx)(`tbody`,{children:[...f.versions].reverse().map(e=>(0,w.jsxs)(`tr`,{children:[(0,w.jsx)(`td`,{className:`mono`,children:`v`+e.version}),(0,w.jsx)(`td`,{className:`mono dim`,children:(0,w.jsx)(g,{value:e.created_at})}),(0,w.jsx)(`td`,{className:`mono dim`,children:(0,w.jsx)(g,{value:e.last_modified_at})}),(0,w.jsx)(`td`,{className:`mono`,style:{textAlign:`right`},children:String(e.run_count??0)})]},e.version))})]})}),(0,w.jsxs)(`div`,{className:`form-actions`,children:[(0,w.jsx)(`button`,{type:`button`,className:`btn`,onClick:l,children:`Close`}),(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:()=>{l(),c(`/queries/${encodeURIComponent(e)}`)},children:`Open in API Repository`})]})]})}var D=(0,C.lazy)(()=>c(()=>import(`./forms-DR5CXITG.js`).then(e=>({default:e.QueryForm})),__vite__mapDeps([0,1,2,3,4,5,6]))),O={query:`Q`,collection:`C`,connection:`W`},k=[`Database`,`Created`,`Last modified`,`Last used`,`Version`],A=[`database`,`created`,`modified`,`lastUsed`,`version`],j=()=>new Date().toISOString().slice(0,10);function M(){let e=m(),{openDrawer:t}=o(),n=d(),i=f(),a=u(),s=p(),[c,l]=(0,C.useState)(x),g=e=>{let t={...c,...e};_(t),l(t)},v=s.data??[],S=e=>v.find(t=>t.name===e)?.db??null,T=y(c.conn||void 0),O=T.data?.tables??[],k=b(c.conn||void 0,S(c.conn),c.table&&!c.tableMatches?[c.table]:[]),A=c.table?c.tableMatches??(k.data?k.data[c.table]??[]:null):null,j=n.data??[],M=[...new Set(j.map(e=>S(e.connection_name)).filter(e=>!!e))].sort(),P=i.data??{},F=a.data??{},I=Object.keys(P).sort(),L=Object.keys(F).sort(),R,z=!0;c.conn?T.isPending?R=(0,w.jsx)(`option`,{value:``,children:`Loading tables…`}):T.isError?R=(0,w.jsx)(`option`,{value:``,children:`Schema unavailable`}):(z=!1,R=(0,w.jsxs)(w.Fragment,{children:[(0,w.jsx)(`option`,{value:``,children:`All tables (${O.length})`}),O.map(e=>(0,w.jsx)(`option`,{value:e.name,children:`${e.name} · ${e.columns.length}${e.columns.length===1?` col`:` cols`}`},e.name))]})):R=(0,w.jsx)(`option`,{value:``,children:`Select a connection first`});let B=(e,t)=>h(e,t.name,t.collection,t.connection_name),V;if(n.isPending)V=(0,w.jsx)(r,{});else if(!j.length)V=(0,w.jsx)(`div`,{className:`empty`,children:(0,w.jsx)(`strong`,{children:`No saved queries yet`})});else if(!I.length&&!L.length)V=(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`No scoped API keys or roles yet`}),(0,w.jsx)(`span`,{children:`Every query is reachable by the admin key only until a scoped key is created.`})]});else{let n=c.q.trim().toLowerCase(),i=[...j].sort((e,t)=>(e.collection||`￿`)+e.name<(t.collection||`￿`)+t.name?-1:1),a=e=>e.toLowerCase().includes(n),o=n?i.filter(e=>a(e.name)||e.collection&&a(e.collection)):i,s=n?I.filter(a):I,l=n?L.filter(a):L,u=o.length||s.length||l.length,d=o.length?o:u?i:[],f=s.length?s:u?I:[],p=l.length?l:u?L:[];if(c.conn&&(d=d.filter(e=>e.connection_name===c.conn)),c.table&&A===null)V=(0,w.jsx)(r,{text:`Checking which queries reference ${c.table}…`});else if(A&&(d=d.filter(e=>A.includes(e.name))),d.length){let n=d.map(e=>({q:e,dbType:S(e.connection_name),reach:f.filter(t=>B(P[t],e).length).length}));c.db&&(n=n.filter(e=>e.dbType===c.db)),c.reach===`reachable`?n=n.filter(e=>e.reach>0):c.reach===`unreachable`&&(n=n.filter(e=>e.reach===0));let r=c.sort;if(r.field){let e=e=>{switch(r.field){case`query`:return e.q.name.toLowerCase();case`database`:return(e.dbType||``).toLowerCase();case`created`:return e.q.created_at||``;case`modified`:return e.q.updated_at||``;case`lastUsed`:return e.q.last_used_at||``;case`version`:return e.q.latest_version;case`connection`:return(e.q.connection_name||``).toLowerCase();case`reach`:return e.reach;case`col`:{let t=r.colType===`key`?P[r.colName]:F[r.colName];return t&&B(t,e.q).length?1:0}default:return``}};n=[...n].sort((t,n)=>{let i=e(t),a=e(n);return i<a?-r.dir:i>a?r.dir:0})}V=n.length?(0,w.jsx)(N,{rows:n,keyCols:f,roleCols:p,keyMap:P,roleMap:F,st:c,onSort:e=>g({sort:e}),onOpen:t=>e(`/queries/${encodeURIComponent(t)}`),onInfo:e=>t({title:e.q.name,kicker:`${e.q.collection||`uncollected`} · ${e.q.version_count}${e.q.version_count===1?` version`:` versions`}`,content:(0,w.jsx)(E,{name:e.q.name,dbType:e.dbType})})}):(0,w.jsx)(`div`,{className:`empty`,children:(0,w.jsx)(`span`,{children:`No queries match these filters.`})})}else{let i=n?`Nothing matches “${n}”.`:c.table?`“${c.table}” on ${c.conn} is not exposed by any saved query yet.`:c.conn?`No saved query uses “${c.conn}” yet.`:`Nothing matches these filters.`;V=(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`span`,{children:i}),!n&&c.conn?(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:()=>{let n=S(c.conn)===`mongo`,i=c.table?n?JSON.stringify({collection:c.table,filter:{}},null,2):`SELECT * FROM `+c.table:void 0;e(`/queries`),t({title:`New API`,kicker:`POST /api/v1/queries`,content:(0,w.jsx)(C.Suspense,{fallback:(0,w.jsx)(r,{}),children:(0,w.jsx)(D,{starter:{connection:c.conn,sql:i}})})})},children:`New API`+(c.table?` on `+c.table:``)}):null]})}}return(0,w.jsxs)(w.Fragment,{children:[(0,w.jsxs)(`div`,{className:`page-head`,children:[(0,w.jsxs)(`div`,{className:`titles`,children:[(0,w.jsx)(`h1`,{children:`Access map`}),(0,w.jsxs)(`span`,{className:`sub`,children:[`Which API keys can reach which saved queries - the whole point of collections and connection grants, in one place.`,` `,(0,w.jsxs)(`span`,{className:`legend`,children:[(0,w.jsx)(`span`,{className:`amap-dot`,children:`Q`}),` named query \xA0`,(0,w.jsx)(`span`,{className:`amap-dot`,children:`C`}),` `,`collection \xA0`,(0,w.jsx)(`span`,{className:`amap-dot conn`,children:`W`}),` whole connection`]})]})]}),(0,w.jsx)(`span`,{className:`spacer`}),(0,w.jsxs)(`select`,{id:`accessmap-conn-filter`,"aria-label":`Connection`,value:v.some(e=>e.name===c.conn)?c.conn:``,onChange:e=>g({conn:e.target.value,table:``,tableMatches:null}),children:[(0,w.jsx)(`option`,{value:``,children:`All connections`}),[...v].sort((e,t)=>e.name<t.name?-1:1).map(e=>(0,w.jsx)(`option`,{value:e.name,children:e.name},e.name))]}),(0,w.jsx)(`select`,{id:`accessmap-table-filter`,"aria-label":`Table`,disabled:z,value:O.some(e=>e.name===c.table)?c.table:``,onChange:e=>g({table:e.target.value,tableMatches:null}),children:R}),(0,w.jsxs)(`select`,{id:`accessmap-db-filter`,"aria-label":`Database`,value:M.includes(c.db)?c.db:``,onChange:e=>g({db:e.target.value}),children:[(0,w.jsx)(`option`,{value:``,children:`All databases`}),M.map(e=>(0,w.jsx)(`option`,{value:e,children:e},e))]}),(0,w.jsxs)(`select`,{id:`accessmap-reach-filter`,"aria-label":`Reach`,value:c.reach,onChange:e=>g({reach:e.target.value}),children:[(0,w.jsx)(`option`,{value:``,children:`Any reach`}),(0,w.jsx)(`option`,{value:`reachable`,children:`Reachable by a key`}),(0,w.jsx)(`option`,{value:`unreachable`,children:`Reachable by no key`})]}),(0,w.jsx)(`input`,{id:`accessmap-filter`,className:`search`,type:`search`,placeholder:`Filter by query or key…`,value:c.q,onChange:e=>g({q:e.target.value})})]}),(0,w.jsx)(`div`,{id:`accessmap-body`,className:`panel`,children:V})]})}function N({rows:e,keyCols:t,roleCols:n,keyMap:r,roleMap:i,st:a,onSort:o,onOpen:s,onInfo:c}){let l=a.sort,u=(e,t=null,n=null)=>l.field===e&&l.colType===t&&l.colName===n,d=(e,t=null,n=null)=>u(e,t,n)?l.dir===1?` ▲`:` ▼`:``,f=(e,t=null,n=null)=>{let r=u(e,t,n);o(r&&l.dir===1?{field:e,colType:t,colName:n,dir:-1}:r?{field:null,colType:null,colName:null,dir:1}:{field:e,colType:t,colName:n,dir:1})},p=(e,t,n)=>(0,w.jsx)(`th`,{className:t,onClick:()=>f(n),children:e+d(n)},n),m=(e,t,n,r)=>(0,w.jsxs)(`th`,{className:`amap-key-head amap-sortable`+(n?` revoked`:``),title:n?e+n:e,onClick:()=>f(`col`,r,e),children:[(0,w.jsxs)(`span`,{className:`amap-key-name`,children:[e,d(`col`,r,e),n?(0,w.jsx)(`span`,{className:`dim`,children:n}):null]}),(0,w.jsxs)(`span`,{className:`amap-key-sub`,children:[(t.allow_writes?`Read/write`:`Read-only`)+` · `+(t.rate_limit||`server default`),t.mask?.length?(0,w.jsx)(`span`,{className:`tag`,title:t.mask.map(e=>`${e.column}: ${e.action}`).join(`, `),children:`masked`}):null]})]},r+`:`+e),_=(e,t,n,r)=>{let i=h(e,t.name,t.collection,t.connection_name);if(!i.length)return(0,w.jsx)(`td`,{className:`amap-cell`},r);let a=[...new Set(i.map(e=>O[e.kind]))],o=a.length===1&&a[0]===`W`;return(0,w.jsx)(`td`,{className:`amap-cell`,title:i.map(e=>e.label).join(`, `),children:(0,w.jsx)(`span`,{className:`amap-dot`+(n?` role`:``)+(o?` conn`:``),children:a.join(`·`)})},r)},v=j();return(0,w.jsx)(`div`,{style:{overflow:`auto`},children:(0,w.jsxs)(`table`,{className:`amap`,children:[(0,w.jsxs)(`thead`,{children:[(0,w.jsxs)(`tr`,{children:[(0,w.jsx)(`th`,{colSpan:3+k.length}),t.length?(0,w.jsx)(`th`,{colSpan:t.length,className:`amap-group`,children:`API KEYS`}):null,n.length?(0,w.jsx)(`th`,{colSpan:n.length,className:`amap-group role`,title:`What a key created from each role would reach - a role grants nothing on its own`,children:`ROLES`}):null]}),(0,w.jsxs)(`tr`,{children:[p(`Query`,`amap-query amap-sortable`,`query`),A.map((e,t)=>p(k[t],`amap-sortable`,e)),p(`Connection`,`amap-sortable`,`connection`),p(`Reach`,`num amap-sortable`,`reach`),t.map(e=>{let t=r[e],n=!!(t.expires_at&&t.expires_at<v);return m(e,t,t.active?n?` (expired ${t.expires_at})`:``:` (revoked)`,`key`)}),n.map(e=>m(e,i[e],``,`role`))]})]}),(0,w.jsx)(`tbody`,{children:e.map(e=>{let a=e.q;return(0,w.jsxs)(`tr`,{children:[(0,w.jsxs)(`td`,{className:`amap-query`,children:[(0,w.jsxs)(`div`,{className:`amap-q-row`,children:[(0,w.jsx)(`button`,{type:`button`,className:`amap-q-name`,title:`Open ${a.name} in API Repository`,onClick:()=>s(a.name),children:a.name}),(0,w.jsx)(`button`,{type:`button`,className:`amap-info`,title:`Details for ${a.name}`,"aria-label":`Details for ${a.name}`,onClick:()=>c(e),children:`i`})]}),a.collection?(0,w.jsx)(`span`,{className:`amap-coll`,children:a.collection}):null]}),(0,w.jsx)(`td`,{children:e.dbType?(0,w.jsx)(`span`,{className:`tag`,children:e.dbType}):(0,w.jsx)(`span`,{className:`dim`,children:`—`})}),(0,w.jsx)(`td`,{className:`mono dim`,children:(0,w.jsx)(g,{value:a.created_at})}),(0,w.jsx)(`td`,{className:`mono dim`,children:(0,w.jsx)(g,{value:a.updated_at})}),(0,w.jsx)(`td`,{className:a.last_used_at?`mono`:`mono dim`,children:(0,w.jsx)(g,{value:a.last_used_at,fallback:`never`})}),(0,w.jsx)(`td`,{className:`mono`,children:`v`+a.latest_version}),(0,w.jsx)(`td`,{className:`mono dim`,children:a.connection_name||`—`}),(0,w.jsx)(`td`,{className:`num`,title:`Reachable by an actual API key (roles alone reach nothing)`,children:e.reach?String(e.reach):`—`}),t.map(e=>_(r[e],a,!1,`k:`+e)),n.map(e=>_(i[e],a,!0,`r:`+e))]},a.name)})}),(0,w.jsx)(`tfoot`,{children:(0,w.jsxs)(`tr`,{children:[(0,w.jsx)(`td`,{className:`amap-query`,children:`Reaches`}),k.map(e=>(0,w.jsx)(`td`,{},e)),(0,w.jsx)(`td`,{}),(0,w.jsx)(`td`,{}),t.map(t=>(0,w.jsx)(`td`,{className:`num`,children:`${e.filter(e=>h(r[t],e.q.name,e.q.collection,e.q.connection_name).length).length} / ${e.length}`},t)),n.map(t=>(0,w.jsx)(`td`,{className:`num`,children:`${e.filter(e=>h(i[t],e.q.name,e.q.collection,e.q.connection_name).length).length} / ${e.length}`},t))]})})]})})}export{M as AccessMapPage};
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/forms-BSqwVHNJ.js","assets/index-sn5NUET1.js","assets/index-BLd0t6vZ.css","assets/SchemaBrowser-Bu0NvoUy.js","assets/useMutation-BF7AWw3p.js","assets/reach-2gDU11-l.js","assets/CodeBox-BZY9RBv5.js"])))=>i.map(i=>d[i]);
2
+ import{$ as e,C as t,D as n,F as r,H as i,I as a,L as o,Q as s,V as c,Z as l,h as u,o as d,s as f,u as p,z as m}from"./index-sn5NUET1.js";import{a as h}from"./reach-2gDU11-l.js";import{t as g}from"./Time-MA05YtZP.js";import{f as _,i as v,n as y,r as b,u as x}from"./SchemaBrowser-Bu0NvoUy.js";import{t as S}from"./CodeBox-BZY9RBv5.js";var C=e(s(),1),w=l();function T({term:e,children:t}){return(0,w.jsxs)(`div`,{children:[(0,w.jsx)(`dt`,{children:e}),(0,w.jsx)(`dd`,{title:typeof t==`string`?t:void 0,children:t})]})}function E({name:e,dbType:s}){let c=m(),{closeDrawer:l,toast:u}=o(),d=i({queryKey:[`queries`,`detail`,e,`info`],queryFn:async()=>n(await t.GET(`/api/v1/queries/{name}`,{params:{path:{name:e}}})),retry:!1}),f=d.data,p=f?.versions[f.versions.length-1],h=p?.query_type===`mongo`?JSON.stringify(p.mongo??{},null,2):p?.sql??``,_=i({queryKey:[`queries`,`flow`,e,p?.version],queryFn:()=>v(e,p.version),enabled:!(!p||p.query_type!==`sql`||h.includes(`
3
+ `)),staleTime:1/0});if(d.isPending)return(0,w.jsx)(r,{});if(!f||!p)return(0,w.jsx)(`div`,{className:`hint`,children:`Could not load the SQL.`});let y=_.data?.formatted||h,b=f.created_at,x=f.last_used_at;return(0,w.jsxs)(`div`,{className:`form`,children:[p.description?(0,w.jsx)(`p`,{className:`d-desc`,children:p.description}):null,(0,w.jsxs)(`dl`,{className:`meta`,children:[(0,w.jsx)(T,{term:`connection`,children:p.connection_name||`—`}),(0,w.jsx)(T,{term:`database`,children:s?(0,w.jsx)(`span`,{className:`tag`,children:s}):(0,w.jsx)(`span`,{className:`dim`,children:`—`})}),(0,w.jsx)(T,{term:`collection`,children:f.collection||`—`}),(0,w.jsx)(T,{term:`status`,children:p.status||`—`}),(0,w.jsx)(T,{term:`author`,children:p.author||`—`}),(0,w.jsx)(T,{term:`created`,children:(0,w.jsx)(g,{value:b})}),(0,w.jsx)(T,{term:`last modified`,children:(0,w.jsx)(g,{value:p.last_modified_at||p.created_at})}),(0,w.jsx)(T,{term:`last used`,children:(0,w.jsx)(g,{value:x,fallback:`never`})}),p.tags.length?(0,w.jsx)(T,{term:`tags`,children:p.tags.join(`, `)}):null]}),(0,w.jsx)(`p`,{className:`sub-h`,style:{marginTop:6},children:`SQL · v${p.version}${f.versions.length>1?` (latest)`:``}`}),(0,w.jsx)(`div`,{children:_.isFetching?(0,w.jsx)(r,{text:`Loading SQL…`}):(0,w.jsxs)(w.Fragment,{children:[(0,w.jsx)(S,{sql:y}),(0,w.jsx)(`div`,{style:{marginTop:6},children:(0,w.jsx)(`button`,{type:`button`,className:`btn sm ghost`,onClick:()=>a(y,u),children:`Copy SQL`})})]})}),(0,w.jsx)(`p`,{className:`sub-h`,style:{marginTop:6},children:`Versions`}),(0,w.jsx)(`div`,{className:`panel`,style:{overflow:`auto`},children:(0,w.jsxs)(`table`,{className:`grid qi-versions`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsx)(`tr`,{children:[`Version`,`Created`,`Last modified`,`Runs`].map(e=>(0,w.jsx)(`th`,{children:e},e))})}),(0,w.jsx)(`tbody`,{children:[...f.versions].reverse().map(e=>(0,w.jsxs)(`tr`,{children:[(0,w.jsx)(`td`,{className:`mono`,children:`v`+e.version}),(0,w.jsx)(`td`,{className:`mono dim`,children:(0,w.jsx)(g,{value:e.created_at})}),(0,w.jsx)(`td`,{className:`mono dim`,children:(0,w.jsx)(g,{value:e.last_modified_at})}),(0,w.jsx)(`td`,{className:`mono`,style:{textAlign:`right`},children:String(e.run_count??0)})]},e.version))})]})}),(0,w.jsxs)(`div`,{className:`form-actions`,children:[(0,w.jsx)(`button`,{type:`button`,className:`btn`,onClick:l,children:`Close`}),(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:()=>{l(),c(`/queries/${encodeURIComponent(e)}`)},children:`Open in API Repository`})]})]})}var D=(0,C.lazy)(()=>c(()=>import(`./forms-BSqwVHNJ.js`).then(e=>({default:e.QueryForm})),__vite__mapDeps([0,1,2,3,4,5,6]))),O={query:`Q`,collection:`C`,connection:`W`},k=[`Database`,`Created`,`Last modified`,`Last used`,`Version`],A=[`database`,`created`,`modified`,`lastUsed`,`version`],j=()=>new Date().toISOString().slice(0,10);function M(){let e=m(),{openDrawer:t}=o(),n=d(),i=f(),a=u(),s=p(),[c,l]=(0,C.useState)(x),g=e=>{let t={...c,...e};_(t),l(t)},v=s.data??[],S=e=>v.find(t=>t.name===e)?.db??null,T=y(c.conn||void 0),O=T.data?.tables??[],k=b(c.conn||void 0,S(c.conn),c.table&&!c.tableMatches?[c.table]:[]),A=c.table?c.tableMatches??(k.data?k.data[c.table]??[]:null):null,j=n.data??[],M=[...new Set(j.map(e=>S(e.connection_name)).filter(e=>!!e))].sort(),P=i.data??{},F=a.data??{},I=Object.keys(P).sort(),L=Object.keys(F).sort(),R,z=!0;c.conn?T.isPending?R=(0,w.jsx)(`option`,{value:``,children:`Loading tables…`}):T.isError?R=(0,w.jsx)(`option`,{value:``,children:`Schema unavailable`}):(z=!1,R=(0,w.jsxs)(w.Fragment,{children:[(0,w.jsx)(`option`,{value:``,children:`All tables (${O.length})`}),O.map(e=>(0,w.jsx)(`option`,{value:e.name,children:`${e.name} · ${e.columns.length}${e.columns.length===1?` col`:` cols`}`},e.name))]})):R=(0,w.jsx)(`option`,{value:``,children:`Select a connection first`});let B=(e,t)=>h(e,t.name,t.collection,t.connection_name),V;if(n.isPending)V=(0,w.jsx)(r,{});else if(!j.length)V=(0,w.jsx)(`div`,{className:`empty`,children:(0,w.jsx)(`strong`,{children:`No saved queries yet`})});else if(!I.length&&!L.length)V=(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`No scoped API keys or roles yet`}),(0,w.jsx)(`span`,{children:`Every query is reachable by the admin key only until a scoped key is created.`})]});else{let n=c.q.trim().toLowerCase(),i=[...j].sort((e,t)=>(e.collection||`￿`)+e.name<(t.collection||`￿`)+t.name?-1:1),a=e=>e.toLowerCase().includes(n),o=n?i.filter(e=>a(e.name)||e.collection&&a(e.collection)):i,s=n?I.filter(a):I,l=n?L.filter(a):L,u=o.length||s.length||l.length,d=o.length?o:u?i:[],f=s.length?s:u?I:[],p=l.length?l:u?L:[];if(c.conn&&(d=d.filter(e=>e.connection_name===c.conn)),c.table&&A===null)V=(0,w.jsx)(r,{text:`Checking which queries reference ${c.table}…`});else if(A&&(d=d.filter(e=>A.includes(e.name))),d.length){let n=d.map(e=>({q:e,dbType:S(e.connection_name),reach:f.filter(t=>B(P[t],e).length).length}));c.db&&(n=n.filter(e=>e.dbType===c.db)),c.reach===`reachable`?n=n.filter(e=>e.reach>0):c.reach===`unreachable`&&(n=n.filter(e=>e.reach===0));let r=c.sort;if(r.field){let e=e=>{switch(r.field){case`query`:return e.q.name.toLowerCase();case`database`:return(e.dbType||``).toLowerCase();case`created`:return e.q.created_at||``;case`modified`:return e.q.updated_at||``;case`lastUsed`:return e.q.last_used_at||``;case`version`:return e.q.latest_version;case`connection`:return(e.q.connection_name||``).toLowerCase();case`reach`:return e.reach;case`col`:{let t=r.colType===`key`?P[r.colName]:F[r.colName];return t&&B(t,e.q).length?1:0}default:return``}};n=[...n].sort((t,n)=>{let i=e(t),a=e(n);return i<a?-r.dir:i>a?r.dir:0})}V=n.length?(0,w.jsx)(N,{rows:n,keyCols:f,roleCols:p,keyMap:P,roleMap:F,st:c,onSort:e=>g({sort:e}),onOpen:t=>e(`/queries/${encodeURIComponent(t)}`),onInfo:e=>t({title:e.q.name,kicker:`${e.q.collection||`uncollected`} · ${e.q.version_count}${e.q.version_count===1?` version`:` versions`}`,content:(0,w.jsx)(E,{name:e.q.name,dbType:e.dbType})})}):(0,w.jsx)(`div`,{className:`empty`,children:(0,w.jsx)(`span`,{children:`No queries match these filters.`})})}else{let i=n?`Nothing matches “${n}”.`:c.table?`“${c.table}” on ${c.conn} is not exposed by any saved query yet.`:c.conn?`No saved query uses “${c.conn}” yet.`:`Nothing matches these filters.`;V=(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`span`,{children:i}),!n&&c.conn?(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:()=>{let n=S(c.conn)===`mongo`,i=c.table?n?JSON.stringify({collection:c.table,filter:{}},null,2):`SELECT * FROM `+c.table:void 0;e(`/queries`),t({title:`New API`,kicker:`POST /api/v1/queries`,content:(0,w.jsx)(C.Suspense,{fallback:(0,w.jsx)(r,{}),children:(0,w.jsx)(D,{starter:{connection:c.conn,sql:i}})})})},children:`New API`+(c.table?` on `+c.table:``)}):null]})}}return(0,w.jsxs)(w.Fragment,{children:[(0,w.jsxs)(`div`,{className:`page-head`,children:[(0,w.jsxs)(`div`,{className:`titles`,children:[(0,w.jsx)(`h1`,{children:`Access map`}),(0,w.jsxs)(`span`,{className:`sub`,children:[`Which API keys can reach which saved queries - the whole point of collections and connection grants, in one place.`,` `,(0,w.jsxs)(`span`,{className:`legend`,children:[(0,w.jsx)(`span`,{className:`amap-dot`,children:`Q`}),` named query \xA0`,(0,w.jsx)(`span`,{className:`amap-dot`,children:`C`}),` `,`collection \xA0`,(0,w.jsx)(`span`,{className:`amap-dot conn`,children:`W`}),` whole connection`]})]})]}),(0,w.jsx)(`span`,{className:`spacer`}),(0,w.jsxs)(`select`,{id:`accessmap-conn-filter`,"aria-label":`Connection`,value:v.some(e=>e.name===c.conn)?c.conn:``,onChange:e=>g({conn:e.target.value,table:``,tableMatches:null}),children:[(0,w.jsx)(`option`,{value:``,children:`All connections`}),[...v].sort((e,t)=>e.name<t.name?-1:1).map(e=>(0,w.jsx)(`option`,{value:e.name,children:e.name},e.name))]}),(0,w.jsx)(`select`,{id:`accessmap-table-filter`,"aria-label":`Table`,disabled:z,value:O.some(e=>e.name===c.table)?c.table:``,onChange:e=>g({table:e.target.value,tableMatches:null}),children:R}),(0,w.jsxs)(`select`,{id:`accessmap-db-filter`,"aria-label":`Database`,value:M.includes(c.db)?c.db:``,onChange:e=>g({db:e.target.value}),children:[(0,w.jsx)(`option`,{value:``,children:`All databases`}),M.map(e=>(0,w.jsx)(`option`,{value:e,children:e},e))]}),(0,w.jsxs)(`select`,{id:`accessmap-reach-filter`,"aria-label":`Reach`,value:c.reach,onChange:e=>g({reach:e.target.value}),children:[(0,w.jsx)(`option`,{value:``,children:`Any reach`}),(0,w.jsx)(`option`,{value:`reachable`,children:`Reachable by a key`}),(0,w.jsx)(`option`,{value:`unreachable`,children:`Reachable by no key`})]}),(0,w.jsx)(`input`,{id:`accessmap-filter`,className:`search`,type:`search`,placeholder:`Filter by query or key…`,value:c.q,onChange:e=>g({q:e.target.value})})]}),(0,w.jsx)(`div`,{id:`accessmap-body`,className:`panel`,children:V})]})}function N({rows:e,keyCols:t,roleCols:n,keyMap:r,roleMap:i,st:a,onSort:o,onOpen:s,onInfo:c}){let l=a.sort,u=(e,t=null,n=null)=>l.field===e&&l.colType===t&&l.colName===n,d=(e,t=null,n=null)=>u(e,t,n)?l.dir===1?` ▲`:` ▼`:``,f=(e,t=null,n=null)=>{let r=u(e,t,n);o(r&&l.dir===1?{field:e,colType:t,colName:n,dir:-1}:r?{field:null,colType:null,colName:null,dir:1}:{field:e,colType:t,colName:n,dir:1})},p=(e,t,n)=>(0,w.jsx)(`th`,{className:t,onClick:()=>f(n),children:e+d(n)},n),m=(e,t,n,r)=>(0,w.jsxs)(`th`,{className:`amap-key-head amap-sortable`+(n?` revoked`:``),title:n?e+n:e,onClick:()=>f(`col`,r,e),children:[(0,w.jsxs)(`span`,{className:`amap-key-name`,children:[e,d(`col`,r,e),n?(0,w.jsx)(`span`,{className:`dim`,children:n}):null]}),(0,w.jsxs)(`span`,{className:`amap-key-sub`,children:[(t.allow_writes?`Read/write`:`Read-only`)+` · `+(t.rate_limit||`server default`),t.mask?.length?(0,w.jsx)(`span`,{className:`tag`,title:t.mask.map(e=>`${e.column}: ${e.action}`).join(`, `),children:`masked`}):null]})]},r+`:`+e),_=(e,t,n,r)=>{let i=h(e,t.name,t.collection,t.connection_name);if(!i.length)return(0,w.jsx)(`td`,{className:`amap-cell`},r);let a=[...new Set(i.map(e=>O[e.kind]))],o=a.length===1&&a[0]===`W`;return(0,w.jsx)(`td`,{className:`amap-cell`,title:i.map(e=>e.label).join(`, `),children:(0,w.jsx)(`span`,{className:`amap-dot`+(n?` role`:``)+(o?` conn`:``),children:a.join(`·`)})},r)},v=j();return(0,w.jsx)(`div`,{style:{overflow:`auto`},children:(0,w.jsxs)(`table`,{className:`amap`,children:[(0,w.jsxs)(`thead`,{children:[(0,w.jsxs)(`tr`,{children:[(0,w.jsx)(`th`,{colSpan:3+k.length}),t.length?(0,w.jsx)(`th`,{colSpan:t.length,className:`amap-group`,children:`API KEYS`}):null,n.length?(0,w.jsx)(`th`,{colSpan:n.length,className:`amap-group role`,title:`What a key created from each role would reach - a role grants nothing on its own`,children:`ROLES`}):null]}),(0,w.jsxs)(`tr`,{children:[p(`Query`,`amap-query amap-sortable`,`query`),A.map((e,t)=>p(k[t],`amap-sortable`,e)),p(`Connection`,`amap-sortable`,`connection`),p(`Reach`,`num amap-sortable`,`reach`),t.map(e=>{let t=r[e],n=!!(t.expires_at&&t.expires_at<v);return m(e,t,t.active?n?` (expired ${t.expires_at})`:``:` (revoked)`,`key`)}),n.map(e=>m(e,i[e],``,`role`))]})]}),(0,w.jsx)(`tbody`,{children:e.map(e=>{let a=e.q;return(0,w.jsxs)(`tr`,{children:[(0,w.jsxs)(`td`,{className:`amap-query`,children:[(0,w.jsxs)(`div`,{className:`amap-q-row`,children:[(0,w.jsx)(`button`,{type:`button`,className:`amap-q-name`,title:`Open ${a.name} in API Repository`,onClick:()=>s(a.name),children:a.name}),(0,w.jsx)(`button`,{type:`button`,className:`amap-info`,title:`Details for ${a.name}`,"aria-label":`Details for ${a.name}`,onClick:()=>c(e),children:`i`})]}),a.collection?(0,w.jsx)(`span`,{className:`amap-coll`,children:a.collection}):null]}),(0,w.jsx)(`td`,{children:e.dbType?(0,w.jsx)(`span`,{className:`tag`,children:e.dbType}):(0,w.jsx)(`span`,{className:`dim`,children:`—`})}),(0,w.jsx)(`td`,{className:`mono dim`,children:(0,w.jsx)(g,{value:a.created_at})}),(0,w.jsx)(`td`,{className:`mono dim`,children:(0,w.jsx)(g,{value:a.updated_at})}),(0,w.jsx)(`td`,{className:a.last_used_at?`mono`:`mono dim`,children:(0,w.jsx)(g,{value:a.last_used_at,fallback:`never`})}),(0,w.jsx)(`td`,{className:`mono`,children:`v`+a.latest_version}),(0,w.jsx)(`td`,{className:`mono dim`,children:a.connection_name||`—`}),(0,w.jsx)(`td`,{className:`num`,title:`Reachable by an actual API key (roles alone reach nothing)`,children:e.reach?String(e.reach):`—`}),t.map(e=>_(r[e],a,!1,`k:`+e)),n.map(e=>_(i[e],a,!0,`r:`+e))]},a.name)})}),(0,w.jsx)(`tfoot`,{children:(0,w.jsxs)(`tr`,{children:[(0,w.jsx)(`td`,{className:`amap-query`,children:`Reaches`}),k.map(e=>(0,w.jsx)(`td`,{},e)),(0,w.jsx)(`td`,{}),(0,w.jsx)(`td`,{}),t.map(t=>(0,w.jsx)(`td`,{className:`num`,children:`${e.filter(e=>h(r[t],e.q.name,e.q.collection,e.q.connection_name).length).length} / ${e.length}`},t)),n.map(t=>(0,w.jsx)(`td`,{className:`num`,children:`${e.filter(e=>h(i[t],e.q.name,e.q.collection,e.q.connection_name).length).length} / ${e.length}`},t))]})})]})})}export{M as AccessMapPage};
@@ -1 +1 @@
1
- import{t as e}from"./useMutation-DCzNFnov.js";import{$ as t,A as n,C as r,D as i,F as a,H as o,L as s,N as c,O as l,P as u,Q as d,S as f,X as p,Z as m,c as h,d as g,k as _,m as v}from"./index-BNMV1syF.js";import{t as y}from"./Time-CR17Ozn9.js";import{t as b}from"./forms-C3dw2wG6.js";import{n as x,t as S}from"./icons-C-i_90Gs.js";var C=t(d(),1),w=m(),T=[{value:`owner`,label:`Owner`,text:`everything, including administrators`},{value:`admin`,label:`Admin`,text:`connections, API keys and roles, queries, cache, settings`},{value:`developer`,label:`Developer`,text:`saved queries and SQL; no keys, no connection changes`},{value:`auditor`,label:`Auditor`,text:`read only - audit log, history, configuration; no SQL`}],E=e=>T.find(t=>t.value===e)?.label??e,D=90,O=e=>new Date(Date.now()+e*864e5).toISOString().slice(0,10),k=`Store this now — it cannot be shown again. Paste it into the API key box at the bottom of the sidebar, or send it as the X-API-Key header.`;function A(){let{showError:e}=s();return t=>t instanceof f?e(t.message,{status:t.status}):e(t.message)}function j(e){let t=g(`admins.read`,`other administrators`);return o({queryKey:[`administrators`],queryFn:async()=>(await t.check(),i(await r.GET(`/api/v1/administrators`)).items),enabled:e,retry:!1})}function M(e){return o({queryKey:[`administrators`,e,`tokens`],queryFn:async()=>i(await r.GET(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:e}}})).items,retry:!1})}function N(){let e=v(),t=h(),n=!!e.data&&t(`admins.read`),r=j(n),{openDrawer:i}=s(),o=()=>i({title:`New administrator`,content:(0,w.jsx)(I,{})}),c;return c=e.isPending?(0,w.jsx)(a,{}):e.isError?(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`Not signed in as an administrator`}),(0,w.jsx)(`span`,{children:`Apply an admin token (qagadm_…) or the shared admin key in the box at the bottom of the sidebar.`})]}):n?r.isPending?(0,w.jsx)(a,{text:`Loading administrators…`}):r.isError?(0,w.jsx)(`div`,{className:`hint`,children:r.error.message}):r.data.length?(0,w.jsx)(P,{items:r.data,self:e.data.via===`token`||e.data.via===`sso`?e.data.name:null}):(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`No administrators yet`}),(0,w.jsx)(`span`,{children:`Everyone shares QUERYAPIGATE_API_KEY today, so the audit log can only say “admin”. Give each person their own sign-in, with a role that fits what they do — then the shared key becomes a break-glass key.`}),(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:o,children:`New administrator`})]}):(0,w.jsx)(F,{name:e.data.name??``,role:e.data.role}),(0,w.jsxs)(w.Fragment,{children:[(0,w.jsxs)(`div`,{className:`page-head`,children:[(0,w.jsxs)(`div`,{className:`titles`,children:[(0,w.jsx)(`h1`,{children:`Administrators`}),(0,w.jsx)(`span`,{className:`sub`,id:`administrators-sub`,children:n?`The people and pipelines that manage this server, each with a role and their own tokens.`:`Your account and your admin tokens.`})]}),(0,w.jsx)(`span`,{className:`spacer`}),n&&(0,w.jsx)(`button`,{id:`new-administrator`,type:`button`,className:`btn primary`,onClick:o,children:`New administrator`})]}),(0,w.jsx)(`div`,{id:`administrators-table`,className:`panel`,children:c})]})}function P({items:e,self:t}){let{openDrawer:n,toast:i}=s(),a=A(),o=p(),c=async e=>{if(window.confirm(`Remove administrator '${e}'? Every token of theirs stops working immediately.`))try{l(await r.DELETE(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),i(`Removed `+e),o.invalidateQueries({queryKey:[`administrators`]})}catch(e){a(e)}};return(0,w.jsx)(`div`,{style:{overflowX:`auto`},children:(0,w.jsxs)(`table`,{className:`grid`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsx)(`tr`,{children:[`Name`,`Role`,`Email`,`Last seen`,`Tokens`,``].map((e,t)=>(0,w.jsx)(`th`,{children:e},t))})}),(0,w.jsx)(`tbody`,{children:e.map(e=>(0,w.jsxs)(`tr`,{"data-name":e.name,children:[(0,w.jsx)(`td`,{children:(0,w.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,w.jsx)(`span`,{className:`dot ${e.active?`ok`:`off`}`,title:e.active?`Active`:`Inactive`}),(0,w.jsx)(`span`,{className:`name`,children:e.name}),e.name===t&&(0,w.jsx)(`span`,{className:`tag`,children:`you`}),e.sso?.status===`linked`&&(0,w.jsx)(`span`,{className:`tag`,title:`Signs in through ${e.sso.issuer??`SSO`}; role from their groups`,children:`SSO`}),e.sso?.status===`pending`&&(0,w.jsx)(`span`,{className:`tag`,title:`Linked at their next SSO sign-in, by their verified email`,children:`SSO pending`})]})}),(0,w.jsx)(`td`,{children:E(e.role)}),(0,w.jsx)(`td`,{className:e.email?void 0:`dim`,children:e.email??`—`}),(0,w.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,w.jsx)(y,{value:e.last_seen_at,fallback:`never`})}),(0,w.jsx)(`td`,{className:`mono`,children:e.tokens}),(0,w.jsx)(`td`,{children:(0,w.jsxs)(`div`,{className:`actions`,children:[(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>n({title:`Admin tokens`,kicker:e.name,wide:!0,content:(0,w.jsx)(R,{name:e.name})}),children:`Tokens`}),(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm icon`,title:`Edit ${e.name}`,"aria-label":`Edit`,onClick:()=>n({title:`Edit administrator`,kicker:e.name,content:(0,w.jsx)(I,{name:e.name})}),children:(0,w.jsx)(S,{})}),(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm icon danger`,title:`Remove ${e.name}`,"aria-label":`Remove`,onClick:()=>void c(e.name),children:(0,w.jsx)(x,{})})]})})]},e.name))})]})})}function F({name:e,role:t}){return(0,w.jsxs)(`div`,{className:`form`,style:{padding:16},children:[(0,w.jsxs)(`div`,{children:[`Signed in as `,(0,w.jsx)(`strong`,{children:e}),` · `,E(t),` — `,T.find(e=>e.value===t)?.text,`.`]}),(0,w.jsx)(R,{name:e})]})}function I({name:e}){let t=o({queryKey:[`administrators`,`detail`,e],queryFn:async()=>_(await r.GET(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),enabled:!!e,retry:!1,gcTime:0});return e&&t.isPending?(0,w.jsx)(a,{}):e&&t.isError?(0,w.jsx)(`div`,{className:`hint`,children:t.error.message}):(0,w.jsx)(L,{name:e,existing:t.data?.data,etag:t.data?.etag??null},e??``)}function L({name:t,existing:a,etag:o}){let l=!!t,{closeDrawer:d,openDrawer:f,showError:m,toast:h}=s(),g=A(),_=p(),[v,y]=(0,C.useState)(t??``),[x,S]=(0,C.useState)(a?.role??`developer`),[E,j]=(0,C.useState)(a?.email??``),[M,N]=(0,C.useState)(!a||a.active),P=n(),F=a?.sso?.status===`linked`,[I,L]=(0,C.useState)(a?.sso?.status??`none`),R=e({mutationFn:async()=>{let e={role:x,email:E.trim()||null};if(l)return i(await r.PATCH(`/api/v1/administrators/{name}`,{params:{path:{name:t},header:o?{"If-Match":o}:{}},body:{...F?{email:e.email}:e,active:M,...I===(a?.sso?.status??`none`)?{}:{sso:I===`pending`?`pending`:null}}})),null;let n=i(await r.POST(`/api/v1/administrators`,{body:{name:v.trim(),...e}}));return i(await r.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:n.name}},body:{label:`first token`,expires_at:O(D)}}))},onSuccess:e=>{if(m(``),_.invalidateQueries({queryKey:[`administrators`]}),!e){d(),h(`Updated `+t);return}f({title:`Administrator created`,kicker:e.admin,content:(0,w.jsx)(b,{secret:e.secret,label:`Admin token for ${e.admin} (expires ${e.expires_at})`,hint:k})})},onError:g});return(0,w.jsxs)(`form`,{className:`form`,noValidate:!0,onSubmit:e=>{if(e.preventDefault(),!l&&!v.trim()){m(`A name is required.`,{errors:{name:`This field is required`}});return}R.mutate()},children:[(0,w.jsx)(c,{id:`a-name`,label:`Name`,hint:l?void 0:`A person (alice) or a pipeline (ci-deploy). Shown in the audit log.`,children:(0,w.jsx)(`input`,{id:`a-name`,value:v,placeholder:`alice`,autoComplete:`off`,spellCheck:!1,disabled:l,autoFocus:!l,onChange:e=>y(e.target.value)})}),(0,w.jsx)(c,{id:`a-role`,label:`Role`,hint:F?`From their groups at the identity provider, at every sign-in - change it there.`:T.find(e=>e.value===x)?.text,children:(0,w.jsx)(`select`,{id:`a-role`,value:x,disabled:F,onChange:e=>S(e.target.value),children:T.map(e=>(0,w.jsx)(`option`,{value:e.value,children:e.label},e.value))})}),(0,w.jsx)(c,{id:`a-email`,label:`Email`,hint:`Optional.`,children:(0,w.jsx)(`input`,{id:`a-email`,type:`email`,value:E,autoComplete:`off`,onChange:e=>j(e.target.value)})}),l&&(0,w.jsxs)(`label`,{className:`switch`,children:[(0,w.jsx)(`input`,{id:`a-active`,type:`checkbox`,checked:M,onChange:e=>N(e.target.checked)}),`Active`,(0,w.jsx)(`span`,{className:`hint`,children:`— unchecking stops every token of theirs immediately`})]}),l&&P.data?.sso&&(0,w.jsx)(c,{id:`a-sso`,label:`Sign-in through SSO`,hint:I===`linked`?`They sign in through the identity provider. Unlinking ends their sessions; their next SSO sign-in makes a new administrator.`:`Pending: their next SSO sign-in, with a verified email matching the one above, is bound to this account - once.`,children:(0,w.jsxs)(`select`,{id:`a-sso`,value:I,onChange:e=>L(e.target.value),children:[F&&(0,w.jsx)(`option`,{value:`linked`,children:`Linked`}),!F&&(0,w.jsx)(`option`,{value:`pending`,children:`Link at their next sign-in`}),(0,w.jsx)(`option`,{value:`none`,children:F?`Unlink`:`Not through SSO`})]})}),!l&&(0,w.jsxs)(`div`,{className:`hint`,children:[`Their first admin token, valid for `,D,` days, is shown once after you create them.`]}),(0,w.jsx)(u,{onCancel:d,children:(0,w.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:R.isPending,children:l?`Save`:`Create`})})]})}function R({name:t}){let n=M(t),o=p(),{toast:u}=s(),d=A(),[f,m]=(0,C.useState)(``),[h,g]=(0,C.useState)(O(D)),[_,v]=(0,C.useState)(null),x=()=>void o.invalidateQueries({queryKey:[`administrators`]}),S=e({mutationFn:async()=>i(await r.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:t}},body:{label:f.trim()||null,expires_at:h||null}})),onSuccess:e=>{v(e),m(``),x()},onError:d}),T=async e=>{if(window.confirm(`Revoke ${e.label?`'${e.label}'`:e.id}? Anything using it stops working now.`))try{l(await r.DELETE(`/api/v1/administrators/{name}/tokens/{token_id}`,{params:{path:{name:t,token_id:e.id}}})),u(`Revoked `+(e.label??e.id)),x()}catch(e){d(e)}},E=new Date().toISOString().slice(0,10);return(0,w.jsxs)(`div`,{className:`form`,id:`admin-tokens`,children:[n.isPending?(0,w.jsx)(a,{text:`Loading tokens…`}):n.isError?(0,w.jsx)(`div`,{className:`hint`,children:n.error.message}):n.data.length?(0,w.jsxs)(`table`,{className:`grid`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsx)(`tr`,{children:[`Label`,`Expires`,`Last used`,``].map((e,t)=>(0,w.jsx)(`th`,{children:e},t))})}),(0,w.jsx)(`tbody`,{children:n.data.map(e=>(0,w.jsxs)(`tr`,{"data-token":e.id,children:[(0,w.jsx)(`td`,{title:e.id,children:e.label??(0,w.jsx)(`span`,{className:`dim mono`,children:e.id})}),(0,w.jsx)(`td`,{style:{whiteSpace:`nowrap`},children:e.expires_at?(0,w.jsx)(`span`,{style:e.expired?{color:`var(--danger)`}:void 0,children:e.expires_at+(e.expired?` (expired)`:e.expires_at===E?` (today)`:``)}):(0,w.jsx)(`span`,{className:`dim`,children:`never`})}),(0,w.jsx)(`td`,{className:`mono dim`,children:(0,w.jsx)(y,{value:e.last_used_at,fallback:`never`})}),(0,w.jsx)(`td`,{children:(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm danger`,onClick:()=>void T(e),children:`Revoke`})})]},e.id))})]}):(0,w.jsx)(`div`,{className:`hint`,children:`No tokens. Issue one below to sign in with.`}),_?(0,w.jsx)(b,{secret:_.secret,label:`New token${_.label?` '${_.label}'`:``} (${_.expires_at?`expires ${_.expires_at}`:`never expires`})`,hint:k,onDone:()=>v(null)}):(0,w.jsxs)(`form`,{className:`grid2`,noValidate:!0,onSubmit:e=>{e.preventDefault(),S.mutate()},children:[(0,w.jsx)(c,{id:`t-label`,label:`New token: label`,hint:`What it's for - laptop, ci.`,children:(0,w.jsx)(`input`,{id:`t-label`,value:f,autoComplete:`off`,onChange:e=>m(e.target.value)})}),(0,w.jsx)(c,{id:`t-expires`,label:`Expires`,hint:`Leave blank for no expiry.`,children:(0,w.jsx)(`input`,{id:`t-expires`,type:`date`,value:h,onChange:e=>g(e.target.value)})}),(0,w.jsx)(`div`,{children:(0,w.jsx)(`button`,{type:`submit`,className:`btn`,disabled:S.isPending,children:`Issue token`})})]})]})}export{N as AdministratorsPage};
1
+ import{t as e}from"./useMutation-BF7AWw3p.js";import{$ as t,A as n,C as r,D as i,F as a,H as o,L as s,N as c,O as l,P as u,Q as d,S as f,X as p,Z as m,c as h,d as g,k as _,m as v}from"./index-sn5NUET1.js";import{t as y}from"./Time-MA05YtZP.js";import{t as b}from"./forms-C6RLe1F3.js";import{n as x,t as S}from"./icons-DQ8_pv9M.js";var C=t(d(),1),w=m(),T=[{value:`owner`,label:`Owner`,text:`everything, including administrators`},{value:`admin`,label:`Admin`,text:`connections, API keys and roles, queries, cache, settings`},{value:`developer`,label:`Developer`,text:`saved queries and SQL; no keys, no connection changes`},{value:`auditor`,label:`Auditor`,text:`read only - audit log, history, configuration; no SQL`}],E=e=>T.find(t=>t.value===e)?.label??e,D=90,O=e=>new Date(Date.now()+e*864e5).toISOString().slice(0,10),k=`Store this now — it cannot be shown again. Paste it into the API key box at the bottom of the sidebar, or send it as the X-API-Key header.`;function A(){let{showError:e}=s();return t=>t instanceof f?e(t.message,{status:t.status}):e(t.message)}function j(e){let t=g(`admins.read`,`other administrators`);return o({queryKey:[`administrators`],queryFn:async()=>(await t.check(),i(await r.GET(`/api/v1/administrators`)).items),enabled:e,retry:!1})}function M(e){return o({queryKey:[`administrators`,e,`tokens`],queryFn:async()=>i(await r.GET(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:e}}})).items,retry:!1})}function N(){let e=v(),t=h(),n=!!e.data&&t(`admins.read`),r=j(n),{openDrawer:i}=s(),o=()=>i({title:`New administrator`,content:(0,w.jsx)(I,{})}),c;return c=e.isPending?(0,w.jsx)(a,{}):e.isError?(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`Not signed in as an administrator`}),(0,w.jsx)(`span`,{children:`Apply an admin token (qagadm_…) or the shared admin key in the box at the bottom of the sidebar.`})]}):n?r.isPending?(0,w.jsx)(a,{text:`Loading administrators…`}):r.isError?(0,w.jsx)(`div`,{className:`hint`,children:r.error.message}):r.data.length?(0,w.jsx)(P,{items:r.data,self:e.data.via===`token`||e.data.via===`sso`?e.data.name:null}):(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`No administrators yet`}),(0,w.jsx)(`span`,{children:`Everyone shares QUERYAPIGATE_API_KEY today, so the audit log can only say “admin”. Give each person their own sign-in, with a role that fits what they do — then the shared key becomes a break-glass key.`}),(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:o,children:`New administrator`})]}):(0,w.jsx)(F,{name:e.data.name??``,role:e.data.role}),(0,w.jsxs)(w.Fragment,{children:[(0,w.jsxs)(`div`,{className:`page-head`,children:[(0,w.jsxs)(`div`,{className:`titles`,children:[(0,w.jsx)(`h1`,{children:`Administrators`}),(0,w.jsx)(`span`,{className:`sub`,id:`administrators-sub`,children:n?`The people and pipelines that manage this server, each with a role and their own tokens.`:`Your account and your admin tokens.`})]}),(0,w.jsx)(`span`,{className:`spacer`}),n&&(0,w.jsx)(`button`,{id:`new-administrator`,type:`button`,className:`btn primary`,onClick:o,children:`New administrator`})]}),(0,w.jsx)(`div`,{id:`administrators-table`,className:`panel`,children:c})]})}function P({items:e,self:t}){let{openDrawer:n,toast:i}=s(),a=A(),o=p(),c=async e=>{if(window.confirm(`Remove administrator '${e}'? Every token of theirs stops working immediately.`))try{l(await r.DELETE(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),i(`Removed `+e),o.invalidateQueries({queryKey:[`administrators`]})}catch(e){a(e)}};return(0,w.jsx)(`div`,{style:{overflowX:`auto`},children:(0,w.jsxs)(`table`,{className:`grid`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsx)(`tr`,{children:[`Name`,`Role`,`Email`,`Last seen`,`Tokens`,``].map((e,t)=>(0,w.jsx)(`th`,{children:e},t))})}),(0,w.jsx)(`tbody`,{children:e.map(e=>(0,w.jsxs)(`tr`,{"data-name":e.name,children:[(0,w.jsx)(`td`,{children:(0,w.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,w.jsx)(`span`,{className:`dot ${e.active?`ok`:`off`}`,title:e.active?`Active`:`Inactive`}),(0,w.jsx)(`span`,{className:`name`,children:e.name}),e.name===t&&(0,w.jsx)(`span`,{className:`tag`,children:`you`}),e.sso?.status===`linked`&&(0,w.jsx)(`span`,{className:`tag`,title:`Signs in through ${e.sso.issuer??`SSO`}; role from their groups`,children:`SSO`}),e.sso?.status===`pending`&&(0,w.jsx)(`span`,{className:`tag`,title:`Linked at their next SSO sign-in, by their verified email`,children:`SSO pending`})]})}),(0,w.jsx)(`td`,{children:E(e.role)}),(0,w.jsx)(`td`,{className:e.email?void 0:`dim`,children:e.email??`—`}),(0,w.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,w.jsx)(y,{value:e.last_seen_at,fallback:`never`})}),(0,w.jsx)(`td`,{className:`mono`,children:e.tokens}),(0,w.jsx)(`td`,{children:(0,w.jsxs)(`div`,{className:`actions`,children:[(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>n({title:`Admin tokens`,kicker:e.name,wide:!0,content:(0,w.jsx)(R,{name:e.name})}),children:`Tokens`}),(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm icon`,title:`Edit ${e.name}`,"aria-label":`Edit`,onClick:()=>n({title:`Edit administrator`,kicker:e.name,content:(0,w.jsx)(I,{name:e.name})}),children:(0,w.jsx)(S,{})}),(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm icon danger`,title:`Remove ${e.name}`,"aria-label":`Remove`,onClick:()=>void c(e.name),children:(0,w.jsx)(x,{})})]})})]},e.name))})]})})}function F({name:e,role:t}){return(0,w.jsxs)(`div`,{className:`form`,style:{padding:16},children:[(0,w.jsxs)(`div`,{children:[`Signed in as `,(0,w.jsx)(`strong`,{children:e}),` · `,E(t),` — `,T.find(e=>e.value===t)?.text,`.`]}),(0,w.jsx)(R,{name:e})]})}function I({name:e}){let t=o({queryKey:[`administrators`,`detail`,e],queryFn:async()=>_(await r.GET(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),enabled:!!e,retry:!1,gcTime:0});return e&&t.isPending?(0,w.jsx)(a,{}):e&&t.isError?(0,w.jsx)(`div`,{className:`hint`,children:t.error.message}):(0,w.jsx)(L,{name:e,existing:t.data?.data,etag:t.data?.etag??null},e??``)}function L({name:t,existing:a,etag:o}){let l=!!t,{closeDrawer:d,openDrawer:f,showError:m,toast:h}=s(),g=A(),_=p(),[v,y]=(0,C.useState)(t??``),[x,S]=(0,C.useState)(a?.role??`developer`),[E,j]=(0,C.useState)(a?.email??``),[M,N]=(0,C.useState)(!a||a.active),P=n(),F=a?.sso?.status===`linked`,[I,L]=(0,C.useState)(a?.sso?.status??`none`),R=e({mutationFn:async()=>{let e={role:x,email:E.trim()||null};if(l)return i(await r.PATCH(`/api/v1/administrators/{name}`,{params:{path:{name:t},header:o?{"If-Match":o}:{}},body:{...F?{email:e.email}:e,active:M,...I===(a?.sso?.status??`none`)?{}:{sso:I===`pending`?`pending`:null}}})),null;let n=i(await r.POST(`/api/v1/administrators`,{body:{name:v.trim(),...e}}));return i(await r.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:n.name}},body:{label:`first token`,expires_at:O(D)}}))},onSuccess:e=>{if(m(``),_.invalidateQueries({queryKey:[`administrators`]}),!e){d(),h(`Updated `+t);return}f({title:`Administrator created`,kicker:e.admin,content:(0,w.jsx)(b,{secret:e.secret,label:`Admin token for ${e.admin} (expires ${e.expires_at})`,hint:k})})},onError:g});return(0,w.jsxs)(`form`,{className:`form`,noValidate:!0,onSubmit:e=>{if(e.preventDefault(),!l&&!v.trim()){m(`A name is required.`,{errors:{name:`This field is required`}});return}R.mutate()},children:[(0,w.jsx)(c,{id:`a-name`,label:`Name`,hint:l?void 0:`A person (alice) or a pipeline (ci-deploy). Shown in the audit log.`,children:(0,w.jsx)(`input`,{id:`a-name`,value:v,placeholder:`alice`,autoComplete:`off`,spellCheck:!1,disabled:l,autoFocus:!l,onChange:e=>y(e.target.value)})}),(0,w.jsx)(c,{id:`a-role`,label:`Role`,hint:F?`From their groups at the identity provider, at every sign-in - change it there.`:T.find(e=>e.value===x)?.text,children:(0,w.jsx)(`select`,{id:`a-role`,value:x,disabled:F,onChange:e=>S(e.target.value),children:T.map(e=>(0,w.jsx)(`option`,{value:e.value,children:e.label},e.value))})}),(0,w.jsx)(c,{id:`a-email`,label:`Email`,hint:`Optional.`,children:(0,w.jsx)(`input`,{id:`a-email`,type:`email`,value:E,autoComplete:`off`,onChange:e=>j(e.target.value)})}),l&&(0,w.jsxs)(`label`,{className:`switch`,children:[(0,w.jsx)(`input`,{id:`a-active`,type:`checkbox`,checked:M,onChange:e=>N(e.target.checked)}),`Active`,(0,w.jsx)(`span`,{className:`hint`,children:`— unchecking stops every token of theirs immediately`})]}),l&&P.data?.sso&&(0,w.jsx)(c,{id:`a-sso`,label:`Sign-in through SSO`,hint:I===`linked`?`They sign in through the identity provider. Unlinking ends their sessions; their next SSO sign-in makes a new administrator.`:`Pending: their next SSO sign-in, with a verified email matching the one above, is bound to this account - once.`,children:(0,w.jsxs)(`select`,{id:`a-sso`,value:I,onChange:e=>L(e.target.value),children:[F&&(0,w.jsx)(`option`,{value:`linked`,children:`Linked`}),!F&&(0,w.jsx)(`option`,{value:`pending`,children:`Link at their next sign-in`}),(0,w.jsx)(`option`,{value:`none`,children:F?`Unlink`:`Not through SSO`})]})}),!l&&(0,w.jsxs)(`div`,{className:`hint`,children:[`Their first admin token, valid for `,D,` days, is shown once after you create them.`]}),(0,w.jsx)(u,{onCancel:d,children:(0,w.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:R.isPending,children:l?`Save`:`Create`})})]})}function R({name:t}){let n=M(t),o=p(),{toast:u}=s(),d=A(),[f,m]=(0,C.useState)(``),[h,g]=(0,C.useState)(O(D)),[_,v]=(0,C.useState)(null),x=()=>void o.invalidateQueries({queryKey:[`administrators`]}),S=e({mutationFn:async()=>i(await r.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:t}},body:{label:f.trim()||null,expires_at:h||null}})),onSuccess:e=>{v(e),m(``),x()},onError:d}),T=async e=>{if(window.confirm(`Revoke ${e.label?`'${e.label}'`:e.id}? Anything using it stops working now.`))try{l(await r.DELETE(`/api/v1/administrators/{name}/tokens/{token_id}`,{params:{path:{name:t,token_id:e.id}}})),u(`Revoked `+(e.label??e.id)),x()}catch(e){d(e)}},E=new Date().toISOString().slice(0,10);return(0,w.jsxs)(`div`,{className:`form`,id:`admin-tokens`,children:[n.isPending?(0,w.jsx)(a,{text:`Loading tokens…`}):n.isError?(0,w.jsx)(`div`,{className:`hint`,children:n.error.message}):n.data.length?(0,w.jsxs)(`table`,{className:`grid`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsx)(`tr`,{children:[`Label`,`Expires`,`Last used`,``].map((e,t)=>(0,w.jsx)(`th`,{children:e},t))})}),(0,w.jsx)(`tbody`,{children:n.data.map(e=>(0,w.jsxs)(`tr`,{"data-token":e.id,children:[(0,w.jsx)(`td`,{title:e.id,children:e.label??(0,w.jsx)(`span`,{className:`dim mono`,children:e.id})}),(0,w.jsx)(`td`,{style:{whiteSpace:`nowrap`},children:e.expires_at?(0,w.jsx)(`span`,{style:e.expired?{color:`var(--danger)`}:void 0,children:e.expires_at+(e.expired?` (expired)`:e.expires_at===E?` (today)`:``)}):(0,w.jsx)(`span`,{className:`dim`,children:`never`})}),(0,w.jsx)(`td`,{className:`mono dim`,children:(0,w.jsx)(y,{value:e.last_used_at,fallback:`never`})}),(0,w.jsx)(`td`,{children:(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm danger`,onClick:()=>void T(e),children:`Revoke`})})]},e.id))})]}):(0,w.jsx)(`div`,{className:`hint`,children:`No tokens. Issue one below to sign in with.`}),_?(0,w.jsx)(b,{secret:_.secret,label:`New token${_.label?` '${_.label}'`:``} (${_.expires_at?`expires ${_.expires_at}`:`never expires`})`,hint:k,onDone:()=>v(null)}):(0,w.jsxs)(`form`,{className:`grid2`,noValidate:!0,onSubmit:e=>{e.preventDefault(),S.mutate()},children:[(0,w.jsx)(c,{id:`t-label`,label:`New token: label`,hint:`What it's for - laptop, ci.`,children:(0,w.jsx)(`input`,{id:`t-label`,value:f,autoComplete:`off`,onChange:e=>m(e.target.value)})}),(0,w.jsx)(c,{id:`t-expires`,label:`Expires`,hint:`Leave blank for no expiry.`,children:(0,w.jsx)(`input`,{id:`t-expires`,type:`date`,value:h,onChange:e=>g(e.target.value)})}),(0,w.jsx)(`div`,{children:(0,w.jsx)(`button`,{type:`submit`,className:`btn`,disabled:S.isPending,children:`Issue token`})})]})]})}export{N as AdministratorsPage};
@@ -1 +1 @@
1
- import{$ as e,C as t,D as n,F as r,H as i,Q as a,Z as o}from"./index-BNMV1syF.js";import{t as s}from"./Time-CR17Ozn9.js";import{t as c}from"./CodeBox-B8iq2Oop.js";var l=e(a(),1),u=o(),d=[{id:`1h`,label:`Last hour`,hours:1},{id:`24h`,label:`Last 24 hours`,hours:24},{id:`7d`,label:`Last 7 days`,hours:168},{id:`30d`,label:`Last 30 days`,hours:720}],f=e=>e.toLocaleString();function p({refusals:e}){let t=Object.entries(e);return t.length?(0,u.jsx)(`span`,{style:{display:`inline-flex`,flexWrap:`wrap`,gap:4},children:t.map(([e,t])=>(0,u.jsx)(`span`,{className:`tag`,title:`${t} call${t===1?``:`s`} failed with ${e}`,children:`${e} × ${t}`},e))}):(0,u.jsx)(`span`,{className:`dim`,children:`none`})}function m({writes:e}){let t=Object.entries(e??{}).filter(([,e])=>e).map(([e,t])=>`${t} ${e}`);return(0,u.jsx)(`div`,{className:`hint`,children:t.length?t.join(` · `):`None.`})}function h({agent:e}){let t=Object.entries(e.queries);return(0,u.jsxs)(`div`,{className:`agent-detail`,children:[(0,u.jsx)(`p`,{className:`sub-h`,children:`Writes asked for`}),(0,u.jsx)(m,{writes:e.writes}),(0,u.jsx)(`p`,{className:`sub-h`,style:{marginTop:12},children:`Saved queries`}),t.length?(0,u.jsx)(`div`,{className:`hint`,children:t.map(([e,t])=>`${e} × ${t}`).join(` · `)}):(0,u.jsx)(`div`,{className:`hint`,children:`None - only ad-hoc SQL.`}),(0,u.jsx)(`p`,{className:`sub-h`,style:{marginTop:12},children:`Most frequent ad-hoc statements`}),e.top_statements.length?e.top_statements.map((e,t)=>(0,u.jsxs)(`div`,{className:`agent-statement`,children:[(0,u.jsx)(`div`,{className:`hint`,children:`${e.count} call${e.count===1?``:`s`} · ${f(e.rows)} rows`+(e.errors?` · ${e.errors} failed`:``)}),e.sql?(0,u.jsx)(c,{sql:e.sql}):(0,u.jsx)(`span`,{className:`mono dim`,children:`sha256 ${e.sql_sha256}`})]},t)):(0,u.jsx)(`div`,{className:`hint`,children:e.adhoc_calls?`Run history keeps no SQL text here (QUERYAPIGATE_HISTORY_ADHOC_SQL is none).`:`No ad-hoc SQL.`})]})}function g(){let[e,a]=(0,l.useState)(`24h`),[o,c]=(0,l.useState)(null),m=d.find(t=>t.id===e).hours,g=i({queryKey:[`usage`,`agents`,e],queryFn:async()=>n(await t.GET(`/api/v1/usage/agents`,{params:{query:{hours:m}}})),retry:!1}),_=g.data?.items??[],v;v=g.isPending?(0,u.jsx)(r,{text:`Loading agents…`}):g.isError?(0,u.jsxs)(`div`,{className:`empty`,children:[(0,u.jsx)(`strong`,{children:`Couldn’t load agent usage`}),(0,u.jsx)(`span`,{children:g.error.message})]}):_.length?(0,u.jsx)(`div`,{style:{overflowX:`auto`},children:(0,u.jsxs)(`table`,{className:`grid`,children:[(0,u.jsx)(`thead`,{children:(0,u.jsx)(`tr`,{children:[`Agent`,`Calls`,`Rows`,`Failed`,`Masked`,`Refused or failed with`,`Last seen`].map(e=>(0,u.jsx)(`th`,{children:e},e))})}),(0,u.jsx)(`tbody`,{children:_.map(e=>(0,u.jsxs)(l.Fragment,{children:[(0,u.jsxs)(`tr`,{children:[(0,u.jsxs)(`td`,{children:[(0,u.jsx)(`button`,{type:`button`,className:`agent-name`,"aria-expanded":o===e.agent,onClick:()=>c(o===e.agent?null:e.agent),children:e.agent}),(0,u.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:e.connections.join(`, `)})]}),(0,u.jsx)(`td`,{className:`num`,children:f(e.calls)}),(0,u.jsx)(`td`,{className:`num`,children:f(e.rows)}),(0,u.jsx)(`td`,{className:`num`,children:f(e.errors)}),(0,u.jsx)(`td`,{className:`num`,children:f(e.masked_calls)}),(0,u.jsx)(`td`,{children:(0,u.jsx)(p,{refusals:e.refusals})}),(0,u.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,u.jsx)(s,{value:e.last_seen,fallback:``})})]}),o===e.agent&&(0,u.jsx)(`tr`,{children:(0,u.jsx)(`td`,{colSpan:7,children:(0,u.jsx)(h,{agent:e})})})]},e.agent))})]})}):(0,u.jsxs)(`div`,{className:`empty`,children:[(0,u.jsx)(`strong`,{children:`No agent called the MCP server in this window`}),(0,u.jsx)(`span`,{children:`Runs over MCP - saved-query tools and execute_sql - appear here, per API key or signed-in user.`})]});let y=[];return g.data&&g.data.sample_rate<1&&y.push(`Run history keeps ${Math.round(g.data.sample_rate*100)}% of successful runs, so successful calls and rows are a sample; failures are all here.`),g.data?.truncated&&y.push(`More runs than one summary reads (100,000) - narrow the window.`),(0,u.jsxs)(u.Fragment,{children:[(0,u.jsxs)(`div`,{className:`page-head`,children:[(0,u.jsxs)(`div`,{className:`titles`,children:[(0,u.jsx)(`h1`,{children:`Agents`}),(0,u.jsx)(`span`,{className:`sub`,children:`What each AI agent did over MCP - per API key or signed-in user, from run history.`})]}),(0,u.jsx)(`span`,{className:`spacer`}),(0,u.jsx)(`button`,{type:`button`,className:`btn`,onClick:()=>void g.refetch(),children:`Refresh`})]}),(0,u.jsxs)(`div`,{className:`panel`,style:{overflow:`hidden`},children:[(0,u.jsxs)(`div`,{className:`panel-bar`,children:[(0,u.jsx)(`select`,{"aria-label":`Window`,value:e,onChange:e=>a(e.target.value),children:d.map(e=>(0,u.jsx)(`option`,{value:e.id,children:e.label},e.id))}),(0,u.jsx)(`span`,{className:`spacer`}),(0,u.jsx)(`span`,{className:`panel-count`,children:g.data?`${_.length} agent${_.length===1?``:`s`}`:``})]}),y.map(e=>(0,u.jsx)(`div`,{className:`hint`,style:{padding:`6px 12px`},children:e},e)),v]})]})}export{g as AgentsPage};
1
+ import{$ as e,C as t,D as n,F as r,H as i,Q as a,Z as o}from"./index-sn5NUET1.js";import{t as s}from"./Time-MA05YtZP.js";import{t as c}from"./CodeBox-BZY9RBv5.js";var l=e(a(),1),u=o(),d=[{id:`1h`,label:`Last hour`,hours:1},{id:`24h`,label:`Last 24 hours`,hours:24},{id:`7d`,label:`Last 7 days`,hours:168},{id:`30d`,label:`Last 30 days`,hours:720}],f=e=>e.toLocaleString();function p({refusals:e}){let t=Object.entries(e);return t.length?(0,u.jsx)(`span`,{style:{display:`inline-flex`,flexWrap:`wrap`,gap:4},children:t.map(([e,t])=>(0,u.jsx)(`span`,{className:`tag`,title:`${t} call${t===1?``:`s`} failed with ${e}`,children:`${e} × ${t}`},e))}):(0,u.jsx)(`span`,{className:`dim`,children:`none`})}function m({writes:e}){let t=Object.entries(e??{}).filter(([,e])=>e).map(([e,t])=>`${t} ${e}`);return(0,u.jsx)(`div`,{className:`hint`,children:t.length?t.join(` · `):`None.`})}function h({agent:e}){let t=Object.entries(e.queries);return(0,u.jsxs)(`div`,{className:`agent-detail`,children:[(0,u.jsx)(`p`,{className:`sub-h`,children:`Writes asked for`}),(0,u.jsx)(m,{writes:e.writes}),(0,u.jsx)(`p`,{className:`sub-h`,style:{marginTop:12},children:`Saved queries`}),t.length?(0,u.jsx)(`div`,{className:`hint`,children:t.map(([e,t])=>`${e} × ${t}`).join(` · `)}):(0,u.jsx)(`div`,{className:`hint`,children:`None - only ad-hoc SQL.`}),(0,u.jsx)(`p`,{className:`sub-h`,style:{marginTop:12},children:`Most frequent ad-hoc statements`}),e.top_statements.length?e.top_statements.map((e,t)=>(0,u.jsxs)(`div`,{className:`agent-statement`,children:[(0,u.jsx)(`div`,{className:`hint`,children:`${e.count} call${e.count===1?``:`s`} · ${f(e.rows)} rows`+(e.errors?` · ${e.errors} failed`:``)}),e.sql?(0,u.jsx)(c,{sql:e.sql}):(0,u.jsx)(`span`,{className:`mono dim`,children:`sha256 ${e.sql_sha256}`})]},t)):(0,u.jsx)(`div`,{className:`hint`,children:e.adhoc_calls?`Run history keeps no SQL text here (QUERYAPIGATE_HISTORY_ADHOC_SQL is none).`:`No ad-hoc SQL.`})]})}function g(){let[e,a]=(0,l.useState)(`24h`),[o,c]=(0,l.useState)(null),m=d.find(t=>t.id===e).hours,g=i({queryKey:[`usage`,`agents`,e],queryFn:async()=>n(await t.GET(`/api/v1/usage/agents`,{params:{query:{hours:m}}})),retry:!1}),_=g.data?.items??[],v;v=g.isPending?(0,u.jsx)(r,{text:`Loading agents…`}):g.isError?(0,u.jsxs)(`div`,{className:`empty`,children:[(0,u.jsx)(`strong`,{children:`Couldn’t load agent usage`}),(0,u.jsx)(`span`,{children:g.error.message})]}):_.length?(0,u.jsx)(`div`,{style:{overflowX:`auto`},children:(0,u.jsxs)(`table`,{className:`grid`,children:[(0,u.jsx)(`thead`,{children:(0,u.jsx)(`tr`,{children:[`Agent`,`Calls`,`Rows`,`Failed`,`Masked`,`Refused or failed with`,`Last seen`].map(e=>(0,u.jsx)(`th`,{children:e},e))})}),(0,u.jsx)(`tbody`,{children:_.map(e=>(0,u.jsxs)(l.Fragment,{children:[(0,u.jsxs)(`tr`,{children:[(0,u.jsxs)(`td`,{children:[(0,u.jsx)(`button`,{type:`button`,className:`agent-name`,"aria-expanded":o===e.agent,onClick:()=>c(o===e.agent?null:e.agent),children:e.agent}),(0,u.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:e.connections.join(`, `)})]}),(0,u.jsx)(`td`,{className:`num`,children:f(e.calls)}),(0,u.jsx)(`td`,{className:`num`,children:f(e.rows)}),(0,u.jsx)(`td`,{className:`num`,children:f(e.errors)}),(0,u.jsx)(`td`,{className:`num`,children:f(e.masked_calls)}),(0,u.jsx)(`td`,{children:(0,u.jsx)(p,{refusals:e.refusals})}),(0,u.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,u.jsx)(s,{value:e.last_seen,fallback:``})})]}),o===e.agent&&(0,u.jsx)(`tr`,{children:(0,u.jsx)(`td`,{colSpan:7,children:(0,u.jsx)(h,{agent:e})})})]},e.agent))})]})}):(0,u.jsxs)(`div`,{className:`empty`,children:[(0,u.jsx)(`strong`,{children:`No agent called the MCP server in this window`}),(0,u.jsx)(`span`,{children:`Runs over MCP - saved-query tools and execute_sql - appear here, per API key or signed-in user.`})]});let y=[];return g.data&&g.data.sample_rate<1&&y.push(`Run history keeps ${Math.round(g.data.sample_rate*100)}% of successful runs, so successful calls and rows are a sample; failures are all here.`),g.data?.truncated&&y.push(`More runs than one summary reads (100,000) - narrow the window.`),(0,u.jsxs)(u.Fragment,{children:[(0,u.jsxs)(`div`,{className:`page-head`,children:[(0,u.jsxs)(`div`,{className:`titles`,children:[(0,u.jsx)(`h1`,{children:`Agents`}),(0,u.jsx)(`span`,{className:`sub`,children:`What each AI agent did over MCP - per API key or signed-in user, from run history.`})]}),(0,u.jsx)(`span`,{className:`spacer`}),(0,u.jsx)(`button`,{type:`button`,className:`btn`,onClick:()=>void g.refetch(),children:`Refresh`})]}),(0,u.jsxs)(`div`,{className:`panel`,style:{overflow:`hidden`},children:[(0,u.jsxs)(`div`,{className:`panel-bar`,children:[(0,u.jsx)(`select`,{"aria-label":`Window`,value:e,onChange:e=>a(e.target.value),children:d.map(e=>(0,u.jsx)(`option`,{value:e.id,children:e.label},e.id))}),(0,u.jsx)(`span`,{className:`spacer`}),(0,u.jsx)(`span`,{className:`panel-count`,children:g.data?`${_.length} agent${_.length===1?``:`s`}`:``})]}),y.map(e=>(0,u.jsx)(`div`,{className:`hint`,style:{padding:`6px 12px`},children:e},e)),v]})]})}export{g as AgentsPage};
@@ -1 +1 @@
1
- import{$ as e,B as t,F as n,Q as r,Z as i,_ as a,b as o,x as s,y as c,z as l}from"./index-BNMV1syF.js";import{t as u}from"./Time-CR17Ozn9.js";import{n as d}from"./state-O13cl7cO.js";var f=e(r(),1),p=i(),m=[{id:`critical`,label:`Critical`},{id:`warning`,label:`Warning`},{id:`info`,label:`For your information`}],h={key:`Open API keys`,query:`Open query`,connection:`Open connections`,settings:`Open settings`,export:`Open exports`,agent:`Open agents`,approvals:`Open approvals`,audit:`Open audit log`};function g(){let e=l();return t=>{t.type===`query`?e(`/queries/${encodeURIComponent(t.name)}`):t.type===`key`?e(`/api-keys`):t.type===`connection`?e(`/connections`):t.type===`export`?e(`/exports`):t.type===`agent`?e(`/agents`):t.type===`approvals`?e(`/approvals`):t.type===`audit`?e(`/audit-log`):(d(t.name),e(`/settings`))}}function _({alert:e,dismissed:t}){let n=g();return(0,p.jsxs)(`div`,{className:`alert-row `+e.severity,"data-alert":e.id,children:[(0,p.jsx)(`span`,{className:`sev `+e.severity,children:e.severity}),(0,p.jsxs)(`div`,{className:`alert-what`,children:[(0,p.jsx)(`strong`,{children:e.title}),(0,p.jsx)(`span`,{className:`alert-detail`,children:e.detail}),e.since&&!e.detail.includes(e.since)&&(0,p.jsxs)(`span`,{className:`alert-since`,children:[e.kind===`key_expiring`?`Expires `:`Since `,(0,p.jsx)(u,{value:e.since})]})]}),(0,p.jsxs)(`div`,{className:`actions`,children:[e.target&&(0,p.jsx)(`button`,{type:`button`,className:`btn sm outlined`,onClick:()=>n(e.target),children:h[e.target.type]??`Open`}),t?(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>o(e.id),children:`Restore`}):(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,title:`Hide it in this browser until it clears`,onClick:()=>c(e.id),children:`Dismiss`})]})]})}function v({alerts:e}){return(0,p.jsx)(p.Fragment,{children:m.map(({id:t,label:n})=>{let r=e.filter(e=>e.severity===t);return r.length?(0,p.jsxs)(`div`,{className:`panel alerts-group`,id:`alerts-`+t,children:[(0,p.jsxs)(`h2`,{children:[n,` `,(0,p.jsx)(`span`,{className:`count`,children:r.length})]}),r.map(e=>(0,p.jsx)(_,{alert:e},e.id))]},t):null})})}function y(){let e=l(),r=t(),i=a.find(e=>e.id===r.tab),{query:o,active:c,dismissed:d}=s(),[m,h]=(0,f.useState)(!1),g=e=>!i||i.kinds.includes(e.kind),y=c.filter(g),b=d.filter(g),x=e=>c.filter(t=>!e||e.includes(t.kind)),S;S=o.isPending?(0,p.jsx)(n,{text:`Checking…`}):o.isError?(0,p.jsx)(`div`,{className:`panel`,children:(0,p.jsxs)(`div`,{className:`empty`,children:[(0,p.jsx)(`strong`,{children:`Couldn’t check for alerts`}),(0,p.jsx)(`span`,{children:o.error.message}),(0,p.jsx)(`button`,{type:`button`,className:`btn sm`,onClick:()=>void o.refetch(),children:`Retry`})]})}):(0,p.jsxs)(p.Fragment,{children:[i&&(0,p.jsx)(`p`,{className:`alerts-about`,children:i.about}),y.length?(0,p.jsx)(v,{alerts:y}):(0,p.jsx)(`div`,{className:`panel`,id:`alerts-clear`,children:(0,p.jsxs)(`div`,{className:`empty`,children:[(0,p.jsx)(`strong`,{children:`All clear`}),(0,p.jsx)(`span`,{children:i?`Nothing to report from this check right now.`:`Nothing needs your attention right now.`})]})}),b.length>0&&(0,p.jsxs)(`div`,{className:`alerts-dismissed`,children:[(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>h(e=>!e),children:`${b.length} dismissed in this browser · ${m?`Hide`:`Show`}`}),m&&(0,p.jsx)(`div`,{className:`panel alerts-group`,id:`alerts-dismissed`,children:b.map(e=>(0,p.jsx)(_,{alert:e,dismissed:!0},e.id))})]})]});let C=(t,n,r)=>(0,p.jsxs)(`button`,{type:`button`,role:`tab`,"aria-selected":(i?.id??`all`)===t,className:`minitab`+((i?.id??`all`)===t?` active`:``),onClick:()=>e(t===`all`?`/alerts`:`/alerts/`+t),children:[n,o.data?(0,p.jsx)(`span`,{className:`n`+(r.length?` on`:``)+(r.some(e=>e.severity===`critical`)?` critical`:``),children:r.length}):null]},t);return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(`div`,{className:`page-head`,children:[(0,p.jsxs)(`div`,{className:`titles`,children:[(0,p.jsx)(`h1`,{children:`Alerts`}),(0,p.jsxs)(`span`,{className:`sub`,children:[`What needs attention now, checked every minute. Each clears by itself once its cause does.`,o.data?(0,p.jsxs)(p.Fragment,{children:[` Last checked `,(0,p.jsx)(u,{value:o.data.checked_at}),`.`]}):null]})]}),(0,p.jsx)(`span`,{className:`spacer`}),(0,p.jsx)(`button`,{id:`refresh-alerts`,type:`button`,className:`btn`,onClick:()=>void o.refetch(),children:`Check now`})]}),(0,p.jsxs)(`div`,{className:`minitabs alerts-tabs`,role:`tablist`,"aria-label":`Checks`,children:[C(`all`,`All`,x()),a.map(e=>C(e.id,e.label,x(e.kinds)))]}),S]})}export{y as AlertsPage};
1
+ import{$ as e,B as t,F as n,Q as r,Z as i,_ as a,b as o,x as s,y as c,z as l}from"./index-sn5NUET1.js";import{t as u}from"./Time-MA05YtZP.js";import{n as d}from"./state-O13cl7cO.js";var f=e(r(),1),p=i(),m=[{id:`critical`,label:`Critical`},{id:`warning`,label:`Warning`},{id:`info`,label:`For your information`}],h={key:`Open API keys`,query:`Open query`,connection:`Open connections`,settings:`Open settings`,export:`Open exports`,agent:`Open agents`,approvals:`Open approvals`,audit:`Open audit log`};function g(){let e=l();return t=>{t.type===`query`?e(`/queries/${encodeURIComponent(t.name)}`):t.type===`key`?e(`/api-keys`):t.type===`connection`?e(`/connections`):t.type===`export`?e(`/exports`):t.type===`agent`?e(`/agents`):t.type===`approvals`?e(`/approvals`):t.type===`audit`?e(`/audit-log`):(d(t.name),e(`/settings`))}}function _({alert:e,dismissed:t}){let n=g();return(0,p.jsxs)(`div`,{className:`alert-row `+e.severity,"data-alert":e.id,children:[(0,p.jsx)(`span`,{className:`sev `+e.severity,children:e.severity}),(0,p.jsxs)(`div`,{className:`alert-what`,children:[(0,p.jsx)(`strong`,{children:e.title}),(0,p.jsx)(`span`,{className:`alert-detail`,children:e.detail}),e.since&&!e.detail.includes(e.since)&&(0,p.jsxs)(`span`,{className:`alert-since`,children:[e.kind===`key_expiring`?`Expires `:`Since `,(0,p.jsx)(u,{value:e.since})]})]}),(0,p.jsxs)(`div`,{className:`actions`,children:[e.target&&(0,p.jsx)(`button`,{type:`button`,className:`btn sm outlined`,onClick:()=>n(e.target),children:h[e.target.type]??`Open`}),t?(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>o(e.id),children:`Restore`}):(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,title:`Hide it in this browser until it clears`,onClick:()=>c(e.id),children:`Dismiss`})]})]})}function v({alerts:e}){return(0,p.jsx)(p.Fragment,{children:m.map(({id:t,label:n})=>{let r=e.filter(e=>e.severity===t);return r.length?(0,p.jsxs)(`div`,{className:`panel alerts-group`,id:`alerts-`+t,children:[(0,p.jsxs)(`h2`,{children:[n,` `,(0,p.jsx)(`span`,{className:`count`,children:r.length})]}),r.map(e=>(0,p.jsx)(_,{alert:e},e.id))]},t):null})})}function y(){let e=l(),r=t(),i=a.find(e=>e.id===r.tab),{query:o,active:c,dismissed:d}=s(),[m,h]=(0,f.useState)(!1),g=e=>!i||i.kinds.includes(e.kind),y=c.filter(g),b=d.filter(g),x=e=>c.filter(t=>!e||e.includes(t.kind)),S;S=o.isPending?(0,p.jsx)(n,{text:`Checking…`}):o.isError?(0,p.jsx)(`div`,{className:`panel`,children:(0,p.jsxs)(`div`,{className:`empty`,children:[(0,p.jsx)(`strong`,{children:`Couldn’t check for alerts`}),(0,p.jsx)(`span`,{children:o.error.message}),(0,p.jsx)(`button`,{type:`button`,className:`btn sm`,onClick:()=>void o.refetch(),children:`Retry`})]})}):(0,p.jsxs)(p.Fragment,{children:[i&&(0,p.jsx)(`p`,{className:`alerts-about`,children:i.about}),y.length?(0,p.jsx)(v,{alerts:y}):(0,p.jsx)(`div`,{className:`panel`,id:`alerts-clear`,children:(0,p.jsxs)(`div`,{className:`empty`,children:[(0,p.jsx)(`strong`,{children:`All clear`}),(0,p.jsx)(`span`,{children:i?`Nothing to report from this check right now.`:`Nothing needs your attention right now.`})]})}),b.length>0&&(0,p.jsxs)(`div`,{className:`alerts-dismissed`,children:[(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>h(e=>!e),children:`${b.length} dismissed in this browser · ${m?`Hide`:`Show`}`}),m&&(0,p.jsx)(`div`,{className:`panel alerts-group`,id:`alerts-dismissed`,children:b.map(e=>(0,p.jsx)(_,{alert:e,dismissed:!0},e.id))})]})]});let C=(t,n,r)=>(0,p.jsxs)(`button`,{type:`button`,role:`tab`,"aria-selected":(i?.id??`all`)===t,className:`minitab`+((i?.id??`all`)===t?` active`:``),onClick:()=>e(t===`all`?`/alerts`:`/alerts/`+t),children:[n,o.data?(0,p.jsx)(`span`,{className:`n`+(r.length?` on`:``)+(r.some(e=>e.severity===`critical`)?` critical`:``),children:r.length}):null]},t);return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(`div`,{className:`page-head`,children:[(0,p.jsxs)(`div`,{className:`titles`,children:[(0,p.jsx)(`h1`,{children:`Alerts`}),(0,p.jsxs)(`span`,{className:`sub`,children:[`What needs attention now, checked every minute. Each clears by itself once its cause does.`,o.data?(0,p.jsxs)(p.Fragment,{children:[` Last checked `,(0,p.jsx)(u,{value:o.data.checked_at}),`.`]}):null]})]}),(0,p.jsx)(`span`,{className:`spacer`}),(0,p.jsx)(`button`,{id:`refresh-alerts`,type:`button`,className:`btn`,onClick:()=>void o.refetch(),children:`Check now`})]}),(0,p.jsxs)(`div`,{className:`minitabs alerts-tabs`,role:`tablist`,"aria-label":`Checks`,children:[C(`all`,`All`,x()),a.map(e=>C(e.id,e.label,x(e.kinds)))]}),S]})}export{y as AlertsPage};
@@ -1 +1 @@
1
- import{F as e,Z as t,c as n,l as r,s as i}from"./index-BNMV1syF.js";import{t as a}from"./reach-0Yvn3NAt.js";import{t as o}from"./Time-CR17Ozn9.js";import{i as s,n as c,o as l}from"./forms-C3dw2wG6.js";import{t as u}from"./icons-C-i_90Gs.js";import{t as d}from"./Managed-DjB6h9Kc.js";var f=t();function p(e,t){let n=new Date(Date.now()+6048e5).toISOString().slice(0,10);return!!(e&&e>=t&&e<=n)}function m({usage:e}){let t=[`calls`,`rows`].map(t=>({what:t,used:e[t],limit:e[`${t}_limit`]})).filter(e=>e.limit!=null),n=t.reduce((e,t)=>t.used/t.limit>e.used/e.limit?t:e,t[0]),r=Math.min(1,n.used/n.limit),i=e.per===`day`?`today`:`this month`;return(0,f.jsxs)(`div`,{className:`quota-usage`,title:t.map(e=>`${e.used.toLocaleString()} of ${e.limit.toLocaleString()} ${e.what}`).join(` · `)+` ${i} · resets ${e.resets_at}`,children:[(0,f.jsxs)(`span`,{className:`dim`,children:[n.used.toLocaleString(),` / `,n.limit.toLocaleString(),` `,n.what,` `,i]}),(0,f.jsx)(`div`,{className:`quota-bar${r>=1?` full`:r>=.8?` near`:``}`,children:(0,f.jsx)(`span`,{style:{width:`${Math.round(r*100)}%`}})})]})}function h(e){if(!e.queries)return null;let t=[e.queries+(e.queries===1?` query`:` queries`)];return e.errors&&t.push(e.errors+` failed`),t.join(` · `)}function g(){let t=n(),g=i(),_=r(),v=c(),y=s(),b=Object.keys(g.data??{}).sort(),x=new Date().toISOString().slice(0,10),S;return S=g.isPending?(0,f.jsx)(e,{text:`Loading API keys…`}):g.isError?(0,f.jsxs)(`div`,{className:`empty`,children:[(0,f.jsx)(`strong`,{children:`Couldn’t load API keys`}),(0,f.jsx)(`span`,{children:g.error.message}),(0,f.jsx)(`button`,{type:`button`,className:`btn sm`,onClick:()=>g.refetch(),children:`Retry`})]}):b.length?(0,f.jsx)(`div`,{style:{overflowX:`auto`},children:(0,f.jsxs)(`table`,{className:`grid`,children:[(0,f.jsx)(`thead`,{children:(0,f.jsx)(`tr`,{children:[`Name`,`Scope`,`Access`,`Rate limit`,`IPs`,`Expires`,`Last used`,`Usage`,``].map((e,t)=>(0,f.jsx)(`th`,{children:e},t))})}),(0,f.jsx)(`tbody`,{children:b.map(e=>{let n=g.data[e],r=!!(n.expires_at&&n.expires_at<x),i=!r&&p(n.expires_at,x),s=n.allowed_ips??[],c=h(n.usage);return(0,f.jsxs)(`tr`,{"data-name":e,children:[(0,f.jsx)(`td`,{children:(0,f.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,f.jsx)(`span`,{className:`dot ${n.active&&!r?`ok`:`off`}`,title:n.active?r?`Expired`:`Active`:`Revoked`}),(0,f.jsx)(`span`,{className:`name`,title:n.created_at?`Created `+n.created_at+(n.created_from_role?` from role `+n.created_from_role:``):void 0,children:e}),(0,f.jsx)(d,{kind:`api_keys`,name:e}),n.previous_expires_at?(0,f.jsx)(`span`,{className:`tag`,title:`Rotated: the old secret works until then too`,children:`rotating`}):null,n.mask?.length?(0,f.jsx)(`span`,{className:`tag`,title:n.mask.map(e=>`${e.column}: ${e.action}`).join(`, `),children:`masked`}):null]})}),(0,f.jsx)(`td`,{children:(0,f.jsx)(l,{entry:n,collections:_.data?.collections})}),(0,f.jsx)(a,{entry:n}),(0,f.jsx)(`td`,{className:n.rate_limit?`mono`:`mono dim`,style:{whiteSpace:`nowrap`},children:n.rate_limit||`server default`}),(0,f.jsx)(`td`,{className:`dim`,children:s.length?(0,f.jsx)(`span`,{title:s.join(`, `),children:s.length+(s.length===1?` IP`:` IPs`)}):(0,f.jsx)(`span`,{className:`dim`,children:`any`})}),(0,f.jsx)(`td`,{style:{whiteSpace:`nowrap`},children:n.expires_at?(0,f.jsx)(`span`,{style:r?{color:`var(--danger)`}:i?{color:`var(--warn)`}:void 0,children:n.expires_at+(r?` (expired)`:i?` (expires soon)`:``)}):(0,f.jsx)(`span`,{className:`dim`,children:`never`})}),(0,f.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,f.jsx)(o,{value:n.last_used_at,fallback:`never`})}),n.usage_this_period?(0,f.jsxs)(`td`,{style:{whiteSpace:`nowrap`},children:[c,(0,f.jsx)(m,{usage:n.usage_this_period})]}):c?(0,f.jsx)(`td`,{title:`${n.usage.rows}${n.usage.rows===1?` row`:` rows`} returned in total · since this process started`,style:{whiteSpace:`nowrap`},children:c}):(0,f.jsx)(`td`,{className:`dim`,style:{whiteSpace:`nowrap`},children:`No activity yet`}),(0,f.jsx)(`td`,{children:(0,f.jsxs)(`div`,{className:`actions`,children:[(0,f.jsx)(`button`,{hidden:!t(`access.write`),type:`button`,className:`btn ghost sm icon`,title:`Edit ${e}`,"aria-label":`Edit`,onClick:()=>v.editKey(e),children:(0,f.jsx)(u,{})}),(0,f.jsx)(`button`,{hidden:!t(`access.write`),type:`button`,className:`btn ghost sm danger`,onClick:()=>void y(e),children:`Revoke`})]})})]},e)})})]})}):(0,f.jsxs)(`div`,{className:`empty`,children:[(0,f.jsx)(`strong`,{children:`No scoped API keys yet`}),(0,f.jsx)(`span`,{children:`QUERYAPIGATE_API_KEY is a full-access admin key. Create a scoped one to limit a caller to specific connections, read-only.`}),(0,f.jsx)(`button`,{hidden:!t(`access.write`),type:`button`,className:`btn primary`,onClick:()=>v.newKey(),children:`New API key`})]}),(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`div`,{className:`page-head`,children:[(0,f.jsxs)(`div`,{className:`titles`,children:[(0,f.jsx)(`h1`,{children:`API keys`}),(0,f.jsx)(`span`,{className:`sub`,id:`apikeys-sub`,children:(b.length?b.length+(b.length===1?` key. `:` keys. `):``)+`Scope each one to connections, collections or roles.`})]}),(0,f.jsx)(`span`,{className:`spacer`}),(0,f.jsx)(`button`,{hidden:!t(`access.write`),id:`new-apikey`,type:`button`,className:`btn primary`,onClick:()=>v.newKey(),children:`New API key`})]}),(0,f.jsx)(`div`,{id:`apikeys-table`,className:`panel`,children:S})]})}export{g as ApiKeysPage};
1
+ import{F as e,Z as t,c as n,l as r,s as i}from"./index-sn5NUET1.js";import{t as a}from"./reach-2gDU11-l.js";import{t as o}from"./Time-MA05YtZP.js";import{i as s,n as c,o as l}from"./forms-C6RLe1F3.js";import{t as u}from"./icons-DQ8_pv9M.js";import{t as d}from"./Managed-CVE0layM.js";var f=t();function p(e,t){let n=new Date(Date.now()+6048e5).toISOString().slice(0,10);return!!(e&&e>=t&&e<=n)}function m({usage:e}){let t=[`calls`,`rows`].map(t=>({what:t,used:e[t],limit:e[`${t}_limit`]})).filter(e=>e.limit!=null),n=t.reduce((e,t)=>t.used/t.limit>e.used/e.limit?t:e,t[0]),r=Math.min(1,n.used/n.limit),i=e.per===`day`?`today`:`this month`;return(0,f.jsxs)(`div`,{className:`quota-usage`,title:t.map(e=>`${e.used.toLocaleString()} of ${e.limit.toLocaleString()} ${e.what}`).join(` · `)+` ${i} · resets ${e.resets_at}`,children:[(0,f.jsxs)(`span`,{className:`dim`,children:[n.used.toLocaleString(),` / `,n.limit.toLocaleString(),` `,n.what,` `,i]}),(0,f.jsx)(`div`,{className:`quota-bar${r>=1?` full`:r>=.8?` near`:``}`,children:(0,f.jsx)(`span`,{style:{width:`${Math.round(r*100)}%`}})})]})}function h(e){if(!e.queries)return null;let t=[e.queries+(e.queries===1?` query`:` queries`)];return e.errors&&t.push(e.errors+` failed`),t.join(` · `)}function g(){let t=n(),g=i(),_=r(),v=c(),y=s(),b=Object.keys(g.data??{}).sort(),x=new Date().toISOString().slice(0,10),S;return S=g.isPending?(0,f.jsx)(e,{text:`Loading API keys…`}):g.isError?(0,f.jsxs)(`div`,{className:`empty`,children:[(0,f.jsx)(`strong`,{children:`Couldn’t load API keys`}),(0,f.jsx)(`span`,{children:g.error.message}),(0,f.jsx)(`button`,{type:`button`,className:`btn sm`,onClick:()=>g.refetch(),children:`Retry`})]}):b.length?(0,f.jsx)(`div`,{style:{overflowX:`auto`},children:(0,f.jsxs)(`table`,{className:`grid`,children:[(0,f.jsx)(`thead`,{children:(0,f.jsx)(`tr`,{children:[`Name`,`Scope`,`Access`,`Rate limit`,`IPs`,`Expires`,`Last used`,`Usage`,``].map((e,t)=>(0,f.jsx)(`th`,{children:e},t))})}),(0,f.jsx)(`tbody`,{children:b.map(e=>{let n=g.data[e],r=!!(n.expires_at&&n.expires_at<x),i=!r&&p(n.expires_at,x),s=n.allowed_ips??[],c=h(n.usage);return(0,f.jsxs)(`tr`,{"data-name":e,children:[(0,f.jsx)(`td`,{children:(0,f.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,f.jsx)(`span`,{className:`dot ${n.active&&!r?`ok`:`off`}`,title:n.active?r?`Expired`:`Active`:`Revoked`}),(0,f.jsx)(`span`,{className:`name`,title:n.created_at?`Created `+n.created_at+(n.created_from_role?` from role `+n.created_from_role:``):void 0,children:e}),(0,f.jsx)(d,{kind:`api_keys`,name:e}),n.previous_expires_at?(0,f.jsx)(`span`,{className:`tag`,title:`Rotated: the old secret works until then too`,children:`rotating`}):null,n.mask?.length?(0,f.jsx)(`span`,{className:`tag`,title:n.mask.map(e=>`${e.column}: ${e.action}`).join(`, `),children:`masked`}):null]})}),(0,f.jsx)(`td`,{children:(0,f.jsx)(l,{entry:n,collections:_.data?.collections})}),(0,f.jsx)(a,{entry:n}),(0,f.jsx)(`td`,{className:n.rate_limit?`mono`:`mono dim`,style:{whiteSpace:`nowrap`},children:n.rate_limit||`server default`}),(0,f.jsx)(`td`,{className:`dim`,children:s.length?(0,f.jsx)(`span`,{title:s.join(`, `),children:s.length+(s.length===1?` IP`:` IPs`)}):(0,f.jsx)(`span`,{className:`dim`,children:`any`})}),(0,f.jsx)(`td`,{style:{whiteSpace:`nowrap`},children:n.expires_at?(0,f.jsx)(`span`,{style:r?{color:`var(--danger)`}:i?{color:`var(--warn)`}:void 0,children:n.expires_at+(r?` (expired)`:i?` (expires soon)`:``)}):(0,f.jsx)(`span`,{className:`dim`,children:`never`})}),(0,f.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,f.jsx)(o,{value:n.last_used_at,fallback:`never`})}),n.usage_this_period?(0,f.jsxs)(`td`,{style:{whiteSpace:`nowrap`},children:[c,(0,f.jsx)(m,{usage:n.usage_this_period})]}):c?(0,f.jsx)(`td`,{title:`${n.usage.rows}${n.usage.rows===1?` row`:` rows`} returned in total · since this process started`,style:{whiteSpace:`nowrap`},children:c}):(0,f.jsx)(`td`,{className:`dim`,style:{whiteSpace:`nowrap`},children:`No activity yet`}),(0,f.jsx)(`td`,{children:(0,f.jsxs)(`div`,{className:`actions`,children:[(0,f.jsx)(`button`,{hidden:!t(`access.write`),type:`button`,className:`btn ghost sm icon`,title:`Edit ${e}`,"aria-label":`Edit`,onClick:()=>v.editKey(e),children:(0,f.jsx)(u,{})}),(0,f.jsx)(`button`,{hidden:!t(`access.write`),type:`button`,className:`btn ghost sm danger`,onClick:()=>void y(e),children:`Revoke`})]})})]},e)})})]})}):(0,f.jsxs)(`div`,{className:`empty`,children:[(0,f.jsx)(`strong`,{children:`No scoped API keys yet`}),(0,f.jsx)(`span`,{children:`QUERYAPIGATE_API_KEY is a full-access admin key. Create a scoped one to limit a caller to specific connections, read-only.`}),(0,f.jsx)(`button`,{hidden:!t(`access.write`),type:`button`,className:`btn primary`,onClick:()=>v.newKey(),children:`New API key`})]}),(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`div`,{className:`page-head`,children:[(0,f.jsxs)(`div`,{className:`titles`,children:[(0,f.jsx)(`h1`,{children:`API keys`}),(0,f.jsx)(`span`,{className:`sub`,id:`apikeys-sub`,children:(b.length?b.length+(b.length===1?` key. `:` keys. `):``)+`Scope each one to connections, collections or roles.`})]}),(0,f.jsx)(`span`,{className:`spacer`}),(0,f.jsx)(`button`,{hidden:!t(`access.write`),id:`new-apikey`,type:`button`,className:`btn primary`,onClick:()=>v.newKey(),children:`New API key`})]}),(0,f.jsx)(`div`,{id:`apikeys-table`,className:`panel`,children:S})]})}export{g as ApiKeysPage};