queryapigate 0.44.0__tar.gz → 0.45.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.44.0/queryapigate.egg-info → queryapigate-0.45.0}/PKG-INFO +1 -1
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/__init__.py +1 -1
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/alerts.py +10 -4
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/app.py +40 -5
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/cli.py +43 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/config.py +48 -3
- queryapigate-0.44.0/queryapigate/console_dist/assets/AccessMapPage-CHMmNdte.js → queryapigate-0.45.0/queryapigate/console_dist/assets/AccessMapPage-CNBzMwLN.js +2 -2
- queryapigate-0.44.0/queryapigate/console_dist/assets/AdministratorsPage-BcRj2iee.js → queryapigate-0.45.0/queryapigate/console_dist/assets/AdministratorsPage-CLfRjojL.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/AgentsPage-DW3FEK58.js → queryapigate-0.45.0/queryapigate/console_dist/assets/AgentsPage-DlmXHHcH.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/AlertsPage-B_rOXFVO.js → queryapigate-0.45.0/queryapigate/console_dist/assets/AlertsPage-Dt4Uwz6s.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/ApiKeyDetailPage-nPdRuGU_.js → queryapigate-0.45.0/queryapigate/console_dist/assets/ApiKeyDetailPage-D7Ycd4O4.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/ApiKeysPage-ghPfvq81.js → queryapigate-0.45.0/queryapigate/console_dist/assets/ApiKeysPage-BGgDoVCd.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/ApprovalsPage-MRLqAd_b.js → queryapigate-0.45.0/queryapigate/console_dist/assets/ApprovalsPage-CKF0I-Kz.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/AuditLogPage-BvZwseJz.js → queryapigate-0.45.0/queryapigate/console_dist/assets/AuditLogPage-C5YiXeK-.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/CachingPage-BlwUvdK5.js → queryapigate-0.45.0/queryapigate/console_dist/assets/CachingPage-Da1rD-zo.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/ConnectionsPage-DiqCYvzf.js → queryapigate-0.45.0/queryapigate/console_dist/assets/ConnectionsPage-BajPP7ix.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/DesignerPage-DOyJWKl2.js → queryapigate-0.45.0/queryapigate/console_dist/assets/DesignerPage-CIzg9Apa.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/DestinationsPage-BER4b6Ry.js → queryapigate-0.45.0/queryapigate/console_dist/assets/DestinationsPage-BMGKQgUe.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/ExportsPage-hkbyClkf.js → queryapigate-0.45.0/queryapigate/console_dist/assets/ExportsPage-C9w4up5N.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/HelpPage-BDfcaLWe.js → queryapigate-0.45.0/queryapigate/console_dist/assets/HelpPage-NSJp_qwt.js +2 -2
- queryapigate-0.44.0/queryapigate/console_dist/assets/HomePage-CmqH7zlU.js → queryapigate-0.45.0/queryapigate/console_dist/assets/HomePage-Cywjwlpb.js +2 -2
- queryapigate-0.44.0/queryapigate/console_dist/assets/KeyActivity-BC6RPPpH.js → queryapigate-0.45.0/queryapigate/console_dist/assets/KeyActivity-D673KcWq.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/Managed-B1rh-_gw.js → queryapigate-0.45.0/queryapigate/console_dist/assets/Managed-VPfeN8Yu.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/McpPage-BIve0LH3.js → queryapigate-0.45.0/queryapigate/console_dist/assets/McpPage-aH1mkjzt.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/MetricsPage-DT-KeJBh.js → queryapigate-0.45.0/queryapigate/console_dist/assets/MetricsPage-BqIkrlP1.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/QueryInfo-DB1HWOF1.js → queryapigate-0.45.0/queryapigate/console_dist/assets/QueryInfo-Bv-0Zmc5.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/RepositoryPage-B1HT63ey.js → queryapigate-0.45.0/queryapigate/console_dist/assets/RepositoryPage-lzLrJRXS.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/Results-BsH71FdD.js → queryapigate-0.45.0/queryapigate/console_dist/assets/Results-XnOh21dp.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/RolesPage-obvoaIWp.js → queryapigate-0.45.0/queryapigate/console_dist/assets/RolesPage-Cv5S5X7g.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/SchemaBrowser-DLfU-4bj.js → queryapigate-0.45.0/queryapigate/console_dist/assets/SchemaBrowser-8tmsr1__.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/SettingsPage-D3_OfVa3.js → queryapigate-0.45.0/queryapigate/console_dist/assets/SettingsPage-pOVvmb4i.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/Time-DE-u5yL9.js → queryapigate-0.45.0/queryapigate/console_dist/assets/Time-s5E5qK3s.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/WorkspacesPage-BLeFFFzT.js → queryapigate-0.45.0/queryapigate/console_dist/assets/WorkspacesPage-B1lg3P5d.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/api-BlpyufoX.js → queryapigate-0.45.0/queryapigate/console_dist/assets/api-DIm7t7Bb.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/forms-DL4MJS43.js → queryapigate-0.45.0/queryapigate/console_dist/assets/forms-B7OrYKBb.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/forms-BOFRAueI.js → queryapigate-0.45.0/queryapigate/console_dist/assets/forms-CTAVu1q5.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/index-DBrO0zMK.js → queryapigate-0.45.0/queryapigate/console_dist/assets/index-Cl93xBjn.js +2 -2
- queryapigate-0.44.0/queryapigate/console_dist/assets/shared-1MeLE7ZH.js → queryapigate-0.45.0/queryapigate/console_dist/assets/shared-epEBEbJD.js +1 -1
- queryapigate-0.44.0/queryapigate/console_dist/assets/useMutation-BHDQeA9M.js → queryapigate-0.45.0/queryapigate/console_dist/assets/useMutation-BIoTFTZ8.js +1 -1
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/console_dist/index.html +1 -1
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/db.py +42 -3
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/events.py +74 -9
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/experimental.py +7 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/history.py +41 -2
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/metrics.py +14 -0
- queryapigate-0.45.0/queryapigate/runstore.py +1028 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/store.py +41 -25
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/usage.py +40 -6
- {queryapigate-0.44.0 → queryapigate-0.45.0/queryapigate.egg-info}/PKG-INFO +1 -1
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate.egg-info/SOURCES.txt +36 -33
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_adhoc_history.py +2 -1
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_backup_restore.py +2 -1
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_collections.py +2 -1
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_data_protection.py +3 -1
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_dataplane.py +6 -3
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_events_server.py +6 -3
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_history.py +8 -2
- queryapigate-0.45.0/tests/test_runstore.py +464 -0
- queryapigate-0.45.0/tests/test_runstore_differential.py +139 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_settings.py +4 -3
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_upgrades.py +3 -1
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_usage.py +3 -1
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_workspace_observability.py +4 -1
- {queryapigate-0.44.0 → queryapigate-0.45.0}/LICENSE +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/NOTICE +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/README.md +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/pyproject.toml +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/__main__.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/accesslog.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/adminroles.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/admins.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/agentwrites.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/apikeys.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/auditchain.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/auditstream.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/aws.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/broadcast.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/bundle.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/cache.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/collection_admin.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/configcode.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/console.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/console_dist/assets/CodeBox-Cwh5KHLp.js +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/console_dist/assets/Experimental-B3dnaeV7.js +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/console_dist/assets/StatTile-Cft4kJPH.js +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/console_dist/assets/client-ZHNiMG8H.js +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/console_dist/assets/icons-CFXE8hAz.js +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/console_dist/assets/index-Cv64XKNs.css +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/console_dist/assets/maskRules-CvKQIPgN.js +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/console_dist/assets/reach-BAOLd75Y.js +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/console_dist/assets/state-O13cl7cO.js +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/console_dist/favicon.svg +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/cors.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/databases.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/dataplane.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/definitions.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/deprecations.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/descriptions.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/destinations.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/duckfiles.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/embeddings.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/engine.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/errors.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/examples.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/exporting.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/exports.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/formats.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/governance.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/gunicorn_conf.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/instances.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/jwtauth.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/lib/h2-2.2.224.jar +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/limits.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/logging_setup.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/masking.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/mcp_server.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/metacache.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/mongodocs.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/mongotools.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/oidc.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/openapi.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/params.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/parquet.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/pool.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/postman.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/quotas.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/ratelimit.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/rediscache.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/rekey.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/runners.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/schema.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/scope.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/secheaders.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/secretrefs.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/services/__init__.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/services/access.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/services/administrators.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/services/audit.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/services/collections.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/services/connections.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/services/descriptions.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/services/destinations.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/services/exports.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/services/mcp.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/services/queries.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/services/workspaces.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/sqlflow.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/sqltools.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/sso.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/tableguard.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/tracing.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/v1.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/v1_spec.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/vault.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate/workspaces.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate.egg-info/dependency_links.txt +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate.egg-info/entry_points.txt +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate.egg-info/requires.txt +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/queryapigate.egg-info/top_level.txt +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/setup.cfg +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_adminroles.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_admins.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_agent_writes.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_alerts.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_api_administrators.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_api_keys.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_api_v1.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_audit_chain.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_audit_log.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_audit_siem.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_audit_stream.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_aws.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_broadcast.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_cli.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_collection_bundle.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_collection_postman.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_configcode.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_connection_encryption.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_console.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_cors_and_rate_limit.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_databases.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_db.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_dependency_floors.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_deprecations.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_descriptions.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_destinations.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_docs_site.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_duckdb.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_duckdb_files.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_embeddings.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_errors.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_events.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_examples.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_experimental.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_export_security.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_exporting.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_exports.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_fips.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_helm.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_howto.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_instances.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_integration.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_jdbc.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_jwt.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_key_rotation.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_limits.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_masking.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_masking_requests.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_mcp.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_mcp_keycloak.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_mcp_oauth.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_metacache.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_mongo_streaming.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_mongotools.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_multinode.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_observability.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_oidc.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_params.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_parquet.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_pool.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_publishing.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_query_window.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_queryapigate.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_quotas.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_rekey.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_release_check.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_response_cache.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_roles.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_rolling_upgrade.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_secretrefs.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_security_headers.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_shared_rate_limit.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_sql_guard_fuzz.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_sql_guard_writes.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_sqlflow.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_sso.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_sso_keycloak.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_supply_chain.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_tableguard.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_tracing.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_vault.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_workspace_config_cli.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_workspace_isolation.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_workspace_store.py +0 -0
- {queryapigate-0.44.0 → queryapigate-0.45.0}/tests/test_workspaces_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: queryapigate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.45.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: Apache-2.0
|
|
@@ -21,7 +21,7 @@ from collections import deque
|
|
|
21
21
|
from datetime import datetime, timedelta
|
|
22
22
|
from statistics import median
|
|
23
23
|
|
|
24
|
-
from . import admins, apikeys, config, db, history, instances, metrics, oidc, ratelimit, scope, usage
|
|
24
|
+
from . import admins, apikeys, config, db, history, instances, metrics, oidc, ratelimit, runstore, scope, usage
|
|
25
25
|
|
|
26
26
|
RECENT_RUNS = 5000 # newest runs the history checks look at
|
|
27
27
|
QUERY_WINDOW = timedelta(days=7) # a query's error rate and speed are judged on its runs this recent ...
|
|
@@ -278,10 +278,16 @@ def _keys(now):
|
|
|
278
278
|
def _recent_runs(now):
|
|
279
279
|
"""The newest runs, as (query_name, executed_at, entry) - written ones, including this process's queued."""
|
|
280
280
|
history.flush()
|
|
281
|
-
rows = db.connection().execute(
|
|
282
|
-
'SELECT query_name, executed_at, entry_json FROM execution_history WHERE workspace = ? '
|
|
283
|
-
'ORDER BY rowid DESC LIMIT ?', (scope.current(), RECENT_RUNS)).fetchall()
|
|
284
281
|
oldest = (now - QUERY_WINDOW).strftime(history.TIME_FORMAT)
|
|
282
|
+
if runstore.enabled():
|
|
283
|
+
runstore.ensure_schema()
|
|
284
|
+
rows = [{'query_name': row['query_name'], 'executed_at': row['executed_at'], 'entry_json': row['entry']}
|
|
285
|
+
for row in runstore.newest_first('workspace = {ws:String}', {'ws': scope.current()}, RECENT_RUNS,
|
|
286
|
+
floor=oldest, columns='query_name, executed_at, entry')]
|
|
287
|
+
else:
|
|
288
|
+
rows = db.connection().execute(
|
|
289
|
+
'SELECT query_name, executed_at, entry_json FROM execution_history WHERE workspace = ? '
|
|
290
|
+
'ORDER BY rowid DESC LIMIT ?', (scope.current(), RECENT_RUNS)).fetchall()
|
|
285
291
|
runs = []
|
|
286
292
|
for row in rows:
|
|
287
293
|
if row['executed_at'] < oldest:
|
|
@@ -48,6 +48,7 @@ from . import (
|
|
|
48
48
|
openapi,
|
|
49
49
|
quotas,
|
|
50
50
|
ratelimit,
|
|
51
|
+
runstore,
|
|
51
52
|
schema,
|
|
52
53
|
scope,
|
|
53
54
|
secheaders,
|
|
@@ -67,9 +68,11 @@ bp = Blueprint('api', __name__)
|
|
|
67
68
|
RESERVED_ARGS = {'format', 'page', 'page_size', 'connection_name', 'version', 'timeout', 'stream'}
|
|
68
69
|
PUBLIC_ENDPOINTS = {'api.index', 'api.favicon', 'api.health', 'api.docs', 'api.openapi_spec', 'api.admin_ui',
|
|
69
70
|
'api.console_page', 'api.metrics_endpoint',
|
|
70
|
-
|
|
71
|
+
# the data plane's reports and run store lease: gate() lets only its secret through (ADR 0028)
|
|
72
|
+
'api.dataplane_runs', 'api.dataplane_lease',
|
|
71
73
|
# Console sign-in (sso.py): each checks what it needs itself - state, the session and its CSRF token
|
|
72
74
|
'auth.login', 'auth.callback', 'auth.session', 'auth.logout'}
|
|
75
|
+
DATAPLANE_ENDPOINTS = {'api.dataplane_runs', 'api.dataplane_lease'}
|
|
73
76
|
# Monitoring keeps working while a client is throttled; the Console's static files are exempt so loading the page
|
|
74
77
|
# (several hashed assets) never spends a client's quota - they carry no data and no key.
|
|
75
78
|
RATE_LIMIT_EXEMPT = {'api.health', 'api.metrics_endpoint', 'api.console_page'}
|
|
@@ -207,6 +210,7 @@ def create_app():
|
|
|
207
210
|
config.check_settings()
|
|
208
211
|
logging_setup.configure(log) # before anything below logs - the legacy import's deprecation warning, for one
|
|
209
212
|
db.init_schema()
|
|
213
|
+
runstore.check_marker()
|
|
210
214
|
store.refuse_legacy_home()
|
|
211
215
|
experimental.warn_in_use(log, os.environ, store.read_connections(), store.workspace_names())
|
|
212
216
|
warn_env_references()
|
|
@@ -325,7 +329,7 @@ def create_app():
|
|
|
325
329
|
# Set first so every request - including one rejected below - gets an ID and a measured duration.
|
|
326
330
|
g.request_id = new_request_id(request.headers.get('X-Request-Id'))
|
|
327
331
|
g.request_started = time.monotonic()
|
|
328
|
-
if request.endpoint
|
|
332
|
+
if request.endpoint in DATAPLANE_ENDPOINTS: # the data plane's own calls: nothing else applies to them
|
|
329
333
|
if request.environ.get('queryapigate.dataplane'):
|
|
330
334
|
return None
|
|
331
335
|
return jsonify(error_body('Not Found', 404)), 404
|
|
@@ -370,7 +374,7 @@ def create_app():
|
|
|
370
374
|
|
|
371
375
|
@app.after_request
|
|
372
376
|
def decorate(response):
|
|
373
|
-
if request.endpoint
|
|
377
|
+
if request.endpoint in DATAPLANE_ENDPOINTS: # counted and logged as the runs it reports, not itself
|
|
374
378
|
return response
|
|
375
379
|
if not request.path.startswith('/auth/'): # Console sign-in is same-origin only (sso.py)
|
|
376
380
|
cors.add_headers(response, request.headers.get('Origin'))
|
|
@@ -1229,7 +1233,8 @@ def dataplane_runs():
|
|
|
1229
1233
|
"""The runs a data plane in front of this server served itself (dataplane.py, ADR 0028), batched: each recorded as
|
|
1230
1234
|
run_saved() records one - run history, the event log, the live feed - and counted in /metrics as it would have been.
|
|
1231
1235
|
Only the data plane reaches it: gate() answers 404 to anything without its secret. `dropped`: runs it served but
|
|
1232
|
-
couldn't queue for here, counted as history's own dropped runs are.
|
|
1236
|
+
couldn't queue for here, counted as history's own dropped runs are. `recorded`: with a run store, the data plane has
|
|
1237
|
+
written them there itself (ADR 0029), and only `kept` and `sampled_out` of them are counted here.
|
|
1233
1238
|
|
|
1234
1239
|
Each run comes with its history entry already JSON (`entry`), and the labels and timings /metrics counts it by: at
|
|
1235
1240
|
the data plane's rates Python's own work per run - encoding it, and its labels - is what it can't keep up with, so
|
|
@@ -1238,7 +1243,11 @@ def dataplane_runs():
|
|
|
1238
1243
|
runs = report.get('runs') or []
|
|
1239
1244
|
broadcaster = current_app.extensions['queryapigate_broadcaster']
|
|
1240
1245
|
with scope.using(scope.current()): # resolved once, not by each of a busy batch's few thousand label lookups
|
|
1241
|
-
|
|
1246
|
+
if report.get('recorded'): # in the run store already: written by the data plane itself (ADR 0029 §4)
|
|
1247
|
+
metrics.inc_history('recorded', int(report.get('kept') or 0))
|
|
1248
|
+
metrics.inc_history('sampled_out', int(report.get('sampled_out') or 0))
|
|
1249
|
+
else:
|
|
1250
|
+
history.record_reported(runs)
|
|
1242
1251
|
if broadcaster.subscriber_count(): # the live feed's events, only when anyone is listening
|
|
1243
1252
|
for run in runs:
|
|
1244
1253
|
broadcaster.publish(events.run_payload(run['query'], run['version'], json.loads(run['entry'])),
|
|
@@ -1263,6 +1272,32 @@ def dataplane_runs():
|
|
|
1263
1272
|
return '', 204
|
|
1264
1273
|
|
|
1265
1274
|
|
|
1275
|
+
@bp.route('/internal/dataplane/lease', methods=['POST'])
|
|
1276
|
+
def dataplane_lease():
|
|
1277
|
+
"""A data plane's block of the event ids it mints when it writes its runs to the run store itself (runstore.py,
|
|
1278
|
+
ADR 0029 §2), leased here so its store access stays read-only: `{"holder": ...}` leases one, `{"holder": ...,
|
|
1279
|
+
"block": n}` renews it (409 once it has lapsed: lease again). With the layout of an id and the settle window, so
|
|
1280
|
+
the two processes can't disagree on them. 404 without a run store."""
|
|
1281
|
+
if not runstore.enabled():
|
|
1282
|
+
return jsonify(error_body('No run store is configured', 404)), 404
|
|
1283
|
+
body = request.get_json(silent=True) or {}
|
|
1284
|
+
holder = body.get('holder')
|
|
1285
|
+
if not isinstance(holder, str) or not 0 < len(holder) <= 200:
|
|
1286
|
+
return jsonify(error_body('holder: a name for the data plane process', 400)), 400
|
|
1287
|
+
if body.get('block') is not None:
|
|
1288
|
+
if not isinstance(body['block'], int) or not runstore.renew(body['block'], holder):
|
|
1289
|
+
return jsonify(error_body('That lease has lapsed - lease a block again', 409)), 409
|
|
1290
|
+
return jsonify({'block': body['block'], 'lease_seconds': runstore.LEASE_SECONDS}), 200
|
|
1291
|
+
try:
|
|
1292
|
+
block = runstore.lease(runstore.DATAPLANE_BLOCK, holder)
|
|
1293
|
+
except runstore.LeaseError as error:
|
|
1294
|
+
return jsonify(error_body(str(error), 503)), 503
|
|
1295
|
+
return jsonify({'block': block, 'size': runstore.DATAPLANE_BLOCK, 'lease_seconds': runstore.LEASE_SECONDS,
|
|
1296
|
+
'epoch_ms': runstore.EPOCH_MS, 'per_ms': runstore.PER_MS,
|
|
1297
|
+
'settle_seconds': runstore.SETTLE_SECONDS, 'database': runstore.database(),
|
|
1298
|
+
'sample_rate': config.history_sample_rate()}), 200
|
|
1299
|
+
|
|
1300
|
+
|
|
1266
1301
|
# --------------------------------------------------------------------------------------
|
|
1267
1302
|
# Live events
|
|
1268
1303
|
# --------------------------------------------------------------------------------------
|
|
@@ -25,6 +25,7 @@ from . import (
|
|
|
25
25
|
instances,
|
|
26
26
|
logging_setup,
|
|
27
27
|
postman,
|
|
28
|
+
runstore,
|
|
28
29
|
scope,
|
|
29
30
|
store,
|
|
30
31
|
workspaces,
|
|
@@ -113,6 +114,7 @@ def _events(args):
|
|
|
113
114
|
experimental.warn_in_use(logging.getLogger('queryapigate'), os.environ, store.read_connections(),
|
|
114
115
|
store.workspace_names())
|
|
115
116
|
db.init_schema()
|
|
117
|
+
runstore.check_marker()
|
|
116
118
|
instances.register('events')
|
|
117
119
|
args.port = args.port or config.events_port()
|
|
118
120
|
from .events import run
|
|
@@ -718,6 +720,37 @@ def _history_forget(args):
|
|
|
718
720
|
return 0
|
|
719
721
|
|
|
720
722
|
|
|
723
|
+
def _runs_copy(args):
|
|
724
|
+
"""`queryapigate runs copy-to-clickhouse`: this store's run history into the run store (ADR 0029 §6)."""
|
|
725
|
+
try:
|
|
726
|
+
config.check_settings()
|
|
727
|
+
if not runstore.enabled():
|
|
728
|
+
raise ValueError('QUERYAPIGATE_RUN_STORE_URL is not set - there is no run store to copy to')
|
|
729
|
+
copied = runstore.copy_history()
|
|
730
|
+
except (ValueError, runstore.RunStoreError) as error:
|
|
731
|
+
print(f'queryapigate runs copy-to-clickhouse: {error}', file=sys.stderr)
|
|
732
|
+
return 1
|
|
733
|
+
print(f'Copied {copied} run(s) of history to the run store at {runstore.location()}.')
|
|
734
|
+
return 0
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
def _runs_leave(args):
|
|
738
|
+
"""`queryapigate runs leave-clickhouse`: keep runs in the metadata store again (ADR 0029 §6)."""
|
|
739
|
+
try:
|
|
740
|
+
config.check_settings()
|
|
741
|
+
if not runstore.enabled():
|
|
742
|
+
raise ValueError('set QUERYAPIGATE_RUN_STORE_URL to the run store being left: its newest event id is read '
|
|
743
|
+
'from it')
|
|
744
|
+
newest = runstore.leave()
|
|
745
|
+
except (ValueError, runstore.RunStoreError) as error:
|
|
746
|
+
print(f'queryapigate runs leave-clickhouse: {error}', file=sys.stderr)
|
|
747
|
+
return 1
|
|
748
|
+
print(f'Runs and events are kept in the metadata store from the next start; its event ids continue above {newest}. '
|
|
749
|
+
'Unset QUERYAPIGATE_RUN_STORE_URL on every instance before starting them: the run store\'s history stays '
|
|
750
|
+
'there, not copied back.')
|
|
751
|
+
return 0
|
|
752
|
+
|
|
753
|
+
|
|
721
754
|
def _audit_verify(args):
|
|
722
755
|
"""`queryapigate audit verify [--file PATH]`: walk the audit log's hash chain (ADR 0012) - the store's, or an export
|
|
723
756
|
file's - and say whether it's intact, or where it first breaks. Exit 0 intact, 1 broken, 2 can't be checked
|
|
@@ -959,6 +992,15 @@ def build_parser():
|
|
|
959
992
|
history_forget.add_argument('--dry-run', action='store_true', help='say how much would go; delete nothing')
|
|
960
993
|
history_forget.set_defaults(func=_history_forget)
|
|
961
994
|
|
|
995
|
+
runs_parser = commands.add_parser('runs', help='where run history and the event log are kept: a ClickHouse run '
|
|
996
|
+
'store (ADR 0029, experimental)')
|
|
997
|
+
runs_parser.set_defaults(func=lambda a: runs_parser.print_help() or 2)
|
|
998
|
+
runs_actions = runs_parser.add_subparsers(dest='action')
|
|
999
|
+
runs_actions.add_parser('copy-to-clickhouse', help="copy this store's run history into the run store, once"
|
|
1000
|
+
).set_defaults(func=_runs_copy)
|
|
1001
|
+
runs_actions.add_parser('leave-clickhouse', help='keep runs in the metadata store again, its event ids continuing '
|
|
1002
|
+
'above the run store\'s').set_defaults(func=_runs_leave)
|
|
1003
|
+
|
|
962
1004
|
rekey_parser = commands.add_parser('rekey', help='re-encrypt every stored password under the first key in '
|
|
963
1005
|
'QUERYAPIGATE_SECRET_KEY, so the others can be removed (rotating '
|
|
964
1006
|
'the key)')
|
|
@@ -1002,6 +1044,7 @@ def main(argv=None):
|
|
|
1002
1044
|
try:
|
|
1003
1045
|
config.check_database_url()
|
|
1004
1046
|
db.init_schema()
|
|
1047
|
+
runstore.check_marker()
|
|
1005
1048
|
store.refuse_legacy_home()
|
|
1006
1049
|
except ValueError as error:
|
|
1007
1050
|
print(f'queryapigate: {error}', file=sys.stderr)
|
|
@@ -170,9 +170,12 @@ def history_adhoc_sql():
|
|
|
170
170
|
def history_retention_days():
|
|
171
171
|
"""QUERYAPIGATE_HISTORY_RETENTION_DAYS: keep every run for this many days instead of only each version's newest
|
|
172
172
|
history_limit() runs - for watching API behaviour over time, best on a PostgreSQL store. None (the
|
|
173
|
-
default) keeps the per-version cap.
|
|
173
|
+
default) keeps the per-version cap - except with a run store (runstore.py), where retention is always by time:
|
|
174
|
+
RUN_STORE_HISTORY_DAYS unless this is set. Validated at startup by check_settings()."""
|
|
174
175
|
raw = os.environ.get('QUERYAPIGATE_HISTORY_RETENTION_DAYS', '').strip()
|
|
175
|
-
|
|
176
|
+
if raw:
|
|
177
|
+
return int(raw)
|
|
178
|
+
return RUN_STORE_HISTORY_DAYS if run_store_url() else None
|
|
176
179
|
|
|
177
180
|
|
|
178
181
|
def history_sample_rate():
|
|
@@ -500,6 +503,7 @@ def check_settings():
|
|
|
500
503
|
raise ValueError('QUERYAPIGATE_HSTS_MAX_AGE must be a whole number of seconds from 1 to '
|
|
501
504
|
f'{MAX_HSTS_SECONDS} (two years) - 31536000 is a year')
|
|
502
505
|
check_database_url()
|
|
506
|
+
check_run_store_url()
|
|
503
507
|
check_jwt_settings()
|
|
504
508
|
check_mcp_oauth_settings()
|
|
505
509
|
check_sso_settings()
|
|
@@ -670,7 +674,7 @@ def _check_vault():
|
|
|
670
674
|
SECRET_SETTINGS = ('QUERYAPIGATE_API_KEY', 'QUERYAPIGATE_SECRET_KEY', 'QUERYAPIGATE_DATABASE_URL',
|
|
671
675
|
'QUERYAPIGATE_REDIS_URL', 'QUERYAPIGATE_AUDIT_KEY', 'QUERYAPIGATE_MASK_KEY',
|
|
672
676
|
'QUERYAPIGATE_JWT_SECRET', 'QUERYAPIGATE_SSO_CLIENT_SECRET', 'QUERYAPIGATE_VAULT_SECRET_ID',
|
|
673
|
-
'QUERYAPIGATE_METRICS_TOKEN')
|
|
677
|
+
'QUERYAPIGATE_METRICS_TOKEN', 'QUERYAPIGATE_RUN_STORE_URL')
|
|
674
678
|
# QUERYAPIGATE_VAULT_TOKEN_FILE isn't one of them: vault.py reads it again at each sign-in, as Vault Agent rotates it.
|
|
675
679
|
_from_files: dict[str, str] = {} # setting -> the file it was read from
|
|
676
680
|
|
|
@@ -817,6 +821,43 @@ def database_url():
|
|
|
817
821
|
return os.environ.get('QUERYAPIGATE_DATABASE_URL', '').strip() or None
|
|
818
822
|
|
|
819
823
|
|
|
824
|
+
RUN_STORE_HISTORY_DAYS = 30 # history's retention in the run store unless QUERYAPIGATE_HISTORY_RETENTION_DAYS says
|
|
825
|
+
_RUN_STORE_DATABASE_RE = re.compile(r'^[A-Za-z_][A-Za-z0-9_]{0,62}$')
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
def run_store_url():
|
|
829
|
+
"""QUERYAPIGATE_RUN_STORE_URL: keep run history, the event log and usage rollups in ClickHouse, at this
|
|
830
|
+
http(s)://user:password@host:port/database URL, instead of in the metadata store (runstore.py, ADR 0029). None (the
|
|
831
|
+
default) keeps them there. Validated at startup by check_run_store_url()."""
|
|
832
|
+
return os.environ.get('QUERYAPIGATE_RUN_STORE_URL', '').strip() or None
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
def check_run_store_url():
|
|
836
|
+
"""Part of check_settings(): the run store's URL is http(s)://, names a host and a database, and is https:// unless
|
|
837
|
+
the host is this machine - it carries a password, and every run's record."""
|
|
838
|
+
from urllib.parse import urlsplit
|
|
839
|
+
raw = run_store_url()
|
|
840
|
+
if raw is None:
|
|
841
|
+
return
|
|
842
|
+
try:
|
|
843
|
+
parts = urlsplit(raw)
|
|
844
|
+
port = parts.port
|
|
845
|
+
except ValueError as error:
|
|
846
|
+
raise ValueError(f'QUERYAPIGATE_RUN_STORE_URL is not a URL: {error}') from None
|
|
847
|
+
if parts.scheme not in ('http', 'https') or not parts.hostname:
|
|
848
|
+
raise ValueError("QUERYAPIGATE_RUN_STORE_URL must be http(s)://user:password@host:port/database - ClickHouse's "
|
|
849
|
+
'HTTP interface')
|
|
850
|
+
if parts.scheme == 'http' and parts.hostname not in ('localhost', '127.0.0.1', '::1'):
|
|
851
|
+
raise ValueError('QUERYAPIGATE_RUN_STORE_URL must be https:// unless ClickHouse is on this machine: it carries '
|
|
852
|
+
'a password and every run')
|
|
853
|
+
database = parts.path.strip('/') or 'queryapigate'
|
|
854
|
+
if not _RUN_STORE_DATABASE_RE.match(database):
|
|
855
|
+
raise ValueError(f"QUERYAPIGATE_RUN_STORE_URL: '{database}' is not a database name ClickHouse takes unquoted - "
|
|
856
|
+
'letters, digits and _')
|
|
857
|
+
if port is not None and not 0 < port < 65536:
|
|
858
|
+
raise ValueError('QUERYAPIGATE_RUN_STORE_URL: the port must be from 1 to 65535')
|
|
859
|
+
|
|
860
|
+
|
|
820
861
|
def redis_url():
|
|
821
862
|
"""QUERYAPIGATE_REDIS_URL: swaps the response cache (cache.py's ResponseCache) for a Redis-backed one
|
|
822
863
|
(rediscache.RedisResponseCache) that survives a process restart and can be shared across horizontally-
|
|
@@ -1362,6 +1403,10 @@ def describe_settings():
|
|
|
1362
1403
|
'database so several instances can run side by side.', 'QUERYAPIGATE_DATABASE_URL',
|
|
1363
1404
|
'SQLite (queryapigate.db)' if database_url() is None
|
|
1364
1405
|
else f'PostgreSQL ({redact_url(database_url())})', secret=True),
|
|
1406
|
+
row('Run store', 'Where run history, the event log and usage rollups are kept: the metadata store '
|
|
1407
|
+
'(default), or ClickHouse, for a data plane\'s rates (ADR 0029).', 'QUERYAPIGATE_RUN_STORE_URL',
|
|
1408
|
+
'the metadata store' if run_store_url() is None
|
|
1409
|
+
else f'ClickHouse ({redact_url(run_store_url())})', secret=True),
|
|
1365
1410
|
row('Load examples', 'Load the example APIs at startup. Idempotent.',
|
|
1366
1411
|
'QUERYAPIGATE_LOAD_EXAMPLES', on_off(load_examples())),
|
|
1367
1412
|
row('Configuration file', 'A configuration file applied at startup, once per file - connections, '
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/forms-
|
|
2
|
-
import{B as e,H as t,z as n}from"./client-ZHNiMG8H.js";import{D as r,M as i,P as a,c as o,d as s,g as c,k as l,s as u}from"./index-DBrO0zMK.js";import{t as d}from"./Time-DE-u5yL9.js";import{t as f}from"./QueryInfo-DB1HWOF1.js";import{a as p}from"./reach-BAOLd75Y.js";import{i as m,n as h,o as g,r as _}from"./SchemaBrowser-DLfU-4bj.js";var v=t(e(),1),y=n(),b=(0,v.lazy)(()=>a(()=>import(`./forms-DL4MJS43.js`).then(e=>({default:e.QueryForm})),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9]))),x={query:`Q`,collection:`C`,connection:`W`},S=[`Database`,`Created`,`Last modified`,`Last used`,`Version`],C=[`database`,`created`,`modified`,`lastUsed`,`version`],w=()=>new Date().toISOString().slice(0,10);function T(){let e=i(),{openDrawer:t}=l(),n=u(),a=o(),d=c(),x=s(),[S,C]=(0,v.useState)(m),w=e=>{let t={...S,...e};g(t),C(t)},T=x.data??[],D=e=>T.find(t=>t.name===e)?.db??null,O=h(S.conn||void 0),k=O.data?.tables??[],A=_(S.conn||void 0,D(S.conn),S.table&&!S.tableMatches?[S.table]:[]),j=S.table?S.tableMatches??(A.data?A.data[S.table]??[]:null):null,M=n.data??[],N=[...new Set(M.map(e=>D(e.connection_name)).filter(e=>!!e))].sort(),P=a.data??{},F=d.data??{},I=Object.keys(P).sort(),L=Object.keys(F).sort(),R,z=!0;S.conn?O.isPending?R=(0,y.jsx)(`option`,{value:``,children:`Loading tables…`}):O.isError?R=(0,y.jsx)(`option`,{value:``,children:`Schema unavailable`}):(z=!1,R=(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(`option`,{value:``,children:`All tables (${k.length})`}),k.map(e=>(0,y.jsx)(`option`,{value:e.name,children:`${e.name} · ${e.columns.length}${e.columns.length===1?` col`:` cols`}`},e.name))]})):R=(0,y.jsx)(`option`,{value:``,children:`Select a connection first`});let B=(e,t)=>p(e,t.name,t.collection,t.connection_name),V;if(n.isPending)V=(0,y.jsx)(r,{});else if(!M.length)V=(0,y.jsx)(`div`,{className:`empty`,children:(0,y.jsx)(`strong`,{children:`No saved queries yet`})});else if(!I.length&&!L.length)V=(0,y.jsxs)(`div`,{className:`empty`,children:[(0,y.jsx)(`strong`,{children:`No scoped API keys or roles yet`}),(0,y.jsx)(`span`,{children:`Every query is reachable by the admin key only until a scoped key is created.`})]});else{let n=S.q.trim().toLowerCase(),i=[...M].sort((e,t)=>(e.collection||``)+e.name<(t.collection||``)+t.name?-1:1),a=e=>e.toLowerCase().includes(n),o=n?i.filter(e=>a(e.name)||e.collection&&a(e.collection)):i,s=n?I.filter(a):I,c=n?L.filter(a):L,l=o.length||s.length||c.length,u=o.length?o:l?i:[],d=s.length?s:l?I:[],p=c.length?c:l?L:[];if(S.conn&&(u=u.filter(e=>e.connection_name===S.conn)),S.table&&j===null)V=(0,y.jsx)(r,{text:`Checking which queries reference ${S.table}…`});else if(j&&(u=u.filter(e=>j.includes(e.name))),u.length){let n=u.map(e=>({q:e,dbType:D(e.connection_name),reach:d.filter(t=>B(P[t],e).length).length}));S.db&&(n=n.filter(e=>e.dbType===S.db)),S.reach===`reachable`?n=n.filter(e=>e.reach>0):S.reach===`unreachable`&&(n=n.filter(e=>e.reach===0));let r=S.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,y.jsx)(E,{rows:n,keyCols:d,roleCols:p,keyMap:P,roleMap:F,st:S,onSort:e=>w({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,y.jsx)(f,{name:e.q.name,dbType:e.dbType})})}):(0,y.jsx)(`div`,{className:`empty`,children:(0,y.jsx)(`span`,{children:`No queries match these filters.`})})}else{let i=n?`Nothing matches “${n}”.`:S.table?`“${S.table}” on ${S.conn} is not exposed by any saved query yet.`:S.conn?`No saved query uses “${S.conn}” yet.`:`Nothing matches these filters.`;V=(0,y.jsxs)(`div`,{className:`empty`,children:[(0,y.jsx)(`span`,{children:i}),!n&&S.conn?(0,y.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:()=>{let n=D(S.conn)===`mongo`,i=S.table?n?JSON.stringify({collection:S.table,filter:{}},null,2):`SELECT * FROM `+S.table:void 0;e(`/queries`),t({title:`New API`,kicker:`POST /api/v1/queries`,content:(0,y.jsx)(v.Suspense,{fallback:(0,y.jsx)(r,{}),children:(0,y.jsx)(b,{starter:{connection:S.conn,sql:i}})})})},children:`New API`+(S.table?` on `+S.table:``)}):null]})}}return(0,y.jsxs)(y.Fragment,{children:[(0,y.jsxs)(`div`,{className:`page-head`,children:[(0,y.jsxs)(`div`,{className:`titles`,children:[(0,y.jsx)(`h1`,{children:`Access map`}),(0,y.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,y.jsxs)(`span`,{className:`legend`,children:[(0,y.jsx)(`span`,{className:`amap-dot`,children:`Q`}),` named query \xA0`,(0,y.jsx)(`span`,{className:`amap-dot`,children:`C`}),` `,`collection \xA0`,(0,y.jsx)(`span`,{className:`amap-dot conn`,children:`W`}),` whole connection`]})]})]}),(0,y.jsx)(`span`,{className:`spacer`}),(0,y.jsxs)(`select`,{id:`accessmap-conn-filter`,"aria-label":`Connection`,value:T.some(e=>e.name===S.conn)?S.conn:``,onChange:e=>w({conn:e.target.value,table:``,tableMatches:null}),children:[(0,y.jsx)(`option`,{value:``,children:`All connections`}),[...T].sort((e,t)=>e.name<t.name?-1:1).map(e=>(0,y.jsx)(`option`,{value:e.name,children:e.name},e.name))]}),(0,y.jsx)(`select`,{id:`accessmap-table-filter`,"aria-label":`Table`,disabled:z,value:k.some(e=>e.name===S.table)?S.table:``,onChange:e=>w({table:e.target.value,tableMatches:null}),children:R}),(0,y.jsxs)(`select`,{id:`accessmap-db-filter`,"aria-label":`Database`,value:N.includes(S.db)?S.db:``,onChange:e=>w({db:e.target.value}),children:[(0,y.jsx)(`option`,{value:``,children:`All databases`}),N.map(e=>(0,y.jsx)(`option`,{value:e,children:e},e))]}),(0,y.jsxs)(`select`,{id:`accessmap-reach-filter`,"aria-label":`Reach`,value:S.reach,onChange:e=>w({reach:e.target.value}),children:[(0,y.jsx)(`option`,{value:``,children:`Any reach`}),(0,y.jsx)(`option`,{value:`reachable`,children:`Reachable by a key`}),(0,y.jsx)(`option`,{value:`unreachable`,children:`Reachable by no key`})]}),(0,y.jsx)(`input`,{id:`accessmap-filter`,className:`search`,type:`search`,placeholder:`Filter by query or key…`,value:S.q,onChange:e=>w({q:e.target.value})})]}),(0,y.jsx)(`div`,{id:`accessmap-body`,className:`panel`,children:V})]})}function E({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,f=(e,t=null,n=null)=>u(e,t,n)?l.dir===1?` ▲`:` ▼`:``,m=(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})},h=(e,t,n)=>(0,y.jsx)(`th`,{className:t,onClick:()=>m(n),children:e+f(n)},n),g=(e,t,n,r)=>(0,y.jsxs)(`th`,{className:`amap-key-head amap-sortable`+(n?` revoked`:``),title:n?e+n:e,onClick:()=>m(`col`,r,e),children:[(0,y.jsxs)(`span`,{className:`amap-key-name`,children:[e,f(`col`,r,e),n?(0,y.jsx)(`span`,{className:`dim`,children:n}):null]}),(0,y.jsxs)(`span`,{className:`amap-key-sub`,children:[(t.allow_writes?`Read/write`:`Read-only`)+` · `+(t.rate_limit||`server default`),t.mask?.length?(0,y.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=p(e,t.name,t.collection,t.connection_name);if(!i.length)return(0,y.jsx)(`td`,{className:`amap-cell`},r);let a=[...new Set(i.map(e=>x[e.kind]))],o=a.length===1&&a[0]===`W`;return(0,y.jsx)(`td`,{className:`amap-cell`,title:i.map(e=>e.label).join(`, `),children:(0,y.jsx)(`span`,{className:`amap-dot`+(n?` role`:``)+(o?` conn`:``),children:a.join(`·`)})},r)},v=w();return(0,y.jsx)(`div`,{style:{overflow:`auto`},children:(0,y.jsxs)(`table`,{className:`amap`,children:[(0,y.jsxs)(`thead`,{children:[(0,y.jsxs)(`tr`,{children:[(0,y.jsx)(`th`,{colSpan:3+S.length}),t.length?(0,y.jsx)(`th`,{colSpan:t.length,className:`amap-group`,children:`API KEYS`}):null,n.length?(0,y.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,y.jsxs)(`tr`,{children:[h(`Query`,`amap-query amap-sortable`,`query`),C.map((e,t)=>h(S[t],`amap-sortable`,e)),h(`Connection`,`amap-sortable`,`connection`),h(`Reach`,`num amap-sortable`,`reach`),t.map(e=>{let t=r[e],n=!!(t.expires_at&&t.expires_at<v);return g(e,t,t.active?n?` (expired ${t.expires_at})`:``:` (revoked)`,`key`)}),n.map(e=>g(e,i[e],``,`role`))]})]}),(0,y.jsx)(`tbody`,{children:e.map(e=>{let a=e.q;return(0,y.jsxs)(`tr`,{children:[(0,y.jsxs)(`td`,{className:`amap-query`,children:[(0,y.jsxs)(`div`,{className:`amap-q-row`,children:[(0,y.jsx)(`button`,{type:`button`,className:`amap-q-name`,title:`Open ${a.name} in API Repository`,onClick:()=>s(a.name),children:a.name}),(0,y.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,y.jsx)(`span`,{className:`amap-coll`,children:a.collection}):null]}),(0,y.jsx)(`td`,{children:e.dbType?(0,y.jsx)(`span`,{className:`tag`,children:e.dbType}):(0,y.jsx)(`span`,{className:`dim`,children:`—`})}),(0,y.jsx)(`td`,{className:`mono dim`,children:(0,y.jsx)(d,{value:a.created_at})}),(0,y.jsx)(`td`,{className:`mono dim`,children:(0,y.jsx)(d,{value:a.updated_at})}),(0,y.jsx)(`td`,{className:a.last_used_at?`mono`:`mono dim`,children:(0,y.jsx)(d,{value:a.last_used_at,fallback:`never`})}),(0,y.jsx)(`td`,{className:`mono`,children:`v`+a.latest_version}),(0,y.jsx)(`td`,{className:`mono dim`,children:a.connection_name||`—`}),(0,y.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,y.jsx)(`tfoot`,{children:(0,y.jsxs)(`tr`,{children:[(0,y.jsx)(`td`,{className:`amap-query`,children:`Reaches`}),S.map(e=>(0,y.jsx)(`td`,{},e)),(0,y.jsx)(`td`,{}),(0,y.jsx)(`td`,{}),t.map(t=>(0,y.jsx)(`td`,{className:`num`,children:`${e.filter(e=>p(r[t],e.q.name,e.q.collection,e.q.connection_name).length).length} / ${e.length}`},t)),n.map(t=>(0,y.jsx)(`td`,{className:`num`,children:`${e.filter(e=>p(i[t],e.q.name,e.q.collection,e.q.connection_name).length).length} / ${e.length}`},t))]})})]})})}export{T as AccessMapPage};
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/forms-B7OrYKBb.js","assets/client-ZHNiMG8H.js","assets/index-Cl93xBjn.js","assets/index-Cv64XKNs.css","assets/CodeBox-Cwh5KHLp.js","assets/api-DIm7t7Bb.js","assets/useMutation-BIoTFTZ8.js","assets/maskRules-CvKQIPgN.js","assets/SchemaBrowser-8tmsr1__.js","assets/reach-BAOLd75Y.js"])))=>i.map(i=>d[i]);
|
|
2
|
+
import{B as e,H as t,z as n}from"./client-ZHNiMG8H.js";import{D as r,M as i,P as a,c as o,d as s,g as c,k as l,s as u}from"./index-Cl93xBjn.js";import{t as d}from"./Time-s5E5qK3s.js";import{t as f}from"./QueryInfo-Bv-0Zmc5.js";import{a as p}from"./reach-BAOLd75Y.js";import{i as m,n as h,o as g,r as _}from"./SchemaBrowser-8tmsr1__.js";var v=t(e(),1),y=n(),b=(0,v.lazy)(()=>a(()=>import(`./forms-B7OrYKBb.js`).then(e=>({default:e.QueryForm})),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9]))),x={query:`Q`,collection:`C`,connection:`W`},S=[`Database`,`Created`,`Last modified`,`Last used`,`Version`],C=[`database`,`created`,`modified`,`lastUsed`,`version`],w=()=>new Date().toISOString().slice(0,10);function T(){let e=i(),{openDrawer:t}=l(),n=u(),a=o(),d=c(),x=s(),[S,C]=(0,v.useState)(m),w=e=>{let t={...S,...e};g(t),C(t)},T=x.data??[],D=e=>T.find(t=>t.name===e)?.db??null,O=h(S.conn||void 0),k=O.data?.tables??[],A=_(S.conn||void 0,D(S.conn),S.table&&!S.tableMatches?[S.table]:[]),j=S.table?S.tableMatches??(A.data?A.data[S.table]??[]:null):null,M=n.data??[],N=[...new Set(M.map(e=>D(e.connection_name)).filter(e=>!!e))].sort(),P=a.data??{},F=d.data??{},I=Object.keys(P).sort(),L=Object.keys(F).sort(),R,z=!0;S.conn?O.isPending?R=(0,y.jsx)(`option`,{value:``,children:`Loading tables…`}):O.isError?R=(0,y.jsx)(`option`,{value:``,children:`Schema unavailable`}):(z=!1,R=(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(`option`,{value:``,children:`All tables (${k.length})`}),k.map(e=>(0,y.jsx)(`option`,{value:e.name,children:`${e.name} · ${e.columns.length}${e.columns.length===1?` col`:` cols`}`},e.name))]})):R=(0,y.jsx)(`option`,{value:``,children:`Select a connection first`});let B=(e,t)=>p(e,t.name,t.collection,t.connection_name),V;if(n.isPending)V=(0,y.jsx)(r,{});else if(!M.length)V=(0,y.jsx)(`div`,{className:`empty`,children:(0,y.jsx)(`strong`,{children:`No saved queries yet`})});else if(!I.length&&!L.length)V=(0,y.jsxs)(`div`,{className:`empty`,children:[(0,y.jsx)(`strong`,{children:`No scoped API keys or roles yet`}),(0,y.jsx)(`span`,{children:`Every query is reachable by the admin key only until a scoped key is created.`})]});else{let n=S.q.trim().toLowerCase(),i=[...M].sort((e,t)=>(e.collection||``)+e.name<(t.collection||``)+t.name?-1:1),a=e=>e.toLowerCase().includes(n),o=n?i.filter(e=>a(e.name)||e.collection&&a(e.collection)):i,s=n?I.filter(a):I,c=n?L.filter(a):L,l=o.length||s.length||c.length,u=o.length?o:l?i:[],d=s.length?s:l?I:[],p=c.length?c:l?L:[];if(S.conn&&(u=u.filter(e=>e.connection_name===S.conn)),S.table&&j===null)V=(0,y.jsx)(r,{text:`Checking which queries reference ${S.table}…`});else if(j&&(u=u.filter(e=>j.includes(e.name))),u.length){let n=u.map(e=>({q:e,dbType:D(e.connection_name),reach:d.filter(t=>B(P[t],e).length).length}));S.db&&(n=n.filter(e=>e.dbType===S.db)),S.reach===`reachable`?n=n.filter(e=>e.reach>0):S.reach===`unreachable`&&(n=n.filter(e=>e.reach===0));let r=S.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,y.jsx)(E,{rows:n,keyCols:d,roleCols:p,keyMap:P,roleMap:F,st:S,onSort:e=>w({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,y.jsx)(f,{name:e.q.name,dbType:e.dbType})})}):(0,y.jsx)(`div`,{className:`empty`,children:(0,y.jsx)(`span`,{children:`No queries match these filters.`})})}else{let i=n?`Nothing matches “${n}”.`:S.table?`“${S.table}” on ${S.conn} is not exposed by any saved query yet.`:S.conn?`No saved query uses “${S.conn}” yet.`:`Nothing matches these filters.`;V=(0,y.jsxs)(`div`,{className:`empty`,children:[(0,y.jsx)(`span`,{children:i}),!n&&S.conn?(0,y.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:()=>{let n=D(S.conn)===`mongo`,i=S.table?n?JSON.stringify({collection:S.table,filter:{}},null,2):`SELECT * FROM `+S.table:void 0;e(`/queries`),t({title:`New API`,kicker:`POST /api/v1/queries`,content:(0,y.jsx)(v.Suspense,{fallback:(0,y.jsx)(r,{}),children:(0,y.jsx)(b,{starter:{connection:S.conn,sql:i}})})})},children:`New API`+(S.table?` on `+S.table:``)}):null]})}}return(0,y.jsxs)(y.Fragment,{children:[(0,y.jsxs)(`div`,{className:`page-head`,children:[(0,y.jsxs)(`div`,{className:`titles`,children:[(0,y.jsx)(`h1`,{children:`Access map`}),(0,y.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,y.jsxs)(`span`,{className:`legend`,children:[(0,y.jsx)(`span`,{className:`amap-dot`,children:`Q`}),` named query \xA0`,(0,y.jsx)(`span`,{className:`amap-dot`,children:`C`}),` `,`collection \xA0`,(0,y.jsx)(`span`,{className:`amap-dot conn`,children:`W`}),` whole connection`]})]})]}),(0,y.jsx)(`span`,{className:`spacer`}),(0,y.jsxs)(`select`,{id:`accessmap-conn-filter`,"aria-label":`Connection`,value:T.some(e=>e.name===S.conn)?S.conn:``,onChange:e=>w({conn:e.target.value,table:``,tableMatches:null}),children:[(0,y.jsx)(`option`,{value:``,children:`All connections`}),[...T].sort((e,t)=>e.name<t.name?-1:1).map(e=>(0,y.jsx)(`option`,{value:e.name,children:e.name},e.name))]}),(0,y.jsx)(`select`,{id:`accessmap-table-filter`,"aria-label":`Table`,disabled:z,value:k.some(e=>e.name===S.table)?S.table:``,onChange:e=>w({table:e.target.value,tableMatches:null}),children:R}),(0,y.jsxs)(`select`,{id:`accessmap-db-filter`,"aria-label":`Database`,value:N.includes(S.db)?S.db:``,onChange:e=>w({db:e.target.value}),children:[(0,y.jsx)(`option`,{value:``,children:`All databases`}),N.map(e=>(0,y.jsx)(`option`,{value:e,children:e},e))]}),(0,y.jsxs)(`select`,{id:`accessmap-reach-filter`,"aria-label":`Reach`,value:S.reach,onChange:e=>w({reach:e.target.value}),children:[(0,y.jsx)(`option`,{value:``,children:`Any reach`}),(0,y.jsx)(`option`,{value:`reachable`,children:`Reachable by a key`}),(0,y.jsx)(`option`,{value:`unreachable`,children:`Reachable by no key`})]}),(0,y.jsx)(`input`,{id:`accessmap-filter`,className:`search`,type:`search`,placeholder:`Filter by query or key…`,value:S.q,onChange:e=>w({q:e.target.value})})]}),(0,y.jsx)(`div`,{id:`accessmap-body`,className:`panel`,children:V})]})}function E({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,f=(e,t=null,n=null)=>u(e,t,n)?l.dir===1?` ▲`:` ▼`:``,m=(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})},h=(e,t,n)=>(0,y.jsx)(`th`,{className:t,onClick:()=>m(n),children:e+f(n)},n),g=(e,t,n,r)=>(0,y.jsxs)(`th`,{className:`amap-key-head amap-sortable`+(n?` revoked`:``),title:n?e+n:e,onClick:()=>m(`col`,r,e),children:[(0,y.jsxs)(`span`,{className:`amap-key-name`,children:[e,f(`col`,r,e),n?(0,y.jsx)(`span`,{className:`dim`,children:n}):null]}),(0,y.jsxs)(`span`,{className:`amap-key-sub`,children:[(t.allow_writes?`Read/write`:`Read-only`)+` · `+(t.rate_limit||`server default`),t.mask?.length?(0,y.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=p(e,t.name,t.collection,t.connection_name);if(!i.length)return(0,y.jsx)(`td`,{className:`amap-cell`},r);let a=[...new Set(i.map(e=>x[e.kind]))],o=a.length===1&&a[0]===`W`;return(0,y.jsx)(`td`,{className:`amap-cell`,title:i.map(e=>e.label).join(`, `),children:(0,y.jsx)(`span`,{className:`amap-dot`+(n?` role`:``)+(o?` conn`:``),children:a.join(`·`)})},r)},v=w();return(0,y.jsx)(`div`,{style:{overflow:`auto`},children:(0,y.jsxs)(`table`,{className:`amap`,children:[(0,y.jsxs)(`thead`,{children:[(0,y.jsxs)(`tr`,{children:[(0,y.jsx)(`th`,{colSpan:3+S.length}),t.length?(0,y.jsx)(`th`,{colSpan:t.length,className:`amap-group`,children:`API KEYS`}):null,n.length?(0,y.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,y.jsxs)(`tr`,{children:[h(`Query`,`amap-query amap-sortable`,`query`),C.map((e,t)=>h(S[t],`amap-sortable`,e)),h(`Connection`,`amap-sortable`,`connection`),h(`Reach`,`num amap-sortable`,`reach`),t.map(e=>{let t=r[e],n=!!(t.expires_at&&t.expires_at<v);return g(e,t,t.active?n?` (expired ${t.expires_at})`:``:` (revoked)`,`key`)}),n.map(e=>g(e,i[e],``,`role`))]})]}),(0,y.jsx)(`tbody`,{children:e.map(e=>{let a=e.q;return(0,y.jsxs)(`tr`,{children:[(0,y.jsxs)(`td`,{className:`amap-query`,children:[(0,y.jsxs)(`div`,{className:`amap-q-row`,children:[(0,y.jsx)(`button`,{type:`button`,className:`amap-q-name`,title:`Open ${a.name} in API Repository`,onClick:()=>s(a.name),children:a.name}),(0,y.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,y.jsx)(`span`,{className:`amap-coll`,children:a.collection}):null]}),(0,y.jsx)(`td`,{children:e.dbType?(0,y.jsx)(`span`,{className:`tag`,children:e.dbType}):(0,y.jsx)(`span`,{className:`dim`,children:`—`})}),(0,y.jsx)(`td`,{className:`mono dim`,children:(0,y.jsx)(d,{value:a.created_at})}),(0,y.jsx)(`td`,{className:`mono dim`,children:(0,y.jsx)(d,{value:a.updated_at})}),(0,y.jsx)(`td`,{className:a.last_used_at?`mono`:`mono dim`,children:(0,y.jsx)(d,{value:a.last_used_at,fallback:`never`})}),(0,y.jsx)(`td`,{className:`mono`,children:`v`+a.latest_version}),(0,y.jsx)(`td`,{className:`mono dim`,children:a.connection_name||`—`}),(0,y.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,y.jsx)(`tfoot`,{children:(0,y.jsxs)(`tr`,{children:[(0,y.jsx)(`td`,{className:`amap-query`,children:`Reaches`}),S.map(e=>(0,y.jsx)(`td`,{},e)),(0,y.jsx)(`td`,{}),(0,y.jsx)(`td`,{}),t.map(t=>(0,y.jsx)(`td`,{className:`num`,children:`${e.filter(e=>p(r[t],e.q.name,e.q.collection,e.q.connection_name).length).length} / ${e.length}`},t)),n.map(t=>(0,y.jsx)(`td`,{className:`num`,children:`${e.filter(e=>p(i[t],e.q.name,e.q.collection,e.q.connection_name).length).length} / ${e.length}`},t))]})})]})})}export{T as AccessMapPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{B as e,H as t,R as n,c as r,h as i,l as a,n as o,s,t as c,v as l,z as u}from"./client-ZHNiMG8H.js";import{t as d}from"./useMutation-BHDQeA9M.js";import{D as f,E as p,T as m,f as h,h as g,k as _,l as v}from"./index-DBrO0zMK.js";import{t as y}from"./Time-DE-u5yL9.js";import{t as b}from"./forms-BOFRAueI.js";import{n as x,t as S}from"./icons-CFXE8hAz.js";var C=t(e(),1),w=u(),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=>e===null?`Workspaces only`: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}=_();return t=>t instanceof c?e(t.message,{status:t.status}):e(t.message)}function j(e){let t=h(`admins.read`,`other administrators`);return l({queryKey:[`administrators`],queryFn:async()=>(await t.check(),s(await o.GET(`/api/v1/administrators`)).items),enabled:e,retry:!1})}function M(e){return l({queryKey:[`administrators`,e,`tokens`],queryFn:async()=>s(await o.GET(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:e}}})).items,retry:!1})}function N(){let e=g(),t=v(),n=!!e.data&&t(`admins.read`),r=j(n),{openDrawer:i}=_(),a=()=>i({title:`New administrator`,content:(0,w.jsx)(I,{})}),o;return o=e.isPending?(0,w.jsx)(f,{}):e.isError?(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`Not signed in as an administrator`}),(0,w.jsx)(`span`,{children:`Apply an admin token (qagadm_…) or the shared admin key in the box at the bottom of the sidebar.`})]}):n?r.isPending?(0,w.jsx)(f,{text:`Loading administrators…`}):r.isError?(0,w.jsx)(`div`,{className:`hint`,children:r.error.message}):r.data.length?(0,w.jsx)(P,{items:r.data,self:e.data.via===`token`||e.data.via===`sso`?e.data.name:null}):(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`No administrators yet`}),(0,w.jsx)(`span`,{children:`Everyone shares QUERYAPIGATE_API_KEY today, so the audit log can only say “admin”. Give each person their own sign-in, with a role that fits what they do — then the shared key becomes a break-glass key.`}),(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:a,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:a,children:`New administrator`})]}),(0,w.jsx)(`div`,{id:`administrators-table`,className:`panel`,children:o})]})}function P({items:e,self:t}){let{openDrawer:i,toast:a}=_(),s=A(),c=n(),l=async e=>{if(window.confirm(`Remove administrator '${e}'? Every token of theirs stops working immediately.`))try{r(await o.DELETE(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),a(`Removed `+e),c.invalidateQueries({queryKey:[`administrators`]})}catch(e){s(e)}};return(0,w.jsx)(`div`,{style:{overflowX:`auto`},children:(0,w.jsxs)(`table`,{className:`grid`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsx)(`tr`,{children:[`Name`,`Role`,`Email`,`Last seen`,`Tokens`,``].map((e,t)=>(0,w.jsx)(`th`,{children:e},t))})}),(0,w.jsx)(`tbody`,{children:e.map(e=>(0,w.jsxs)(`tr`,{"data-name":e.name,children:[(0,w.jsx)(`td`,{children:(0,w.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,w.jsx)(`span`,{className:`dot ${e.active?`ok`:`off`}`,title:e.active?`Active`:`Inactive`}),(0,w.jsx)(`span`,{className:`name`,children:e.name}),e.name===t&&(0,w.jsx)(`span`,{className:`tag`,children:`you`}),e.sso?.status===`linked`&&(0,w.jsx)(`span`,{className:`tag`,title:`Signs in through ${e.sso.issuer??`SSO`}; role from their groups`,children:`SSO`}),e.sso?.status===`pending`&&(0,w.jsx)(`span`,{className:`tag`,title:`Linked at their next SSO sign-in, by their verified email`,children:`SSO pending`})]})}),(0,w.jsx)(`td`,{children:E(e.role)}),(0,w.jsx)(`td`,{className:e.email?void 0:`dim`,children:e.email??`—`}),(0,w.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,w.jsx)(y,{value:e.last_seen_at,fallback:`never`})}),(0,w.jsx)(`td`,{className:`mono`,children:e.tokens}),(0,w.jsx)(`td`,{children:(0,w.jsxs)(`div`,{className:`actions`,children:[(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>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 l(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=l({queryKey:[`administrators`,`detail`,e],queryFn:async()=>a(await o.GET(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),enabled:!!e,retry:!1,gcTime:0});return e&&t.isPending?(0,w.jsx)(f,{}):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:e,existing:t,etag:r}){let a=!!e,{closeDrawer:c,openDrawer:l,showError:u,toast:f}=_(),h=A(),g=n(),[v,y]=(0,C.useState)(e??``),[x,S]=(0,C.useState)(t?.role??`developer`),[E,j]=(0,C.useState)(t?.email??``),[M,N]=(0,C.useState)(!t||t.active),P=i(),F=t?.sso?.status===`linked`,[I,L]=(0,C.useState)(t?.sso?.status??`none`),R=d({mutationFn:async()=>{let n={role:x,email:E.trim()||null};if(a)return s(await o.PATCH(`/api/v1/administrators/{name}`,{params:{path:{name:e},header:r?{"If-Match":r}:{}},body:{...F?{email:n.email}:n,active:M,...I===(t?.sso?.status??`none`)?{}:{sso:I===`pending`?`pending`:null}}})),null;let i=s(await o.POST(`/api/v1/administrators`,{body:{name:v.trim(),...n}}));return s(await o.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:i.name}},body:{label:`first token`,expires_at:O(D)}}))},onSuccess:t=>{if(u(``),g.invalidateQueries({queryKey:[`administrators`]}),!t){c(),f(`Updated `+e);return}l({title:`Administrator created`,kicker:t.admin,content:(0,w.jsx)(b,{secret:t.secret,label:`Admin token for ${t.admin} (expires ${t.expires_at})`,hint:k})})},onError:h});return(0,w.jsxs)(`form`,{className:`form`,noValidate:!0,onSubmit:e=>{if(e.preventDefault(),!a&&!v.trim()){u(`A name is required.`,{errors:{name:`This field is required`}});return}R.mutate()},children:[(0,w.jsx)(m,{id:`a-name`,label:`Name`,hint:a?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:a,autoFocus:!a,onChange:e=>y(e.target.value)})}),(0,w.jsx)(m,{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)(m,{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)})}),a&&(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`})]}),a&&P.data?.sso&&(0,w.jsx)(m,{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`})]})}),!a&&(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)(p,{onCancel:c,children:(0,w.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:R.isPending,children:a?`Save`:`Create`})})]})}function R({name:e}){let t=M(e),i=n(),{toast:a}=_(),c=A(),[l,u]=(0,C.useState)(``),[p,h]=(0,C.useState)(O(D)),[g,v]=(0,C.useState)(null),x=()=>void i.invalidateQueries({queryKey:[`administrators`]}),S=d({mutationFn:async()=>s(await o.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:e}},body:{label:l.trim()||null,expires_at:p||null}})),onSuccess:e=>{v(e),u(``),x()},onError:c}),T=async t=>{if(window.confirm(`Revoke ${t.label?`'${t.label}'`:t.id}? Anything using it stops working now.`))try{r(await o.DELETE(`/api/v1/administrators/{name}/tokens/{token_id}`,{params:{path:{name:e,token_id:t.id}}})),a(`Revoked `+(t.label??t.id)),x()}catch(e){c(e)}},E=new Date().toISOString().slice(0,10);return(0,w.jsxs)(`div`,{className:`form`,id:`admin-tokens`,children:[t.isPending?(0,w.jsx)(f,{text:`Loading tokens…`}):t.isError?(0,w.jsx)(`div`,{className:`hint`,children:t.error.message}):t.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:t.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.`}),g?(0,w.jsx)(b,{secret:g.secret,label:`New token${g.label?` '${g.label}'`:``} (${g.expires_at?`expires ${g.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)(m,{id:`t-label`,label:`New token: label`,hint:`What it's for - laptop, ci.`,children:(0,w.jsx)(`input`,{id:`t-label`,value:l,autoComplete:`off`,onChange:e=>u(e.target.value)})}),(0,w.jsx)(m,{id:`t-expires`,label:`Expires`,hint:`Leave blank for no expiry.`,children:(0,w.jsx)(`input`,{id:`t-expires`,type:`date`,value:p,onChange:e=>h(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{B as e,H as t,R as n,c as r,h as i,l as a,n as o,s,t as c,v as l,z as u}from"./client-ZHNiMG8H.js";import{t as d}from"./useMutation-BIoTFTZ8.js";import{D as f,E as p,T as m,f as h,h as g,k as _,l as v}from"./index-Cl93xBjn.js";import{t as y}from"./Time-s5E5qK3s.js";import{t as b}from"./forms-CTAVu1q5.js";import{n as x,t as S}from"./icons-CFXE8hAz.js";var C=t(e(),1),w=u(),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=>e===null?`Workspaces only`: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}=_();return t=>t instanceof c?e(t.message,{status:t.status}):e(t.message)}function j(e){let t=h(`admins.read`,`other administrators`);return l({queryKey:[`administrators`],queryFn:async()=>(await t.check(),s(await o.GET(`/api/v1/administrators`)).items),enabled:e,retry:!1})}function M(e){return l({queryKey:[`administrators`,e,`tokens`],queryFn:async()=>s(await o.GET(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:e}}})).items,retry:!1})}function N(){let e=g(),t=v(),n=!!e.data&&t(`admins.read`),r=j(n),{openDrawer:i}=_(),a=()=>i({title:`New administrator`,content:(0,w.jsx)(I,{})}),o;return o=e.isPending?(0,w.jsx)(f,{}):e.isError?(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`Not signed in as an administrator`}),(0,w.jsx)(`span`,{children:`Apply an admin token (qagadm_…) or the shared admin key in the box at the bottom of the sidebar.`})]}):n?r.isPending?(0,w.jsx)(f,{text:`Loading administrators…`}):r.isError?(0,w.jsx)(`div`,{className:`hint`,children:r.error.message}):r.data.length?(0,w.jsx)(P,{items:r.data,self:e.data.via===`token`||e.data.via===`sso`?e.data.name:null}):(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`No administrators yet`}),(0,w.jsx)(`span`,{children:`Everyone shares QUERYAPIGATE_API_KEY today, so the audit log can only say “admin”. Give each person their own sign-in, with a role that fits what they do — then the shared key becomes a break-glass key.`}),(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:a,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:a,children:`New administrator`})]}),(0,w.jsx)(`div`,{id:`administrators-table`,className:`panel`,children:o})]})}function P({items:e,self:t}){let{openDrawer:i,toast:a}=_(),s=A(),c=n(),l=async e=>{if(window.confirm(`Remove administrator '${e}'? Every token of theirs stops working immediately.`))try{r(await o.DELETE(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),a(`Removed `+e),c.invalidateQueries({queryKey:[`administrators`]})}catch(e){s(e)}};return(0,w.jsx)(`div`,{style:{overflowX:`auto`},children:(0,w.jsxs)(`table`,{className:`grid`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsx)(`tr`,{children:[`Name`,`Role`,`Email`,`Last seen`,`Tokens`,``].map((e,t)=>(0,w.jsx)(`th`,{children:e},t))})}),(0,w.jsx)(`tbody`,{children:e.map(e=>(0,w.jsxs)(`tr`,{"data-name":e.name,children:[(0,w.jsx)(`td`,{children:(0,w.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,w.jsx)(`span`,{className:`dot ${e.active?`ok`:`off`}`,title:e.active?`Active`:`Inactive`}),(0,w.jsx)(`span`,{className:`name`,children:e.name}),e.name===t&&(0,w.jsx)(`span`,{className:`tag`,children:`you`}),e.sso?.status===`linked`&&(0,w.jsx)(`span`,{className:`tag`,title:`Signs in through ${e.sso.issuer??`SSO`}; role from their groups`,children:`SSO`}),e.sso?.status===`pending`&&(0,w.jsx)(`span`,{className:`tag`,title:`Linked at their next SSO sign-in, by their verified email`,children:`SSO pending`})]})}),(0,w.jsx)(`td`,{children:E(e.role)}),(0,w.jsx)(`td`,{className:e.email?void 0:`dim`,children:e.email??`—`}),(0,w.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,w.jsx)(y,{value:e.last_seen_at,fallback:`never`})}),(0,w.jsx)(`td`,{className:`mono`,children:e.tokens}),(0,w.jsx)(`td`,{children:(0,w.jsxs)(`div`,{className:`actions`,children:[(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>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 l(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=l({queryKey:[`administrators`,`detail`,e],queryFn:async()=>a(await o.GET(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),enabled:!!e,retry:!1,gcTime:0});return e&&t.isPending?(0,w.jsx)(f,{}):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:e,existing:t,etag:r}){let a=!!e,{closeDrawer:c,openDrawer:l,showError:u,toast:f}=_(),h=A(),g=n(),[v,y]=(0,C.useState)(e??``),[x,S]=(0,C.useState)(t?.role??`developer`),[E,j]=(0,C.useState)(t?.email??``),[M,N]=(0,C.useState)(!t||t.active),P=i(),F=t?.sso?.status===`linked`,[I,L]=(0,C.useState)(t?.sso?.status??`none`),R=d({mutationFn:async()=>{let n={role:x,email:E.trim()||null};if(a)return s(await o.PATCH(`/api/v1/administrators/{name}`,{params:{path:{name:e},header:r?{"If-Match":r}:{}},body:{...F?{email:n.email}:n,active:M,...I===(t?.sso?.status??`none`)?{}:{sso:I===`pending`?`pending`:null}}})),null;let i=s(await o.POST(`/api/v1/administrators`,{body:{name:v.trim(),...n}}));return s(await o.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:i.name}},body:{label:`first token`,expires_at:O(D)}}))},onSuccess:t=>{if(u(``),g.invalidateQueries({queryKey:[`administrators`]}),!t){c(),f(`Updated `+e);return}l({title:`Administrator created`,kicker:t.admin,content:(0,w.jsx)(b,{secret:t.secret,label:`Admin token for ${t.admin} (expires ${t.expires_at})`,hint:k})})},onError:h});return(0,w.jsxs)(`form`,{className:`form`,noValidate:!0,onSubmit:e=>{if(e.preventDefault(),!a&&!v.trim()){u(`A name is required.`,{errors:{name:`This field is required`}});return}R.mutate()},children:[(0,w.jsx)(m,{id:`a-name`,label:`Name`,hint:a?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:a,autoFocus:!a,onChange:e=>y(e.target.value)})}),(0,w.jsx)(m,{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)(m,{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)})}),a&&(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`})]}),a&&P.data?.sso&&(0,w.jsx)(m,{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`})]})}),!a&&(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)(p,{onCancel:c,children:(0,w.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:R.isPending,children:a?`Save`:`Create`})})]})}function R({name:e}){let t=M(e),i=n(),{toast:a}=_(),c=A(),[l,u]=(0,C.useState)(``),[p,h]=(0,C.useState)(O(D)),[g,v]=(0,C.useState)(null),x=()=>void i.invalidateQueries({queryKey:[`administrators`]}),S=d({mutationFn:async()=>s(await o.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:e}},body:{label:l.trim()||null,expires_at:p||null}})),onSuccess:e=>{v(e),u(``),x()},onError:c}),T=async t=>{if(window.confirm(`Revoke ${t.label?`'${t.label}'`:t.id}? Anything using it stops working now.`))try{r(await o.DELETE(`/api/v1/administrators/{name}/tokens/{token_id}`,{params:{path:{name:e,token_id:t.id}}})),a(`Revoked `+(t.label??t.id)),x()}catch(e){c(e)}},E=new Date().toISOString().slice(0,10);return(0,w.jsxs)(`div`,{className:`form`,id:`admin-tokens`,children:[t.isPending?(0,w.jsx)(f,{text:`Loading tokens…`}):t.isError?(0,w.jsx)(`div`,{className:`hint`,children:t.error.message}):t.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:t.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.`}),g?(0,w.jsx)(b,{secret:g.secret,label:`New token${g.label?` '${g.label}'`:``} (${g.expires_at?`expires ${g.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)(m,{id:`t-label`,label:`New token: label`,hint:`What it's for - laptop, ci.`,children:(0,w.jsx)(`input`,{id:`t-label`,value:l,autoComplete:`off`,onChange:e=>u(e.target.value)})}),(0,w.jsx)(m,{id:`t-expires`,label:`Expires`,hint:`Leave blank for no expiry.`,children:(0,w.jsx)(`input`,{id:`t-expires`,type:`date`,value:p,onChange:e=>h(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{B as e,H as t,n,s as r,v as i,z as a}from"./client-ZHNiMG8H.js";import{D as o}from"./index-
|
|
1
|
+
import{B as e,H as t,n,s as r,v as i,z as a}from"./client-ZHNiMG8H.js";import{D as o}from"./index-Cl93xBjn.js";import{t as s}from"./Time-s5E5qK3s.js";import{t as c}from"./CodeBox-Cwh5KHLp.js";var l=t(e(),1),u=a(),d=[{id:`1h`,label:`Last hour`,hours:1},{id:`24h`,label:`Last 24 hours`,hours:24},{id:`7d`,label:`Last 7 days`,hours:168},{id:`30d`,label:`Last 30 days`,hours:720}],f=e=>e.toLocaleString();function p({refusals:e}){let t=Object.entries(e);return t.length?(0,u.jsx)(`span`,{style:{display:`inline-flex`,flexWrap:`wrap`,gap:4},children:t.map(([e,t])=>(0,u.jsx)(`span`,{className:`tag`,title:`${t} call${t===1?``:`s`} failed with ${e}`,children:`${e} × ${t}`},e))}):(0,u.jsx)(`span`,{className:`dim`,children:`none`})}function m({writes:e}){let t=Object.entries(e??{}).filter(([,e])=>e).map(([e,t])=>`${t} ${e}`);return(0,u.jsx)(`div`,{className:`hint`,children:t.length?t.join(` · `):`None.`})}function h({agent:e}){let t=Object.entries(e.queries);return(0,u.jsxs)(`div`,{className:`agent-detail`,children:[(0,u.jsx)(`p`,{className:`sub-h`,children:`Writes asked for`}),(0,u.jsx)(m,{writes:e.writes}),(0,u.jsx)(`p`,{className:`sub-h`,style:{marginTop:12},children:`Saved queries`}),t.length?(0,u.jsx)(`div`,{className:`hint`,children:t.map(([e,t])=>`${e} × ${t}`).join(` · `)}):(0,u.jsx)(`div`,{className:`hint`,children:`None - only ad-hoc SQL.`}),(0,u.jsx)(`p`,{className:`sub-h`,style:{marginTop:12},children:`Most frequent ad-hoc statements`}),e.top_statements.length?e.top_statements.map((e,t)=>(0,u.jsxs)(`div`,{className:`agent-statement`,children:[(0,u.jsx)(`div`,{className:`hint`,children:`${e.count} call${e.count===1?``:`s`} · ${f(e.rows)} rows`+(e.errors?` · ${e.errors} failed`:``)}),e.sql?(0,u.jsx)(c,{sql:e.sql}):(0,u.jsx)(`span`,{className:`mono dim`,children:`sha256 ${e.sql_sha256}`})]},t)):(0,u.jsx)(`div`,{className:`hint`,children:e.adhoc_calls?`Run history keeps no SQL text here (QUERYAPIGATE_HISTORY_ADHOC_SQL is none).`:`No ad-hoc SQL.`})]})}function g(){let[e,t]=(0,l.useState)(`24h`),[a,c]=(0,l.useState)(null),m=d.find(t=>t.id===e).hours,g=i({queryKey:[`usage`,`agents`,e],queryFn:async()=>r(await n.GET(`/api/v1/usage/agents`,{params:{query:{hours:m}}})),retry:!1}),_=g.data?.items??[],v;v=g.isPending?(0,u.jsx)(o,{text:`Loading agents…`}):g.isError?(0,u.jsxs)(`div`,{className:`empty`,children:[(0,u.jsx)(`strong`,{children:`Couldn’t load agent usage`}),(0,u.jsx)(`span`,{children:g.error.message})]}):_.length?(0,u.jsx)(`div`,{style:{overflowX:`auto`},children:(0,u.jsxs)(`table`,{className:`grid`,children:[(0,u.jsx)(`thead`,{children:(0,u.jsx)(`tr`,{children:[`Agent`,`Calls`,`Rows`,`Failed`,`Masked`,`Refused or failed with`,`Last seen`].map(e=>(0,u.jsx)(`th`,{children:e},e))})}),(0,u.jsx)(`tbody`,{children:_.map(e=>(0,u.jsxs)(l.Fragment,{children:[(0,u.jsxs)(`tr`,{children:[(0,u.jsxs)(`td`,{children:[(0,u.jsx)(`button`,{type:`button`,className:`agent-name`,"aria-expanded":a===e.agent,onClick:()=>c(a===e.agent?null:e.agent),children:e.agent}),(0,u.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:e.connections.join(`, `)})]}),(0,u.jsx)(`td`,{className:`num`,children:f(e.calls)}),(0,u.jsx)(`td`,{className:`num`,children:f(e.rows)}),(0,u.jsx)(`td`,{className:`num`,children:f(e.errors)}),(0,u.jsx)(`td`,{className:`num`,children:f(e.masked_calls)}),(0,u.jsx)(`td`,{children:(0,u.jsx)(p,{refusals:e.refusals})}),(0,u.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,u.jsx)(s,{value:e.last_seen,fallback:``})})]}),a===e.agent&&(0,u.jsx)(`tr`,{children:(0,u.jsx)(`td`,{colSpan:7,children:(0,u.jsx)(h,{agent:e})})})]},e.agent))})]})}):(0,u.jsxs)(`div`,{className:`empty`,children:[(0,u.jsx)(`strong`,{children:`No agent called the MCP server in this window`}),(0,u.jsx)(`span`,{children:`Runs over MCP - saved-query tools and execute_sql - appear here, per API key or signed-in user.`})]});let y=[];return g.data&&g.data.sample_rate<1&&y.push(`Run history keeps ${Math.round(g.data.sample_rate*100)}% of successful runs, so successful calls and rows are a sample; failures are all here.`),g.data?.truncated&&y.push(`More runs than one summary reads (100,000) - narrow the window.`),(0,u.jsxs)(u.Fragment,{children:[(0,u.jsxs)(`div`,{className:`page-head`,children:[(0,u.jsxs)(`div`,{className:`titles`,children:[(0,u.jsx)(`h1`,{children:`Agents`}),(0,u.jsx)(`span`,{className:`sub`,children:`What each AI agent did over MCP - per API key or signed-in user, from run history.`})]}),(0,u.jsx)(`span`,{className:`spacer`}),(0,u.jsx)(`button`,{type:`button`,className:`btn`,onClick:()=>void g.refetch(),children:`Refresh`})]}),(0,u.jsxs)(`div`,{className:`panel`,style:{overflow:`hidden`},children:[(0,u.jsxs)(`div`,{className:`panel-bar`,children:[(0,u.jsx)(`select`,{"aria-label":`Window`,value:e,onChange:e=>t(e.target.value),children:d.map(e=>(0,u.jsx)(`option`,{value:e.id,children:e.label},e.id))}),(0,u.jsx)(`span`,{className:`spacer`}),(0,u.jsx)(`span`,{className:`panel-count`,children:g.data?`${_.length} agent${_.length===1?``:`s`}`:``})]}),y.map(e=>(0,u.jsx)(`div`,{className:`hint`,style:{padding:`6px 12px`},children:e},e)),v]})]})}export{g as AgentsPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{B as e,H as t,z as n}from"./client-ZHNiMG8H.js";import{C as r,D as i,M as a,N as o,S as s,x as c,y as l}from"./index-
|
|
1
|
+
import{B as e,H as t,z as n}from"./client-ZHNiMG8H.js";import{C as r,D as i,M as a,N as o,S as s,x as c,y as l}from"./index-Cl93xBjn.js";import{t as u}from"./Time-s5E5qK3s.js";import{n as d}from"./state-O13cl7cO.js";var f=t(e(),1),p=n(),m=[{id:`critical`,label:`Critical`},{id:`warning`,label:`Warning`},{id:`info`,label:`For your information`}],h={key:`Open API keys`,query:`Open query`,connection:`Open connections`,settings:`Open settings`,export:`Open exports`,agent:`Open agents`,approvals:`Open approvals`,audit:`Open audit log`};function g(){let e=a();return t=>{t.type===`query`?e(`/queries/${encodeURIComponent(t.name)}`):t.type===`key`?e(`/api-keys`):t.type===`connection`?e(`/connections`):t.type===`export`?e(`/exports`):t.type===`agent`?e(`/agents`):t.type===`approvals`?e(`/approvals`):t.type===`audit`?e(`/audit-log`):(d(t.name),e(`/settings`))}}function _({alert:e,dismissed:t}){let n=g();return(0,p.jsxs)(`div`,{className:`alert-row `+e.severity,"data-alert":e.id,children:[(0,p.jsx)(`span`,{className:`sev `+e.severity,children:e.severity}),(0,p.jsxs)(`div`,{className:`alert-what`,children:[(0,p.jsx)(`strong`,{children:e.title}),(0,p.jsx)(`span`,{className:`alert-detail`,children:e.detail}),e.since&&!e.detail.includes(e.since)&&(0,p.jsxs)(`span`,{className:`alert-since`,children:[e.kind===`key_expiring`?`Expires `:`Since `,(0,p.jsx)(u,{value:e.since})]})]}),(0,p.jsxs)(`div`,{className:`actions`,children:[e.target&&(0,p.jsx)(`button`,{type:`button`,className:`btn sm outlined`,onClick:()=>n(e.target),children:h[e.target.type]??`Open`}),t?(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>s(e.id),children:`Restore`}):(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,title:`Hide it in this browser until it clears`,onClick:()=>c(e.id),children:`Dismiss`})]})]})}function v({alerts:e}){return(0,p.jsx)(p.Fragment,{children:m.map(({id:t,label:n})=>{let r=e.filter(e=>e.severity===t);return r.length?(0,p.jsxs)(`div`,{className:`panel alerts-group`,id:`alerts-`+t,children:[(0,p.jsxs)(`h2`,{children:[n,` `,(0,p.jsx)(`span`,{className:`count`,children:r.length})]}),r.map(e=>(0,p.jsx)(_,{alert:e},e.id))]},t):null})})}function y(){let e=a(),t=o(),n=l.find(e=>e.id===t.tab),{query:s,active:c,dismissed:d}=r(),[m,h]=(0,f.useState)(!1),g=e=>!n||n.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)(i,{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:[n&&(0,p.jsx)(`p`,{className:`alerts-about`,children:n.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:n?`Nothing to report from this check right now.`:`Nothing needs your attention right now.`})]})}),b.length>0&&(0,p.jsxs)(`div`,{className:`alerts-dismissed`,children:[(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>h(e=>!e),children:`${b.length} dismissed in this browser · ${m?`Hide`:`Show`}`}),m&&(0,p.jsx)(`div`,{className:`panel alerts-group`,id:`alerts-dismissed`,children:b.map(e=>(0,p.jsx)(_,{alert:e,dismissed:!0},e.id))})]})]});let C=(t,r,i)=>(0,p.jsxs)(`button`,{type:`button`,role:`tab`,"aria-selected":(n?.id??`all`)===t,className:`minitab`+((n?.id??`all`)===t?` active`:``),onClick:()=>e(t===`all`?`/alerts`:`/alerts/`+t),children:[r,s.data?(0,p.jsx)(`span`,{className:`n`+(i.length?` on`:``)+(i.some(e=>e.severity===`critical`)?` critical`:``),children:i.length}):null]},t);return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(`div`,{className:`page-head`,children:[(0,p.jsxs)(`div`,{className:`titles`,children:[(0,p.jsx)(`h1`,{children:`Alerts`}),(0,p.jsxs)(`span`,{className:`sub`,children:[`What needs attention now, checked every minute. Each clears by itself once its cause does.`,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()),l.map(e=>C(e.id,e.label,x(e.kinds)))]}),S]})}export{y as AlertsPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,s as t,v as n,z as r}from"./client-ZHNiMG8H.js";import{A as i,D as a,N as o,c as s,d as c,k as l,s as u}from"./index-
|
|
1
|
+
import{n as e,s as t,v as n,z as r}from"./client-ZHNiMG8H.js";import{A as i,D as a,N as o,c as s,d as c,k as l,s as u}from"./index-Cl93xBjn.js";import{t as d}from"./Time-s5E5qK3s.js";import{t as f}from"./QueryInfo-Bv-0Zmc5.js";import{a as p}from"./reach-BAOLd75Y.js";import{i as m,t as h}from"./KeyActivity-D673KcWq.js";var g=r(),_={query:`named query`,collection:`collection`,connection:`connection`};function v({term:e,children:t}){return(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`dt`,{children:e}),(0,g.jsx)(`dd`,{children:t})]})}function y({title:e,sub:t,children:n}){return(0,g.jsxs)(`section`,{className:`panel`,style:{marginTop:16},children:[(0,g.jsxs)(`div`,{className:`panel-bar`,children:[(0,g.jsx)(`strong`,{children:e}),t?(0,g.jsx)(`span`,{className:`dim`,children:t}):null]}),n]})}function b(){let{name:r=``}=o(),b=s(),x=u(),S=c(),C=m(r),{openDrawer:w}=l(),T=new Date().toISOString().slice(0,10),E=n({queryKey:[`apikey-history`,r],queryFn:async()=>t(await e.GET(`/api/v1/history`,{params:{query:{key:r,limit:50}}})).items,retry:!1});if(b.isPending)return(0,g.jsx)(a,{text:`Loading API key…`});let D=b.data?.[r];if(!D)return(0,g.jsxs)(g.Fragment,{children:[(0,g.jsx)(`div`,{className:`page-head`,children:(0,g.jsxs)(`div`,{className:`titles`,children:[(0,g.jsx)(i,{to:`/api-keys`,className:`sub`,children:`← API keys`}),(0,g.jsx)(`h1`,{children:r||`API key`})]})}),(0,g.jsx)(`div`,{className:`panel`,children:(0,g.jsxs)(`div`,{className:`empty`,children:[(0,g.jsx)(`strong`,{children:`No such API key`}),(0,g.jsx)(`span`,{children:`It may have been revoked and removed, or the name is wrong.`}),(0,g.jsx)(i,{to:`/api-keys`,className:`btn sm`,children:`Back to API keys`})]})})]});let O=!!(D.expires_at&&D.expires_at<T),k=D.active?O?`Expired`:`Active`:`Revoked`,A=(x.data??[]).map(e=>({q:e,via:p(D,e.name,e.collection,e.connection_name)})).filter(e=>e.via.length),j=(S.data??[]).map(e=>e.name),M=D.connections===`*`?j:D.connections??[],N=A.map(e=>e.q.connection_name).filter(e=>e!=null&&!M.includes(e)),P=[...new Set([...M,...N])].sort(),F=e=>(S.data??[]).find(t=>t.name===e)?.db??null,I=e=>w({title:e.name,kicker:`${e.collection||`uncollected`} · ${e.connection_name||`no connection`}`,content:(0,g.jsx)(f,{name:e.name,dbType:e.connection_name?F(e.connection_name):null})}),L=D.allowed_tables??[],R=D.allowed_ips??[],z=D.usage_this_period;return(0,g.jsxs)(g.Fragment,{children:[(0,g.jsxs)(`div`,{className:`page-head`,children:[(0,g.jsxs)(`div`,{className:`titles`,children:[(0,g.jsx)(i,{to:`/api-keys`,className:`sub`,children:`← API keys`}),(0,g.jsxs)(`h1`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,g.jsx)(`span`,{className:`dot ${D.active&&!O?`ok`:`off`}`,title:k}),r,D.example?(0,g.jsx)(`span`,{className:`tag`,children:`example`}):null]}),(0,g.jsxs)(`span`,{className:`sub`,children:[k,D.created_from_role?` · created from role ${D.created_from_role}`:``]})]}),(0,g.jsx)(`span`,{className:`spacer`}),(0,g.jsx)(i,{to:`/api-keys`,className:`btn sm`,children:`Back`})]}),(0,g.jsxs)(`dl`,{className:`meta`,children:[(0,g.jsx)(v,{term:`status`,children:k}),(0,g.jsx)(v,{term:`access`,children:D.allow_writes?`Read / write`:`Read-only`}),(0,g.jsx)(v,{term:`created`,children:(0,g.jsx)(d,{value:D.created_at})}),(0,g.jsx)(v,{term:`last used`,children:(0,g.jsx)(d,{value:D.last_used_at,fallback:`never`})}),(0,g.jsx)(v,{term:`expires`,children:D.expires_at?D.expires_at+(O?` (expired)`:``):`never`}),(0,g.jsx)(v,{term:`rate limit`,children:D.rate_limit||`server default`}),(0,g.jsx)(v,{term:`query timeout`,children:D.effective_limits?.query_timeout==null?`server default`:`${D.effective_limits.query_timeout}s`}),(0,g.jsx)(v,{term:`row limit (page)`,children:D.effective_limits?.max_rows?.page??`—`}),(0,g.jsx)(v,{term:`max concurrent`,children:D.effective_limits?.max_concurrent??`unlimited`}),(0,g.jsx)(v,{term:`allowed IPs`,children:R.length?R.join(`, `):`any`})]}),(0,g.jsx)(y,{title:`APIs it can reach`,sub:D.queries===`*`?`all saved queries`:`${A.length} saved ${A.length===1?`query`:`queries`}`,children:D.queries===`*`?(0,g.jsxs)(`div`,{className:`empty`,children:[(0,g.jsx)(`strong`,{children:`Every saved query`}),(0,g.jsx)(`span`,{children:`This key carries a wildcard query grant, so it can run any saved query.`})]}):A.length?(0,g.jsx)(`div`,{style:{overflowX:`auto`},children:(0,g.jsxs)(`table`,{className:`grid`,children:[(0,g.jsx)(`thead`,{children:(0,g.jsx)(`tr`,{children:[`API endpoint`,`Query`,`Connection`,`Collection`,`Granted via`,`Activity (30d)`].map(e=>(0,g.jsx)(`th`,{children:e},e))})}),(0,g.jsx)(`tbody`,{children:A.map(({q:e,via:t})=>(0,g.jsxs)(`tr`,{"data-query":e.name,children:[(0,g.jsx)(`td`,{className:`mono`,children:e.published_version?e.endpoint:(0,g.jsxs)(`span`,{className:`dim`,title:`No published version yet`,children:[e.endpoint,` (draft)`]})}),(0,g.jsx)(`td`,{children:(0,g.jsx)(`button`,{type:`button`,className:`as-link name`,title:`Show the SQL for ${e.name}`,onClick:()=>I(e),children:e.name})}),(0,g.jsx)(`td`,{className:`dim`,children:e.connection_name||`—`}),(0,g.jsx)(`td`,{className:`dim`,children:e.collection||`—`}),(0,g.jsx)(`td`,{children:t.map((e,t)=>(0,g.jsx)(`span`,{className:`tag`,style:{marginRight:4},children:e.kind===`query`?_.query:e.label},t))}),(0,g.jsx)(`td`,{className:`spark-cell`,children:(0,g.jsx)(h,{label:e.name,items:C.by[e.name],isPending:C.isPending,isError:C.isError})})]},e.name))})]})}):(0,g.jsxs)(`div`,{className:`empty`,children:[(0,g.jsx)(`strong`,{children:`No saved queries`}),(0,g.jsx)(`span`,{children:`This key reaches no saved query by name or collection. It may still run ad-hoc SQL on the connections below.`})]})}),(0,g.jsx)(y,{title:`Databases it can reach`,sub:D.connections===`*`?`all connections`:`${P.length} connection(s)`,children:P.length?(0,g.jsx)(`div`,{style:{overflowX:`auto`},children:(0,g.jsxs)(`table`,{className:`grid`,children:[(0,g.jsx)(`thead`,{children:(0,g.jsx)(`tr`,{children:[`Connection`,`Type`,`Reached by`].map(e=>(0,g.jsx)(`th`,{children:e},e))})}),(0,g.jsx)(`tbody`,{children:P.map(e=>(0,g.jsxs)(`tr`,{"data-conn":e,children:[(0,g.jsx)(`td`,{className:`name`,children:e}),(0,g.jsx)(`td`,{children:F(e)?(0,g.jsx)(`span`,{className:`tag`,children:F(e)}):(0,g.jsx)(`span`,{className:`dim`,children:`—`})}),(0,g.jsx)(`td`,{className:`dim`,children:D.connections===`*`?`all connections`:M.includes(e)?`direct grant (ad-hoc SQL)`:`saved query only`})]},e))})]})}):(0,g.jsxs)(`div`,{className:`empty`,children:[(0,g.jsx)(`strong`,{children:`No connections`}),(0,g.jsx)(`span`,{children:`This key cannot reach any connection directly or through a saved query.`})]})}),(0,g.jsxs)(y,{title:`Tables it can reach`,sub:L.length?`${L.length} allowed`:`all tables on reachable connections`,children:[L.length?(0,g.jsx)(`div`,{style:{padding:`12px 14px`,display:`flex`,flexWrap:`wrap`,gap:6},children:L.map(e=>(0,g.jsx)(`span`,{className:`tag mono`,children:e},e))}):(0,g.jsx)(`div`,{style:{padding:`12px 14px`},className:`dim`,children:`No table allow-list — this key may read every table on the connections it reaches.`}),D.allow_writes?(0,g.jsx)(`div`,{style:{padding:`0 14px 12px`},children:(0,g.jsxs)(`span`,{className:`tag`,children:[`writes: `,D.allowed_write_ops?.length?D.allowed_write_ops.join(`, `):`all operations`]})}):null,D.mask?.length?(0,g.jsx)(`div`,{style:{padding:`0 14px 12px`,display:`flex`,flexWrap:`wrap`,gap:6},children:D.mask.map((e,t)=>(0,g.jsxs)(`span`,{className:`tag`,title:`${e.column}: ${e.action}`,children:[`masked `,e.column]},t))}):null]}),(0,g.jsx)(y,{title:`Usage history`,sub:z?`${D.usage.queries} queries total · ${D.usage.rows.toLocaleString()} rows`:`${D.usage.queries} queries · ${D.usage.errors} failed · since this process started`,children:E.isPending?(0,g.jsx)(a,{text:`Loading usage…`}):E.isError?(0,g.jsx)(`div`,{style:{padding:`12px 14px`},className:`dim`,children:`Usage history isn’t available for this key (the run log may be off, or your role can’t read it).`}):E.data.length?(0,g.jsx)(`div`,{style:{overflowX:`auto`},children:(0,g.jsxs)(`table`,{className:`grid`,children:[(0,g.jsx)(`thead`,{children:(0,g.jsx)(`tr`,{children:[`When`,`What`,`Connection`,`Via`,`Rows`,`Duration`,`Result`].map(e=>(0,g.jsx)(`th`,{children:e},e))})}),(0,g.jsx)(`tbody`,{children:E.data.map((e,t)=>(0,g.jsxs)(`tr`,{children:[(0,g.jsx)(`td`,{style:{whiteSpace:`nowrap`},children:(0,g.jsx)(d,{value:e.executed_at})}),(0,g.jsx)(`td`,{className:`name`,children:e.kind===`adhoc`?(0,g.jsx)(`span`,{className:`dim`,children:`ad-hoc SQL`}):e.query}),(0,g.jsx)(`td`,{className:`dim`,children:e.connection_name||`—`}),(0,g.jsx)(`td`,{className:`dim`,children:e.transport||`—`}),(0,g.jsx)(`td`,{className:`mono`,children:e.rows??`—`}),(0,g.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:e.duration_ms==null?`—`:`${e.duration_ms} ms`}),(0,g.jsx)(`td`,{style:e.status===`error`?{color:`var(--danger)`}:void 0,children:e.status===`error`?(0,g.jsx)(`span`,{title:e.error??void 0,children:e.code||`error`}):`ok`})]},e.request_id??t))})]})}):(0,g.jsxs)(`div`,{className:`empty`,children:[(0,g.jsx)(`strong`,{children:`No runs recorded`}),(0,g.jsx)(`span`,{children:`This key hasn’t run a query yet, or its runs have aged out of the log.`})]})})]})}export{b as ApiKeyDetailPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as e}from"./client-ZHNiMG8H.js";import{A as t,D as n,c as r,l as i,u as a}from"./index-
|
|
1
|
+
import{z as e}from"./client-ZHNiMG8H.js";import{A as t,D as n,c as r,l as i,u as a}from"./index-Cl93xBjn.js";import{t as o}from"./Time-s5E5qK3s.js";import{t as s}from"./reach-BAOLd75Y.js";import{r as c,t as l}from"./KeyActivity-D673KcWq.js";import{i as u,n as d,o as f}from"./forms-CTAVu1q5.js";import{t as p}from"./icons-CFXE8hAz.js";import{t as m}from"./Managed-VPfeN8Yu.js";var h=e();function g(e,t){let n=new Date(Date.now()+6048e5).toISOString().slice(0,10);return!!(e&&e>=t&&e<=n)}function _({usage:e}){let t=[`calls`,`rows`].map(t=>({what:t,used:e[t],limit:e[`${t}_limit`]})).filter(e=>e.limit!=null),n=t.reduce((e,t)=>t.used/t.limit>e.used/e.limit?t:e,t[0]),r=Math.min(1,n.used/n.limit),i=e.per===`day`?`today`:`this month`;return(0,h.jsxs)(`div`,{className:`quota-usage`,title:t.map(e=>`${e.used.toLocaleString()} of ${e.limit.toLocaleString()} ${e.what}`).join(` · `)+` ${i} · resets ${e.resets_at}`,children:[(0,h.jsxs)(`span`,{className:`dim`,children:[n.used.toLocaleString(),` / `,n.limit.toLocaleString(),` `,n.what,` `,i]}),(0,h.jsx)(`div`,{className:`quota-bar${r>=1?` full`:r>=.8?` near`:``}`,children:(0,h.jsx)(`span`,{style:{width:`${Math.round(r*100)}%`}})})]})}function v(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 y(){let e=i(),y=r(),b=a(),x=c(),S=d(),C=u(),w=Object.keys(y.data??{}).sort(),T=new Date().toISOString().slice(0,10),E;return E=y.isPending?(0,h.jsx)(n,{text:`Loading API keys…`}):y.isError?(0,h.jsxs)(`div`,{className:`empty`,children:[(0,h.jsx)(`strong`,{children:`Couldn’t load API keys`}),(0,h.jsx)(`span`,{children:y.error.message}),(0,h.jsx)(`button`,{type:`button`,className:`btn sm`,onClick:()=>y.refetch(),children:`Retry`})]}):w.length?(0,h.jsx)(`div`,{style:{overflowX:`auto`},children:(0,h.jsxs)(`table`,{className:`grid`,children:[(0,h.jsx)(`thead`,{children:(0,h.jsx)(`tr`,{children:[`Name`,`Scope`,`Access`,`Rate limit`,`IPs`,`Expires`,`Last used`,`Usage`,`Activity (30d)`,``].map((e,t)=>(0,h.jsx)(`th`,{children:e},t))})}),(0,h.jsx)(`tbody`,{children:w.map(n=>{let r=y.data[n],i=!!(r.expires_at&&r.expires_at<T),a=!i&&g(r.expires_at,T),c=r.allowed_ips??[],u=v(r.usage);return(0,h.jsxs)(`tr`,{"data-name":n,children:[(0,h.jsx)(`td`,{children:(0,h.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,h.jsx)(`span`,{className:`dot ${r.active&&!i?`ok`:`off`}`,title:r.active?i?`Expired`:`Active`:`Revoked`}),(0,h.jsx)(`span`,{className:`name`,title:r.created_at?`Created `+r.created_at+(r.created_from_role?` from role `+r.created_from_role:``):void 0,children:n}),(0,h.jsx)(m,{kind:`api_keys`,name:n}),r.previous_expires_at?(0,h.jsx)(`span`,{className:`tag`,title:`Rotated: the old secret works until then too`,children:`rotating`}):null,r.mask?.length?(0,h.jsx)(`span`,{className:`tag`,title:r.mask.map(e=>`${e.column}: ${e.action}`).join(`, `),children:`masked`}):null]})}),(0,h.jsx)(`td`,{children:(0,h.jsx)(f,{entry:r,collections:b.data?.collections})}),(0,h.jsx)(s,{entry:r}),(0,h.jsx)(`td`,{className:r.rate_limit?`mono`:`mono dim`,style:{whiteSpace:`nowrap`},children:r.rate_limit||`server default`}),(0,h.jsx)(`td`,{className:`dim`,children:c.length?(0,h.jsx)(`span`,{title:c.join(`, `),children:c.length+(c.length===1?` IP`:` IPs`)}):(0,h.jsx)(`span`,{className:`dim`,children:`any`})}),(0,h.jsx)(`td`,{style:{whiteSpace:`nowrap`},children:r.expires_at?(0,h.jsx)(`span`,{style:i?{color:`var(--danger)`}:a?{color:`var(--warn)`}:void 0,children:r.expires_at+(i?` (expired)`:a?` (expires soon)`:``)}):(0,h.jsx)(`span`,{className:`dim`,children:`never`})}),(0,h.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,h.jsx)(o,{value:r.last_used_at,fallback:`never`})}),r.usage_this_period?(0,h.jsxs)(`td`,{style:{whiteSpace:`nowrap`},children:[u,(0,h.jsx)(_,{usage:r.usage_this_period})]}):u?(0,h.jsx)(`td`,{title:`${r.usage.rows}${r.usage.rows===1?` row`:` rows`} returned in total · since this process started`,style:{whiteSpace:`nowrap`},children:u}):(0,h.jsx)(`td`,{className:`dim`,style:{whiteSpace:`nowrap`},children:`No activity yet`}),(0,h.jsx)(`td`,{className:`spark-cell`,children:(0,h.jsx)(l,{label:n,items:x.by[n],isPending:x.isPending,isError:x.isError,to:`/api-keys/${encodeURIComponent(n)}`})}),(0,h.jsx)(`td`,{children:(0,h.jsxs)(`div`,{className:`actions`,children:[(0,h.jsx)(t,{to:`/api-keys/${encodeURIComponent(n)}`,className:`btn ghost sm`,title:`Full details of ${n}`,children:`Details`}),(0,h.jsx)(`button`,{hidden:!e(`access.write`),type:`button`,className:`btn ghost sm icon`,title:`Edit ${n}`,"aria-label":`Edit`,onClick:()=>S.editKey(n),children:(0,h.jsx)(p,{})}),(0,h.jsx)(`button`,{hidden:!e(`access.write`),type:`button`,className:`btn ghost sm danger`,onClick:()=>void C(n),children:`Revoke`})]})})]},n)})})]})}):(0,h.jsxs)(`div`,{className:`empty`,children:[(0,h.jsx)(`strong`,{children:`No scoped API keys yet`}),(0,h.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,h.jsx)(`button`,{hidden:!e(`access.write`),type:`button`,className:`btn primary`,onClick:()=>S.newKey(),children:`New API key`})]}),(0,h.jsxs)(h.Fragment,{children:[(0,h.jsxs)(`div`,{className:`page-head`,children:[(0,h.jsxs)(`div`,{className:`titles`,children:[(0,h.jsx)(`h1`,{children:`API keys`}),(0,h.jsx)(`span`,{className:`sub`,id:`apikeys-sub`,children:(w.length?w.length+(w.length===1?` key. `:` keys. `):``)+`Scope each one to connections, collections or roles.`})]}),(0,h.jsx)(`span`,{className:`spacer`}),(0,h.jsx)(`button`,{hidden:!e(`access.write`),id:`new-apikey`,type:`button`,className:`btn primary`,onClick:()=>S.newKey(),children:`New API key`})]}),(0,h.jsx)(`div`,{id:`apikeys-table`,className:`panel`,children:E})]})}export{y as ApiKeysPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{B as e,H as t,R as n,n as r,s as i,t as a,v as o,z as s}from"./client-ZHNiMG8H.js";import{t as c}from"./useMutation-
|
|
1
|
+
import{B as e,H as t,R as n,n as r,s as i,t as a,v as o,z as s}from"./client-ZHNiMG8H.js";import{t as c}from"./useMutation-BIoTFTZ8.js";import{D as l,M as u,N as d,T as f,f as p,k as m}from"./index-Cl93xBjn.js";import{t as h}from"./Time-s5E5qK3s.js";import{t as g}from"./CodeBox-Cwh5KHLp.js";var _=t(e(),1),v=s(),y={pending:`Waiting`,approved:`Approved - not run yet`,running:`Running`,done:`Done`,failed:`Failed`,rejected:`Rejected`,expired:`Expired`};function b(e){let t=p(`writes.approve`,`write requests`);return o({queryKey:[`write-requests`,e||`all`],queryFn:async()=>(await t.check(),i(await r.GET(`/api/v1/write-requests`,{params:{query:e?{status:e}:{}}})).items),enabled:t.ready,retry:!1})}var x=e=>JSON.stringify(e);function S({args:e}){let t=Object.entries(e);return t.length?(0,v.jsx)(`table`,{className:`grid approval-args`,children:(0,v.jsx)(`tbody`,{children:t.map(([e,t])=>(0,v.jsxs)(`tr`,{children:[(0,v.jsx)(`td`,{className:`mono`,children:e}),(0,v.jsx)(`td`,{className:`mono`,children:x(t)})]},e))})}):(0,v.jsx)(`span`,{className:`dim`,children:`none`})}function C({id:e}){let{closeDrawer:t,showError:s,toast:u}=m(),d=n(),[p,b]=(0,_.useState)(``),C=o({queryKey:[`write-requests`,`one`,e],queryFn:async()=>i(await r.GET(`/api/v1/write-requests/{id}`,{params:{path:{id:e}}})),retry:!1}),w=c({mutationFn:async t=>i(t?await r.POST(`/api/v1/write-requests/{id}/approve`,{params:{path:{id:e}}}):await r.POST(`/api/v1/write-requests/{id}/reject`,{params:{path:{id:e}},body:p.trim()?{reason:p.trim()}:{}})),onSuccess:e=>{s(``),d.invalidateQueries({queryKey:[`write-requests`]}),d.invalidateQueries({queryKey:[`alerts`]}),t(),u(e.status===`approved`?`Approved - ${e.caller} may run it now`:`Rejected`)},onError:e=>s(e.message,e instanceof a?{status:e.status}:{})});if(C.isPending)return(0,v.jsx)(l,{});if(C.isError)return(0,v.jsx)(`div`,{className:`hint`,children:C.error.message});let T=C.data,E=T.status===`pending`;return(0,v.jsxs)(`div`,{className:`form`,children:[(0,v.jsxs)(`p`,{className:`hint`,children:[(0,v.jsx)(`strong`,{children:T.caller}),` asked to run `,(0,v.jsx)(`strong`,{children:T.query}),` (version `,T.version,`) on`,` `,(0,v.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,v.jsx)(f,{label:`SQL`,children:(0,v.jsx)(g,{sql:T.sql})}),(0,v.jsx)(f,{label:`With exactly these arguments`,hint:`An approval is for these alone - no other write.`,children:(0,v.jsx)(S,{args:T.arguments})}),(0,v.jsxs)(`div`,{className:`hint`,children:[`Asked `,(0,v.jsx)(h,{value:T.created_at}),` · `,y[T.status]??T.status,E||T.status===`approved`?(0,v.jsxs)(v.Fragment,{children:[` `,`· expires `,(0,v.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,v.jsx)(f,{label:`Result`,children:(0,v.jsx)(`span`,{className:`mono`,children:x(T.result)})}):null,E?(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(f,{id:`approval-reason`,label:`Reason`,hint:`Optional for a rejection - the agent is told.`,children:(0,v.jsx)(`textarea`,{id:`approval-reason`,rows:2,value:p,onChange:e=>b(e.target.value)})}),(0,v.jsxs)(`div`,{className:`form-actions`,children:[(0,v.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:w.isPending,onClick:()=>w.mutate(!0),children:`Approve`}),(0,v.jsx)(`button`,{type:`button`,className:`btn danger`,disabled:w.isPending,onClick:()=>w.mutate(!1),children:`Reject`}),(0,v.jsx)(`button`,{type:`button`,className:`btn`,onClick:t,children:`Close`})]})]}):(0,v.jsx)(`div`,{className:`form-actions`,children:(0,v.jsx)(`button`,{type:`button`,className:`btn`,onClick:t,children:`Close`})})]})}function w(){let{id:e}=d(),t=u(),{openDrawer:n}=m(),[r,i]=(0,_.useState)(`pending`),a=b(r),o=a.data??[],s=e=>n({title:`Approve a write`,kicker:e,wide:!0,content:(0,v.jsx)(C,{id:e})});(0,_.useEffect)(()=>{e&&(s(e),t(`/approvals`,{replace:!0}))},[e]);let c;return c=a.isPending?(0,v.jsx)(l,{text:`Loading write requests…`}):a.isError?(0,v.jsxs)(`div`,{className:`empty`,children:[(0,v.jsx)(`strong`,{children:`Couldn’t load write requests`}),(0,v.jsx)(`span`,{children:a.error.message})]}):o.length?(0,v.jsx)(`div`,{style:{overflowX:`auto`},children:(0,v.jsxs)(`table`,{className:`grid`,children:[(0,v.jsx)(`thead`,{children:(0,v.jsx)(`tr`,{children:[`Asked`,`Agent`,`Tool`,`Arguments`,r===`pending`?`Expires`:`Status`].map(e=>(0,v.jsx)(`th`,{children:e},e))})}),(0,v.jsx)(`tbody`,{children:o.map(e=>(0,v.jsxs)(`tr`,{"data-request":e.id,children:[(0,v.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,v.jsx)(h,{value:e.created_at,fallback:``})}),(0,v.jsx)(`td`,{className:`mono`,children:e.caller}),(0,v.jsx)(`td`,{children:(0,v.jsx)(`button`,{type:`button`,className:`agent-name`,onClick:()=>s(e.id),children:e.query})}),(0,v.jsx)(`td`,{className:`mono dim approval-args-cell`,children:x(e.arguments)}),(0,v.jsx)(`td`,{className:`dim`,style:{whiteSpace:`nowrap`},children:r===`pending`?(0,v.jsx)(h,{value:e.expires_at}):y[e.status]??e.status})]},e.id))})]})}):(0,v.jsxs)(`div`,{className:`empty`,children:[(0,v.jsx)(`strong`,{children:r===`pending`?`No writes are waiting for approval`:`No write requests yet`}),(0,v.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,v.jsxs)(v.Fragment,{children:[(0,v.jsxs)(`div`,{className:`page-head`,children:[(0,v.jsxs)(`div`,{className:`titles`,children:[(0,v.jsx)(`h1`,{children:`Approvals`}),(0,v.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,v.jsx)(`span`,{className:`spacer`}),(0,v.jsx)(`button`,{type:`button`,className:`btn`,onClick:()=>void a.refetch(),children:`Refresh`})]}),(0,v.jsxs)(`div`,{className:`panel`,style:{overflow:`hidden`},children:[(0,v.jsxs)(`div`,{className:`panel-bar`,children:[(0,v.jsxs)(`select`,{"aria-label":`Show`,value:r,onChange:e=>i(e.target.value),children:[(0,v.jsx)(`option`,{value:`pending`,children:`Waiting`}),(0,v.jsx)(`option`,{value:``,children:`All`}),(0,v.jsx)(`option`,{value:`done`,children:`Done`}),(0,v.jsx)(`option`,{value:`rejected`,children:`Rejected`}),(0,v.jsx)(`option`,{value:`expired`,children:`Expired`}),(0,v.jsx)(`option`,{value:`failed`,children:`Failed`})]}),(0,v.jsx)(`span`,{className:`spacer`}),(0,v.jsx)(`span`,{className:`panel-count`,children:a.data?`${o.length} request${o.length===1?``:`s`}`:``})]}),c]})]})}export{w as ApprovalsPage};
|