queryapigate 0.22.0__tar.gz → 0.23.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 (193) hide show
  1. {queryapigate-0.22.0/queryapigate.egg-info → queryapigate-0.23.0}/PKG-INFO +6 -1
  2. {queryapigate-0.22.0 → queryapigate-0.23.0}/README.md +1 -0
  3. {queryapigate-0.22.0 → queryapigate-0.23.0}/pyproject.toml +4 -1
  4. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/__init__.py +1 -1
  5. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/app.py +60 -6
  6. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/cli.py +20 -1
  7. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/config.py +35 -1
  8. queryapigate-0.22.0/queryapigate/console_dist/assets/AccessMapPage-CJHNlXPv.js → queryapigate-0.23.0/queryapigate/console_dist/assets/AccessMapPage-CSHtKZv2.js +3 -3
  9. queryapigate-0.22.0/queryapigate/console_dist/assets/AdministratorsPage-BbYhO2eo.js → queryapigate-0.23.0/queryapigate/console_dist/assets/AdministratorsPage-CSHD7DKM.js +1 -1
  10. queryapigate-0.22.0/queryapigate/console_dist/assets/AgentsPage-DDCauW-7.js → queryapigate-0.23.0/queryapigate/console_dist/assets/AgentsPage-Czt0Mnw4.js +1 -1
  11. queryapigate-0.22.0/queryapigate/console_dist/assets/AlertsPage-CazNtrHo.js → queryapigate-0.23.0/queryapigate/console_dist/assets/AlertsPage-TD-wmxpr.js +1 -1
  12. queryapigate-0.22.0/queryapigate/console_dist/assets/ApiKeysPage-DhK31mF7.js → queryapigate-0.23.0/queryapigate/console_dist/assets/ApiKeysPage-nXSY2YPH.js +1 -1
  13. queryapigate-0.22.0/queryapigate/console_dist/assets/ApprovalsPage-feItqvUh.js → queryapigate-0.23.0/queryapigate/console_dist/assets/ApprovalsPage-CczJ9Eq4.js +1 -1
  14. queryapigate-0.22.0/queryapigate/console_dist/assets/AuditLogPage-Sv21w03B.js → queryapigate-0.23.0/queryapigate/console_dist/assets/AuditLogPage-CzBlBkhP.js +1 -1
  15. queryapigate-0.22.0/queryapigate/console_dist/assets/CachingPage-DUyqx9Dg.js → queryapigate-0.23.0/queryapigate/console_dist/assets/CachingPage-Cyh1l8KY.js +1 -1
  16. queryapigate-0.22.0/queryapigate/console_dist/assets/CodeBox-DPiCVuYm.js → queryapigate-0.23.0/queryapigate/console_dist/assets/CodeBox-dmGPjinL.js +1 -1
  17. queryapigate-0.22.0/queryapigate/console_dist/assets/ConnectionsPage-B1FW0Sv-.js → queryapigate-0.23.0/queryapigate/console_dist/assets/ConnectionsPage-DMO68-mZ.js +1 -1
  18. queryapigate-0.22.0/queryapigate/console_dist/assets/DesignerPage-Da52fUjh.js → queryapigate-0.23.0/queryapigate/console_dist/assets/DesignerPage-9AWE5mGX.js +1 -1
  19. queryapigate-0.22.0/queryapigate/console_dist/assets/DestinationsPage-BvcXYIwC.js → queryapigate-0.23.0/queryapigate/console_dist/assets/DestinationsPage-Bh1zevuv.js +1 -1
  20. queryapigate-0.22.0/queryapigate/console_dist/assets/Experimental-Cev0t6Ci.js → queryapigate-0.23.0/queryapigate/console_dist/assets/Experimental-BZ5eU3RF.js +1 -1
  21. queryapigate-0.22.0/queryapigate/console_dist/assets/ExportsPage-Csyn3Ghf.js → queryapigate-0.23.0/queryapigate/console_dist/assets/ExportsPage-CgedN5uU.js +1 -1
  22. queryapigate-0.22.0/queryapigate/console_dist/assets/HelpPage-C1cQioeI.js → queryapigate-0.23.0/queryapigate/console_dist/assets/HelpPage-LsbTGm8h.js +2 -2
  23. queryapigate-0.22.0/queryapigate/console_dist/assets/HomePage-BRhhEL2z.js → queryapigate-0.23.0/queryapigate/console_dist/assets/HomePage-CPyUXjdF.js +2 -2
  24. queryapigate-0.22.0/queryapigate/console_dist/assets/MetricsPage-CgSxzDOE.js → queryapigate-0.23.0/queryapigate/console_dist/assets/MetricsPage-twDFTNE9.js +1 -1
  25. queryapigate-0.22.0/queryapigate/console_dist/assets/RepositoryPage-xiZEg7f4.js → queryapigate-0.23.0/queryapigate/console_dist/assets/RepositoryPage-D2LzRD8u.js +1 -1
  26. queryapigate-0.22.0/queryapigate/console_dist/assets/Results-CKzkIgXw.js → queryapigate-0.23.0/queryapigate/console_dist/assets/Results-kZ_dQ23r.js +1 -1
  27. queryapigate-0.22.0/queryapigate/console_dist/assets/RolesPage-C9G0dlSf.js → queryapigate-0.23.0/queryapigate/console_dist/assets/RolesPage-C66qonnT.js +1 -1
  28. queryapigate-0.22.0/queryapigate/console_dist/assets/SchemaBrowser-7JFHE2g9.js → queryapigate-0.23.0/queryapigate/console_dist/assets/SchemaBrowser-Ug8fhNeZ.js +1 -1
  29. queryapigate-0.22.0/queryapigate/console_dist/assets/SettingsPage-BFoHW1XP.js → queryapigate-0.23.0/queryapigate/console_dist/assets/SettingsPage-CTVO4iQ7.js +1 -1
  30. queryapigate-0.22.0/queryapigate/console_dist/assets/StatTile-Dx6Azb_P.js → queryapigate-0.23.0/queryapigate/console_dist/assets/StatTile-D25j3fMS.js +1 -1
  31. queryapigate-0.22.0/queryapigate/console_dist/assets/Time-GG1SZb8S.js → queryapigate-0.23.0/queryapigate/console_dist/assets/Time-DbFAPI8a.js +1 -1
  32. queryapigate-0.22.0/queryapigate/console_dist/assets/forms-BCfnOm9v.js → queryapigate-0.23.0/queryapigate/console_dist/assets/forms-DYXv3f3c.js +1 -1
  33. queryapigate-0.22.0/queryapigate/console_dist/assets/forms-C8ZdFl7Q.js → queryapigate-0.23.0/queryapigate/console_dist/assets/forms-_J9EIFdi.js +1 -1
  34. queryapigate-0.22.0/queryapigate/console_dist/assets/icons-plWox3cc.js → queryapigate-0.23.0/queryapigate/console_dist/assets/icons-D4JpNqC8.js +1 -1
  35. queryapigate-0.22.0/queryapigate/console_dist/assets/index-k1HFugKb.js → queryapigate-0.23.0/queryapigate/console_dist/assets/index-BW1KMgAb.js +2 -2
  36. queryapigate-0.22.0/queryapigate/console_dist/assets/maskRules-B0nXSvKz.js → queryapigate-0.23.0/queryapigate/console_dist/assets/maskRules-D6QtuKiR.js +1 -1
  37. queryapigate-0.22.0/queryapigate/console_dist/assets/reach-BKjZYR1f.js → queryapigate-0.23.0/queryapigate/console_dist/assets/reach-DMG9gT8F.js +1 -1
  38. queryapigate-0.22.0/queryapigate/console_dist/assets/shared-UtKm-F9c.js → queryapigate-0.23.0/queryapigate/console_dist/assets/shared-CpbV2HSa.js +1 -1
  39. queryapigate-0.22.0/queryapigate/console_dist/assets/useMutation-CzHKbjEU.js → queryapigate-0.23.0/queryapigate/console_dist/assets/useMutation-BFmh_lpo.js +1 -1
  40. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/console_dist/index.html +1 -1
  41. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/engine.py +27 -9
  42. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/events.py +14 -3
  43. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/experimental.py +7 -0
  44. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/exports.py +14 -0
  45. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/logging_setup.py +4 -1
  46. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/mcp_server.py +30 -12
  47. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/openapi.py +49 -19
  48. queryapigate-0.23.0/queryapigate/secheaders.py +56 -0
  49. queryapigate-0.23.0/queryapigate/tracing.py +213 -0
  50. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/v1_spec.py +3 -0
  51. {queryapigate-0.22.0 → queryapigate-0.23.0/queryapigate.egg-info}/PKG-INFO +6 -1
  52. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate.egg-info/SOURCES.txt +36 -32
  53. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate.egg-info/requires.txt +5 -0
  54. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_events_server.py +26 -0
  55. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_response_cache.py +5 -1
  56. queryapigate-0.23.0/tests/test_security_headers.py +180 -0
  57. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_settings.py +1 -1
  58. queryapigate-0.23.0/tests/test_tracing.py +235 -0
  59. {queryapigate-0.22.0 → queryapigate-0.23.0}/LICENSE +0 -0
  60. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/__main__.py +0 -0
  61. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/adminroles.py +0 -0
  62. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/admins.py +0 -0
  63. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/agentwrites.py +0 -0
  64. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/alerts.py +0 -0
  65. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/apikeys.py +0 -0
  66. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/broadcast.py +0 -0
  67. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/bundle.py +0 -0
  68. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/cache.py +0 -0
  69. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/collection_admin.py +0 -0
  70. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/console.py +0 -0
  71. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/console_dist/assets/index-CBaM_ckR.css +0 -0
  72. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/console_dist/assets/state-O13cl7cO.js +0 -0
  73. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/console_dist/favicon.svg +0 -0
  74. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/cors.py +0 -0
  75. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/databases.py +0 -0
  76. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/db.py +0 -0
  77. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/definitions.py +0 -0
  78. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/deprecations.py +0 -0
  79. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/descriptions.py +0 -0
  80. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/destinations.py +0 -0
  81. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/duckfiles.py +0 -0
  82. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/errors.py +0 -0
  83. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/examples.py +0 -0
  84. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/exporting.py +0 -0
  85. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/formats.py +0 -0
  86. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/governance.py +0 -0
  87. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/history.py +0 -0
  88. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/instances.py +0 -0
  89. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/jwtauth.py +0 -0
  90. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/lib/h2-2.2.224.jar +0 -0
  91. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/masking.py +0 -0
  92. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/metrics.py +0 -0
  93. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/mongotools.py +0 -0
  94. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/oidc.py +0 -0
  95. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/params.py +0 -0
  96. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/parquet.py +0 -0
  97. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/pool.py +0 -0
  98. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/postman.py +0 -0
  99. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/ratelimit.py +0 -0
  100. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/rediscache.py +0 -0
  101. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/rekey.py +0 -0
  102. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/runners.py +0 -0
  103. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/schema.py +0 -0
  104. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/__init__.py +0 -0
  105. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/access.py +0 -0
  106. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/administrators.py +0 -0
  107. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/audit.py +0 -0
  108. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/collections.py +0 -0
  109. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/connections.py +0 -0
  110. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/descriptions.py +0 -0
  111. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/destinations.py +0 -0
  112. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/exports.py +0 -0
  113. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/mcp.py +0 -0
  114. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/queries.py +0 -0
  115. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/sqlflow.py +0 -0
  116. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/sqltools.py +0 -0
  117. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/sso.py +0 -0
  118. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/store.py +0 -0
  119. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/tableguard.py +0 -0
  120. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/usage.py +0 -0
  121. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/v1.py +0 -0
  122. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate.egg-info/dependency_links.txt +0 -0
  123. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate.egg-info/entry_points.txt +0 -0
  124. {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate.egg-info/top_level.txt +0 -0
  125. {queryapigate-0.22.0 → queryapigate-0.23.0}/setup.cfg +0 -0
  126. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_adhoc_history.py +0 -0
  127. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_adminroles.py +0 -0
  128. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_admins.py +0 -0
  129. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_agent_writes.py +0 -0
  130. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_alerts.py +0 -0
  131. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_api_administrators.py +0 -0
  132. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_api_keys.py +0 -0
  133. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_api_v1.py +0 -0
  134. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_audit_log.py +0 -0
  135. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_backup_restore.py +0 -0
  136. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_broadcast.py +0 -0
  137. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_cli.py +0 -0
  138. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_collection_bundle.py +0 -0
  139. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_collection_postman.py +0 -0
  140. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_collections.py +0 -0
  141. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_connection_encryption.py +0 -0
  142. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_console.py +0 -0
  143. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_cors_and_rate_limit.py +0 -0
  144. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_databases.py +0 -0
  145. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_db.py +0 -0
  146. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_dependency_floors.py +0 -0
  147. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_deprecations.py +0 -0
  148. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_descriptions.py +0 -0
  149. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_destinations.py +0 -0
  150. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_duckdb.py +0 -0
  151. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_duckdb_files.py +0 -0
  152. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_errors.py +0 -0
  153. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_events.py +0 -0
  154. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_examples.py +0 -0
  155. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_experimental.py +0 -0
  156. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_export_security.py +0 -0
  157. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_exporting.py +0 -0
  158. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_exports.py +0 -0
  159. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_history.py +0 -0
  160. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_howto.py +0 -0
  161. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_instances.py +0 -0
  162. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_integration.py +0 -0
  163. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_jdbc.py +0 -0
  164. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_jwt.py +0 -0
  165. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_key_rotation.py +0 -0
  166. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_masking.py +0 -0
  167. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_masking_requests.py +0 -0
  168. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_mcp.py +0 -0
  169. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_mcp_keycloak.py +0 -0
  170. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_mcp_oauth.py +0 -0
  171. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_mongotools.py +0 -0
  172. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_multinode.py +0 -0
  173. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_observability.py +0 -0
  174. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_oidc.py +0 -0
  175. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_params.py +0 -0
  176. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_parquet.py +0 -0
  177. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_pool.py +0 -0
  178. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_publishing.py +0 -0
  179. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_query_window.py +0 -0
  180. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_queryapigate.py +0 -0
  181. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_rekey.py +0 -0
  182. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_release_check.py +0 -0
  183. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_roles.py +0 -0
  184. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_rolling_upgrade.py +0 -0
  185. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_shared_rate_limit.py +0 -0
  186. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_sql_guard_fuzz.py +0 -0
  187. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_sql_guard_writes.py +0 -0
  188. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_sqlflow.py +0 -0
  189. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_sso.py +0 -0
  190. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_sso_keycloak.py +0 -0
  191. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_tableguard.py +0 -0
  192. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_upgrades.py +0 -0
  193. {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_usage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: queryapigate
3
- Version: 0.22.0
3
+ Version: 0.23.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
@@ -53,6 +53,9 @@ Provides-Extra: jwt
53
53
  Requires-Dist: PyJWT[crypto]>=2.14; extra == "jwt"
54
54
  Provides-Extra: all
55
55
  Requires-Dist: queryapigate[clickhouse,duckdb,encryption,flow,h2,jwt,mongo,mysql,postgres]; extra == "all"
56
+ Provides-Extra: otel
57
+ Requires-Dist: opentelemetry-sdk>=1.27; extra == "otel"
58
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27; extra == "otel"
56
59
  Provides-Extra: server
57
60
  Requires-Dist: gunicorn>=21; extra == "server"
58
61
  Provides-Extra: docs
@@ -60,6 +63,7 @@ Requires-Dist: mkdocs-material<10,>=9; extra == "docs"
60
63
  Provides-Extra: dev
61
64
  Requires-Dist: queryapigate[all]; extra == "dev"
62
65
  Requires-Dist: queryapigate[redis]; extra == "dev"
66
+ Requires-Dist: queryapigate[otel]; extra == "dev"
63
67
  Requires-Dist: queryapigate[mcp]; python_version >= "3.10" and extra == "dev"
64
68
  Requires-Dist: ruff>=0.5; extra == "dev"
65
69
  Requires-Dist: openapi-spec-validator>=0.7; extra == "dev"
@@ -659,6 +663,7 @@ Everything is configured through environment variables (all optional):
659
663
  | `QUERYAPIGATE_CORS_ORIGINS` | unset | Websites allowed to call the API from a browser: comma-separated origins such as `https://app.example.com`, or `*`. Off by default. |
660
664
  | `QUERYAPIGATE_RATE_LIMIT` | unset | Requests allowed per client address, e.g. `60/minute` (also `second`, `hour`, `day`). Off by default; a malformed value stops startup. |
661
665
  | `QUERYAPIGATE_TRUST_PROXY` | `0` | Number of reverse proxies in front of the app whose `X-Forwarded-*` headers are trusted. Set it (usually `1`) behind nginx, a load balancer or a platform router, or every client looks like the proxy. |
666
+ | `QUERYAPIGATE_HSTS_MAX_AGE` | *(unset)* | Seconds browsers should use only HTTPS for this host (`Strict-Transport-Security`), sent only on requests that arrived over HTTPS. Unset: not sent - set it once every client reaches the server over HTTPS. |
662
667
  | `QUERYAPIGATE_POOL_SIZE` | `5` | Idle connections kept per distinct connection setting. `0` turns pooling off. |
663
668
  | `QUERYAPIGATE_POOL_IDLE_TIMEOUT` | `300` | Seconds an idle pooled connection is kept before it is closed. |
664
669
  | `QUERYAPIGATE_QUERY_TIMEOUT` | `30` | Seconds a query may run before it is cancelled (HTTP 504). `0` disables the limit. A request can lower it with `?timeout=`, never raise it. |
@@ -589,6 +589,7 @@ Everything is configured through environment variables (all optional):
589
589
  | `QUERYAPIGATE_CORS_ORIGINS` | unset | Websites allowed to call the API from a browser: comma-separated origins such as `https://app.example.com`, or `*`. Off by default. |
590
590
  | `QUERYAPIGATE_RATE_LIMIT` | unset | Requests allowed per client address, e.g. `60/minute` (also `second`, `hour`, `day`). Off by default; a malformed value stops startup. |
591
591
  | `QUERYAPIGATE_TRUST_PROXY` | `0` | Number of reverse proxies in front of the app whose `X-Forwarded-*` headers are trusted. Set it (usually `1`) behind nginx, a load balancer or a platform router, or every client looks like the proxy. |
592
+ | `QUERYAPIGATE_HSTS_MAX_AGE` | *(unset)* | Seconds browsers should use only HTTPS for this host (`Strict-Transport-Security`), sent only on requests that arrived over HTTPS. Unset: not sent - set it once every client reaches the server over HTTPS. |
592
593
  | `QUERYAPIGATE_POOL_SIZE` | `5` | Idle connections kept per distinct connection setting. `0` turns pooling off. |
593
594
  | `QUERYAPIGATE_POOL_IDLE_TIMEOUT` | `300` | Seconds an idle pooled connection is kept before it is closed. |
594
595
  | `QUERYAPIGATE_QUERY_TIMEOUT` | `30` | Seconds a query may run before it is cancelled (HTTP 504). `0` disables the limit. A request can lower it with `?timeout=`, never raise it. |
@@ -45,9 +45,12 @@ jwt = ["PyJWT[crypto]>=2.14"] # only needed when QUERYAPIGATE_JWT_SECRET or QUE
45
45
  # 2.14 is the floor because that is where PyJWKClient stops refetching the JWKS for
46
46
  # every unknown key id: before it, forged tokens could make the server hammer the provider.
47
47
  all = ["queryapigate[mysql,postgres,clickhouse,h2,duckdb,mongo,flow,encryption,jwt]"]
48
+ # only needed when an OTLP endpoint is set (OTEL_EXPORTER_OTLP_ENDPOINT) - see tracing.py
49
+ otel = ["opentelemetry-sdk>=1.27", "opentelemetry-exporter-otlp-proto-http>=1.27"]
48
50
  server = ["gunicorn>=21"]
49
51
  docs = ["mkdocs-material>=9,<10"]
50
- dev = ["queryapigate[all]", "queryapigate[redis]", "queryapigate[mcp]; python_version>='3.10'", "ruff>=0.5",
52
+ dev = ["queryapigate[all]", "queryapigate[redis]", "queryapigate[otel]", "queryapigate[mcp]; python_version>='3.10'",
53
+ "ruff>=0.5",
51
54
  "openapi-spec-validator>=0.7", "hypothesis>=6.100", "mypy>=1.10", "coverage>=7.4"]
52
55
 
53
56
  [project.urls]
@@ -1,5 +1,5 @@
1
1
  """QueryAPIGate - expose SQL databases as a REST API."""
2
- __version__ = '0.22.0'
2
+ __version__ = '0.23.0'
3
3
 
4
4
  from .app import create_app # noqa: E402 (app imports __version__)
5
5
 
@@ -41,9 +41,11 @@ from . import (
41
41
  openapi,
42
42
  ratelimit,
43
43
  schema,
44
+ secheaders,
44
45
  sqltools,
45
46
  sso,
46
47
  store,
48
+ tracing,
47
49
  )
48
50
  from . import params as param_rules
49
51
  from .errors import ApiError, code_for
@@ -215,11 +217,21 @@ def create_app():
215
217
  log.exception('Unhandled error')
216
218
  return jsonify(error_body('An error occurred', 500)), 500
217
219
 
220
+ @app.teardown_request
221
+ def end_trace(_error):
222
+ tracing.finish(g.pop('trace', None))
223
+
218
224
  @app.before_request
219
225
  def gate():
220
226
  # Set first so every request - including one rejected below - gets an ID and a measured duration.
221
227
  g.request_id = new_request_id(request.headers.get('X-Request-Id'))
222
228
  g.request_started = time.monotonic()
229
+ # The request's span (tracing.py, ADR 0010): named by the route rule, never the path's values or query string
230
+ rule = request.url_rule.rule if request.url_rule is not None else None
231
+ g.trace = tracing.start(f'{request.method} {rule}' if rule else request.method, 'server', {
232
+ 'http.request.method': request.method, 'http.route': rule, 'url.scheme': request.scheme,
233
+ 'client.address': request.remote_addr, 'queryapigate.request_id': g.request_id},
234
+ parent=tracing.extract(request.headers))
223
235
  instances.heartbeat() # at most one small write per HEARTBEAT seconds
224
236
  # Order matters: a browser's preflight cannot carry the API key, and rate limiting comes before the key
225
237
  # check so that guessing keys is throttled too.
@@ -250,6 +262,15 @@ def create_app():
250
262
  response.headers['X-RateLimit-Key-Limit'] = str(limit)
251
263
  response.headers['X-RateLimit-Key-Remaining'] = str(remaining)
252
264
  response.headers['X-Request-Id'] = g.get('request_id', '-')
265
+ secheaders.apply(response, request.is_secure)
266
+ if g.get('trace') is not None:
267
+ span = g.trace[0]
268
+ tracing.set_attributes(span, http__response__status_code=response.status_code,
269
+ queryapigate__caller=caller_key_name())
270
+ if response.status_code >= 400:
271
+ span.set_attribute('error.type', g.get('error_code') or str(response.status_code))
272
+ if response.status_code >= 500:
273
+ tracing.mark_error(span, g.get('error_code') or str(response.status_code))
253
274
  rule = request.url_rule.rule if request.url_rule is not None else None
254
275
  if rule in deprecations.ROUTES: # on its way out (deprecations.py): say so, and name its successor
255
276
  response.headers['Deprecation'] = deprecations.header_value(rule)
@@ -283,7 +304,10 @@ def error_body(message, status, extra=None, code=None):
283
304
  """The JSON body of every error response (errors.py, BACKLOG #69): the message, a stable `code` (the raiser's
284
305
  own, or the one for the status), whatever extras the raiser attached, and the `request_id` that finds the
285
306
  matching log line."""
286
- return {'error': message, 'code': code_for(status, code), **(extra or {}), 'request_id': g.get('request_id', '-')}
307
+ g.error_code = code_for(status, code)
308
+ trace_id = tracing.ids()[0]
309
+ return {'error': message, 'code': g.error_code, **(extra or {}), 'request_id': g.get('request_id', '-'),
310
+ **({'trace_id': trace_id} if trace_id else {})}
287
311
 
288
312
 
289
313
  # --------------------------------------------------------------------------------------
@@ -420,7 +444,9 @@ def render(result, output_format, page, page_size):
420
444
  metrics.observe_serialization(). Streaming responses never go through here; there's no equivalent
421
445
  single serialization span to measure for those (see that function's docstring)."""
422
446
  started = time.monotonic()
423
- response = jsonify({'message': 'No results returned'}) if not result else FORMATTERS[output_format](result)
447
+ with tracing.span(f'serialize {output_format}', attributes={'queryapigate.format': output_format}) as span:
448
+ response = jsonify({'message': 'No results returned'}) if not result else FORMATTERS[output_format](result)
449
+ tracing.set_attributes(span, queryapigate__bytes=response.content_length)
424
450
  elapsed = time.monotonic() - started
425
451
  metrics.observe_serialization(output_format, elapsed)
426
452
  g.serialization_ms = round(elapsed * 1000, 1)
@@ -479,8 +505,10 @@ def masked_entry(fired):
479
505
 
480
506
  def run_entry(connection_name):
481
507
  """The start of one run's history entry: when, where, by whom, through which front door."""
508
+ trace_id = tracing.ids()[0]
482
509
  return {'executed_at': store.now(), 'connection_name': connection_name, 'request_id': g.get('request_id'),
483
- 'key_name': caller_key_name(), 'transport': g.get('transport', 'rest')}
510
+ 'key_name': caller_key_name(), 'transport': g.get('transport', 'rest'),
511
+ **({'trace_id': trace_id} if trace_id else {})}
484
512
 
485
513
 
486
514
  def _record_adhoc_and_broadcast(sql, params, connection_name, entry, broadcaster=None):
@@ -607,6 +635,14 @@ def execute_mongo_endpoint():
607
635
  return render(result, output_format, page, limit)
608
636
 
609
637
 
638
+ def _client_cache_control(ttl):
639
+ """How long the *caller* may keep a cached response - `private`, so only the caller's own client may: a request
640
+ authenticates with X-API-Key, not Authorization, so HTTP's rule keeping authenticated responses out of shared
641
+ caches (RFC 9111 3.5) doesn't apply, and a CDN or caching proxy in front would otherwise keep one caller's rows -
642
+ masked for its key - and serve them to the next caller of that URL, with any key or none."""
643
+ return f'private, max-age={ttl}'
644
+
645
+
610
646
  def cache_lookup(cache_key, ttl):
611
647
  """A live entry rendered as a response (304 if the client already has it), or None on a cache miss."""
612
648
  hit = current_app.extensions['queryapigate_cache'].get(cache_key)
@@ -621,7 +657,7 @@ def cache_lookup(cache_key, ttl):
621
657
  for name, value in headers:
622
658
  response.headers[name] = value
623
659
  response.headers['ETag'] = quoted
624
- response.headers['Cache-Control'] = f'max-age={ttl}'
660
+ response.headers['Cache-Control'] = _client_cache_control(ttl)
625
661
  response.headers['X-Cache'] = 'HIT'
626
662
  return response
627
663
 
@@ -635,7 +671,7 @@ def cache_store(cache_key, response, ttl, meta=None):
635
671
  etag = current_app.extensions['queryapigate_cache'].set(cache_key, response.get_data(), response.content_type,
636
672
  replay_headers, ttl, meta)
637
673
  response.headers['ETag'] = f'"{etag}"'
638
- response.headers['Cache-Control'] = f'max-age={ttl}'
674
+ response.headers['Cache-Control'] = _client_cache_control(ttl)
639
675
  response.headers['X-Cache'] = 'MISS'
640
676
  return response
641
677
 
@@ -678,6 +714,13 @@ def bind_claims(saved, raw):
678
714
 
679
715
 
680
716
  def run_saved(ref, body, url_params):
717
+ """_run_saved(), in its own span (tracing.py): named once the query is found, never by what the caller sent."""
718
+ with tracing.span('query') as span:
719
+ g.query_span = span
720
+ return _run_saved(ref, body, url_params)
721
+
722
+
723
+ def _run_saved(ref, body, url_params):
681
724
  """Execute a saved query (its published version unless one is requested) and record the run - or, for one
682
725
  with a cache_ttl whose SQL is read-only, serve a cached response instead.
683
726
 
@@ -695,6 +738,11 @@ def run_saved(ref, body, url_params):
695
738
  if not connection_name:
696
739
  raise ApiError('Connection name is missing', code='connection_required')
697
740
  query_name = store.query_name(path)
741
+ query_span = g.get('query_span')
742
+ if query_span is not None:
743
+ query_span.update_name(f'query {query_name}')
744
+ tracing.set_attributes(query_span, queryapigate__query=query_name, queryapigate__version=number,
745
+ queryapigate__connection=connection_name)
698
746
  # A key's `queries` and `collections` grants are additive on top of `connections` (see apikeys.py's module
699
747
  # docstring): either can reach this exact saved query without any connection access - but only on the
700
748
  # connection the query itself names. Letting a caller choose another with ?connection_name= would turn
@@ -762,6 +810,7 @@ def run_saved(ref, body, url_params):
762
810
  cache_meta = {'name': path, 'version': number, 'connection': connection_name,
763
811
  'format': output_format, 'page': page}
764
812
  cached = cache_lookup(cache_key, ttl)
813
+ tracing.set_attributes(query_span, queryapigate__cache='hit' if cached is not None else 'miss')
765
814
  if cached is not None:
766
815
  return cached
767
816
 
@@ -775,6 +824,7 @@ def run_saved(ref, body, url_params):
775
824
  _record_and_broadcast(path, number, connection_name,
776
825
  {**entry, 'status': 'error', 'error': error.message, 'code': error.code})
777
826
  raise
827
+ tracing.set_attributes(query_span, queryapigate__rows=len(result.rows), queryapigate__masked=bool(result.masked))
778
828
  response = render(result, output_format, page, limit) # sets g.serialization_ms - see render()
779
829
  _record_and_broadcast(path, number, connection_name, {**entry, 'status': 'success', 'rows': len(result.rows),
780
830
  'duration_ms': elapsed_ms,
@@ -792,6 +842,7 @@ def run_agent_write(saved, path, number, name, connection_name, values, sql, tim
792
842
  from . import agentwrites
793
843
  args = g.get('agent_write_args') or {}
794
844
  caller = caller_key_name()
845
+ tracing.set_attributes(g.get('query_span'), queryapigate__agent_write=saved['agent_writes'])
795
846
  if saved['agent_writes'] == 'insert':
796
847
  hashed = None
797
848
  if args.get('idempotency_key') is not None:
@@ -1113,7 +1164,10 @@ def catalog():
1113
1164
 
1114
1165
  @bp.route('/docs', methods=['GET'])
1115
1166
  def docs():
1116
- return Response(openapi.DOCS_HTML, mimetype='text/html')
1167
+ response = Response(openapi.DOCS_HTML, mimetype='text/html')
1168
+ response.headers['Content-Security-Policy'] = openapi.DOCS_CSP
1169
+ response.headers['Referrer-Policy'] = 'same-origin'
1170
+ return response
1117
1171
 
1118
1172
 
1119
1173
  @bp.route('/ui', methods=['GET'])
@@ -41,7 +41,7 @@ def _serve(args):
41
41
  'Listening on %s without QUERYAPIGATE_API_KEY set: anyone who can reach this port can run SQL '
42
42
  'on your active connections.', args.host)
43
43
  logging.getLogger('queryapigate').info('Using %s (metadata: %s)', config.home(), db.describe())
44
- app.run(host=args.host, port=args.port, debug=args.debug)
44
+ app.run(host=args.host, port=args.port, debug=args.debug, request_handler=_handler())
45
45
  return 0
46
46
 
47
47
 
@@ -160,6 +160,14 @@ def _resolve_export_path(template, name):
160
160
 
161
161
 
162
162
  def _export(args):
163
+ """_export_run(), in a span (tracing.py) - continuing TRACEPARENT, when the scheduler running it sets one."""
164
+ from . import tracing
165
+ with tracing.span(f'export {args.query}', attributes={'queryapigate.query': args.query},
166
+ parent=tracing.from_environment()):
167
+ return _export_run(args)
168
+
169
+
170
+ def _export_run(args):
163
171
  """``queryapigate export <query> --format csv --out /path/{date}.csv`` - the last small step a
164
172
  cron/systemd/Kubernetes CronJob needs to turn the existing ?stream=true export into a scheduled file
165
173
  drop, without becoming a scheduler itself (see BACKLOG.md #31 for why that stays out of scope). Runs
@@ -520,6 +528,17 @@ def _describe_keys(found):
520
528
  return f"{found['values']} encrypted password(s), {keys} key(s) listed: " + ', '.join(parts) + '.'
521
529
 
522
530
 
531
+ def _handler():
532
+ """Werkzeug's request handler, without its version banner (`Server: Werkzeug/3.x Python/3.12`, ADR 0010)."""
533
+ from werkzeug.serving import WSGIRequestHandler
534
+
535
+ class Handler(WSGIRequestHandler):
536
+ def version_string(self):
537
+ return 'QueryAPIGate'
538
+
539
+ return Handler
540
+
541
+
523
542
  def _rekey(args):
524
543
  from . import rekey
525
544
  try:
@@ -282,6 +282,17 @@ def proxy_hops():
282
282
  return 0
283
283
 
284
284
 
285
+ MAX_HSTS_SECONDS = 2 * 365 * 24 * 3600
286
+
287
+
288
+ def hsts_max_age():
289
+ """QUERYAPIGATE_HSTS_MAX_AGE: seconds browsers should insist on HTTPS for this host (Strict-Transport-Security,
290
+ ADR 0010), or None - the default, so a server without HTTPS never locks browsers out. Sent only on requests that
291
+ arrived over HTTPS (secheaders.py). Validated at startup by check_settings()."""
292
+ raw = os.environ.get('QUERYAPIGATE_HSTS_MAX_AGE', '').strip()
293
+ return int(raw) if raw.isdigit() and int(raw) > 0 else None
294
+
295
+
285
296
  _SERVICE_LINK_RE = re.compile(r'^tcp://[^:/]+:\d+$')
286
297
  PORT_SETTINGS = ('QUERYAPIGATE_PORT', 'QUERYAPIGATE_EVENTS_PORT', 'QUERYAPIGATE_MCP_PORT')
287
298
 
@@ -384,6 +395,12 @@ def check_settings():
384
395
  if raw and (not raw.isdigit() or int(raw) < 1):
385
396
  raise ValueError('QUERYAPIGATE_MCP_MAX_ROWS must be a positive integer')
386
397
  check_secret_keys()
398
+ from . import tracing
399
+ tracing.check()
400
+ raw = os.environ.get('QUERYAPIGATE_HSTS_MAX_AGE', '').strip()
401
+ if raw and not (raw.isdigit() and 0 < int(raw) <= MAX_HSTS_SECONDS):
402
+ raise ValueError('QUERYAPIGATE_HSTS_MAX_AGE must be a whole number of seconds from 1 to '
403
+ f'{MAX_HSTS_SECONDS} (two years) - 31536000 is a year')
387
404
  check_database_url()
388
405
  check_jwt_settings()
389
406
  check_mcp_oauth_settings()
@@ -993,7 +1010,10 @@ def describe_settings():
993
1010
  row('Export folders', 'Local folders a destination may write under. Unset: object storage only.',
994
1011
  'QUERYAPIGATE_EXPORT_DIRS', ', '.join(export_dirs()) or 'object storage only'),
995
1012
  row('Trusted proxy hops', 'Reverse proxies whose X-Forwarded-* headers are trusted.',
996
- 'QUERYAPIGATE_TRUST_PROXY', str(proxy_hops()))]},
1013
+ 'QUERYAPIGATE_TRUST_PROXY', str(proxy_hops())),
1014
+ row('HTTPS only (HSTS)', 'Seconds browsers should use only HTTPS for this host; sent only on requests '
1015
+ 'that arrived over HTTPS. Unset: not sent.', 'QUERYAPIGATE_HSTS_MAX_AGE',
1016
+ f'{hsts_max_age()} seconds' if hsts_max_age() else 'off')]},
997
1017
  {'id': 'jwt', 'title': 'Signed-in users (JWT)',
998
1018
  'description': 'Accept `Authorization: Bearer` tokens from your own login or identity provider, so each '
999
1019
  'app user calls the API as themselves - no API key per user. Off unless a secret or JWKS URL is set.',
@@ -1037,6 +1057,20 @@ def describe_settings():
1037
1057
  row('CORS origins', 'Comma-separated origins, or * for any. Unset turns CORS off.',
1038
1058
  'QUERYAPIGATE_CORS_ORIGINS',
1039
1059
  'off' if cors is None else '*' if cors == '*' else ', '.join(sorted(cors)))]},
