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.
- {queryapigate-0.22.0/queryapigate.egg-info → queryapigate-0.23.0}/PKG-INFO +6 -1
- {queryapigate-0.22.0 → queryapigate-0.23.0}/README.md +1 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/pyproject.toml +4 -1
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/__init__.py +1 -1
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/app.py +60 -6
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/cli.py +20 -1
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/config.py +35 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/AccessMapPage-CJHNlXPv.js → queryapigate-0.23.0/queryapigate/console_dist/assets/AccessMapPage-CSHtKZv2.js +3 -3
- queryapigate-0.22.0/queryapigate/console_dist/assets/AdministratorsPage-BbYhO2eo.js → queryapigate-0.23.0/queryapigate/console_dist/assets/AdministratorsPage-CSHD7DKM.js +1 -1
- 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
- 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
- queryapigate-0.22.0/queryapigate/console_dist/assets/ApiKeysPage-DhK31mF7.js → queryapigate-0.23.0/queryapigate/console_dist/assets/ApiKeysPage-nXSY2YPH.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/ApprovalsPage-feItqvUh.js → queryapigate-0.23.0/queryapigate/console_dist/assets/ApprovalsPage-CczJ9Eq4.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/AuditLogPage-Sv21w03B.js → queryapigate-0.23.0/queryapigate/console_dist/assets/AuditLogPage-CzBlBkhP.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/CachingPage-DUyqx9Dg.js → queryapigate-0.23.0/queryapigate/console_dist/assets/CachingPage-Cyh1l8KY.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/CodeBox-DPiCVuYm.js → queryapigate-0.23.0/queryapigate/console_dist/assets/CodeBox-dmGPjinL.js +1 -1
- 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
- queryapigate-0.22.0/queryapigate/console_dist/assets/DesignerPage-Da52fUjh.js → queryapigate-0.23.0/queryapigate/console_dist/assets/DesignerPage-9AWE5mGX.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/DestinationsPage-BvcXYIwC.js → queryapigate-0.23.0/queryapigate/console_dist/assets/DestinationsPage-Bh1zevuv.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/Experimental-Cev0t6Ci.js → queryapigate-0.23.0/queryapigate/console_dist/assets/Experimental-BZ5eU3RF.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/ExportsPage-Csyn3Ghf.js → queryapigate-0.23.0/queryapigate/console_dist/assets/ExportsPage-CgedN5uU.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/HelpPage-C1cQioeI.js → queryapigate-0.23.0/queryapigate/console_dist/assets/HelpPage-LsbTGm8h.js +2 -2
- queryapigate-0.22.0/queryapigate/console_dist/assets/HomePage-BRhhEL2z.js → queryapigate-0.23.0/queryapigate/console_dist/assets/HomePage-CPyUXjdF.js +2 -2
- queryapigate-0.22.0/queryapigate/console_dist/assets/MetricsPage-CgSxzDOE.js → queryapigate-0.23.0/queryapigate/console_dist/assets/MetricsPage-twDFTNE9.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/RepositoryPage-xiZEg7f4.js → queryapigate-0.23.0/queryapigate/console_dist/assets/RepositoryPage-D2LzRD8u.js +1 -1
- 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
- queryapigate-0.22.0/queryapigate/console_dist/assets/RolesPage-C9G0dlSf.js → queryapigate-0.23.0/queryapigate/console_dist/assets/RolesPage-C66qonnT.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/SchemaBrowser-7JFHE2g9.js → queryapigate-0.23.0/queryapigate/console_dist/assets/SchemaBrowser-Ug8fhNeZ.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/SettingsPage-BFoHW1XP.js → queryapigate-0.23.0/queryapigate/console_dist/assets/SettingsPage-CTVO4iQ7.js +1 -1
- 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
- queryapigate-0.22.0/queryapigate/console_dist/assets/Time-GG1SZb8S.js → queryapigate-0.23.0/queryapigate/console_dist/assets/Time-DbFAPI8a.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/forms-BCfnOm9v.js → queryapigate-0.23.0/queryapigate/console_dist/assets/forms-DYXv3f3c.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/forms-C8ZdFl7Q.js → queryapigate-0.23.0/queryapigate/console_dist/assets/forms-_J9EIFdi.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/icons-plWox3cc.js → queryapigate-0.23.0/queryapigate/console_dist/assets/icons-D4JpNqC8.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/index-k1HFugKb.js → queryapigate-0.23.0/queryapigate/console_dist/assets/index-BW1KMgAb.js +2 -2
- queryapigate-0.22.0/queryapigate/console_dist/assets/maskRules-B0nXSvKz.js → queryapigate-0.23.0/queryapigate/console_dist/assets/maskRules-D6QtuKiR.js +1 -1
- queryapigate-0.22.0/queryapigate/console_dist/assets/reach-BKjZYR1f.js → queryapigate-0.23.0/queryapigate/console_dist/assets/reach-DMG9gT8F.js +1 -1
- 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
- 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
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/console_dist/index.html +1 -1
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/engine.py +27 -9
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/events.py +14 -3
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/experimental.py +7 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/exports.py +14 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/logging_setup.py +4 -1
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/mcp_server.py +30 -12
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/openapi.py +49 -19
- queryapigate-0.23.0/queryapigate/secheaders.py +56 -0
- queryapigate-0.23.0/queryapigate/tracing.py +213 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/v1_spec.py +3 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0/queryapigate.egg-info}/PKG-INFO +6 -1
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate.egg-info/SOURCES.txt +36 -32
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate.egg-info/requires.txt +5 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_events_server.py +26 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_response_cache.py +5 -1
- queryapigate-0.23.0/tests/test_security_headers.py +180 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_settings.py +1 -1
- queryapigate-0.23.0/tests/test_tracing.py +235 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/LICENSE +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/__main__.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/adminroles.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/admins.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/agentwrites.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/alerts.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/apikeys.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/broadcast.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/bundle.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/cache.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/collection_admin.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/console.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/console_dist/assets/index-CBaM_ckR.css +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/console_dist/assets/state-O13cl7cO.js +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/console_dist/favicon.svg +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/cors.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/databases.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/db.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/definitions.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/deprecations.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/descriptions.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/destinations.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/duckfiles.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/errors.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/examples.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/exporting.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/formats.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/governance.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/history.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/instances.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/jwtauth.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/lib/h2-2.2.224.jar +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/masking.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/metrics.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/mongotools.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/oidc.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/params.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/parquet.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/pool.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/postman.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/ratelimit.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/rediscache.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/rekey.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/runners.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/schema.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/__init__.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/access.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/administrators.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/audit.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/collections.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/connections.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/descriptions.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/destinations.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/exports.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/mcp.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/services/queries.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/sqlflow.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/sqltools.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/sso.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/store.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/tableguard.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/usage.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate/v1.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate.egg-info/dependency_links.txt +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate.egg-info/entry_points.txt +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/queryapigate.egg-info/top_level.txt +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/setup.cfg +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_adhoc_history.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_adminroles.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_admins.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_agent_writes.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_alerts.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_api_administrators.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_api_keys.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_api_v1.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_audit_log.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_backup_restore.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_broadcast.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_cli.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_collection_bundle.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_collection_postman.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_collections.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_connection_encryption.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_console.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_cors_and_rate_limit.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_databases.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_db.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_dependency_floors.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_deprecations.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_descriptions.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_destinations.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_duckdb.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_duckdb_files.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_errors.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_events.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_examples.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_experimental.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_export_security.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_exporting.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_exports.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_history.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_howto.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_instances.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_integration.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_jdbc.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_jwt.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_key_rotation.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_masking.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_masking_requests.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_mcp.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_mcp_keycloak.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_mcp_oauth.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_mongotools.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_multinode.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_observability.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_oidc.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_params.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_parquet.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_pool.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_publishing.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_query_window.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_queryapigate.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_rekey.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_release_check.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_roles.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_rolling_upgrade.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_shared_rate_limit.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_sql_guard_fuzz.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_sql_guard_writes.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_sqlflow.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_sso.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_sso_keycloak.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_tableguard.py +0 -0
- {queryapigate-0.22.0 → queryapigate-0.23.0}/tests/test_upgrades.py +0 -0
- {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.
|
|
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'",
|
|
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]
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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'] =
|
|
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'] =
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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};
|