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