1060
+ {'id': 'tracing', 'title': 'Tracing (OpenTelemetry)',
1061
+ 'description': 'Spans to your tracing backend through an OpenTelemetry collector - experimental. On when an '
1062
+ 'OTLP endpoint is set, with `pip install "queryapigate[otel]"`; every other OTEL_* variable works as '
1063
+ 'for any service. Spans never hold parameter values, rows, statements or credentials.', 'rows': [
1064
+ row('OTLP endpoint', 'Where spans are sent (OTLP over HTTP). Unset: no tracing.',
1065
+ 'OTEL_EXPORTER_OTLP_ENDPOINT', redact_url(os.environ['OTEL_EXPORTER_OTLP_ENDPOINT'])
1066
+ if os.environ.get('OTEL_EXPORTER_OTLP_ENDPOINT', '').strip() else 'off'),
1067
+ row('OTLP traces endpoint', 'The same, for traces only - wins over the one above.',
1068
+ 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT', redact_url(os.environ['OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'])
1069
+ if os.environ.get('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT', '').strip() else 'not set'),
1070
+ row('Service name', "The name spans are filed under.", 'OTEL_SERVICE_NAME',
1071
+ os.environ.get('OTEL_SERVICE_NAME', '').strip() or 'queryapigate'),
1072
+ row('Sampler', "Which traces are kept: the SDK's default follows the caller's decision.",
1073
+ 'OTEL_TRACES_SAMPLER', os.environ.get('OTEL_TRACES_SAMPLER', '').strip() or 'parentbased_always_on')]},
1040
1074
  {'id': 'audit', 'title': 'Audit & logging',
1041
1075
  'description': 'Retention of administrative actions and log format.', 'rows': [
1042
1076
  row('Audit log limit', 'Entries kept in queryapigate.db.', 'QUERYAPIGATE_AUDIT_LOG_LIMIT',
@@ -1,3 +1,3 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/forms-BCfnOm9v.js","assets/index-k1HFugKb.js","assets/index-CBaM_ckR.css","assets/SchemaBrowser-7JFHE2g9.js","assets/useMutation-CzHKbjEU.js","assets/reach-BKjZYR1f.js","assets/CodeBox-DPiCVuYm.js"])))=>i.map(i=>d[i]);
2
- import{B as e,E as t,F as n,I as r,P as i,Q as a,R as o,S as s,V as c,X as l,Z as u,m as d,o as f,s as p,u as m}from"./index-k1HFugKb.js";import{a as h}from"./reach-BKjZYR1f.js";import{t as g}from"./Time-GG1SZb8S.js";import{f as _,i as v,n as y,r as b,u as x}from"./SchemaBrowser-7JFHE2g9.js";import{t as S}from"./CodeBox-DPiCVuYm.js";var C=a(u(),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:a}){let l=o(),{closeDrawer:u,toast:d}=r(),f=c({queryKey:[`queries`,`detail`,e,`info`],queryFn:async()=>t(await s.GET(`/api/v1/queries/{name}`,{params:{path:{name:e}}})),retry:!1}),p=f.data,m=p?.versions[p.versions.length-1],h=m?.query_type===`mongo`?JSON.stringify(m.mongo??{},null,2):m?.sql??``,_=c({queryKey:[`queries`,`flow`,e,m?.version],queryFn:()=>v(e,m.version),enabled:!(!m||m.query_type!==`sql`||h.includes(`
3
- `)),staleTime:1/0});if(f.isPending)return(0,w.jsx)(i,{});if(!p||!m)return(0,w.jsx)(`div`,{className:`hint`,children:`Could not load the SQL.`});let y=_.data?.formatted||h,b=p.created_at,x=p.last_used_at;return(0,w.jsxs)(`div`,{className:`form`,children:[m.description?(0,w.jsx)(`p`,{className:`d-desc`,children:m.description}):null,(0,w.jsxs)(`dl`,{className:`meta`,children:[(0,w.jsx)(T,{term:`connection`,children:m.connection_name||`—`}),(0,w.jsx)(T,{term:`database`,children:a?(0,w.jsx)(`span`,{className:`tag`,children:a}):(0,w.jsx)(`span`,{className:`dim`,children:`—`})}),(0,w.jsx)(T,{term:`collection`,children:p.collection||`—`}),(0,w.jsx)(T,{term:`status`,children:m.status||`—`}),(0,w.jsx)(T,{term:`author`,children:m.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:m.last_modified_at||m.created_at})}),(0,w.jsx)(T,{term:`last used`,children:(0,w.jsx)(g,{value:x,fallback:`never`})}),m.tags.length?(0,w.jsx)(T,{term:`tags`,children:m.tags.join(`, `)}):null]}),(0,w.jsx)(`p`,{className:`sub-h`,style:{marginTop:6},children:`SQL · v${m.version}${p.versions.length>1?` (latest)`:``}`}),(0,w.jsx)(`div`,{children:_.isFetching?(0,w.jsx)(i,{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:()=>n(y,d),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:[...p.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:u,children:`Close`}),(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:()=>{u(),l(`/queries/${encodeURIComponent(e)}`)},children:`Open in API Repository`})]})]})}var D=(0,C.lazy)(()=>e(()=>import(`./forms-BCfnOm9v.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=o(),{openDrawer:t}=r(),n=f(),a=p(),s=d(),c=m(),[l,u]=(0,C.useState)(x),g=e=>{let t={...l,...e};_(t),u(t)},v=c.data??[],S=e=>v.find(t=>t.name===e)?.db??null,T=y(l.conn||void 0),O=T.data?.tables??[],k=b(l.conn||void 0,S(l.conn),l.table&&!l.tableMatches?[l.table]:[]),A=l.table?l.tableMatches??(k.data?k.data[l.table]??[]:null):null,j=n.data??[],M=[...new Set(j.map(e=>S(e.connection_name)).filter(e=>!!e))].sort(),P=a.data??{},F=s.data??{},I=Object.keys(P).sort(),L=Object.keys(F).sort(),R,z=!0;l.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)(i,{});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=l.q.trim().toLowerCase(),r=[...j].sort((e,t)=>(e.collection||`￿`)+e.name<(t.collection||`￿`)+t.name?-1:1),a=e=>e.toLowerCase().includes(n),o=n?r.filter(e=>a(e.name)||e.collection&&a(e.collection)):r,s=n?I.filter(a):I,c=n?L.filter(a):L,u=o.length||s.length||c.length,d=o.length?o:u?r:[],f=s.length?s:u?I:[],p=c.length?c:u?L:[];if(l.conn&&(d=d.filter(e=>e.connection_name===l.conn)),l.table&&A===null)V=(0,w.jsx)(i,{text:`Checking which queries reference ${l.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}));l.db&&(n=n.filter(e=>e.dbType===l.db)),l.reach===`reachable`?n=n.filter(e=>e.reach>0):l.reach===`unreachable`&&(n=n.filter(e=>e.reach===0));let r=l.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:l,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 r=n?`Nothing matches “${n}”.`:l.table?`“${l.table}” on ${l.conn} is not exposed by any saved query yet.`:l.conn?`No saved query uses “${l.conn}” yet.`:`Nothing matches these filters.`;V=(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`span`,{children:r}),!n&&l.conn?(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:()=>{let n=S(l.conn)===`mongo`,r=l.table?n?JSON.stringify({collection:l.table,filter:{}},null,2):`SELECT * FROM `+l.table:void 0;e(`/queries`),t({title:`New API`,kicker:`POST /api/v1/queries`,content:(0,w.jsx)(C.Suspense,{fallback:(0,w.jsx)(i,{}),children:(0,w.jsx)(D,{starter:{connection:l.conn,sql:r}})})})},children:`New API`+(l.table?` on `+l.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===l.conn)?l.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===l.table)?l.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(l.db)?l.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:l.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:l.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-DYXv3f3c.js","assets/index-BW1KMgAb.js","assets/index-CBaM_ckR.css","assets/SchemaBrowser-Ug8fhNeZ.js","assets/useMutation-BFmh_lpo.js","assets/reach-DMG9gT8F.js","assets/CodeBox-dmGPjinL.js"])))=>i.map(i=>d[i]);
2
+ import{B as e,E as t,F as n,I as r,P as i,Q as a,R as o,S as s,V as c,X as l,Z as u,m as d,o as f,s as p,u as m}from"./index-BW1KMgAb.js";import{a as h}from"./reach-DMG9gT8F.js";import{t as g}from"./Time-DbFAPI8a.js";import{f as _,i as v,n as y,r as b,u as x}from"./SchemaBrowser-Ug8fhNeZ.js";import{t as S}from"./CodeBox-dmGPjinL.js";var C=a(u(),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:a}){let l=o(),{closeDrawer:u,toast:d}=r(),f=c({queryKey:[`queries`,`detail`,e,`info`],queryFn:async()=>t(await s.GET(`/api/v1/queries/{name}`,{params:{path:{name:e}}})),retry:!1}),p=f.data,m=p?.versions[p.versions.length-1],h=m?.query_type===`mongo`?JSON.stringify(m.mongo??{},null,2):m?.sql??``,_=c({queryKey:[`queries`,`flow`,e,m?.version],queryFn:()=>v(e,m.version),enabled:!(!m||m.query_type!==`sql`||h.includes(`
3
+ `)),staleTime:1/0});if(f.isPending)return(0,w.jsx)(i,{});if(!p||!m)return(0,w.jsx)(`div`,{className:`hint`,children:`Could not load the SQL.`});let y=_.data?.formatted||h,b=p.created_at,x=p.last_used_at;return(0,w.jsxs)(`div`,{className:`form`,children:[m.description?(0,w.jsx)(`p`,{className:`d-desc`,children:m.description}):null,(0,w.jsxs)(`dl`,{className:`meta`,children:[(0,w.jsx)(T,{term:`connection`,children:m.connection_name||`—`}),(0,w.jsx)(T,{term:`database`,children:a?(0,w.jsx)(`span`,{className:`tag`,children:a}):(0,w.jsx)(`span`,{className:`dim`,children:`—`})}),(0,w.jsx)(T,{term:`collection`,children:p.collection||`—`}),(0,w.jsx)(T,{term:`status`,children:m.status||`—`}),(0,w.jsx)(T,{term:`author`,children:m.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:m.last_modified_at||m.created_at})}),(0,w.jsx)(T,{term:`last used`,children:(0,w.jsx)(g,{value:x,fallback:`never`})}),m.tags.length?(0,w.jsx)(T,{term:`tags`,children:m.tags.join(`, `)}):null]}),(0,w.jsx)(`p`,{className:`sub-h`,style:{marginTop:6},children:`SQL · v${m.version}${p.versions.length>1?` (latest)`:``}`}),(0,w.jsx)(`div`,{children:_.isFetching?(0,w.jsx)(i,{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:()=>n(y,d),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:[...p.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:u,children:`Close`}),(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:()=>{u(),l(`/queries/${encodeURIComponent(e)}`)},children:`Open in API Repository`})]})]})}var D=(0,C.lazy)(()=>e(()=>import(`./forms-DYXv3f3c.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=o(),{openDrawer:t}=r(),n=f(),a=p(),s=d(),c=m(),[l,u]=(0,C.useState)(x),g=e=>{let t={...l,...e};_(t),u(t)},v=c.data??[],S=e=>v.find(t=>t.name===e)?.db??null,T=y(l.conn||void 0),O=T.data?.tables??[],k=b(l.conn||void 0,S(l.conn),l.table&&!l.tableMatches?[l.table]:[]),A=l.table?l.tableMatches??(k.data?k.data[l.table]??[]:null):null,j=n.data??[],M=[...new Set(j.map(e=>S(e.connection_name)).filter(e=>!!e))].sort(),P=a.data??{},F=s.data??{},I=Object.keys(P).sort(),L=Object.keys(F).sort(),R,z=!0;l.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)(i,{});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=l.q.trim().toLowerCase(),r=[...j].sort((e,t)=>(e.collection||`￿`)+e.name<(t.collection||`￿`)+t.name?-1:1),a=e=>e.toLowerCase().includes(n),o=n?r.filter(e=>a(e.name)||e.collection&&a(e.collection)):r,s=n?I.filter(a):I,c=n?L.filter(a):L,u=o.length||s.length||c.length,d=o.length?o:u?r:[],f=s.length?s:u?I:[],p=c.length?c:u?L:[];if(l.conn&&(d=d.filter(e=>e.connection_name===l.conn)),l.table&&A===null)V=(0,w.jsx)(i,{text:`Checking which queries reference ${l.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}));l.db&&(n=n.filter(e=>e.dbType===l.db)),l.reach===`reachable`?n=n.filter(e=>e.reach>0):l.reach===`unreachable`&&(n=n.filter(e=>e.reach===0));let r=l.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:l,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 r=n?`Nothing matches “${n}”.`:l.table?`“${l.table}” on ${l.conn} is not exposed by any saved query yet.`:l.conn?`No saved query uses “${l.conn}” yet.`:`Nothing matches these filters.`;V=(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`span`,{children:r}),!n&&l.conn?(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:()=>{let n=S(l.conn)===`mongo`,r=l.table?n?JSON.stringify({collection:l.table,filter:{}},null,2):`SELECT * FROM `+l.table:void 0;e(`/queries`),t({title:`New API`,kicker:`POST /api/v1/queries`,content:(0,w.jsx)(C.Suspense,{fallback:(0,w.jsx)(i,{}),children:(0,w.jsx)(D,{starter:{connection:l.conn,sql:r}})})})},children:`New API`+(l.table?` on `+l.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===l.conn)?l.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===l.table)?l.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(l.db)?l.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:l.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:l.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-CzHKbjEU.js";import{D as t,E as n,I as r,M as i,N as a,O as o,P as s,Q as c,S as l,V as u,X as d,Y as f,Z as p,c as m,d as h,k as g,p as _,x as v}from"./index-k1HFugKb.js";import{t as y}from"./Time-GG1SZb8S.js";import{t as b}from"./forms-C8ZdFl7Q.js";import{n as x,t as S}from"./icons-plWox3cc.js";var C=c(p(),1),w=d(),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}=r();return t=>t instanceof v?e(t.message,{status:t.status}):e(t.message)}function j(e){let t=h(`admins.read`,`other administrators`);return u({queryKey:[`administrators`],queryFn:async()=>(await t.check(),n(await l.GET(`/api/v1/administrators`)).items),enabled:e,retry:!1})}function M(e){return u({queryKey:[`administrators`,e,`tokens`],queryFn:async()=>n(await l.GET(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:e}}})).items,retry:!1})}function N(){let e=_(),t=m(),n=!!e.data&&t(`admins.read`),i=j(n),{openDrawer:a}=r(),o=()=>a({title:`New administrator`,content:(0,w.jsx)(I,{})}),c;return c=e.isPending?(0,w.jsx)(s,{}):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?i.isPending?(0,w.jsx)(s,{text:`Loading administrators…`}):i.isError?(0,w.jsx)(`div`,{className:`hint`,children:i.error.message}):i.data.length?(0,w.jsx)(P,{items:i.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:n}){let{openDrawer:i,toast:a}=r(),o=A(),s=f(),c=async e=>{if(window.confirm(`Remove administrator '${e}'? Every token of theirs stops working immediately.`))try{t(await l.DELETE(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),a(`Removed `+e),s.invalidateQueries({queryKey:[`administrators`]})}catch(e){o(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===n&&(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:()=>i({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:()=>i({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=u({queryKey:[`administrators`,`detail`,e],queryFn:async()=>o(await l.GET(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),enabled:!!e,retry:!1,gcTime:0});return e&&t.isPending?(0,w.jsx)(s,{}):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:o,etag:s}){let c=!!t,{closeDrawer:u,openDrawer:d,showError:p,toast:m}=r(),h=A(),_=f(),[v,y]=(0,C.useState)(t??``),[x,S]=(0,C.useState)(o?.role??`developer`),[E,j]=(0,C.useState)(o?.email??``),[M,N]=(0,C.useState)(!o||o.active),P=g(),F=o?.sso?.status===`linked`,[I,L]=(0,C.useState)(o?.sso?.status??`none`),R=e({mutationFn:async()=>{let e={role:x,email:E.trim()||null};if(c)return n(await l.PATCH(`/api/v1/administrators/{name}`,{params:{path:{name:t},header:s?{"If-Match":s}:{}},body:{...F?{email:e.email}:e,active:M,...I===(o?.sso?.status??`none`)?{}:{sso:I===`pending`?`pending`:null}}})),null;let r=n(await l.POST(`/api/v1/administrators`,{body:{name:v.trim(),...e}}));return n(await l.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:r.name}},body:{label:`first token`,expires_at:O(D)}}))},onSuccess:e=>{if(p(``),_.invalidateQueries({queryKey:[`administrators`]}),!e){u(),m(`Updated `+t);return}d({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:h});return(0,w.jsxs)(`form`,{className:`form`,noValidate:!0,onSubmit:e=>{if(e.preventDefault(),!c&&!v.trim()){p(`A name is required.`,{errors:{name:`This field is required`}});return}R.mutate()},children:[(0,w.jsx)(i,{id:`a-name`,label:`Name`,hint:c?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:c,autoFocus:!c,onChange:e=>y(e.target.value)})}),(0,w.jsx)(i,{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)(i,{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)})}),c&&(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`})]}),c&&P.data?.sso&&(0,w.jsx)(i,{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`})]})}),!c&&(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)(a,{onCancel:u,children:(0,w.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:R.isPending,children:c?`Save`:`Create`})})]})}function R({name:a}){let o=M(a),c=f(),{toast:u}=r(),d=A(),[p,m]=(0,C.useState)(``),[h,g]=(0,C.useState)(O(D)),[_,v]=(0,C.useState)(null),x=()=>void c.invalidateQueries({queryKey:[`administrators`]}),S=e({mutationFn:async()=>n(await l.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:a}},body:{label:p.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{t(await l.DELETE(`/api/v1/administrators/{name}/tokens/{token_id}`,{params:{path:{name:a,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:[o.isPending?(0,w.jsx)(s,{text:`Loading tokens…`}):o.isError?(0,w.jsx)(`div`,{className:`hint`,children:o.error.message}):o.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:o.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)(i,{id:`t-label`,label:`New token: label`,hint:`What it's for - laptop, ci.`,children:(0,w.jsx)(`input`,{id:`t-label`,value:p,autoComplete:`off`,onChange:e=>m(e.target.value)})}),(0,w.jsx)(i,{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-BFmh_lpo.js";import{D as t,E as n,I as r,M as i,N as a,O as o,P as s,Q as c,S as l,V as u,X as d,Y as f,Z as p,c as m,d as h,k as g,p as _,x as v}from"./index-BW1KMgAb.js";import{t as y}from"./Time-DbFAPI8a.js";import{t as b}from"./forms-_J9EIFdi.js";import{n as x,t as S}from"./icons-D4JpNqC8.js";var C=c(p(),1),w=d(),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}=r();return t=>t instanceof v?e(t.message,{status:t.status}):e(t.message)}function j(e){let t=h(`admins.read`,`other administrators`);return u({queryKey:[`administrators`],queryFn:async()=>(await t.check(),n(await l.GET(`/api/v1/administrators`)).items),enabled:e,retry:!1})}function M(e){return u({queryKey:[`administrators`,e,`tokens`],queryFn:async()=>n(await l.GET(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:e}}})).items,retry:!1})}function N(){let e=_(),t=m(),n=!!e.data&&t(`admins.read`),i=j(n),{openDrawer:a}=r(),o=()=>a({title:`New administrator`,content:(0,w.jsx)(I,{})}),c;return c=e.isPending?(0,w.jsx)(s,{}):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?i.isPending?(0,w.jsx)(s,{text:`Loading administrators…`}):i.isError?(0,w.jsx)(`div`,{className:`hint`,children:i.error.message}):i.data.length?(0,w.jsx)(P,{items:i.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:n}){let{openDrawer:i,toast:a}=r(),o=A(),s=f(),c=async e=>{if(window.confirm(`Remove administrator '${e}'? Every token of theirs stops working immediately.`))try{t(await l.DELETE(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),a(`Removed `+e),s.invalidateQueries({queryKey:[`administrators`]})}catch(e){o(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===n&&(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:()=>i({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:()=>i({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=u({queryKey:[`administrators`,`detail`,e],queryFn:async()=>o(await l.GET(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),enabled:!!e,retry:!1,gcTime:0});return e&&t.isPending?(0,w.jsx)(s,{}):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:o,etag:s}){let c=!!t,{closeDrawer:u,openDrawer:d,showError:p,toast:m}=r(),h=A(),_=f(),[v,y]=(0,C.useState)(t??``),[x,S]=(0,C.useState)(o?.role??`developer`),[E,j]=(0,C.useState)(o?.email??``),[M,N]=(0,C.useState)(!o||o.active),P=g(),F=o?.sso?.status===`linked`,[I,L]=(0,C.useState)(o?.sso?.status??`none`),R=e({mutationFn:async()=>{let e={role:x,email:E.trim()||null};if(c)return n(await l.PATCH(`/api/v1/administrators/{name}`,{params:{path:{name:t},header:s?{"If-Match":s}:{}},body:{...F?{email:e.email}:e,active:M,...I===(o?.sso?.status??`none`)?{}:{sso:I===`pending`?`pending`:null}}})),null;let r=n(await l.POST(`/api/v1/administrators`,{body:{name:v.trim(),...e}}));return n(await l.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:r.name}},body:{label:`first token`,expires_at:O(D)}}))},onSuccess:e=>{if(p(``),_.invalidateQueries({queryKey:[`administrators`]}),!e){u(),m(`Updated `+t);return}d({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:h});return(0,w.jsxs)(`form`,{className:`form`,noValidate:!0,onSubmit:e=>{if(e.preventDefault(),!c&&!v.trim()){p(`A name is required.`,{errors:{name:`This field is required`}});return}R.mutate()},children:[(0,w.jsx)(i,{id:`a-name`,label:`Name`,hint:c?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:c,autoFocus:!c,onChange:e=>y(e.target.value)})}),(0,w.jsx)(i,{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)(i,{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)})}),c&&(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`})]}),c&&P.data?.sso&&(0,w.jsx)(i,{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`})]})}),!c&&(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)(a,{onCancel:u,children:(0,w.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:R.isPending,children:c?`Save`:`Create`})})]})}function R({name:a}){let o=M(a),c=f(),{toast:u}=r(),d=A(),[p,m]=(0,C.useState)(``),[h,g]=(0,C.useState)(O(D)),[_,v]=(0,C.useState)(null),x=()=>void c.invalidateQueries({queryKey:[`administrators`]}),S=e({mutationFn:async()=>n(await l.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:a}},body:{label:p.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{t(await l.DELETE(`/api/v1/administrators/{name}/tokens/{token_id}`,{params:{path:{name:a,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:[o.isPending?(0,w.jsx)(s,{text:`Loading tokens…`}):o.isError?(0,w.jsx)(`div`,{className:`hint`,children:o.error.message}):o.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:o.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)(i,{id:`t-label`,label:`New token: label`,hint:`What it's for - laptop, ci.`,children:(0,w.jsx)(`input`,{id:`t-label`,value:p,autoComplete:`off`,onChange:e=>m(e.target.value)})}),(0,w.jsx)(i,{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{E as e,P as t,Q as n,S as r,V as i,X as a,Z as o}from"./index-k1HFugKb.js";import{t as s}from"./Time-GG1SZb8S.js";import{t as c}from"./CodeBox-DPiCVuYm.js";import{t as l}from"./Experimental-Cev0t6Ci.js";var u=n(o(),1),d=a(),f=[{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}],p=e=>e.toLocaleString();function m({refusals:e}){let t=Object.entries(e);return t.length?(0,d.jsx)(`span`,{style:{display:`inline-flex`,flexWrap:`wrap`,gap:4},children:t.map(([e,t])=>(0,d.jsx)(`span`,{className:`tag`,title:`${t} call${t===1?``:`s`} failed with ${e}`,children:`${e} × ${t}`},e))}):(0,d.jsx)(`span`,{className:`dim`,children:`none`})}function h({writes:e}){let t=Object.entries(e??{}).filter(([,e])=>e).map(([e,t])=>`${t} ${e}`);return(0,d.jsx)(`div`,{className:`hint`,children:t.length?t.join(` · `):`None.`})}function g({agent:e}){let t=Object.entries(e.queries);return(0,d.jsxs)(`div`,{className:`agent-detail`,children:[(0,d.jsx)(`p`,{className:`sub-h`,children:`Writes asked for`}),(0,d.jsx)(h,{writes:e.writes}),(0,d.jsx)(`p`,{className:`sub-h`,style:{marginTop:12},children:`Saved queries`}),t.length?(0,d.jsx)(`div`,{className:`hint`,children:t.map(([e,t])=>`${e} × ${t}`).join(` · `)}):(0,d.jsx)(`div`,{className:`hint`,children:`None - only ad-hoc SQL.`}),(0,d.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,d.jsxs)(`div`,{className:`agent-statement`,children:[(0,d.jsx)(`div`,{className:`hint`,children:`${e.count} call${e.count===1?``:`s`} · ${p(e.rows)} rows`+(e.errors?` · ${e.errors} failed`:``)}),e.sql?(0,d.jsx)(c,{sql:e.sql}):(0,d.jsx)(`span`,{className:`mono dim`,children:`sha256 ${e.sql_sha256}`})]},t)):(0,d.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 _(){let[n,a]=(0,u.useState)(`24h`),[o,c]=(0,u.useState)(null),h=f.find(e=>e.id===n).hours,_=i({queryKey:[`usage`,`agents`,n],queryFn:async()=>e(await r.GET(`/api/v1/usage/agents`,{params:{query:{hours:h}}})),retry:!1}),v=_.data?.items??[],y;y=_.isPending?(0,d.jsx)(t,{text:`Loading agents…`}):_.isError?(0,d.jsxs)(`div`,{className:`empty`,children:[(0,d.jsx)(`strong`,{children:`Couldn’t load agent usage`}),(0,d.jsx)(`span`,{children:_.error.message})]}):v.length?(0,d.jsx)(`div`,{style:{overflowX:`auto`},children:(0,d.jsxs)(`table`,{className:`grid`,children:[(0,d.jsx)(`thead`,{children:(0,d.jsx)(`tr`,{children:[`Agent`,`Calls`,`Rows`,`Failed`,`Masked`,`Refused or failed with`,`Last seen`].map(e=>(0,d.jsx)(`th`,{children:e},e))})}),(0,d.jsx)(`tbody`,{children:v.map(e=>(0,d.jsxs)(u.Fragment,{children:[(0,d.jsxs)(`tr`,{children:[(0,d.jsxs)(`td`,{children:[(0,d.jsx)(`button`,{type:`button`,className:`agent-name`,"aria-expanded":o===e.agent,onClick:()=>c(o===e.agent?null:e.agent),children:e.agent}),(0,d.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:e.connections.join(`, `)})]}),(0,d.jsx)(`td`,{className:`num`,children:p(e.calls)}),(0,d.jsx)(`td`,{className:`num`,children:p(e.rows)}),(0,d.jsx)(`td`,{className:`num`,children:p(e.errors)}),(0,d.jsx)(`td`,{className:`num`,children:p(e.masked_calls)}),(0,d.jsx)(`td`,{children:(0,d.jsx)(m,{refusals:e.refusals})}),(0,d.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,d.jsx)(s,{value:e.last_seen,fallback:``})})]}),o===e.agent&&(0,d.jsx)(`tr`,{children:(0,d.jsx)(`td`,{colSpan:7,children:(0,d.jsx)(g,{agent:e})})})]},e.agent))})]})}):(0,d.jsxs)(`div`,{className:`empty`,children:[(0,d.jsx)(`strong`,{children:`No agent called the MCP server in this window`}),(0,d.jsx)(`span`,{children:`Runs over MCP - saved-query tools and execute_sql - appear here, per API key or signed-in user.`})]});let b=[];return _.data&&_.data.sample_rate<1&&b.push(`Run history keeps ${Math.round(_.data.sample_rate*100)}% of successful runs, so successful calls and rows are a sample; failures are all here.`),_.data?.truncated&&b.push(`More runs than one summary reads (100,000) - narrow the window.`),(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)(`div`,{className:`page-head`,children:[(0,d.jsxs)(`div`,{className:`titles`,children:[(0,d.jsxs)(`h1`,{children:[`Agents `,(0,d.jsx)(l,{})]}),(0,d.jsx)(`span`,{className:`sub`,children:`What each AI agent did over MCP - per API key or signed-in user, from run history.`})]}),(0,d.jsx)(`span`,{className:`spacer`}),(0,d.jsx)(`button`,{type:`button`,className:`btn`,onClick:()=>void _.refetch(),children:`Refresh`})]}),(0,d.jsxs)(`div`,{className:`panel`,style:{overflow:`hidden`},children:[(0,d.jsxs)(`div`,{className:`panel-bar`,children:[(0,d.jsx)(`select`,{"aria-label":`Window`,value:n,onChange:e=>a(e.target.value),children:f.map(e=>(0,d.jsx)(`option`,{value:e.id,children:e.label},e.id))}),(0,d.jsx)(`span`,{className:`spacer`}),(0,d.jsx)(`span`,{className:`panel-count`,children:_.data?`${v.length} agent${v.length===1?``:`s`}`:``})]}),b.map(e=>(0,d.jsx)(`div`,{className:`hint`,style:{padding:`6px 12px`},children:e},e)),y]})]})}export{_ as AgentsPage};
1
+ import{E as e,P as t,Q as n,S as r,V as i,X as a,Z as o}from"./index-BW1KMgAb.js";import{t as s}from"./Time-DbFAPI8a.js";import{t as c}from"./CodeBox-dmGPjinL.js";import{t as l}from"./Experimental-BZ5eU3RF.js";var u=n(o(),1),d=a(),f=[{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}],p=e=>e.toLocaleString();function m({refusals:e}){let t=Object.entries(e);return t.length?(0,d.jsx)(`span`,{style:{display:`inline-flex`,flexWrap:`wrap`,gap:4},children:t.map(([e,t])=>(0,d.jsx)(`span`,{className:`tag`,title:`${t} call${t===1?``:`s`} failed with ${e}`,children:`${e} × ${t}`},e))}):(0,d.jsx)(`span`,{className:`dim`,children:`none`})}function h({writes:e}){let t=Object.entries(e??{}).filter(([,e])=>e).map(([e,t])=>`${t} ${e}`);return(0,d.jsx)(`div`,{className:`hint`,children:t.length?t.join(` · `):`None.`})}function g({agent:e}){let t=Object.entries(e.queries);return(0,d.jsxs)(`div`,{className:`agent-detail`,children:[(0,d.jsx)(`p`,{className:`sub-h`,children:`Writes asked for`}),(0,d.jsx)(h,{writes:e.writes}),(0,d.jsx)(`p`,{className:`sub-h`,style:{marginTop:12},children:`Saved queries`}),t.length?(0,d.jsx)(`div`,{className:`hint`,children:t.map(([e,t])=>`${e} × ${t}`).join(` · `)}):(0,d.jsx)(`div`,{className:`hint`,children:`None - only ad-hoc SQL.`}),(0,d.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,d.jsxs)(`div`,{className:`agent-statement`,children:[(0,d.jsx)(`div`,{className:`hint`,children:`${e.count} call${e.count===1?``:`s`} · ${p(e.rows)} rows`+(e.errors?` · ${e.errors} failed`:``)}),e.sql?(0,d.jsx)(c,{sql:e.sql}):(0,d.jsx)(`span`,{className:`mono dim`,children:`sha256 ${e.sql_sha256}`})]},t)):(0,d.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 _(){let[n,a]=(0,u.useState)(`24h`),[o,c]=(0,u.useState)(null),h=f.find(e=>e.id===n).hours,_=i({queryKey:[`usage`,`agents`,n],queryFn:async()=>e(await r.GET(`/api/v1/usage/agents`,{params:{query:{hours:h}}})),retry:!1}),v=_.data?.items??[],y;y=_.isPending?(0,d.jsx)(t,{text:`Loading agents…`}):_.isError?(0,d.jsxs)(`div`,{className:`empty`,children:[(0,d.jsx)(`strong`,{children:`Couldn’t load agent usage`}),(0,d.jsx)(`span`,{children:_.error.message})]}):v.length?(0,d.jsx)(`div`,{style:{overflowX:`auto`},children:(0,d.jsxs)(`table`,{className:`grid`,children:[(0,d.jsx)(`thead`,{children:(0,d.jsx)(`tr`,{children:[`Agent`,`Calls`,`Rows`,`Failed`,`Masked`,`Refused or failed with`,`Last seen`].map(e=>(0,d.jsx)(`th`,{children:e},e))})}),(0,d.jsx)(`tbody`,{children:v.map(e=>(0,d.jsxs)(u.Fragment,{children:[(0,d.jsxs)(`tr`,{children:[(0,d.jsxs)(`td`,{children:[(0,d.jsx)(`button`,{type:`button`,className:`agent-name`,"aria-expanded":o===e.agent,onClick:()=>c(o===e.agent?null:e.agent),children:e.agent}),(0,d.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:e.connections.join(`, `)})]}),(0,d.jsx)(`td`,{className:`num`,children:p(e.calls)}),(0,d.jsx)(`td`,{className:`num`,children:p(e.rows)}),(0,d.jsx)(`td`,{className:`num`,children:p(e.errors)}),(0,d.jsx)(`td`,{className:`num`,children:p(e.masked_calls)}),(0,d.jsx)(`td`,{children:(0,d.jsx)(m,{refusals:e.refusals})}),(0,d.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,d.jsx)(s,{value:e.last_seen,fallback:``})})]}),o===e.agent&&(0,d.jsx)(`tr`,{children:(0,d.jsx)(`td`,{colSpan:7,children:(0,d.jsx)(g,{agent:e})})})]},e.agent))})]})}):(0,d.jsxs)(`div`,{className:`empty`,children:[(0,d.jsx)(`strong`,{children:`No agent called the MCP server in this window`}),(0,d.jsx)(`span`,{children:`Runs over MCP - saved-query tools and execute_sql - appear here, per API key or signed-in user.`})]});let b=[];return _.data&&_.data.sample_rate<1&&b.push(`Run history keeps ${Math.round(_.data.sample_rate*100)}% of successful runs, so successful calls and rows are a sample; failures are all here.`),_.data?.truncated&&b.push(`More runs than one summary reads (100,000) - narrow the window.`),(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)(`div`,{className:`page-head`,children:[(0,d.jsxs)(`div`,{className:`titles`,children:[(0,d.jsxs)(`h1`,{children:[`Agents `,(0,d.jsx)(l,{})]}),(0,d.jsx)(`span`,{className:`sub`,children:`What each AI agent did over MCP - per API key or signed-in user, from run history.`})]}),(0,d.jsx)(`span`,{className:`spacer`}),(0,d.jsx)(`button`,{type:`button`,className:`btn`,onClick:()=>void _.refetch(),children:`Refresh`})]}),(0,d.jsxs)(`div`,{className:`panel`,style:{overflow:`hidden`},children:[(0,d.jsxs)(`div`,{className:`panel-bar`,children:[(0,d.jsx)(`select`,{"aria-label":`Window`,value:n,onChange:e=>a(e.target.value),children:f.map(e=>(0,d.jsx)(`option`,{value:e.id,children:e.label},e.id))}),(0,d.jsx)(`span`,{className:`spacer`}),(0,d.jsx)(`span`,{className:`panel-count`,children:_.data?`${v.length} agent${v.length===1?``:`s`}`:``})]}),b.map(e=>(0,d.jsx)(`div`,{className:`hint`,style:{padding:`6px 12px`},children:e},e)),y]})]})}export{_ as AgentsPage};
@@ -1 +1 @@
1
- import{P as e,Q as t,R as n,X as r,Z as i,b as a,g as o,v as s,y as c,z as l}from"./index-k1HFugKb.js";import{t as u}from"./Time-GG1SZb8S.js";import{n as d}from"./state-O13cl7cO.js";var f=t(i(),1),p=r(),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`};function g(){let e=n();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`):(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:()=>c(e.id),children:`Restore`}):(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,title:`Hide it in this browser until it clears`,onClick:()=>s(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 t=n(),r=l(),i=o.find(e=>e.id===r.tab),{query:s,active:c,dismissed:d}=a(),[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=s.isPending?(0,p.jsx)(e,{text:`Checking…`}):s.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:s.error.message}),(0,p.jsx)(`button`,{type:`button`,className:`btn sm`,onClick:()=>void s.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=(e,n,r)=>(0,p.jsxs)(`button`,{type:`button`,role:`tab`,"aria-selected":(i?.id??`all`)===e,className:`minitab`+((i?.id??`all`)===e?` active`:``),onClick:()=>t(e===`all`?`/alerts`:`/alerts/`+e),children:[n,s.data?(0,p.jsx)(`span`,{className:`n`+(r.length?` on`:``)+(r.some(e=>e.severity===`critical`)?` critical`:``),children:r.length}):null]},e);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.`,s.data?(0,p.jsxs)(p.Fragment,{children:[` Last checked `,(0,p.jsx)(u,{value:s.data.checked_at}),`.`]}):null]})]}),(0,p.jsx)(`span`,{className:`spacer`}),(0,p.jsx)(`button`,{id:`refresh-alerts`,type:`button`,className:`btn`,onClick:()=>void s.refetch(),children:`Check now`})]}),(0,p.jsxs)(`div`,{className:`minitabs alerts-tabs`,role:`tablist`,"aria-label":`Checks`,children:[C(`all`,`All`,x()),o.map(e=>C(e.id,e.label,x(e.kinds)))]}),S]})}export{y as AlertsPage};
1
+ import{P as e,Q as t,R as n,X as r,Z as i,b as a,g as o,v as s,y as c,z as l}from"./index-BW1KMgAb.js";import{t as u}from"./Time-DbFAPI8a.js";import{n as d}from"./state-O13cl7cO.js";var f=t(i(),1),p=r(),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`};function g(){let e=n();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`):(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:()=>c(e.id),children:`Restore`}):(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,title:`Hide it in this browser until it clears`,onClick:()=>s(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 t=n(),r=l(),i=o.find(e=>e.id===r.tab),{query:s,active:c,dismissed:d}=a(),[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=s.isPending?(0,p.jsx)(e,{text:`Checking…`}):s.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:s.error.message}),(0,p.jsx)(`button`,{type:`button`,className:`btn sm`,onClick:()=>void s.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=(e,n,r)=>(0,p.jsxs)(`button`,{type:`button`,role:`tab`,"aria-selected":(i?.id??`all`)===e,className:`minitab`+((i?.id??`all`)===e?` active`:``),onClick:()=>t(e===`all`?`/alerts`:`/alerts/`+e),children:[n,s.data?(0,p.jsx)(`span`,{className:`n`+(r.length?` on`:``)+(r.some(e=>e.severity===`critical`)?` critical`:``),children:r.length}):null]},e);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.`,s.data?(0,p.jsxs)(p.Fragment,{children:[` Last checked `,(0,p.jsx)(u,{value:s.data.checked_at}),`.`]}):null]})]}),(0,p.jsx)(`span`,{className:`spacer`}),(0,p.jsx)(`button`,{id:`refresh-alerts`,type:`button`,className:`btn`,onClick:()=>void s.refetch(),children:`Check now`})]}),(0,p.jsxs)(`div`,{className:`minitabs alerts-tabs`,role:`tablist`,"aria-label":`Checks`,children:[C(`all`,`All`,x()),o.map(e=>C(e.id,e.label,x(e.kinds)))]}),S]})}export{y as AlertsPage};
@@ -1 +1 @@
1
- import{P as e,X as t,c as n,l as r,s as i}from"./index-k1HFugKb.js";import{t as a}from"./reach-BKjZYR1f.js";import{t as o}from"./Time-GG1SZb8S.js";import{i as s,n as c,o as l}from"./forms-C8ZdFl7Q.js";import{t as u}from"./icons-plWox3cc.js";var d=t();function f(e,t){let n=new Date(Date.now()+6048e5).toISOString().slice(0,10);return!!(e&&e>=t&&e<=n)}function p(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 m(){let t=n(),m=i(),h=r(),g=c(),_=s(),v=Object.keys(m.data??{}).sort(),y=new Date().toISOString().slice(0,10),b;return b=m.isPending?(0,d.jsx)(e,{text:`Loading API keys…`}):m.isError?(0,d.jsxs)(`div`,{className:`empty`,children:[(0,d.jsx)(`strong`,{children:`Couldn’t load API keys`}),(0,d.jsx)(`span`,{children:m.error.message}),(0,d.jsx)(`button`,{type:`button`,className:`btn sm`,onClick:()=>m.refetch(),children:`Retry`})]}):v.length?(0,d.jsx)(`div`,{style:{overflowX:`auto`},children:(0,d.jsxs)(`table`,{className:`grid`,children:[(0,d.jsx)(`thead`,{children:(0,d.jsx)(`tr`,{children:[`Name`,`Scope`,`Access`,`Rate limit`,`IPs`,`Expires`,`Last used`,`Usage`,``].map((e,t)=>(0,d.jsx)(`th`,{children:e},t))})}),(0,d.jsx)(`tbody`,{children:v.map(e=>{let n=m.data[e],r=!!(n.expires_at&&n.expires_at<y),i=!r&&f(n.expires_at,y),s=n.allowed_ips??[],c=p(n.usage);return(0,d.jsxs)(`tr`,{"data-name":e,children:[(0,d.jsx)(`td`,{children:(0,d.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,d.jsx)(`span`,{className:`dot ${n.active&&!r?`ok`:`off`}`,title:n.active?r?`Expired`:`Active`:`Revoked`}),(0,d.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}),n.previous_expires_at?(0,d.jsx)(`span`,{className:`tag`,title:`Rotated: the old secret works until then too`,children:`rotating`}):null,n.mask?.length?(0,d.jsx)(`span`,{className:`tag`,title:n.mask.map(e=>`${e.column}: ${e.action}`).join(`, `),children:`masked`}):null]})}),(0,d.jsx)(`td`,{children:(0,d.jsx)(l,{entry:n,collections:h.data?.collections})}),(0,d.jsx)(a,{entry:n}),(0,d.jsx)(`td`,{className:n.rate_limit?`mono`:`mono dim`,style:{whiteSpace:`nowrap`},children:n.rate_limit||`server default`}),(0,d.jsx)(`td`,{className:`dim`,children:s.length?(0,d.jsx)(`span`,{title:s.join(`, `),children:s.length+(s.length===1?` IP`:` IPs`)}):(0,d.jsx)(`span`,{className:`dim`,children:`any`})}),(0,d.jsx)(`td`,{style:{whiteSpace:`nowrap`},children:n.expires_at?(0,d.jsx)(`span`,{style:r?{color:`var(--danger)`}:i?{color:`var(--warn)`}:void 0,children:n.expires_at+(r?` (expired)`:i?` (expires soon)`:``)}):(0,d.jsx)(`span`,{className:`dim`,children:`never`})}),(0,d.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,d.jsx)(o,{value:n.last_used_at,fallback:`never`})}),c?(0,d.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,d.jsx)(`td`,{className:`dim`,style:{whiteSpace:`nowrap`},children:`No activity yet`}),(0,d.jsx)(`td`,{children:(0,d.jsxs)(`div`,{className:`actions`,children:[(0,d.jsx)(`button`,{hidden:!t(`access.write`),type:`button`,className:`btn ghost sm icon`,title:`Edit ${e}`,"aria-label":`Edit`,onClick:()=>g.editKey(e),children:(0,d.jsx)(u,{})}),(0,d.jsx)(`button`,{hidden:!t(`access.write`),type:`button`,className:`btn ghost sm danger`,onClick:()=>void _(e),children:`Revoke`})]})})]},e)})})]})}):(0,d.jsxs)(`div`,{className:`empty`,children:[(0,d.jsx)(`strong`,{children:`No scoped API keys yet`}),(0,d.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,d.jsx)(`button`,{hidden:!t(`access.write`),type:`button`,className:`btn primary`,onClick:()=>g.newKey(),children:`New API key`})]}),(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)(`div`,{className:`page-head`,children:[(0,d.jsxs)(`div`,{className:`titles`,children:[(0,d.jsx)(`h1`,{children:`API keys`}),(0,d.jsx)(`span`,{className:`sub`,id:`apikeys-sub`,children:(v.length?v.length+(v.length===1?` key. `:` keys. `):``)+`Scope each one to connections, collections or roles.`})]}),(0,d.jsx)(`span`,{className:`spacer`}),(0,d.jsx)(`button`,{hidden:!t(`access.write`),id:`new-apikey`,type:`button`,className:`btn primary`,onClick:()=>g.newKey(),children:`New API key`})]}),(0,d.jsx)(`div`,{id:`apikeys-table`,className:`panel`,children:b})]})}export{m as ApiKeysPage};
1
+ import{P as e,X as t,c as n,l as r,s as i}from"./index-BW1KMgAb.js";import{t as a}from"./reach-DMG9gT8F.js";import{t as o}from"./Time-DbFAPI8a.js";import{i as s,n as c,o as l}from"./forms-_J9EIFdi.js";import{t as u}from"./icons-D4JpNqC8.js";var d=t();function f(e,t){let n=new Date(Date.now()+6048e5).toISOString().slice(0,10);return!!(e&&e>=t&&e<=n)}function p(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 m(){let t=n(),m=i(),h=r(),g=c(),_=s(),v=Object.keys(m.data??{}).sort(),y=new Date().toISOString().slice(0,10),b;return b=m.isPending?(0,d.jsx)(e,{text:`Loading API keys…`}):m.isError?(0,d.jsxs)(`div`,{className:`empty`,children:[(0,d.jsx)(`strong`,{children:`Couldn’t load API keys`}),(0,d.jsx)(`span`,{children:m.error.message}),(0,d.jsx)(`button`,{type:`button`,className:`btn sm`,onClick:()=>m.refetch(),children:`Retry`})]}):v.length?(0,d.jsx)(`div`,{style:{overflowX:`auto`},children:(0,d.jsxs)(`table`,{className:`grid`,children:[(0,d.jsx)(`thead`,{children:(0,d.jsx)(`tr`,{children:[`Name`,`Scope`,`Access`,`Rate limit`,`IPs`,`Expires`,`Last used`,`Usage`,``].map((e,t)=>(0,d.jsx)(`th`,{children:e},t))})}),(0,d.jsx)(`tbody`,{children:v.map(e=>{let n=m.data[e],r=!!(n.expires_at&&n.expires_at<y),i=!r&&f(n.expires_at,y),s=n.allowed_ips??[],c=p(n.usage);return(0,d.jsxs)(`tr`,{"data-name":e,children:[(0,d.jsx)(`td`,{children:(0,d.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,d.jsx)(`span`,{className:`dot ${n.active&&!r?`ok`:`off`}`,title:n.active?r?`Expired`:`Active`:`Revoked`}),(0,d.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}),n.previous_expires_at?(0,d.jsx)(`span`,{className:`tag`,title:`Rotated: the old secret works until then too`,children:`rotating`}):null,n.mask?.length?(0,d.jsx)(`span`,{className:`tag`,title:n.mask.map(e=>`${e.column}: ${e.action}`).join(`, `),children:`masked`}):null]})}),(0,d.jsx)(`td`,{children:(0,d.jsx)(l,{entry:n,collections:h.data?.collections})}),(0,d.jsx)(a,{entry:n}),(0,d.jsx)(`td`,{className:n.rate_limit?`mono`:`mono dim`,style:{whiteSpace:`nowrap`},children:n.rate_limit||`server default`}),(0,d.jsx)(`td`,{className:`dim`,children:s.length?(0,d.jsx)(`span`,{title:s.join(`, `),children:s.length+(s.length===1?` IP`:` IPs`)}):(0,d.jsx)(`span`,{className:`dim`,children:`any`})}),(0,d.jsx)(`td`,{style:{whiteSpace:`nowrap`},children:n.expires_at?(0,d.jsx)(`span`,{style:r?{color:`var(--danger)`}:i?{color:`var(--warn)`}:void 0,children:n.expires_at+(r?` (expired)`:i?` (expires soon)`:``)}):(0,d.jsx)(`span`,{className:`dim`,children:`never`})}),(0,d.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,d.jsx)(o,{value:n.last_used_at,fallback:`never`})}),c?(0,d.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,d.jsx)(`td`,{className:`dim`,style:{whiteSpace:`nowrap`},children:`No activity yet`}),(0,d.jsx)(`td`,{children:(0,d.jsxs)(`div`,{className:`actions`,children:[(0,d.jsx)(`button`,{hidden:!t(`access.write`),type:`button`,className:`btn ghost sm icon`,title:`Edit ${e}`,"aria-label":`Edit`,onClick:()=>g.editKey(e),children:(0,d.jsx)(u,{})}),(0,d.jsx)(`button`,{hidden:!t(`access.write`),type:`button`,className:`btn ghost sm danger`,onClick:()=>void _(e),children:`Revoke`})]})})]},e)})})]})}):(0,d.jsxs)(`div`,{className:`empty`,children:[(0,d.jsx)(`strong`,{children:`No scoped API keys yet`}),(0,d.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,d.jsx)(`button`,{hidden:!t(`access.write`),type:`button`,className:`btn primary`,onClick:()=>g.newKey(),children:`New API key`})]}),(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)(`div`,{className:`page-head`,children:[(0,d.jsxs)(`div`,{className:`titles`,children:[(0,d.jsx)(`h1`,{children:`API keys`}),(0,d.jsx)(`span`,{className:`sub`,id:`apikeys-sub`,children:(v.length?v.length+(v.length===1?` key. `:` keys. `):``)+`Scope each one to connections, collections or roles.`})]}),(0,d.jsx)(`span`,{className:`spacer`}),(0,d.jsx)(`button`,{hidden:!t(`access.write`),id:`new-apikey`,type:`button`,className:`btn primary`,onClick:()=>g.newKey(),children:`New API key`})]}),(0,d.jsx)(`div`,{id:`apikeys-table`,className:`panel`,children:b})]})}export{m as ApiKeysPage};
@@ -1 +1 @@
1
- import{t as e}from"./useMutation-CzHKbjEU.js";import{E as t,I as n,M as r,P as i,Q as a,R as o,S as s,V as c,X as l,Y as u,Z as d,d as f,x as p,z as m}from"./index-k1HFugKb.js";import{t as h}from"./Time-GG1SZb8S.js";import{t as g}from"./CodeBox-DPiCVuYm.js";import{t as _}from"./Experimental-Cev0t6Ci.js";var v=a(d(),1),y=l(),b={pending:`Waiting`,approved:`Approved - not run yet`,running:`Running`,done:`Done`,failed:`Failed`,rejected:`Rejected`,expired:`Expired`};function x(e){let n=f(`writes.approve`,`write requests`);return c({queryKey:[`write-requests`,e||`all`],queryFn:async()=>(await n.check(),t(await s.GET(`/api/v1/write-requests`,{params:{query:e?{status:e}:{}}})).items),enabled:n.ready,retry:!1})}var S=e=>JSON.stringify(e);function C({args:e}){let t=Object.entries(e);return t.length?(0,y.jsx)(`table`,{className:`grid approval-args`,children:(0,y.jsx)(`tbody`,{children:t.map(([e,t])=>(0,y.jsxs)(`tr`,{children:[(0,y.jsx)(`td`,{className:`mono`,children:e}),(0,y.jsx)(`td`,{className:`mono`,children:S(t)})]},e))})}):(0,y.jsx)(`span`,{className:`dim`,children:`none`})}function w({id:a}){let{closeDrawer:o,showError:l,toast:d}=n(),f=u(),[m,_]=(0,v.useState)(``),x=c({queryKey:[`write-requests`,`one`,a],queryFn:async()=>t(await s.GET(`/api/v1/write-requests/{id}`,{params:{path:{id:a}}})),retry:!1}),w=e({mutationFn:async e=>t(e?await s.POST(`/api/v1/write-requests/{id}/approve`,{params:{path:{id:a}}}):await s.POST(`/api/v1/write-requests/{id}/reject`,{params:{path:{id:a}},body:m.trim()?{reason:m.trim()}:{}})),onSuccess:e=>{l(``),f.invalidateQueries({queryKey:[`write-requests`]}),f.invalidateQueries({queryKey:[`alerts`]}),o(),d(e.status===`approved`?`Approved - ${e.caller} may run it now`:`Rejected`)},onError:e=>l(e.message,e instanceof p?{status:e.status}:{})});if(x.isPending)return(0,y.jsx)(i,{});if(x.isError)return(0,y.jsx)(`div`,{className:`hint`,children:x.error.message});let T=x.data,E=T.status===`pending`;return(0,y.jsxs)(`div`,{className:`form`,children:[(0,y.jsxs)(`p`,{className:`hint`,children:[(0,y.jsx)(`strong`,{children:T.caller}),` asked to run `,(0,y.jsx)(`strong`,{children:T.query}),` (version `,T.version,`) on`,` `,(0,y.jsx)(`span`,{className:`mono`,children:T.connection}),`. It changes at most `,T.max_affected_rows,` row`,T.max_affected_rows===1?``:`s`,`; past that it is rolled back.`]}),(0,y.jsx)(r,{label:`SQL`,children:(0,y.jsx)(g,{sql:T.sql})}),(0,y.jsx)(r,{label:`With exactly these arguments`,hint:`An approval is for these alone - no other write.`,children:(0,y.jsx)(C,{args:T.arguments})}),(0,y.jsxs)(`div`,{className:`hint`,children:[`Asked `,(0,y.jsx)(h,{value:T.created_at}),` · `,b[T.status]??T.status,E||T.status===`approved`?(0,y.jsxs)(y.Fragment,{children:[` `,`· expires `,(0,y.jsx)(h,{value:T.expires_at})]}):null,T.decided_by?` · ${T.status===`rejected`?`rejected`:`approved`} by ${T.decided_by}`:``,T.reason?` - ${T.reason}`:``]}),T.result?(0,y.jsx)(r,{label:`Result`,children:(0,y.jsx)(`span`,{className:`mono`,children:S(T.result)})}):null,E?(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(r,{id:`approval-reason`,label:`Reason`,hint:`Optional for a rejection - the agent is told.`,children:(0,y.jsx)(`textarea`,{id:`approval-reason`,rows:2,value:m,onChange:e=>_(e.target.value)})}),(0,y.jsxs)(`div`,{className:`form-actions`,children:[(0,y.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:w.isPending,onClick:()=>w.mutate(!0),children:`Approve`}),(0,y.jsx)(`button`,{type:`button`,className:`btn danger`,disabled:w.isPending,onClick:()=>w.mutate(!1),children:`Reject`}),(0,y.jsx)(`button`,{type:`button`,className:`btn`,onClick:o,children:`Close`})]})]}):(0,y.jsx)(`div`,{className:`form-actions`,children:(0,y.jsx)(`button`,{type:`button`,className:`btn`,onClick:o,children:`Close`})})]})}function T(){let{id:e}=m(),t=o(),{openDrawer:r}=n(),[a,s]=(0,v.useState)(`pending`),c=x(a),l=c.data??[],u=e=>r({title:`Approve a write`,kicker:e,wide:!0,content:(0,y.jsx)(w,{id:e})});(0,v.useEffect)(()=>{e&&(u(e),t(`/approvals`,{replace:!0}))},[e]);let d;return d=c.isPending?(0,y.jsx)(i,{text:`Loading write requests…`}):c.isError?(0,y.jsxs)(`div`,{className:`empty`,children:[(0,y.jsx)(`strong`,{children:`Couldn’t load write requests`}),(0,y.jsx)(`span`,{children:c.error.message})]}):l.length?(0,y.jsx)(`div`,{style:{overflowX:`auto`},children:(0,y.jsxs)(`table`,{className:`grid`,children:[(0,y.jsx)(`thead`,{children:(0,y.jsx)(`tr`,{children:[`Asked`,`Agent`,`Tool`,`Arguments`,a===`pending`?`Expires`:`Status`].map(e=>(0,y.jsx)(`th`,{children:e},e))})}),(0,y.jsx)(`tbody`,{children:l.map(e=>(0,y.jsxs)(`tr`,{"data-request":e.id,children:[(0,y.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,y.jsx)(h,{value:e.created_at,fallback:``})}),(0,y.jsx)(`td`,{className:`mono`,children:e.caller}),(0,y.jsx)(`td`,{children:(0,y.jsx)(`button`,{type:`button`,className:`agent-name`,onClick:()=>u(e.id),children:e.query})}),(0,y.jsx)(`td`,{className:`mono dim approval-args-cell`,children:S(e.arguments)}),(0,y.jsx)(`td`,{className:`dim`,style:{whiteSpace:`nowrap`},children:a===`pending`?(0,y.jsx)(h,{value:e.expires_at}):b[e.status]??e.status})]},e.id))})]})}):(0,y.jsxs)(`div`,{className:`empty`,children:[(0,y.jsx)(`strong`,{children:a===`pending`?`No writes are waiting for approval`:`No write requests yet`}),(0,y.jsx)(`span`,{children:`When an AI agent calls a write tool, its write waits here for a person to approve it - the agent is given a link to this screen.`})]}),(0,y.jsxs)(y.Fragment,{children:[(0,y.jsxs)(`div`,{className:`page-head`,children:[(0,y.jsxs)(`div`,{className:`titles`,children:[(0,y.jsxs)(`h1`,{children:[`Approvals `,(0,y.jsx)(_,{})]}),(0,y.jsx)(`span`,{className:`sub`,children:`Writes AI agents asked to make over MCP. Each runs only once a person approves it - with exactly the arguments shown.`})]}),(0,y.jsx)(`span`,{className:`spacer`}),(0,y.jsx)(`button`,{type:`button`,className:`btn`,onClick:()=>void c.refetch(),children:`Refresh`})]}),(0,y.jsxs)(`div`,{className:`panel`,style:{overflow:`hidden`},children:[(0,y.jsxs)(`div`,{className:`panel-bar`,children:[(0,y.jsxs)(`select`,{"aria-label":`Show`,value:a,onChange:e=>s(e.target.value),children:[(0,y.jsx)(`option`,{value:`pending`,children:`Waiting`}),(0,y.jsx)(`option`,{value:``,children:`All`}),(0,y.jsx)(`option`,{value:`done`,children:`Done`}),(0,y.jsx)(`option`,{value:`rejected`,children:`Rejected`}),(0,y.jsx)(`option`,{value:`expired`,children:`Expired`}),(0,y.jsx)(`option`,{value:`failed`,children:`Failed`})]}),(0,y.jsx)(`span`,{className:`spacer`}),(0,y.jsx)(`span`,{className:`panel-count`,children:c.data?`${l.length} request${l.length===1?``:`s`}`:``})]}),d]})]})}export{T as ApprovalsPage};
1
+ import{t as e}from"./useMutation-BFmh_lpo.js";import{E as t,I as n,M as r,P as i,Q as a,R as o,S as s,V as c,X as l,Y as u,Z as d,d as f,x as p,z as m}from"./index-BW1KMgAb.js";import{t as h}from"./Time-DbFAPI8a.js";import{t as g}from"./CodeBox-dmGPjinL.js";import{t as _}from"./Experimental-BZ5eU3RF.js";var v=a(d(),1),y=l(),b={pending:`Waiting`,approved:`Approved - not run yet`,running:`Running`,done:`Done`,failed:`Failed`,rejected:`Rejected`,expired:`Expired`};function x(e){let n=f(`writes.approve`,`write requests`);return c({queryKey:[`write-requests`,e||`all`],queryFn:async()=>(await n.check(),t(await s.GET(`/api/v1/write-requests`,{params:{query:e?{status:e}:{}}})).items),enabled:n.ready,retry:!1})}var S=e=>JSON.stringify(e);function C({args:e}){let t=Object.entries(e);return t.length?(0,y.jsx)(`table`,{className:`grid approval-args`,children:(0,y.jsx)(`tbody`,{children:t.map(([e,t])=>(0,y.jsxs)(`tr`,{children:[(0,y.jsx)(`td`,{className:`mono`,children:e}),(0,y.jsx)(`td`,{className:`mono`,children:S(t)})]},e))})}):(0,y.jsx)(`span`,{className:`dim`,children:`none`})}function w({id:a}){let{closeDrawer:o,showError:l,toast:d}=n(),f=u(),[m,_]=(0,v.useState)(``),x=c({queryKey:[`write-requests`,`one`,a],queryFn:async()=>t(await s.GET(`/api/v1/write-requests/{id}`,{params:{path:{id:a}}})),retry:!1}),w=e({mutationFn:async e=>t(e?await s.POST(`/api/v1/write-requests/{id}/approve`,{params:{path:{id:a}}}):await s.POST(`/api/v1/write-requests/{id}/reject`,{params:{path:{id:a}},body:m.trim()?{reason:m.trim()}:{}})),onSuccess:e=>{l(``),f.invalidateQueries({queryKey:[`write-requests`]}),f.invalidateQueries({queryKey:[`alerts`]}),o(),d(e.status===`approved`?`Approved - ${e.caller} may run it now`:`Rejected`)},onError:e=>l(e.message,e instanceof p?{status:e.status}:{})});if(x.isPending)return(0,y.jsx)(i,{});if(x.isError)return(0,y.jsx)(`div`,{className:`hint`,children:x.error.message});let T=x.data,E=T.status===`pending`;return(0,y.jsxs)(`div`,{className:`form`,children:[(0,y.jsxs)(`p`,{className:`hint`,children:[(0,y.jsx)(`strong`,{children:T.caller}),` asked to run `,(0,y.jsx)(`strong`,{children:T.query}),` (version `,T.version,`) on`,` `,(0,y.jsx)(`span`,{className:`mono`,children:T.connection}),`. It changes at most `,T.max_affected_rows,` row`,T.max_affected_rows===1?``:`s`,`; past that it is rolled back.`]}),(0,y.jsx)(r,{label:`SQL`,children:(0,y.jsx)(g,{sql:T.sql})}),(0,y.jsx)(r,{label:`With exactly these arguments`,hint:`An approval is for these alone - no other write.`,children:(0,y.jsx)(C,{args:T.arguments})}),(0,y.jsxs)(`div`,{className:`hint`,children:[`Asked `,(0,y.jsx)(h,{value:T.created_at}),` · `,b[T.status]??T.status,E||T.status===`approved`?(0,y.jsxs)(y.Fragment,{children:[` `,`· expires `,(0,y.jsx)(h,{value:T.expires_at})]}):null,T.decided_by?` · ${T.status===`rejected`?`rejected`:`approved`} by ${T.decided_by}`:``,T.reason?` - ${T.reason}`:``]}),T.result?(0,y.jsx)(r,{label:`Result`,children:(0,y.jsx)(`span`,{className:`mono`,children:S(T.result)})}):null,E?(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(r,{id:`approval-reason`,label:`Reason`,hint:`Optional for a rejection - the agent is told.`,children:(0,y.jsx)(`textarea`,{id:`approval-reason`,rows:2,value:m,onChange:e=>_(e.target.value)})}),(0,y.jsxs)(`div`,{className:`form-actions`,children:[(0,y.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:w.isPending,onClick:()=>w.mutate(!0),children:`Approve`}),(0,y.jsx)(`button`,{type:`button`,className:`btn danger`,disabled:w.isPending,onClick:()=>w.mutate(!1),children:`Reject`}),(0,y.jsx)(`button`,{type:`button`,className:`btn`,onClick:o,children:`Close`})]})]}):(0,y.jsx)(`div`,{className:`form-actions`,children:(0,y.jsx)(`button`,{type:`button`,className:`btn`,onClick:o,children:`Close`})})]})}function T(){let{id:e}=m(),t=o(),{openDrawer:r}=n(),[a,s]=(0,v.useState)(`pending`),c=x(a),l=c.data??[],u=e=>r({title:`Approve a write`,kicker:e,wide:!0,content:(0,y.jsx)(w,{id:e})});(0,v.useEffect)(()=>{e&&(u(e),t(`/approvals`,{replace:!0}))},[e]);let d;return d=c.isPending?(0,y.jsx)(i,{text:`Loading write requests…`}):c.isError?(0,y.jsxs)(`div`,{className:`empty`,children:[(0,y.jsx)(`strong`,{children:`Couldn’t load write requests`}),(0,y.jsx)(`span`,{children:c.error.message})]}):l.length?(0,y.jsx)(`div`,{style:{overflowX:`auto`},children:(0,y.jsxs)(`table`,{className:`grid`,children:[(0,y.jsx)(`thead`,{children:(0,y.jsx)(`tr`,{children:[`Asked`,`Agent`,`Tool`,`Arguments`,a===`pending`?`Expires`:`Status`].map(e=>(0,y.jsx)(`th`,{children:e},e))})}),(0,y.jsx)(`tbody`,{children:l.map(e=>(0,y.jsxs)(`tr`,{"data-request":e.id,children:[(0,y.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,y.jsx)(h,{value:e.created_at,fallback:``})}),(0,y.jsx)(`td`,{className:`mono`,children:e.caller}),(0,y.jsx)(`td`,{children:(0,y.jsx)(`button`,{type:`button`,className:`agent-name`,onClick:()=>u(e.id),children:e.query})}),(0,y.jsx)(`td`,{className:`mono dim approval-args-cell`,children:S(e.arguments)}),(0,y.jsx)(`td`,{className:`dim`,style:{whiteSpace:`nowrap`},children:a===`pending`?(0,y.jsx)(h,{value:e.expires_at}):b[e.status]??e.status})]},e.id))})]})}):(0,y.jsxs)(`div`,{className:`empty`,children:[(0,y.jsx)(`strong`,{children:a===`pending`?`No writes are waiting for approval`:`No write requests yet`}),(0,y.jsx)(`span`,{children:`When an AI agent calls a write tool, its write waits here for a person to approve it - the agent is given a link to this screen.`})]}),(0,y.jsxs)(y.Fragment,{children:[(0,y.jsxs)(`div`,{className:`page-head`,children:[(0,y.jsxs)(`div`,{className:`titles`,children:[(0,y.jsxs)(`h1`,{children:[`Approvals `,(0,y.jsx)(_,{})]}),(0,y.jsx)(`span`,{className:`sub`,children:`Writes AI agents asked to make over MCP. Each runs only once a person approves it - with exactly the arguments shown.`})]}),(0,y.jsx)(`span`,{className:`spacer`}),(0,y.jsx)(`button`,{type:`button`,className:`btn`,onClick:()=>void c.refetch(),children:`Refresh`})]}),(0,y.jsxs)(`div`,{className:`panel`,style:{overflow:`hidden`},children:[(0,y.jsxs)(`div`,{className:`panel-bar`,children:[(0,y.jsxs)(`select`,{"aria-label":`Show`,value:a,onChange:e=>s(e.target.value),children:[(0,y.jsx)(`option`,{value:`pending`,children:`Waiting`}),(0,y.jsx)(`option`,{value:``,children:`All`}),(0,y.jsx)(`option`,{value:`done`,children:`Done`}),(0,y.jsx)(`option`,{value:`rejected`,children:`Rejected`}),(0,y.jsx)(`option`,{value:`expired`,children:`Expired`}),(0,y.jsx)(`option`,{value:`failed`,children:`Failed`})]}),(0,y.jsx)(`span`,{className:`spacer`}),(0,y.jsx)(`span`,{className:`panel-count`,children:c.data?`${l.length} request${l.length===1?``:`s`}`:``})]}),d]})]})}export{T as ApprovalsPage};