queryapigate 0.41.0__tar.gz → 0.42.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.41.0/queryapigate.egg-info → queryapigate-0.42.0}/PKG-INFO +11 -5
- {queryapigate-0.41.0 → queryapigate-0.42.0}/README.md +10 -4
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/__init__.py +1 -1
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/apikeys.py +4 -3
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/app.py +1 -1
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/config.py +15 -2
- queryapigate-0.41.0/queryapigate/console_dist/assets/AccessMapPage-BchGwtBA.js → queryapigate-0.42.0/queryapigate/console_dist/assets/AccessMapPage-BaSwxeZn.js +2 -2
- queryapigate-0.41.0/queryapigate/console_dist/assets/AdministratorsPage-POqoMkbx.js → queryapigate-0.42.0/queryapigate/console_dist/assets/AdministratorsPage-D-bNJXpD.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/AgentsPage-M9N0xdeE.js → queryapigate-0.42.0/queryapigate/console_dist/assets/AgentsPage-ClKcqjWg.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/AlertsPage-XaMYNy94.js → queryapigate-0.42.0/queryapigate/console_dist/assets/AlertsPage-DvGXTQF4.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/ApiKeyDetailPage-CuWfrg7s.js → queryapigate-0.42.0/queryapigate/console_dist/assets/ApiKeyDetailPage-Sn4xvzpB.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/ApiKeysPage-4Tn9wAVl.js → queryapigate-0.42.0/queryapigate/console_dist/assets/ApiKeysPage-C5MdnSOs.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/ApprovalsPage-BpG0_mCJ.js → queryapigate-0.42.0/queryapigate/console_dist/assets/ApprovalsPage-DZifYSAl.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/AuditLogPage-sV_If6-v.js → queryapigate-0.42.0/queryapigate/console_dist/assets/AuditLogPage-CNdPk9fO.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/CachingPage-6sAn-XyO.js → queryapigate-0.42.0/queryapigate/console_dist/assets/CachingPage-BWWecKXf.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/ConnectionsPage-DO4N8S24.js → queryapigate-0.42.0/queryapigate/console_dist/assets/ConnectionsPage-637zZ9KK.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/DesignerPage-C8Y-IxVI.js → queryapigate-0.42.0/queryapigate/console_dist/assets/DesignerPage-DackfcTo.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/DestinationsPage-CW-XmEqb.js → queryapigate-0.42.0/queryapigate/console_dist/assets/DestinationsPage-9-xJTMMy.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/ExportsPage-DxroTrQZ.js → queryapigate-0.42.0/queryapigate/console_dist/assets/ExportsPage-CFFtbXpK.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/HelpPage-CvwlMOvX.js → queryapigate-0.42.0/queryapigate/console_dist/assets/HelpPage-Cig8tsEd.js +2 -2
- queryapigate-0.41.0/queryapigate/console_dist/assets/HomePage-BEg_rfvB.js → queryapigate-0.42.0/queryapigate/console_dist/assets/HomePage-C2WB4Iai.js +2 -2
- queryapigate-0.41.0/queryapigate/console_dist/assets/KeyActivity-EO98IG9y.js → queryapigate-0.42.0/queryapigate/console_dist/assets/KeyActivity-mYv2bu9-.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/Managed-BwU65gDK.js → queryapigate-0.42.0/queryapigate/console_dist/assets/Managed-BYc6wyeG.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/McpPage-upBu1Zmx.js → queryapigate-0.42.0/queryapigate/console_dist/assets/McpPage-DkaEcr-x.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/MetricsPage-DO9gA7sY.js → queryapigate-0.42.0/queryapigate/console_dist/assets/MetricsPage-DJc65cxL.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/QueryInfo-F9F7if8P.js → queryapigate-0.42.0/queryapigate/console_dist/assets/QueryInfo-DBmvcpR5.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/RepositoryPage-gSUZcW25.js → queryapigate-0.42.0/queryapigate/console_dist/assets/RepositoryPage-DASAVB9b.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/Results-3iK2cKFL.js → queryapigate-0.42.0/queryapigate/console_dist/assets/Results-D-iAyzY7.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/RolesPage-BNpqKDS6.js → queryapigate-0.42.0/queryapigate/console_dist/assets/RolesPage-XUgkTQdK.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/SchemaBrowser-CAIraoYm.js → queryapigate-0.42.0/queryapigate/console_dist/assets/SchemaBrowser-C56FVhL1.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/SettingsPage-B2ZGXWK4.js → queryapigate-0.42.0/queryapigate/console_dist/assets/SettingsPage-C1DK8b5r.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/Time-Bbp6q-9f.js → queryapigate-0.42.0/queryapigate/console_dist/assets/Time-BJyVzig6.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/WorkspacesPage-u6taj7Y0.js → queryapigate-0.42.0/queryapigate/console_dist/assets/WorkspacesPage-8HEyJwR9.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/api-Dc-2iLvt.js → queryapigate-0.42.0/queryapigate/console_dist/assets/api-CtLPT47L.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/forms--qfP4qjO.js → queryapigate-0.42.0/queryapigate/console_dist/assets/forms-DI1_QY6E.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/forms-CfcbrGmf.js → queryapigate-0.42.0/queryapigate/console_dist/assets/forms-jtkIvvrV.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/index--QHzN3S_.js → queryapigate-0.42.0/queryapigate/console_dist/assets/index-D3-9-G4i.js +2 -2
- queryapigate-0.41.0/queryapigate/console_dist/assets/shared-CnZhEfLp.js → queryapigate-0.42.0/queryapigate/console_dist/assets/shared-BWGKBF5W.js +1 -1
- queryapigate-0.41.0/queryapigate/console_dist/assets/useMutation-DQTq-cNw.js → queryapigate-0.42.0/queryapigate/console_dist/assets/useMutation-BbQqPorm.js +1 -1
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/console_dist/index.html +1 -1
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/db.py +10 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/history.py +20 -13
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/masking.py +36 -6
- queryapigate-0.42.0/queryapigate/metacache.py +53 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/store.py +25 -15
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/tableguard.py +12 -1
- {queryapigate-0.41.0 → queryapigate-0.42.0/queryapigate.egg-info}/PKG-INFO +11 -5
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate.egg-info/SOURCES.txt +35 -33
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_integration.py +2 -1
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_masking.py +87 -0
- queryapigate-0.42.0/tests/test_metacache.py +137 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_settings.py +3 -1
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_tableguard.py +35 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/LICENSE +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/NOTICE +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/pyproject.toml +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/__main__.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/accesslog.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/adminroles.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/admins.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/agentwrites.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/alerts.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/auditchain.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/auditstream.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/aws.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/broadcast.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/bundle.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/cache.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/cli.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/collection_admin.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/configcode.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/console.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/console_dist/assets/CodeBox-Cwh5KHLp.js +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/console_dist/assets/Experimental-B3dnaeV7.js +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/console_dist/assets/StatTile-Cft4kJPH.js +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/console_dist/assets/client-ZHNiMG8H.js +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/console_dist/assets/icons-CFXE8hAz.js +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/console_dist/assets/index-Cv64XKNs.css +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/console_dist/assets/maskRules-CvKQIPgN.js +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/console_dist/assets/reach-BAOLd75Y.js +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/console_dist/assets/state-O13cl7cO.js +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/console_dist/favicon.svg +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/cors.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/databases.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/definitions.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/deprecations.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/descriptions.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/destinations.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/duckfiles.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/embeddings.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/engine.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/errors.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/events.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/examples.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/experimental.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/exporting.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/exports.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/formats.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/governance.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/gunicorn_conf.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/instances.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/jwtauth.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/lib/h2-2.2.224.jar +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/limits.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/logging_setup.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/mcp_server.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/metrics.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/mongodocs.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/mongotools.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/oidc.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/openapi.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/params.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/parquet.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/pool.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/postman.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/quotas.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/ratelimit.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/rediscache.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/rekey.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/runners.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/schema.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/scope.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/secheaders.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/secretrefs.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/services/__init__.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/services/access.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/services/administrators.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/services/audit.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/services/collections.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/services/connections.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/services/descriptions.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/services/destinations.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/services/exports.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/services/mcp.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/services/queries.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/services/workspaces.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/sqlflow.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/sqltools.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/sso.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/tracing.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/usage.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/v1.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/v1_spec.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/vault.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate/workspaces.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate.egg-info/dependency_links.txt +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate.egg-info/entry_points.txt +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate.egg-info/requires.txt +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/queryapigate.egg-info/top_level.txt +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/setup.cfg +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_adhoc_history.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_adminroles.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_admins.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_agent_writes.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_alerts.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_api_administrators.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_api_keys.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_api_v1.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_audit_chain.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_audit_log.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_audit_siem.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_audit_stream.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_aws.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_backup_restore.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_broadcast.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_cli.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_collection_bundle.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_collection_postman.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_collections.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_configcode.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_connection_encryption.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_console.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_cors_and_rate_limit.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_data_protection.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_databases.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_db.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_dependency_floors.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_deprecations.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_descriptions.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_destinations.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_docs_site.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_duckdb.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_duckdb_files.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_embeddings.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_errors.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_events.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_events_server.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_examples.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_experimental.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_export_security.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_exporting.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_exports.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_fips.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_helm.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_history.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_howto.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_instances.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_jdbc.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_jwt.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_key_rotation.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_limits.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_masking_requests.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_mcp.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_mcp_keycloak.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_mcp_oauth.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_mongo_streaming.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_mongotools.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_multinode.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_observability.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_oidc.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_params.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_parquet.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_pool.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_publishing.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_query_window.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_queryapigate.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_quotas.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_rekey.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_release_check.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_response_cache.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_roles.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_rolling_upgrade.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_secretrefs.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_security_headers.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_shared_rate_limit.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_sql_guard_fuzz.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_sql_guard_writes.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_sqlflow.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_sso.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_sso_keycloak.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_supply_chain.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_tracing.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_upgrades.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_usage.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_vault.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_workspace_config_cli.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_workspace_isolation.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_workspace_observability.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_workspace_store.py +0 -0
- {queryapigate-0.41.0 → queryapigate-0.42.0}/tests/test_workspaces_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: queryapigate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.42.0
|
|
4
4
|
Summary: Turn SQL into governed REST APIs over MySQL, PostgreSQL, ClickHouse, SQLite, DuckDB, H2, MongoDB, and Parquet/CSV/JSON files - JSON, CSV, Parquet or Excel out, with scoped keys, an admin Console and MCP for AI agents.
|
|
5
5
|
Author-email: Anantha Raju C <arcswdev@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -90,7 +90,7 @@ Dynamic: license-file
|
|
|
90
90
|
[](https://scorecard.dev/viewer/?uri=github.com/AnanthaRajuC/QueryAPIGate)
|
|
91
91
|
[](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/SECURITY.md#slsa-level)
|
|
92
92
|
|
|
93
|
-
### Turn SQL
|
|
93
|
+
### Turn SQL into secure, governed REST APIs - and MCP tools for AI agents.
|
|
94
94
|
|
|
95
95
|
[**Full documentation**](https://AnanthaRajuC.github.io/QueryAPIGate/)
|
|
96
96
|
|
|
@@ -169,6 +169,11 @@ backend service written around each query. QueryAPIGate makes the query itself t
|
|
|
169
169
|
a versioned endpoint with typed parameters, governed by who may call it, how often and what they may see - and the same
|
|
170
170
|
query is an MCP tool for AI agents, a live event stream and a scheduled export.
|
|
171
171
|
|
|
172
|
+
That MCP surface is a reason in itself: it's how you let an AI agent query production data without handing it a
|
|
173
|
+
database connection. The agent sees only governed tools, ad-hoc SQL over MCP is always read-only, writes wait for a
|
|
174
|
+
person's approval, and the same masking, limits and audit apply - the limits live in the gateway, where no prompt can
|
|
175
|
+
talk its way around them. One governed core, two front doors that matter most: REST for your apps, MCP for your agents.
|
|
176
|
+
|
|
172
177
|
It doesn't replace an application backend; it removes the boilerplate when the requirement is "expose this query
|
|
173
178
|
safely". Use cases, how it compares to PostgREST, Hasura and others, and the thinking behind it:
|
|
174
179
|
[why QueryAPIGate](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/WHY.md).
|
|
@@ -179,14 +184,15 @@ safely". Use cases, how it compares to PostgREST, Hasura and others, and the thi
|
|
|
179
184
|
injection-safe parameters, drafts and publishing, run history.
|
|
180
185
|
- **[Scoped API keys, roles and collections](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/API.md#authentication-and-permissions)** - per
|
|
181
186
|
connection, per query or per collection; expiry, IP allow-lists, rate limits, quotas, table allow-lists.
|
|
187
|
+
- **[A governed MCP server for AI agents](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/MCP.md)** - saved queries as self-describing tools,
|
|
188
|
+
read-only ad-hoc SQL, masking-aware schema discovery, OAuth sign-in, and write tools that wait for a person's
|
|
189
|
+
approval - the same grants, masking and audit as every other caller.
|
|
182
190
|
- **[Named administrators and SSO](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/API.md#administrators-and-admin-roles)** - admin roles and
|
|
183
191
|
tokens, Console sign-in through your identity provider, signed-in users' JWTs.
|
|
184
192
|
- **[Workspaces](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/API.md#workspaces)** (experimental) - several teams on one server,
|
|
185
193
|
each running its own connections, queries and keys, and administering nothing of another's.
|
|
186
194
|
- **[Column masking](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/API.md#column-masking)** - hash, mask or drop what a caller mustn't see, traced
|
|
187
195
|
through the SQL to the source column.
|
|
188
|
-
- **[An MCP server for AI agents](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/MCP.md)** - saved queries as tools, OAuth sign-in, write tools that
|
|
189
|
-
wait for a person's approval.
|
|
190
196
|
- **[Eight database types, and files](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/DATABASE_CONNECTION_CONFIGURATION.md#support-matrix)** -
|
|
191
197
|
PostgreSQL, MySQL, SQLite, DuckDB, ClickHouse and MongoDB; H2 and JDBC; Parquet, CSV and JSON on S3, GCS, R2 or disk.
|
|
192
198
|
- **[Eight output formats, and streaming](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/API.md#streaming-exports)** - JSON, NDJSON, XML, YAML, CSV,
|
|
@@ -308,7 +314,7 @@ The [documentation site](https://AnanthaRajuC.github.io/QueryAPIGate/) has all o
|
|
|
308
314
|
|---|---|
|
|
309
315
|
| **Start** | [Installation and setup](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/INSTALLATION_AND_SETUP.md) · [Configuration](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/CONFIGURATION.md) · [Example APIs](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/EXAMPLES.md) · [How-to guides](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/how-to/how-to.md) |
|
|
310
316
|
| **Reference** | [API](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/API.md) · [MCP server](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/MCP.md) · [Database connections](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/DATABASE_CONNECTION_CONFIGURATION.md) · [Query metadata](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/QUERY_METADATA_MANAGEMENT.md) · [Features](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/FEATURES.md) |
|
|
311
|
-
| **Run it** | [Deployment](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/DEPLOYMENT.md) · [Technology stack](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/TECHNOLOGY_STACK.MD) · [A tour of the Console](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/SCREENSHOTS.md) |
|
|
317
|
+
| **Run it** | [Deployment](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/DEPLOYMENT.md) · [Performance](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/PERFORMANCE.md) · [Technology stack](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/TECHNOLOGY_STACK.MD) · [A tour of the Console](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/SCREENSHOTS.md) |
|
|
312
318
|
| **Security** | [Security policy](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/SECURITY.md) · [Threat model](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/THREAT_MODEL.md) · [Where data goes](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/DATA_PROTECTION.md) |
|
|
313
319
|
| **Project** | [Why QueryAPIGate](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/WHY.md) · [Changelog](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/CHANGELOG.md) · [Roadmap](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/BACKLOG.md) · [Architecture decisions](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/adr/README.md) |
|
|
314
320
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://scorecard.dev/viewer/?uri=github.com/AnanthaRajuC/QueryAPIGate)
|
|
10
10
|
[](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/SECURITY.md#slsa-level)
|
|
11
11
|
|
|
12
|
-
### Turn SQL
|
|
12
|
+
### Turn SQL into secure, governed REST APIs - and MCP tools for AI agents.
|
|
13
13
|
|
|
14
14
|
[**Full documentation**](https://AnanthaRajuC.github.io/QueryAPIGate/)
|
|
15
15
|
|
|
@@ -88,6 +88,11 @@ backend service written around each query. QueryAPIGate makes the query itself t
|
|
|
88
88
|
a versioned endpoint with typed parameters, governed by who may call it, how often and what they may see - and the same
|
|
89
89
|
query is an MCP tool for AI agents, a live event stream and a scheduled export.
|
|
90
90
|
|
|
91
|
+
That MCP surface is a reason in itself: it's how you let an AI agent query production data without handing it a
|
|
92
|
+
database connection. The agent sees only governed tools, ad-hoc SQL over MCP is always read-only, writes wait for a
|
|
93
|
+
person's approval, and the same masking, limits and audit apply - the limits live in the gateway, where no prompt can
|
|
94
|
+
talk its way around them. One governed core, two front doors that matter most: REST for your apps, MCP for your agents.
|
|
95
|
+
|
|
91
96
|
It doesn't replace an application backend; it removes the boilerplate when the requirement is "expose this query
|
|
92
97
|
safely". Use cases, how it compares to PostgREST, Hasura and others, and the thinking behind it:
|
|
93
98
|
[why QueryAPIGate](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/WHY.md).
|
|
@@ -98,14 +103,15 @@ safely". Use cases, how it compares to PostgREST, Hasura and others, and the thi
|
|
|
98
103
|
injection-safe parameters, drafts and publishing, run history.
|
|
99
104
|
- **[Scoped API keys, roles and collections](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/API.md#authentication-and-permissions)** - per
|
|
100
105
|
connection, per query or per collection; expiry, IP allow-lists, rate limits, quotas, table allow-lists.
|
|
106
|
+
- **[A governed MCP server for AI agents](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/MCP.md)** - saved queries as self-describing tools,
|
|
107
|
+
read-only ad-hoc SQL, masking-aware schema discovery, OAuth sign-in, and write tools that wait for a person's
|
|
108
|
+
approval - the same grants, masking and audit as every other caller.
|
|
101
109
|
- **[Named administrators and SSO](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/API.md#administrators-and-admin-roles)** - admin roles and
|
|
102
110
|
tokens, Console sign-in through your identity provider, signed-in users' JWTs.
|
|
103
111
|
- **[Workspaces](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/API.md#workspaces)** (experimental) - several teams on one server,
|
|
104
112
|
each running its own connections, queries and keys, and administering nothing of another's.
|
|
105
113
|
- **[Column masking](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/API.md#column-masking)** - hash, mask or drop what a caller mustn't see, traced
|
|
106
114
|
through the SQL to the source column.
|
|
107
|
-
- **[An MCP server for AI agents](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/MCP.md)** - saved queries as tools, OAuth sign-in, write tools that
|
|
108
|
-
wait for a person's approval.
|
|
109
115
|
- **[Eight database types, and files](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/DATABASE_CONNECTION_CONFIGURATION.md#support-matrix)** -
|
|
110
116
|
PostgreSQL, MySQL, SQLite, DuckDB, ClickHouse and MongoDB; H2 and JDBC; Parquet, CSV and JSON on S3, GCS, R2 or disk.
|
|
111
117
|
- **[Eight output formats, and streaming](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/API.md#streaming-exports)** - JSON, NDJSON, XML, YAML, CSV,
|
|
@@ -227,7 +233,7 @@ The [documentation site](https://AnanthaRajuC.github.io/QueryAPIGate/) has all o
|
|
|
227
233
|
|---|---|
|
|
228
234
|
| **Start** | [Installation and setup](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/INSTALLATION_AND_SETUP.md) · [Configuration](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/CONFIGURATION.md) · [Example APIs](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/EXAMPLES.md) · [How-to guides](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/how-to/how-to.md) |
|
|
229
235
|
| **Reference** | [API](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/API.md) · [MCP server](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/MCP.md) · [Database connections](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/DATABASE_CONNECTION_CONFIGURATION.md) · [Query metadata](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/QUERY_METADATA_MANAGEMENT.md) · [Features](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/FEATURES.md) |
|
|
230
|
-
| **Run it** | [Deployment](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/DEPLOYMENT.md) · [Technology stack](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/TECHNOLOGY_STACK.MD) · [A tour of the Console](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/SCREENSHOTS.md) |
|
|
236
|
+
| **Run it** | [Deployment](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/DEPLOYMENT.md) · [Performance](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/PERFORMANCE.md) · [Technology stack](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/TECHNOLOGY_STACK.MD) · [A tour of the Console](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/SCREENSHOTS.md) |
|
|
231
237
|
| **Security** | [Security policy](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/SECURITY.md) · [Threat model](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/THREAT_MODEL.md) · [Where data goes](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/DATA_PROTECTION.md) |
|
|
232
238
|
| **Project** | [Why QueryAPIGate](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/WHY.md) · [Changelog](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/CHANGELOG.md) · [Roadmap](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/BACKLOG.md) · [Architecture decisions](https://github.com/AnanthaRajuC/QueryAPIGate/blob/main/documentation/adr/README.md) |
|
|
233
239
|
|
|
@@ -96,7 +96,7 @@ import time
|
|
|
96
96
|
from collections import namedtuple
|
|
97
97
|
from datetime import datetime, timedelta
|
|
98
98
|
|
|
99
|
-
from . import admins, config, db, masking, quotas, scope, store
|
|
99
|
+
from . import admins, config, db, masking, metacache, quotas, scope, store
|
|
100
100
|
from .errors import ApiError
|
|
101
101
|
|
|
102
102
|
ALL_CONNECTIONS = '*'
|
|
@@ -929,10 +929,11 @@ def authenticate(supplied, client_ip=None):
|
|
|
929
929
|
# useless for guessing a secret; compare_digest() below stays as a belt-and-braces final check.
|
|
930
930
|
# An old secret still in its grace period after a rotation (ADR 0009) matches by previous_hash - indexed too -
|
|
931
931
|
# and only until previous_expires_at; everything else (active, expiry, allowed_ips) applies to it the same.
|
|
932
|
-
rows
|
|
932
|
+
# The rows may come from metacache.py: every check below still runs on them, on every request.
|
|
933
|
+
rows = metacache.rows('api_key', supplied_hash, lambda: db.connection().execute(
|
|
933
934
|
'SELECT workspace, name, hash, active, expires_at, created_at, details_json, previous_hash, '
|
|
934
935
|
'previous_expires_at FROM api_keys WHERE hash = ? OR previous_hash = ?',
|
|
935
|
-
(supplied_hash, supplied_hash)).fetchall()
|
|
936
|
+
(supplied_hash, supplied_hash)).fetchall())
|
|
936
937
|
for row in rows: # across every workspace: a key's secret is unique on the server, and says which it is in
|
|
937
938
|
name, entry = row['name'], _row_to_entry(row)
|
|
938
939
|
previous = row['hash'] != supplied_hash
|
|
@@ -961,7 +961,7 @@ def _run_saved(ref, body, url_params):
|
|
|
961
961
|
A draft - a version newer than the published one, or any version of a query with none published - runs only
|
|
962
962
|
for the admin key, and only when asked for by number (to test it before publishing). For anyone else it
|
|
963
963
|
doesn't exist: the same 404 as a version number that was never used, so drafts can't be discovered."""
|
|
964
|
-
path = store.resolve_saved_file(ref)
|
|
964
|
+
path = store.resolve_saved_file(ref, check_exists=False) # load_versions() 404s the same, one read fewer
|
|
965
965
|
content = store.load_versions(path, with_history=False)
|
|
966
966
|
requested = get_int(request.args.get('version') or body.get('version'), 'version')
|
|
967
967
|
if requested is not None and store.is_draft(content, requested) and not g.permission.admin:
|
|
@@ -24,7 +24,7 @@ DEFAULT_EVENTS_MAX_STREAMS = 4 # concurrent GET /events streams the main server
|
|
|
24
24
|
DEFAULT_EVENTS_RETENTION_HOURS = 24 # how long the event log keeps an event (ADR 0020)
|
|
25
25
|
AUDIT_LOG_LIMIT = 500 # administrative-action entries remembered across the whole server
|
|
26
26
|
DEFAULT_QUERY_TIMEOUT = 30.0 # seconds
|
|
27
|
-
DEFAULT_POOL_SIZE =
|
|
27
|
+
DEFAULT_POOL_SIZE = 8 # idle connections kept per distinct connection - the Docker image's 8 threads
|
|
28
28
|
DEFAULT_POOL_IDLE_TIMEOUT = 300.0 # seconds
|
|
29
29
|
DEFAULT_SLOW_QUERY_THRESHOLD = 1.0 # seconds
|
|
30
30
|
DEFAULT_ALERT_ERROR_RATE = 20 # percent of a saved query's recent runs failing before it is an alert (alerts.py)
|
|
@@ -240,6 +240,16 @@ def pool_size():
|
|
|
240
240
|
return DEFAULT_POOL_SIZE
|
|
241
241
|
|
|
242
242
|
|
|
243
|
+
def metadata_cache_ttl():
|
|
244
|
+
"""Seconds an API key, saved query or connection read from the store is reused (QUERYAPIGATE_METADATA_CACHE_TTL,
|
|
245
|
+
metacache.py). 0, the default, reads them on every request. A change made through this process is seen at once;
|
|
246
|
+
one made through another process (another instance, the CLI) up to this long later."""
|
|
247
|
+
try:
|
|
248
|
+
return max(0.0, float(os.environ.get('QUERYAPIGATE_METADATA_CACHE_TTL', 0)))
|
|
249
|
+
except ValueError:
|
|
250
|
+
return 0.0
|
|
251
|
+
|
|
252
|
+
|
|
243
253
|
def pool_idle_timeout():
|
|
244
254
|
"""Seconds an idle pooled connection is kept before it is closed (QUERYAPIGATE_POOL_IDLE_TIMEOUT)."""
|
|
245
255
|
try:
|
|
@@ -1402,7 +1412,10 @@ def describe_settings():
|
|
|
1402
1412
|
row('Pool size', 'Idle connections kept per connection. 0 disables pooling.',
|
|
1403
1413
|
'QUERYAPIGATE_POOL_SIZE', str(pool_size())),
|
|
1404
1414
|
row('Idle timeout', 'How long an idle pooled connection is kept before it is closed.',
|
|
1405
|
-
'QUERYAPIGATE_POOL_IDLE_TIMEOUT', _seconds(pool_idle_timeout()))
|
|
1415
|
+
'QUERYAPIGATE_POOL_IDLE_TIMEOUT', _seconds(pool_idle_timeout())),
|
|
1416
|
+
row('Metadata cache', 'How long a key, saved query or connection read from the store is reused. '
|
|
1417
|
+
'0 reads them on every request.', 'QUERYAPIGATE_METADATA_CACHE_TTL',
|
|
1418
|
+
_seconds(metadata_cache_ttl() or None) or 'off')]},
|
|
1406
1419
|
{'id': 'traffic', 'title': 'Rate limits & CORS',
|
|
1407
1420
|
'description': 'Per-client throttling and browser origins allowed to call the API.', 'rows': [
|
|
1408
1421
|
row('Rate limit', 'Requests allowed per client. API keys and roles can set their own.',
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/forms
|
|
2
|
-
import{B as e,H as t,z as n}from"./client-ZHNiMG8H.js";import{D as r,M as i,P as a,c as o,d as s,g as c,k as l,s as u}from"./index--QHzN3S_.js";import{t as d}from"./Time-Bbp6q-9f.js";import{t as f}from"./QueryInfo-F9F7if8P.js";import{a as p}from"./reach-BAOLd75Y.js";import{i as m,n as h,o as g,r as _}from"./SchemaBrowser-CAIraoYm.js";var v=t(e(),1),y=n(),b=(0,v.lazy)(()=>a(()=>import(`./forms--qfP4qjO.js`).then(e=>({default:e.QueryForm})),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9]))),x={query:`Q`,collection:`C`,connection:`W`},S=[`Database`,`Created`,`Last modified`,`Last used`,`Version`],C=[`database`,`created`,`modified`,`lastUsed`,`version`],w=()=>new Date().toISOString().slice(0,10);function T(){let e=i(),{openDrawer:t}=l(),n=u(),a=o(),d=c(),x=s(),[S,C]=(0,v.useState)(m),w=e=>{let t={...S,...e};g(t),C(t)},T=x.data??[],D=e=>T.find(t=>t.name===e)?.db??null,O=h(S.conn||void 0),k=O.data?.tables??[],A=_(S.conn||void 0,D(S.conn),S.table&&!S.tableMatches?[S.table]:[]),j=S.table?S.tableMatches??(A.data?A.data[S.table]??[]:null):null,M=n.data??[],N=[...new Set(M.map(e=>D(e.connection_name)).filter(e=>!!e))].sort(),P=a.data??{},F=d.data??{},I=Object.keys(P).sort(),L=Object.keys(F).sort(),R,z=!0;S.conn?O.isPending?R=(0,y.jsx)(`option`,{value:``,children:`Loading tables…`}):O.isError?R=(0,y.jsx)(`option`,{value:``,children:`Schema unavailable`}):(z=!1,R=(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(`option`,{value:``,children:`All tables (${k.length})`}),k.map(e=>(0,y.jsx)(`option`,{value:e.name,children:`${e.name} · ${e.columns.length}${e.columns.length===1?` col`:` cols`}`},e.name))]})):R=(0,y.jsx)(`option`,{value:``,children:`Select a connection first`});let B=(e,t)=>p(e,t.name,t.collection,t.connection_name),V;if(n.isPending)V=(0,y.jsx)(r,{});else if(!M.length)V=(0,y.jsx)(`div`,{className:`empty`,children:(0,y.jsx)(`strong`,{children:`No saved queries yet`})});else if(!I.length&&!L.length)V=(0,y.jsxs)(`div`,{className:`empty`,children:[(0,y.jsx)(`strong`,{children:`No scoped API keys or roles yet`}),(0,y.jsx)(`span`,{children:`Every query is reachable by the admin key only until a scoped key is created.`})]});else{let n=S.q.trim().toLowerCase(),i=[...M].sort((e,t)=>(e.collection||``)+e.name<(t.collection||``)+t.name?-1:1),a=e=>e.toLowerCase().includes(n),o=n?i.filter(e=>a(e.name)||e.collection&&a(e.collection)):i,s=n?I.filter(a):I,c=n?L.filter(a):L,l=o.length||s.length||c.length,u=o.length?o:l?i:[],d=s.length?s:l?I:[],p=c.length?c:l?L:[];if(S.conn&&(u=u.filter(e=>e.connection_name===S.conn)),S.table&&j===null)V=(0,y.jsx)(r,{text:`Checking which queries reference ${S.table}…`});else if(j&&(u=u.filter(e=>j.includes(e.name))),u.length){let n=u.map(e=>({q:e,dbType:D(e.connection_name),reach:d.filter(t=>B(P[t],e).length).length}));S.db&&(n=n.filter(e=>e.dbType===S.db)),S.reach===`reachable`?n=n.filter(e=>e.reach>0):S.reach===`unreachable`&&(n=n.filter(e=>e.reach===0));let r=S.sort;if(r.field){let e=e=>{switch(r.field){case`query`:return e.q.name.toLowerCase();case`database`:return(e.dbType||``).toLowerCase();case`created`:return e.q.created_at||``;case`modified`:return e.q.updated_at||``;case`lastUsed`:return e.q.last_used_at||``;case`version`:return e.q.latest_version;case`connection`:return(e.q.connection_name||``).toLowerCase();case`reach`:return e.reach;case`col`:{let t=r.colType===`key`?P[r.colName]:F[r.colName];return t&&B(t,e.q).length?1:0}default:return``}};n=[...n].sort((t,n)=>{let i=e(t),a=e(n);return i<a?-r.dir:i>a?r.dir:0})}V=n.length?(0,y.jsx)(E,{rows:n,keyCols:d,roleCols:p,keyMap:P,roleMap:F,st:S,onSort:e=>w({sort:e}),onOpen:t=>e(`/queries/${encodeURIComponent(t)}`),onInfo:e=>t({title:e.q.name,kicker:`${e.q.collection||`uncollected`} · ${e.q.version_count}${e.q.version_count===1?` version`:` versions`}`,content:(0,y.jsx)(f,{name:e.q.name,dbType:e.dbType})})}):(0,y.jsx)(`div`,{className:`empty`,children:(0,y.jsx)(`span`,{children:`No queries match these filters.`})})}else{let i=n?`Nothing matches “${n}”.`:S.table?`“${S.table}” on ${S.conn} is not exposed by any saved query yet.`:S.conn?`No saved query uses “${S.conn}” yet.`:`Nothing matches these filters.`;V=(0,y.jsxs)(`div`,{className:`empty`,children:[(0,y.jsx)(`span`,{children:i}),!n&&S.conn?(0,y.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:()=>{let n=D(S.conn)===`mongo`,i=S.table?n?JSON.stringify({collection:S.table,filter:{}},null,2):`SELECT * FROM `+S.table:void 0;e(`/queries`),t({title:`New API`,kicker:`POST /api/v1/queries`,content:(0,y.jsx)(v.Suspense,{fallback:(0,y.jsx)(r,{}),children:(0,y.jsx)(b,{starter:{connection:S.conn,sql:i}})})})},children:`New API`+(S.table?` on `+S.table:``)}):null]})}}return(0,y.jsxs)(y.Fragment,{children:[(0,y.jsxs)(`div`,{className:`page-head`,children:[(0,y.jsxs)(`div`,{className:`titles`,children:[(0,y.jsx)(`h1`,{children:`Access map`}),(0,y.jsxs)(`span`,{className:`sub`,children:[`Which API keys can reach which saved queries - the whole point of collections and connection grants, in one place.`,` `,(0,y.jsxs)(`span`,{className:`legend`,children:[(0,y.jsx)(`span`,{className:`amap-dot`,children:`Q`}),` named query \xA0`,(0,y.jsx)(`span`,{className:`amap-dot`,children:`C`}),` `,`collection \xA0`,(0,y.jsx)(`span`,{className:`amap-dot conn`,children:`W`}),` whole connection`]})]})]}),(0,y.jsx)(`span`,{className:`spacer`}),(0,y.jsxs)(`select`,{id:`accessmap-conn-filter`,"aria-label":`Connection`,value:T.some(e=>e.name===S.conn)?S.conn:``,onChange:e=>w({conn:e.target.value,table:``,tableMatches:null}),children:[(0,y.jsx)(`option`,{value:``,children:`All connections`}),[...T].sort((e,t)=>e.name<t.name?-1:1).map(e=>(0,y.jsx)(`option`,{value:e.name,children:e.name},e.name))]}),(0,y.jsx)(`select`,{id:`accessmap-table-filter`,"aria-label":`Table`,disabled:z,value:k.some(e=>e.name===S.table)?S.table:``,onChange:e=>w({table:e.target.value,tableMatches:null}),children:R}),(0,y.jsxs)(`select`,{id:`accessmap-db-filter`,"aria-label":`Database`,value:N.includes(S.db)?S.db:``,onChange:e=>w({db:e.target.value}),children:[(0,y.jsx)(`option`,{value:``,children:`All databases`}),N.map(e=>(0,y.jsx)(`option`,{value:e,children:e},e))]}),(0,y.jsxs)(`select`,{id:`accessmap-reach-filter`,"aria-label":`Reach`,value:S.reach,onChange:e=>w({reach:e.target.value}),children:[(0,y.jsx)(`option`,{value:``,children:`Any reach`}),(0,y.jsx)(`option`,{value:`reachable`,children:`Reachable by a key`}),(0,y.jsx)(`option`,{value:`unreachable`,children:`Reachable by no key`})]}),(0,y.jsx)(`input`,{id:`accessmap-filter`,className:`search`,type:`search`,placeholder:`Filter by query or key…`,value:S.q,onChange:e=>w({q:e.target.value})})]}),(0,y.jsx)(`div`,{id:`accessmap-body`,className:`panel`,children:V})]})}function E({rows:e,keyCols:t,roleCols:n,keyMap:r,roleMap:i,st:a,onSort:o,onOpen:s,onInfo:c}){let l=a.sort,u=(e,t=null,n=null)=>l.field===e&&l.colType===t&&l.colName===n,f=(e,t=null,n=null)=>u(e,t,n)?l.dir===1?` ▲`:` ▼`:``,m=(e,t=null,n=null)=>{let r=u(e,t,n);o(r&&l.dir===1?{field:e,colType:t,colName:n,dir:-1}:r?{field:null,colType:null,colName:null,dir:1}:{field:e,colType:t,colName:n,dir:1})},h=(e,t,n)=>(0,y.jsx)(`th`,{className:t,onClick:()=>m(n),children:e+f(n)},n),g=(e,t,n,r)=>(0,y.jsxs)(`th`,{className:`amap-key-head amap-sortable`+(n?` revoked`:``),title:n?e+n:e,onClick:()=>m(`col`,r,e),children:[(0,y.jsxs)(`span`,{className:`amap-key-name`,children:[e,f(`col`,r,e),n?(0,y.jsx)(`span`,{className:`dim`,children:n}):null]}),(0,y.jsxs)(`span`,{className:`amap-key-sub`,children:[(t.allow_writes?`Read/write`:`Read-only`)+` · `+(t.rate_limit||`server default`),t.mask?.length?(0,y.jsx)(`span`,{className:`tag`,title:t.mask.map(e=>`${e.column}: ${e.action}`).join(`, `),children:`masked`}):null]})]},r+`:`+e),_=(e,t,n,r)=>{let i=p(e,t.name,t.collection,t.connection_name);if(!i.length)return(0,y.jsx)(`td`,{className:`amap-cell`},r);let a=[...new Set(i.map(e=>x[e.kind]))],o=a.length===1&&a[0]===`W`;return(0,y.jsx)(`td`,{className:`amap-cell`,title:i.map(e=>e.label).join(`, `),children:(0,y.jsx)(`span`,{className:`amap-dot`+(n?` role`:``)+(o?` conn`:``),children:a.join(`·`)})},r)},v=w();return(0,y.jsx)(`div`,{style:{overflow:`auto`},children:(0,y.jsxs)(`table`,{className:`amap`,children:[(0,y.jsxs)(`thead`,{children:[(0,y.jsxs)(`tr`,{children:[(0,y.jsx)(`th`,{colSpan:3+S.length}),t.length?(0,y.jsx)(`th`,{colSpan:t.length,className:`amap-group`,children:`API KEYS`}):null,n.length?(0,y.jsx)(`th`,{colSpan:n.length,className:`amap-group role`,title:`What a key created from each role would reach - a role grants nothing on its own`,children:`ROLES`}):null]}),(0,y.jsxs)(`tr`,{children:[h(`Query`,`amap-query amap-sortable`,`query`),C.map((e,t)=>h(S[t],`amap-sortable`,e)),h(`Connection`,`amap-sortable`,`connection`),h(`Reach`,`num amap-sortable`,`reach`),t.map(e=>{let t=r[e],n=!!(t.expires_at&&t.expires_at<v);return g(e,t,t.active?n?` (expired ${t.expires_at})`:``:` (revoked)`,`key`)}),n.map(e=>g(e,i[e],``,`role`))]})]}),(0,y.jsx)(`tbody`,{children:e.map(e=>{let a=e.q;return(0,y.jsxs)(`tr`,{children:[(0,y.jsxs)(`td`,{className:`amap-query`,children:[(0,y.jsxs)(`div`,{className:`amap-q-row`,children:[(0,y.jsx)(`button`,{type:`button`,className:`amap-q-name`,title:`Open ${a.name} in API Repository`,onClick:()=>s(a.name),children:a.name}),(0,y.jsx)(`button`,{type:`button`,className:`amap-info`,title:`Details for ${a.name}`,"aria-label":`Details for ${a.name}`,onClick:()=>c(e),children:`i`})]}),a.collection?(0,y.jsx)(`span`,{className:`amap-coll`,children:a.collection}):null]}),(0,y.jsx)(`td`,{children:e.dbType?(0,y.jsx)(`span`,{className:`tag`,children:e.dbType}):(0,y.jsx)(`span`,{className:`dim`,children:`—`})}),(0,y.jsx)(`td`,{className:`mono dim`,children:(0,y.jsx)(d,{value:a.created_at})}),(0,y.jsx)(`td`,{className:`mono dim`,children:(0,y.jsx)(d,{value:a.updated_at})}),(0,y.jsx)(`td`,{className:a.last_used_at?`mono`:`mono dim`,children:(0,y.jsx)(d,{value:a.last_used_at,fallback:`never`})}),(0,y.jsx)(`td`,{className:`mono`,children:`v`+a.latest_version}),(0,y.jsx)(`td`,{className:`mono dim`,children:a.connection_name||`—`}),(0,y.jsx)(`td`,{className:`num`,title:`Reachable by an actual API key (roles alone reach nothing)`,children:e.reach?String(e.reach):`—`}),t.map(e=>_(r[e],a,!1,`k:`+e)),n.map(e=>_(i[e],a,!0,`r:`+e))]},a.name)})}),(0,y.jsx)(`tfoot`,{children:(0,y.jsxs)(`tr`,{children:[(0,y.jsx)(`td`,{className:`amap-query`,children:`Reaches`}),S.map(e=>(0,y.jsx)(`td`,{},e)),(0,y.jsx)(`td`,{}),(0,y.jsx)(`td`,{}),t.map(t=>(0,y.jsx)(`td`,{className:`num`,children:`${e.filter(e=>p(r[t],e.q.name,e.q.collection,e.q.connection_name).length).length} / ${e.length}`},t)),n.map(t=>(0,y.jsx)(`td`,{className:`num`,children:`${e.filter(e=>p(i[t],e.q.name,e.q.collection,e.q.connection_name).length).length} / ${e.length}`},t))]})})]})})}export{T as AccessMapPage};
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/forms-DI1_QY6E.js","assets/client-ZHNiMG8H.js","assets/index-D3-9-G4i.js","assets/index-Cv64XKNs.css","assets/CodeBox-Cwh5KHLp.js","assets/api-CtLPT47L.js","assets/useMutation-BbQqPorm.js","assets/maskRules-CvKQIPgN.js","assets/SchemaBrowser-C56FVhL1.js","assets/reach-BAOLd75Y.js"])))=>i.map(i=>d[i]);
|
|
2
|
+
import{B as e,H as t,z as n}from"./client-ZHNiMG8H.js";import{D as r,M as i,P as a,c as o,d as s,g as c,k as l,s as u}from"./index-D3-9-G4i.js";import{t as d}from"./Time-BJyVzig6.js";import{t as f}from"./QueryInfo-DBmvcpR5.js";import{a as p}from"./reach-BAOLd75Y.js";import{i as m,n as h,o as g,r as _}from"./SchemaBrowser-C56FVhL1.js";var v=t(e(),1),y=n(),b=(0,v.lazy)(()=>a(()=>import(`./forms-DI1_QY6E.js`).then(e=>({default:e.QueryForm})),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9]))),x={query:`Q`,collection:`C`,connection:`W`},S=[`Database`,`Created`,`Last modified`,`Last used`,`Version`],C=[`database`,`created`,`modified`,`lastUsed`,`version`],w=()=>new Date().toISOString().slice(0,10);function T(){let e=i(),{openDrawer:t}=l(),n=u(),a=o(),d=c(),x=s(),[S,C]=(0,v.useState)(m),w=e=>{let t={...S,...e};g(t),C(t)},T=x.data??[],D=e=>T.find(t=>t.name===e)?.db??null,O=h(S.conn||void 0),k=O.data?.tables??[],A=_(S.conn||void 0,D(S.conn),S.table&&!S.tableMatches?[S.table]:[]),j=S.table?S.tableMatches??(A.data?A.data[S.table]??[]:null):null,M=n.data??[],N=[...new Set(M.map(e=>D(e.connection_name)).filter(e=>!!e))].sort(),P=a.data??{},F=d.data??{},I=Object.keys(P).sort(),L=Object.keys(F).sort(),R,z=!0;S.conn?O.isPending?R=(0,y.jsx)(`option`,{value:``,children:`Loading tables…`}):O.isError?R=(0,y.jsx)(`option`,{value:``,children:`Schema unavailable`}):(z=!1,R=(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(`option`,{value:``,children:`All tables (${k.length})`}),k.map(e=>(0,y.jsx)(`option`,{value:e.name,children:`${e.name} · ${e.columns.length}${e.columns.length===1?` col`:` cols`}`},e.name))]})):R=(0,y.jsx)(`option`,{value:``,children:`Select a connection first`});let B=(e,t)=>p(e,t.name,t.collection,t.connection_name),V;if(n.isPending)V=(0,y.jsx)(r,{});else if(!M.length)V=(0,y.jsx)(`div`,{className:`empty`,children:(0,y.jsx)(`strong`,{children:`No saved queries yet`})});else if(!I.length&&!L.length)V=(0,y.jsxs)(`div`,{className:`empty`,children:[(0,y.jsx)(`strong`,{children:`No scoped API keys or roles yet`}),(0,y.jsx)(`span`,{children:`Every query is reachable by the admin key only until a scoped key is created.`})]});else{let n=S.q.trim().toLowerCase(),i=[...M].sort((e,t)=>(e.collection||``)+e.name<(t.collection||``)+t.name?-1:1),a=e=>e.toLowerCase().includes(n),o=n?i.filter(e=>a(e.name)||e.collection&&a(e.collection)):i,s=n?I.filter(a):I,c=n?L.filter(a):L,l=o.length||s.length||c.length,u=o.length?o:l?i:[],d=s.length?s:l?I:[],p=c.length?c:l?L:[];if(S.conn&&(u=u.filter(e=>e.connection_name===S.conn)),S.table&&j===null)V=(0,y.jsx)(r,{text:`Checking which queries reference ${S.table}…`});else if(j&&(u=u.filter(e=>j.includes(e.name))),u.length){let n=u.map(e=>({q:e,dbType:D(e.connection_name),reach:d.filter(t=>B(P[t],e).length).length}));S.db&&(n=n.filter(e=>e.dbType===S.db)),S.reach===`reachable`?n=n.filter(e=>e.reach>0):S.reach===`unreachable`&&(n=n.filter(e=>e.reach===0));let r=S.sort;if(r.field){let e=e=>{switch(r.field){case`query`:return e.q.name.toLowerCase();case`database`:return(e.dbType||``).toLowerCase();case`created`:return e.q.created_at||``;case`modified`:return e.q.updated_at||``;case`lastUsed`:return e.q.last_used_at||``;case`version`:return e.q.latest_version;case`connection`:return(e.q.connection_name||``).toLowerCase();case`reach`:return e.reach;case`col`:{let t=r.colType===`key`?P[r.colName]:F[r.colName];return t&&B(t,e.q).length?1:0}default:return``}};n=[...n].sort((t,n)=>{let i=e(t),a=e(n);return i<a?-r.dir:i>a?r.dir:0})}V=n.length?(0,y.jsx)(E,{rows:n,keyCols:d,roleCols:p,keyMap:P,roleMap:F,st:S,onSort:e=>w({sort:e}),onOpen:t=>e(`/queries/${encodeURIComponent(t)}`),onInfo:e=>t({title:e.q.name,kicker:`${e.q.collection||`uncollected`} · ${e.q.version_count}${e.q.version_count===1?` version`:` versions`}`,content:(0,y.jsx)(f,{name:e.q.name,dbType:e.dbType})})}):(0,y.jsx)(`div`,{className:`empty`,children:(0,y.jsx)(`span`,{children:`No queries match these filters.`})})}else{let i=n?`Nothing matches “${n}”.`:S.table?`“${S.table}” on ${S.conn} is not exposed by any saved query yet.`:S.conn?`No saved query uses “${S.conn}” yet.`:`Nothing matches these filters.`;V=(0,y.jsxs)(`div`,{className:`empty`,children:[(0,y.jsx)(`span`,{children:i}),!n&&S.conn?(0,y.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:()=>{let n=D(S.conn)===`mongo`,i=S.table?n?JSON.stringify({collection:S.table,filter:{}},null,2):`SELECT * FROM `+S.table:void 0;e(`/queries`),t({title:`New API`,kicker:`POST /api/v1/queries`,content:(0,y.jsx)(v.Suspense,{fallback:(0,y.jsx)(r,{}),children:(0,y.jsx)(b,{starter:{connection:S.conn,sql:i}})})})},children:`New API`+(S.table?` on `+S.table:``)}):null]})}}return(0,y.jsxs)(y.Fragment,{children:[(0,y.jsxs)(`div`,{className:`page-head`,children:[(0,y.jsxs)(`div`,{className:`titles`,children:[(0,y.jsx)(`h1`,{children:`Access map`}),(0,y.jsxs)(`span`,{className:`sub`,children:[`Which API keys can reach which saved queries - the whole point of collections and connection grants, in one place.`,` `,(0,y.jsxs)(`span`,{className:`legend`,children:[(0,y.jsx)(`span`,{className:`amap-dot`,children:`Q`}),` named query \xA0`,(0,y.jsx)(`span`,{className:`amap-dot`,children:`C`}),` `,`collection \xA0`,(0,y.jsx)(`span`,{className:`amap-dot conn`,children:`W`}),` whole connection`]})]})]}),(0,y.jsx)(`span`,{className:`spacer`}),(0,y.jsxs)(`select`,{id:`accessmap-conn-filter`,"aria-label":`Connection`,value:T.some(e=>e.name===S.conn)?S.conn:``,onChange:e=>w({conn:e.target.value,table:``,tableMatches:null}),children:[(0,y.jsx)(`option`,{value:``,children:`All connections`}),[...T].sort((e,t)=>e.name<t.name?-1:1).map(e=>(0,y.jsx)(`option`,{value:e.name,children:e.name},e.name))]}),(0,y.jsx)(`select`,{id:`accessmap-table-filter`,"aria-label":`Table`,disabled:z,value:k.some(e=>e.name===S.table)?S.table:``,onChange:e=>w({table:e.target.value,tableMatches:null}),children:R}),(0,y.jsxs)(`select`,{id:`accessmap-db-filter`,"aria-label":`Database`,value:N.includes(S.db)?S.db:``,onChange:e=>w({db:e.target.value}),children:[(0,y.jsx)(`option`,{value:``,children:`All databases`}),N.map(e=>(0,y.jsx)(`option`,{value:e,children:e},e))]}),(0,y.jsxs)(`select`,{id:`accessmap-reach-filter`,"aria-label":`Reach`,value:S.reach,onChange:e=>w({reach:e.target.value}),children:[(0,y.jsx)(`option`,{value:``,children:`Any reach`}),(0,y.jsx)(`option`,{value:`reachable`,children:`Reachable by a key`}),(0,y.jsx)(`option`,{value:`unreachable`,children:`Reachable by no key`})]}),(0,y.jsx)(`input`,{id:`accessmap-filter`,className:`search`,type:`search`,placeholder:`Filter by query or key…`,value:S.q,onChange:e=>w({q:e.target.value})})]}),(0,y.jsx)(`div`,{id:`accessmap-body`,className:`panel`,children:V})]})}function E({rows:e,keyCols:t,roleCols:n,keyMap:r,roleMap:i,st:a,onSort:o,onOpen:s,onInfo:c}){let l=a.sort,u=(e,t=null,n=null)=>l.field===e&&l.colType===t&&l.colName===n,f=(e,t=null,n=null)=>u(e,t,n)?l.dir===1?` ▲`:` ▼`:``,m=(e,t=null,n=null)=>{let r=u(e,t,n);o(r&&l.dir===1?{field:e,colType:t,colName:n,dir:-1}:r?{field:null,colType:null,colName:null,dir:1}:{field:e,colType:t,colName:n,dir:1})},h=(e,t,n)=>(0,y.jsx)(`th`,{className:t,onClick:()=>m(n),children:e+f(n)},n),g=(e,t,n,r)=>(0,y.jsxs)(`th`,{className:`amap-key-head amap-sortable`+(n?` revoked`:``),title:n?e+n:e,onClick:()=>m(`col`,r,e),children:[(0,y.jsxs)(`span`,{className:`amap-key-name`,children:[e,f(`col`,r,e),n?(0,y.jsx)(`span`,{className:`dim`,children:n}):null]}),(0,y.jsxs)(`span`,{className:`amap-key-sub`,children:[(t.allow_writes?`Read/write`:`Read-only`)+` · `+(t.rate_limit||`server default`),t.mask?.length?(0,y.jsx)(`span`,{className:`tag`,title:t.mask.map(e=>`${e.column}: ${e.action}`).join(`, `),children:`masked`}):null]})]},r+`:`+e),_=(e,t,n,r)=>{let i=p(e,t.name,t.collection,t.connection_name);if(!i.length)return(0,y.jsx)(`td`,{className:`amap-cell`},r);let a=[...new Set(i.map(e=>x[e.kind]))],o=a.length===1&&a[0]===`W`;return(0,y.jsx)(`td`,{className:`amap-cell`,title:i.map(e=>e.label).join(`, `),children:(0,y.jsx)(`span`,{className:`amap-dot`+(n?` role`:``)+(o?` conn`:``),children:a.join(`·`)})},r)},v=w();return(0,y.jsx)(`div`,{style:{overflow:`auto`},children:(0,y.jsxs)(`table`,{className:`amap`,children:[(0,y.jsxs)(`thead`,{children:[(0,y.jsxs)(`tr`,{children:[(0,y.jsx)(`th`,{colSpan:3+S.length}),t.length?(0,y.jsx)(`th`,{colSpan:t.length,className:`amap-group`,children:`API KEYS`}):null,n.length?(0,y.jsx)(`th`,{colSpan:n.length,className:`amap-group role`,title:`What a key created from each role would reach - a role grants nothing on its own`,children:`ROLES`}):null]}),(0,y.jsxs)(`tr`,{children:[h(`Query`,`amap-query amap-sortable`,`query`),C.map((e,t)=>h(S[t],`amap-sortable`,e)),h(`Connection`,`amap-sortable`,`connection`),h(`Reach`,`num amap-sortable`,`reach`),t.map(e=>{let t=r[e],n=!!(t.expires_at&&t.expires_at<v);return g(e,t,t.active?n?` (expired ${t.expires_at})`:``:` (revoked)`,`key`)}),n.map(e=>g(e,i[e],``,`role`))]})]}),(0,y.jsx)(`tbody`,{children:e.map(e=>{let a=e.q;return(0,y.jsxs)(`tr`,{children:[(0,y.jsxs)(`td`,{className:`amap-query`,children:[(0,y.jsxs)(`div`,{className:`amap-q-row`,children:[(0,y.jsx)(`button`,{type:`button`,className:`amap-q-name`,title:`Open ${a.name} in API Repository`,onClick:()=>s(a.name),children:a.name}),(0,y.jsx)(`button`,{type:`button`,className:`amap-info`,title:`Details for ${a.name}`,"aria-label":`Details for ${a.name}`,onClick:()=>c(e),children:`i`})]}),a.collection?(0,y.jsx)(`span`,{className:`amap-coll`,children:a.collection}):null]}),(0,y.jsx)(`td`,{children:e.dbType?(0,y.jsx)(`span`,{className:`tag`,children:e.dbType}):(0,y.jsx)(`span`,{className:`dim`,children:`—`})}),(0,y.jsx)(`td`,{className:`mono dim`,children:(0,y.jsx)(d,{value:a.created_at})}),(0,y.jsx)(`td`,{className:`mono dim`,children:(0,y.jsx)(d,{value:a.updated_at})}),(0,y.jsx)(`td`,{className:a.last_used_at?`mono`:`mono dim`,children:(0,y.jsx)(d,{value:a.last_used_at,fallback:`never`})}),(0,y.jsx)(`td`,{className:`mono`,children:`v`+a.latest_version}),(0,y.jsx)(`td`,{className:`mono dim`,children:a.connection_name||`—`}),(0,y.jsx)(`td`,{className:`num`,title:`Reachable by an actual API key (roles alone reach nothing)`,children:e.reach?String(e.reach):`—`}),t.map(e=>_(r[e],a,!1,`k:`+e)),n.map(e=>_(i[e],a,!0,`r:`+e))]},a.name)})}),(0,y.jsx)(`tfoot`,{children:(0,y.jsxs)(`tr`,{children:[(0,y.jsx)(`td`,{className:`amap-query`,children:`Reaches`}),S.map(e=>(0,y.jsx)(`td`,{},e)),(0,y.jsx)(`td`,{}),(0,y.jsx)(`td`,{}),t.map(t=>(0,y.jsx)(`td`,{className:`num`,children:`${e.filter(e=>p(r[t],e.q.name,e.q.collection,e.q.connection_name).length).length} / ${e.length}`},t)),n.map(t=>(0,y.jsx)(`td`,{className:`num`,children:`${e.filter(e=>p(i[t],e.q.name,e.q.collection,e.q.connection_name).length).length} / ${e.length}`},t))]})})]})})}export{T as AccessMapPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{B as e,H as t,R as n,c as r,h as i,l as a,n as o,s,t as c,v as l,z as u}from"./client-ZHNiMG8H.js";import{t as d}from"./useMutation-DQTq-cNw.js";import{D as f,E as p,T as m,f as h,h as g,k as _,l as v}from"./index--QHzN3S_.js";import{t as y}from"./Time-Bbp6q-9f.js";import{t as b}from"./forms-CfcbrGmf.js";import{n as x,t as S}from"./icons-CFXE8hAz.js";var C=t(e(),1),w=u(),T=[{value:`owner`,label:`Owner`,text:`everything, including administrators`},{value:`admin`,label:`Admin`,text:`connections, API keys and roles, queries, cache, settings`},{value:`developer`,label:`Developer`,text:`saved queries and SQL; no keys, no connection changes`},{value:`auditor`,label:`Auditor`,text:`read only - audit log, history, configuration; no SQL`}],E=e=>e===null?`Workspaces only`:T.find(t=>t.value===e)?.label??e,D=90,O=e=>new Date(Date.now()+e*864e5).toISOString().slice(0,10),k=`Store this now — it cannot be shown again. Paste it into the API key box at the bottom of the sidebar, or send it as the X-API-Key header.`;function A(){let{showError:e}=_();return t=>t instanceof c?e(t.message,{status:t.status}):e(t.message)}function j(e){let t=h(`admins.read`,`other administrators`);return l({queryKey:[`administrators`],queryFn:async()=>(await t.check(),s(await o.GET(`/api/v1/administrators`)).items),enabled:e,retry:!1})}function M(e){return l({queryKey:[`administrators`,e,`tokens`],queryFn:async()=>s(await o.GET(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:e}}})).items,retry:!1})}function N(){let e=g(),t=v(),n=!!e.data&&t(`admins.read`),r=j(n),{openDrawer:i}=_(),a=()=>i({title:`New administrator`,content:(0,w.jsx)(I,{})}),o;return o=e.isPending?(0,w.jsx)(f,{}):e.isError?(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`Not signed in as an administrator`}),(0,w.jsx)(`span`,{children:`Apply an admin token (qagadm_…) or the shared admin key in the box at the bottom of the sidebar.`})]}):n?r.isPending?(0,w.jsx)(f,{text:`Loading administrators…`}):r.isError?(0,w.jsx)(`div`,{className:`hint`,children:r.error.message}):r.data.length?(0,w.jsx)(P,{items:r.data,self:e.data.via===`token`||e.data.via===`sso`?e.data.name:null}):(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`No administrators yet`}),(0,w.jsx)(`span`,{children:`Everyone shares QUERYAPIGATE_API_KEY today, so the audit log can only say “admin”. Give each person their own sign-in, with a role that fits what they do — then the shared key becomes a break-glass key.`}),(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:a,children:`New administrator`})]}):(0,w.jsx)(F,{name:e.data.name??``,role:e.data.role}),(0,w.jsxs)(w.Fragment,{children:[(0,w.jsxs)(`div`,{className:`page-head`,children:[(0,w.jsxs)(`div`,{className:`titles`,children:[(0,w.jsx)(`h1`,{children:`Administrators`}),(0,w.jsx)(`span`,{className:`sub`,id:`administrators-sub`,children:n?`The people and pipelines that manage this server, each with a role and their own tokens.`:`Your account and your admin tokens.`})]}),(0,w.jsx)(`span`,{className:`spacer`}),n&&(0,w.jsx)(`button`,{id:`new-administrator`,type:`button`,className:`btn primary`,onClick:a,children:`New administrator`})]}),(0,w.jsx)(`div`,{id:`administrators-table`,className:`panel`,children:o})]})}function P({items:e,self:t}){let{openDrawer:i,toast:a}=_(),s=A(),c=n(),l=async e=>{if(window.confirm(`Remove administrator '${e}'? Every token of theirs stops working immediately.`))try{r(await o.DELETE(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),a(`Removed `+e),c.invalidateQueries({queryKey:[`administrators`]})}catch(e){s(e)}};return(0,w.jsx)(`div`,{style:{overflowX:`auto`},children:(0,w.jsxs)(`table`,{className:`grid`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsx)(`tr`,{children:[`Name`,`Role`,`Email`,`Last seen`,`Tokens`,``].map((e,t)=>(0,w.jsx)(`th`,{children:e},t))})}),(0,w.jsx)(`tbody`,{children:e.map(e=>(0,w.jsxs)(`tr`,{"data-name":e.name,children:[(0,w.jsx)(`td`,{children:(0,w.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,w.jsx)(`span`,{className:`dot ${e.active?`ok`:`off`}`,title:e.active?`Active`:`Inactive`}),(0,w.jsx)(`span`,{className:`name`,children:e.name}),e.name===t&&(0,w.jsx)(`span`,{className:`tag`,children:`you`}),e.sso?.status===`linked`&&(0,w.jsx)(`span`,{className:`tag`,title:`Signs in through ${e.sso.issuer??`SSO`}; role from their groups`,children:`SSO`}),e.sso?.status===`pending`&&(0,w.jsx)(`span`,{className:`tag`,title:`Linked at their next SSO sign-in, by their verified email`,children:`SSO pending`})]})}),(0,w.jsx)(`td`,{children:E(e.role)}),(0,w.jsx)(`td`,{className:e.email?void 0:`dim`,children:e.email??`—`}),(0,w.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,w.jsx)(y,{value:e.last_seen_at,fallback:`never`})}),(0,w.jsx)(`td`,{className:`mono`,children:e.tokens}),(0,w.jsx)(`td`,{children:(0,w.jsxs)(`div`,{className:`actions`,children:[(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>i({title:`Admin tokens`,kicker:e.name,wide:!0,content:(0,w.jsx)(R,{name:e.name})}),children:`Tokens`}),(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm icon`,title:`Edit ${e.name}`,"aria-label":`Edit`,onClick:()=>i({title:`Edit administrator`,kicker:e.name,content:(0,w.jsx)(I,{name:e.name})}),children:(0,w.jsx)(S,{})}),(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm icon danger`,title:`Remove ${e.name}`,"aria-label":`Remove`,onClick:()=>void l(e.name),children:(0,w.jsx)(x,{})})]})})]},e.name))})]})})}function F({name:e,role:t}){return(0,w.jsxs)(`div`,{className:`form`,style:{padding:16},children:[(0,w.jsxs)(`div`,{children:[`Signed in as `,(0,w.jsx)(`strong`,{children:e}),` · `,E(t),` — `,T.find(e=>e.value===t)?.text,`.`]}),(0,w.jsx)(R,{name:e})]})}function I({name:e}){let t=l({queryKey:[`administrators`,`detail`,e],queryFn:async()=>a(await o.GET(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),enabled:!!e,retry:!1,gcTime:0});return e&&t.isPending?(0,w.jsx)(f,{}):e&&t.isError?(0,w.jsx)(`div`,{className:`hint`,children:t.error.message}):(0,w.jsx)(L,{name:e,existing:t.data?.data,etag:t.data?.etag??null},e??``)}function L({name:e,existing:t,etag:r}){let a=!!e,{closeDrawer:c,openDrawer:l,showError:u,toast:f}=_(),h=A(),g=n(),[v,y]=(0,C.useState)(e??``),[x,S]=(0,C.useState)(t?.role??`developer`),[E,j]=(0,C.useState)(t?.email??``),[M,N]=(0,C.useState)(!t||t.active),P=i(),F=t?.sso?.status===`linked`,[I,L]=(0,C.useState)(t?.sso?.status??`none`),R=d({mutationFn:async()=>{let n={role:x,email:E.trim()||null};if(a)return s(await o.PATCH(`/api/v1/administrators/{name}`,{params:{path:{name:e},header:r?{"If-Match":r}:{}},body:{...F?{email:n.email}:n,active:M,...I===(t?.sso?.status??`none`)?{}:{sso:I===`pending`?`pending`:null}}})),null;let i=s(await o.POST(`/api/v1/administrators`,{body:{name:v.trim(),...n}}));return s(await o.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:i.name}},body:{label:`first token`,expires_at:O(D)}}))},onSuccess:t=>{if(u(``),g.invalidateQueries({queryKey:[`administrators`]}),!t){c(),f(`Updated `+e);return}l({title:`Administrator created`,kicker:t.admin,content:(0,w.jsx)(b,{secret:t.secret,label:`Admin token for ${t.admin} (expires ${t.expires_at})`,hint:k})})},onError:h});return(0,w.jsxs)(`form`,{className:`form`,noValidate:!0,onSubmit:e=>{if(e.preventDefault(),!a&&!v.trim()){u(`A name is required.`,{errors:{name:`This field is required`}});return}R.mutate()},children:[(0,w.jsx)(m,{id:`a-name`,label:`Name`,hint:a?void 0:`A person (alice) or a pipeline (ci-deploy). Shown in the audit log.`,children:(0,w.jsx)(`input`,{id:`a-name`,value:v,placeholder:`alice`,autoComplete:`off`,spellCheck:!1,disabled:a,autoFocus:!a,onChange:e=>y(e.target.value)})}),(0,w.jsx)(m,{id:`a-role`,label:`Role`,hint:F?`From their groups at the identity provider, at every sign-in - change it there.`:T.find(e=>e.value===x)?.text,children:(0,w.jsx)(`select`,{id:`a-role`,value:x,disabled:F,onChange:e=>S(e.target.value),children:T.map(e=>(0,w.jsx)(`option`,{value:e.value,children:e.label},e.value))})}),(0,w.jsx)(m,{id:`a-email`,label:`Email`,hint:`Optional.`,children:(0,w.jsx)(`input`,{id:`a-email`,type:`email`,value:E,autoComplete:`off`,onChange:e=>j(e.target.value)})}),a&&(0,w.jsxs)(`label`,{className:`switch`,children:[(0,w.jsx)(`input`,{id:`a-active`,type:`checkbox`,checked:M,onChange:e=>N(e.target.checked)}),`Active`,(0,w.jsx)(`span`,{className:`hint`,children:`— unchecking stops every token of theirs immediately`})]}),a&&P.data?.sso&&(0,w.jsx)(m,{id:`a-sso`,label:`Sign-in through SSO`,hint:I===`linked`?`They sign in through the identity provider. Unlinking ends their sessions; their next SSO sign-in makes a new administrator.`:`Pending: their next SSO sign-in, with a verified email matching the one above, is bound to this account - once.`,children:(0,w.jsxs)(`select`,{id:`a-sso`,value:I,onChange:e=>L(e.target.value),children:[F&&(0,w.jsx)(`option`,{value:`linked`,children:`Linked`}),!F&&(0,w.jsx)(`option`,{value:`pending`,children:`Link at their next sign-in`}),(0,w.jsx)(`option`,{value:`none`,children:F?`Unlink`:`Not through SSO`})]})}),!a&&(0,w.jsxs)(`div`,{className:`hint`,children:[`Their first admin token, valid for `,D,` days, is shown once after you create them.`]}),(0,w.jsx)(p,{onCancel:c,children:(0,w.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:R.isPending,children:a?`Save`:`Create`})})]})}function R({name:e}){let t=M(e),i=n(),{toast:a}=_(),c=A(),[l,u]=(0,C.useState)(``),[p,h]=(0,C.useState)(O(D)),[g,v]=(0,C.useState)(null),x=()=>void i.invalidateQueries({queryKey:[`administrators`]}),S=d({mutationFn:async()=>s(await o.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:e}},body:{label:l.trim()||null,expires_at:p||null}})),onSuccess:e=>{v(e),u(``),x()},onError:c}),T=async t=>{if(window.confirm(`Revoke ${t.label?`'${t.label}'`:t.id}? Anything using it stops working now.`))try{r(await o.DELETE(`/api/v1/administrators/{name}/tokens/{token_id}`,{params:{path:{name:e,token_id:t.id}}})),a(`Revoked `+(t.label??t.id)),x()}catch(e){c(e)}},E=new Date().toISOString().slice(0,10);return(0,w.jsxs)(`div`,{className:`form`,id:`admin-tokens`,children:[t.isPending?(0,w.jsx)(f,{text:`Loading tokens…`}):t.isError?(0,w.jsx)(`div`,{className:`hint`,children:t.error.message}):t.data.length?(0,w.jsxs)(`table`,{className:`grid`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsx)(`tr`,{children:[`Label`,`Expires`,`Last used`,``].map((e,t)=>(0,w.jsx)(`th`,{children:e},t))})}),(0,w.jsx)(`tbody`,{children:t.data.map(e=>(0,w.jsxs)(`tr`,{"data-token":e.id,children:[(0,w.jsx)(`td`,{title:e.id,children:e.label??(0,w.jsx)(`span`,{className:`dim mono`,children:e.id})}),(0,w.jsx)(`td`,{style:{whiteSpace:`nowrap`},children:e.expires_at?(0,w.jsx)(`span`,{style:e.expired?{color:`var(--danger)`}:void 0,children:e.expires_at+(e.expired?` (expired)`:e.expires_at===E?` (today)`:``)}):(0,w.jsx)(`span`,{className:`dim`,children:`never`})}),(0,w.jsx)(`td`,{className:`mono dim`,children:(0,w.jsx)(y,{value:e.last_used_at,fallback:`never`})}),(0,w.jsx)(`td`,{children:(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm danger`,onClick:()=>void T(e),children:`Revoke`})})]},e.id))})]}):(0,w.jsx)(`div`,{className:`hint`,children:`No tokens. Issue one below to sign in with.`}),g?(0,w.jsx)(b,{secret:g.secret,label:`New token${g.label?` '${g.label}'`:``} (${g.expires_at?`expires ${g.expires_at}`:`never expires`})`,hint:k,onDone:()=>v(null)}):(0,w.jsxs)(`form`,{className:`grid2`,noValidate:!0,onSubmit:e=>{e.preventDefault(),S.mutate()},children:[(0,w.jsx)(m,{id:`t-label`,label:`New token: label`,hint:`What it's for - laptop, ci.`,children:(0,w.jsx)(`input`,{id:`t-label`,value:l,autoComplete:`off`,onChange:e=>u(e.target.value)})}),(0,w.jsx)(m,{id:`t-expires`,label:`Expires`,hint:`Leave blank for no expiry.`,children:(0,w.jsx)(`input`,{id:`t-expires`,type:`date`,value:p,onChange:e=>h(e.target.value)})}),(0,w.jsx)(`div`,{children:(0,w.jsx)(`button`,{type:`submit`,className:`btn`,disabled:S.isPending,children:`Issue token`})})]})]})}export{N as AdministratorsPage};
|
|
1
|
+
import{B as e,H as t,R as n,c as r,h as i,l as a,n as o,s,t as c,v as l,z as u}from"./client-ZHNiMG8H.js";import{t as d}from"./useMutation-BbQqPorm.js";import{D as f,E as p,T as m,f as h,h as g,k as _,l as v}from"./index-D3-9-G4i.js";import{t as y}from"./Time-BJyVzig6.js";import{t as b}from"./forms-jtkIvvrV.js";import{n as x,t as S}from"./icons-CFXE8hAz.js";var C=t(e(),1),w=u(),T=[{value:`owner`,label:`Owner`,text:`everything, including administrators`},{value:`admin`,label:`Admin`,text:`connections, API keys and roles, queries, cache, settings`},{value:`developer`,label:`Developer`,text:`saved queries and SQL; no keys, no connection changes`},{value:`auditor`,label:`Auditor`,text:`read only - audit log, history, configuration; no SQL`}],E=e=>e===null?`Workspaces only`:T.find(t=>t.value===e)?.label??e,D=90,O=e=>new Date(Date.now()+e*864e5).toISOString().slice(0,10),k=`Store this now — it cannot be shown again. Paste it into the API key box at the bottom of the sidebar, or send it as the X-API-Key header.`;function A(){let{showError:e}=_();return t=>t instanceof c?e(t.message,{status:t.status}):e(t.message)}function j(e){let t=h(`admins.read`,`other administrators`);return l({queryKey:[`administrators`],queryFn:async()=>(await t.check(),s(await o.GET(`/api/v1/administrators`)).items),enabled:e,retry:!1})}function M(e){return l({queryKey:[`administrators`,e,`tokens`],queryFn:async()=>s(await o.GET(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:e}}})).items,retry:!1})}function N(){let e=g(),t=v(),n=!!e.data&&t(`admins.read`),r=j(n),{openDrawer:i}=_(),a=()=>i({title:`New administrator`,content:(0,w.jsx)(I,{})}),o;return o=e.isPending?(0,w.jsx)(f,{}):e.isError?(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`Not signed in as an administrator`}),(0,w.jsx)(`span`,{children:`Apply an admin token (qagadm_…) or the shared admin key in the box at the bottom of the sidebar.`})]}):n?r.isPending?(0,w.jsx)(f,{text:`Loading administrators…`}):r.isError?(0,w.jsx)(`div`,{className:`hint`,children:r.error.message}):r.data.length?(0,w.jsx)(P,{items:r.data,self:e.data.via===`token`||e.data.via===`sso`?e.data.name:null}):(0,w.jsxs)(`div`,{className:`empty`,children:[(0,w.jsx)(`strong`,{children:`No administrators yet`}),(0,w.jsx)(`span`,{children:`Everyone shares QUERYAPIGATE_API_KEY today, so the audit log can only say “admin”. Give each person their own sign-in, with a role that fits what they do — then the shared key becomes a break-glass key.`}),(0,w.jsx)(`button`,{type:`button`,className:`btn primary`,onClick:a,children:`New administrator`})]}):(0,w.jsx)(F,{name:e.data.name??``,role:e.data.role}),(0,w.jsxs)(w.Fragment,{children:[(0,w.jsxs)(`div`,{className:`page-head`,children:[(0,w.jsxs)(`div`,{className:`titles`,children:[(0,w.jsx)(`h1`,{children:`Administrators`}),(0,w.jsx)(`span`,{className:`sub`,id:`administrators-sub`,children:n?`The people and pipelines that manage this server, each with a role and their own tokens.`:`Your account and your admin tokens.`})]}),(0,w.jsx)(`span`,{className:`spacer`}),n&&(0,w.jsx)(`button`,{id:`new-administrator`,type:`button`,className:`btn primary`,onClick:a,children:`New administrator`})]}),(0,w.jsx)(`div`,{id:`administrators-table`,className:`panel`,children:o})]})}function P({items:e,self:t}){let{openDrawer:i,toast:a}=_(),s=A(),c=n(),l=async e=>{if(window.confirm(`Remove administrator '${e}'? Every token of theirs stops working immediately.`))try{r(await o.DELETE(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),a(`Removed `+e),c.invalidateQueries({queryKey:[`administrators`]})}catch(e){s(e)}};return(0,w.jsx)(`div`,{style:{overflowX:`auto`},children:(0,w.jsxs)(`table`,{className:`grid`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsx)(`tr`,{children:[`Name`,`Role`,`Email`,`Last seen`,`Tokens`,``].map((e,t)=>(0,w.jsx)(`th`,{children:e},t))})}),(0,w.jsx)(`tbody`,{children:e.map(e=>(0,w.jsxs)(`tr`,{"data-name":e.name,children:[(0,w.jsx)(`td`,{children:(0,w.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,w.jsx)(`span`,{className:`dot ${e.active?`ok`:`off`}`,title:e.active?`Active`:`Inactive`}),(0,w.jsx)(`span`,{className:`name`,children:e.name}),e.name===t&&(0,w.jsx)(`span`,{className:`tag`,children:`you`}),e.sso?.status===`linked`&&(0,w.jsx)(`span`,{className:`tag`,title:`Signs in through ${e.sso.issuer??`SSO`}; role from their groups`,children:`SSO`}),e.sso?.status===`pending`&&(0,w.jsx)(`span`,{className:`tag`,title:`Linked at their next SSO sign-in, by their verified email`,children:`SSO pending`})]})}),(0,w.jsx)(`td`,{children:E(e.role)}),(0,w.jsx)(`td`,{className:e.email?void 0:`dim`,children:e.email??`—`}),(0,w.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,w.jsx)(y,{value:e.last_seen_at,fallback:`never`})}),(0,w.jsx)(`td`,{className:`mono`,children:e.tokens}),(0,w.jsx)(`td`,{children:(0,w.jsxs)(`div`,{className:`actions`,children:[(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>i({title:`Admin tokens`,kicker:e.name,wide:!0,content:(0,w.jsx)(R,{name:e.name})}),children:`Tokens`}),(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm icon`,title:`Edit ${e.name}`,"aria-label":`Edit`,onClick:()=>i({title:`Edit administrator`,kicker:e.name,content:(0,w.jsx)(I,{name:e.name})}),children:(0,w.jsx)(S,{})}),(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm icon danger`,title:`Remove ${e.name}`,"aria-label":`Remove`,onClick:()=>void l(e.name),children:(0,w.jsx)(x,{})})]})})]},e.name))})]})})}function F({name:e,role:t}){return(0,w.jsxs)(`div`,{className:`form`,style:{padding:16},children:[(0,w.jsxs)(`div`,{children:[`Signed in as `,(0,w.jsx)(`strong`,{children:e}),` · `,E(t),` — `,T.find(e=>e.value===t)?.text,`.`]}),(0,w.jsx)(R,{name:e})]})}function I({name:e}){let t=l({queryKey:[`administrators`,`detail`,e],queryFn:async()=>a(await o.GET(`/api/v1/administrators/{name}`,{params:{path:{name:e}}})),enabled:!!e,retry:!1,gcTime:0});return e&&t.isPending?(0,w.jsx)(f,{}):e&&t.isError?(0,w.jsx)(`div`,{className:`hint`,children:t.error.message}):(0,w.jsx)(L,{name:e,existing:t.data?.data,etag:t.data?.etag??null},e??``)}function L({name:e,existing:t,etag:r}){let a=!!e,{closeDrawer:c,openDrawer:l,showError:u,toast:f}=_(),h=A(),g=n(),[v,y]=(0,C.useState)(e??``),[x,S]=(0,C.useState)(t?.role??`developer`),[E,j]=(0,C.useState)(t?.email??``),[M,N]=(0,C.useState)(!t||t.active),P=i(),F=t?.sso?.status===`linked`,[I,L]=(0,C.useState)(t?.sso?.status??`none`),R=d({mutationFn:async()=>{let n={role:x,email:E.trim()||null};if(a)return s(await o.PATCH(`/api/v1/administrators/{name}`,{params:{path:{name:e},header:r?{"If-Match":r}:{}},body:{...F?{email:n.email}:n,active:M,...I===(t?.sso?.status??`none`)?{}:{sso:I===`pending`?`pending`:null}}})),null;let i=s(await o.POST(`/api/v1/administrators`,{body:{name:v.trim(),...n}}));return s(await o.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:i.name}},body:{label:`first token`,expires_at:O(D)}}))},onSuccess:t=>{if(u(``),g.invalidateQueries({queryKey:[`administrators`]}),!t){c(),f(`Updated `+e);return}l({title:`Administrator created`,kicker:t.admin,content:(0,w.jsx)(b,{secret:t.secret,label:`Admin token for ${t.admin} (expires ${t.expires_at})`,hint:k})})},onError:h});return(0,w.jsxs)(`form`,{className:`form`,noValidate:!0,onSubmit:e=>{if(e.preventDefault(),!a&&!v.trim()){u(`A name is required.`,{errors:{name:`This field is required`}});return}R.mutate()},children:[(0,w.jsx)(m,{id:`a-name`,label:`Name`,hint:a?void 0:`A person (alice) or a pipeline (ci-deploy). Shown in the audit log.`,children:(0,w.jsx)(`input`,{id:`a-name`,value:v,placeholder:`alice`,autoComplete:`off`,spellCheck:!1,disabled:a,autoFocus:!a,onChange:e=>y(e.target.value)})}),(0,w.jsx)(m,{id:`a-role`,label:`Role`,hint:F?`From their groups at the identity provider, at every sign-in - change it there.`:T.find(e=>e.value===x)?.text,children:(0,w.jsx)(`select`,{id:`a-role`,value:x,disabled:F,onChange:e=>S(e.target.value),children:T.map(e=>(0,w.jsx)(`option`,{value:e.value,children:e.label},e.value))})}),(0,w.jsx)(m,{id:`a-email`,label:`Email`,hint:`Optional.`,children:(0,w.jsx)(`input`,{id:`a-email`,type:`email`,value:E,autoComplete:`off`,onChange:e=>j(e.target.value)})}),a&&(0,w.jsxs)(`label`,{className:`switch`,children:[(0,w.jsx)(`input`,{id:`a-active`,type:`checkbox`,checked:M,onChange:e=>N(e.target.checked)}),`Active`,(0,w.jsx)(`span`,{className:`hint`,children:`— unchecking stops every token of theirs immediately`})]}),a&&P.data?.sso&&(0,w.jsx)(m,{id:`a-sso`,label:`Sign-in through SSO`,hint:I===`linked`?`They sign in through the identity provider. Unlinking ends their sessions; their next SSO sign-in makes a new administrator.`:`Pending: their next SSO sign-in, with a verified email matching the one above, is bound to this account - once.`,children:(0,w.jsxs)(`select`,{id:`a-sso`,value:I,onChange:e=>L(e.target.value),children:[F&&(0,w.jsx)(`option`,{value:`linked`,children:`Linked`}),!F&&(0,w.jsx)(`option`,{value:`pending`,children:`Link at their next sign-in`}),(0,w.jsx)(`option`,{value:`none`,children:F?`Unlink`:`Not through SSO`})]})}),!a&&(0,w.jsxs)(`div`,{className:`hint`,children:[`Their first admin token, valid for `,D,` days, is shown once after you create them.`]}),(0,w.jsx)(p,{onCancel:c,children:(0,w.jsx)(`button`,{type:`submit`,className:`btn primary`,disabled:R.isPending,children:a?`Save`:`Create`})})]})}function R({name:e}){let t=M(e),i=n(),{toast:a}=_(),c=A(),[l,u]=(0,C.useState)(``),[p,h]=(0,C.useState)(O(D)),[g,v]=(0,C.useState)(null),x=()=>void i.invalidateQueries({queryKey:[`administrators`]}),S=d({mutationFn:async()=>s(await o.POST(`/api/v1/administrators/{name}/tokens`,{params:{path:{name:e}},body:{label:l.trim()||null,expires_at:p||null}})),onSuccess:e=>{v(e),u(``),x()},onError:c}),T=async t=>{if(window.confirm(`Revoke ${t.label?`'${t.label}'`:t.id}? Anything using it stops working now.`))try{r(await o.DELETE(`/api/v1/administrators/{name}/tokens/{token_id}`,{params:{path:{name:e,token_id:t.id}}})),a(`Revoked `+(t.label??t.id)),x()}catch(e){c(e)}},E=new Date().toISOString().slice(0,10);return(0,w.jsxs)(`div`,{className:`form`,id:`admin-tokens`,children:[t.isPending?(0,w.jsx)(f,{text:`Loading tokens…`}):t.isError?(0,w.jsx)(`div`,{className:`hint`,children:t.error.message}):t.data.length?(0,w.jsxs)(`table`,{className:`grid`,children:[(0,w.jsx)(`thead`,{children:(0,w.jsx)(`tr`,{children:[`Label`,`Expires`,`Last used`,``].map((e,t)=>(0,w.jsx)(`th`,{children:e},t))})}),(0,w.jsx)(`tbody`,{children:t.data.map(e=>(0,w.jsxs)(`tr`,{"data-token":e.id,children:[(0,w.jsx)(`td`,{title:e.id,children:e.label??(0,w.jsx)(`span`,{className:`dim mono`,children:e.id})}),(0,w.jsx)(`td`,{style:{whiteSpace:`nowrap`},children:e.expires_at?(0,w.jsx)(`span`,{style:e.expired?{color:`var(--danger)`}:void 0,children:e.expires_at+(e.expired?` (expired)`:e.expires_at===E?` (today)`:``)}):(0,w.jsx)(`span`,{className:`dim`,children:`never`})}),(0,w.jsx)(`td`,{className:`mono dim`,children:(0,w.jsx)(y,{value:e.last_used_at,fallback:`never`})}),(0,w.jsx)(`td`,{children:(0,w.jsx)(`button`,{type:`button`,className:`btn ghost sm danger`,onClick:()=>void T(e),children:`Revoke`})})]},e.id))})]}):(0,w.jsx)(`div`,{className:`hint`,children:`No tokens. Issue one below to sign in with.`}),g?(0,w.jsx)(b,{secret:g.secret,label:`New token${g.label?` '${g.label}'`:``} (${g.expires_at?`expires ${g.expires_at}`:`never expires`})`,hint:k,onDone:()=>v(null)}):(0,w.jsxs)(`form`,{className:`grid2`,noValidate:!0,onSubmit:e=>{e.preventDefault(),S.mutate()},children:[(0,w.jsx)(m,{id:`t-label`,label:`New token: label`,hint:`What it's for - laptop, ci.`,children:(0,w.jsx)(`input`,{id:`t-label`,value:l,autoComplete:`off`,onChange:e=>u(e.target.value)})}),(0,w.jsx)(m,{id:`t-expires`,label:`Expires`,hint:`Leave blank for no expiry.`,children:(0,w.jsx)(`input`,{id:`t-expires`,type:`date`,value:p,onChange:e=>h(e.target.value)})}),(0,w.jsx)(`div`,{children:(0,w.jsx)(`button`,{type:`submit`,className:`btn`,disabled:S.isPending,children:`Issue token`})})]})]})}export{N as AdministratorsPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{B as e,H as t,n,s as r,v as i,z as a}from"./client-ZHNiMG8H.js";import{D as o}from"./index
|
|
1
|
+
import{B as e,H as t,n,s as r,v as i,z as a}from"./client-ZHNiMG8H.js";import{D as o}from"./index-D3-9-G4i.js";import{t as s}from"./Time-BJyVzig6.js";import{t as c}from"./CodeBox-Cwh5KHLp.js";var l=t(e(),1),u=a(),d=[{id:`1h`,label:`Last hour`,hours:1},{id:`24h`,label:`Last 24 hours`,hours:24},{id:`7d`,label:`Last 7 days`,hours:168},{id:`30d`,label:`Last 30 days`,hours:720}],f=e=>e.toLocaleString();function p({refusals:e}){let t=Object.entries(e);return t.length?(0,u.jsx)(`span`,{style:{display:`inline-flex`,flexWrap:`wrap`,gap:4},children:t.map(([e,t])=>(0,u.jsx)(`span`,{className:`tag`,title:`${t} call${t===1?``:`s`} failed with ${e}`,children:`${e} × ${t}`},e))}):(0,u.jsx)(`span`,{className:`dim`,children:`none`})}function m({writes:e}){let t=Object.entries(e??{}).filter(([,e])=>e).map(([e,t])=>`${t} ${e}`);return(0,u.jsx)(`div`,{className:`hint`,children:t.length?t.join(` · `):`None.`})}function h({agent:e}){let t=Object.entries(e.queries);return(0,u.jsxs)(`div`,{className:`agent-detail`,children:[(0,u.jsx)(`p`,{className:`sub-h`,children:`Writes asked for`}),(0,u.jsx)(m,{writes:e.writes}),(0,u.jsx)(`p`,{className:`sub-h`,style:{marginTop:12},children:`Saved queries`}),t.length?(0,u.jsx)(`div`,{className:`hint`,children:t.map(([e,t])=>`${e} × ${t}`).join(` · `)}):(0,u.jsx)(`div`,{className:`hint`,children:`None - only ad-hoc SQL.`}),(0,u.jsx)(`p`,{className:`sub-h`,style:{marginTop:12},children:`Most frequent ad-hoc statements`}),e.top_statements.length?e.top_statements.map((e,t)=>(0,u.jsxs)(`div`,{className:`agent-statement`,children:[(0,u.jsx)(`div`,{className:`hint`,children:`${e.count} call${e.count===1?``:`s`} · ${f(e.rows)} rows`+(e.errors?` · ${e.errors} failed`:``)}),e.sql?(0,u.jsx)(c,{sql:e.sql}):(0,u.jsx)(`span`,{className:`mono dim`,children:`sha256 ${e.sql_sha256}`})]},t)):(0,u.jsx)(`div`,{className:`hint`,children:e.adhoc_calls?`Run history keeps no SQL text here (QUERYAPIGATE_HISTORY_ADHOC_SQL is none).`:`No ad-hoc SQL.`})]})}function g(){let[e,t]=(0,l.useState)(`24h`),[a,c]=(0,l.useState)(null),m=d.find(t=>t.id===e).hours,g=i({queryKey:[`usage`,`agents`,e],queryFn:async()=>r(await n.GET(`/api/v1/usage/agents`,{params:{query:{hours:m}}})),retry:!1}),_=g.data?.items??[],v;v=g.isPending?(0,u.jsx)(o,{text:`Loading agents…`}):g.isError?(0,u.jsxs)(`div`,{className:`empty`,children:[(0,u.jsx)(`strong`,{children:`Couldn’t load agent usage`}),(0,u.jsx)(`span`,{children:g.error.message})]}):_.length?(0,u.jsx)(`div`,{style:{overflowX:`auto`},children:(0,u.jsxs)(`table`,{className:`grid`,children:[(0,u.jsx)(`thead`,{children:(0,u.jsx)(`tr`,{children:[`Agent`,`Calls`,`Rows`,`Failed`,`Masked`,`Refused or failed with`,`Last seen`].map(e=>(0,u.jsx)(`th`,{children:e},e))})}),(0,u.jsx)(`tbody`,{children:_.map(e=>(0,u.jsxs)(l.Fragment,{children:[(0,u.jsxs)(`tr`,{children:[(0,u.jsxs)(`td`,{children:[(0,u.jsx)(`button`,{type:`button`,className:`agent-name`,"aria-expanded":a===e.agent,onClick:()=>c(a===e.agent?null:e.agent),children:e.agent}),(0,u.jsx)(`div`,{className:`dim`,style:{fontSize:12},children:e.connections.join(`, `)})]}),(0,u.jsx)(`td`,{className:`num`,children:f(e.calls)}),(0,u.jsx)(`td`,{className:`num`,children:f(e.rows)}),(0,u.jsx)(`td`,{className:`num`,children:f(e.errors)}),(0,u.jsx)(`td`,{className:`num`,children:f(e.masked_calls)}),(0,u.jsx)(`td`,{children:(0,u.jsx)(p,{refusals:e.refusals})}),(0,u.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,u.jsx)(s,{value:e.last_seen,fallback:``})})]}),a===e.agent&&(0,u.jsx)(`tr`,{children:(0,u.jsx)(`td`,{colSpan:7,children:(0,u.jsx)(h,{agent:e})})})]},e.agent))})]})}):(0,u.jsxs)(`div`,{className:`empty`,children:[(0,u.jsx)(`strong`,{children:`No agent called the MCP server in this window`}),(0,u.jsx)(`span`,{children:`Runs over MCP - saved-query tools and execute_sql - appear here, per API key or signed-in user.`})]});let y=[];return g.data&&g.data.sample_rate<1&&y.push(`Run history keeps ${Math.round(g.data.sample_rate*100)}% of successful runs, so successful calls and rows are a sample; failures are all here.`),g.data?.truncated&&y.push(`More runs than one summary reads (100,000) - narrow the window.`),(0,u.jsxs)(u.Fragment,{children:[(0,u.jsxs)(`div`,{className:`page-head`,children:[(0,u.jsxs)(`div`,{className:`titles`,children:[(0,u.jsx)(`h1`,{children:`Agents`}),(0,u.jsx)(`span`,{className:`sub`,children:`What each AI agent did over MCP - per API key or signed-in user, from run history.`})]}),(0,u.jsx)(`span`,{className:`spacer`}),(0,u.jsx)(`button`,{type:`button`,className:`btn`,onClick:()=>void g.refetch(),children:`Refresh`})]}),(0,u.jsxs)(`div`,{className:`panel`,style:{overflow:`hidden`},children:[(0,u.jsxs)(`div`,{className:`panel-bar`,children:[(0,u.jsx)(`select`,{"aria-label":`Window`,value:e,onChange:e=>t(e.target.value),children:d.map(e=>(0,u.jsx)(`option`,{value:e.id,children:e.label},e.id))}),(0,u.jsx)(`span`,{className:`spacer`}),(0,u.jsx)(`span`,{className:`panel-count`,children:g.data?`${_.length} agent${_.length===1?``:`s`}`:``})]}),y.map(e=>(0,u.jsx)(`div`,{className:`hint`,style:{padding:`6px 12px`},children:e},e)),v]})]})}export{g as AgentsPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{B as e,H as t,z as n}from"./client-ZHNiMG8H.js";import{C as r,D as i,M as a,N as o,S as s,x as c,y as l}from"./index
|
|
1
|
+
import{B as e,H as t,z as n}from"./client-ZHNiMG8H.js";import{C as r,D as i,M as a,N as o,S as s,x as c,y as l}from"./index-D3-9-G4i.js";import{t as u}from"./Time-BJyVzig6.js";import{n as d}from"./state-O13cl7cO.js";var f=t(e(),1),p=n(),m=[{id:`critical`,label:`Critical`},{id:`warning`,label:`Warning`},{id:`info`,label:`For your information`}],h={key:`Open API keys`,query:`Open query`,connection:`Open connections`,settings:`Open settings`,export:`Open exports`,agent:`Open agents`,approvals:`Open approvals`,audit:`Open audit log`};function g(){let e=a();return t=>{t.type===`query`?e(`/queries/${encodeURIComponent(t.name)}`):t.type===`key`?e(`/api-keys`):t.type===`connection`?e(`/connections`):t.type===`export`?e(`/exports`):t.type===`agent`?e(`/agents`):t.type===`approvals`?e(`/approvals`):t.type===`audit`?e(`/audit-log`):(d(t.name),e(`/settings`))}}function _({alert:e,dismissed:t}){let n=g();return(0,p.jsxs)(`div`,{className:`alert-row `+e.severity,"data-alert":e.id,children:[(0,p.jsx)(`span`,{className:`sev `+e.severity,children:e.severity}),(0,p.jsxs)(`div`,{className:`alert-what`,children:[(0,p.jsx)(`strong`,{children:e.title}),(0,p.jsx)(`span`,{className:`alert-detail`,children:e.detail}),e.since&&!e.detail.includes(e.since)&&(0,p.jsxs)(`span`,{className:`alert-since`,children:[e.kind===`key_expiring`?`Expires `:`Since `,(0,p.jsx)(u,{value:e.since})]})]}),(0,p.jsxs)(`div`,{className:`actions`,children:[e.target&&(0,p.jsx)(`button`,{type:`button`,className:`btn sm outlined`,onClick:()=>n(e.target),children:h[e.target.type]??`Open`}),t?(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>s(e.id),children:`Restore`}):(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,title:`Hide it in this browser until it clears`,onClick:()=>c(e.id),children:`Dismiss`})]})]})}function v({alerts:e}){return(0,p.jsx)(p.Fragment,{children:m.map(({id:t,label:n})=>{let r=e.filter(e=>e.severity===t);return r.length?(0,p.jsxs)(`div`,{className:`panel alerts-group`,id:`alerts-`+t,children:[(0,p.jsxs)(`h2`,{children:[n,` `,(0,p.jsx)(`span`,{className:`count`,children:r.length})]}),r.map(e=>(0,p.jsx)(_,{alert:e},e.id))]},t):null})})}function y(){let e=a(),t=o(),n=l.find(e=>e.id===t.tab),{query:s,active:c,dismissed:d}=r(),[m,h]=(0,f.useState)(!1),g=e=>!n||n.kinds.includes(e.kind),y=c.filter(g),b=d.filter(g),x=e=>c.filter(t=>!e||e.includes(t.kind)),S;S=s.isPending?(0,p.jsx)(i,{text:`Checking…`}):s.isError?(0,p.jsx)(`div`,{className:`panel`,children:(0,p.jsxs)(`div`,{className:`empty`,children:[(0,p.jsx)(`strong`,{children:`Couldn’t check for alerts`}),(0,p.jsx)(`span`,{children:s.error.message}),(0,p.jsx)(`button`,{type:`button`,className:`btn sm`,onClick:()=>void s.refetch(),children:`Retry`})]})}):(0,p.jsxs)(p.Fragment,{children:[n&&(0,p.jsx)(`p`,{className:`alerts-about`,children:n.about}),y.length?(0,p.jsx)(v,{alerts:y}):(0,p.jsx)(`div`,{className:`panel`,id:`alerts-clear`,children:(0,p.jsxs)(`div`,{className:`empty`,children:[(0,p.jsx)(`strong`,{children:`All clear`}),(0,p.jsx)(`span`,{children:n?`Nothing to report from this check right now.`:`Nothing needs your attention right now.`})]})}),b.length>0&&(0,p.jsxs)(`div`,{className:`alerts-dismissed`,children:[(0,p.jsx)(`button`,{type:`button`,className:`btn ghost sm`,onClick:()=>h(e=>!e),children:`${b.length} dismissed in this browser · ${m?`Hide`:`Show`}`}),m&&(0,p.jsx)(`div`,{className:`panel alerts-group`,id:`alerts-dismissed`,children:b.map(e=>(0,p.jsx)(_,{alert:e,dismissed:!0},e.id))})]})]});let C=(t,r,i)=>(0,p.jsxs)(`button`,{type:`button`,role:`tab`,"aria-selected":(n?.id??`all`)===t,className:`minitab`+((n?.id??`all`)===t?` active`:``),onClick:()=>e(t===`all`?`/alerts`:`/alerts/`+t),children:[r,s.data?(0,p.jsx)(`span`,{className:`n`+(i.length?` on`:``)+(i.some(e=>e.severity===`critical`)?` critical`:``),children:i.length}):null]},t);return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(`div`,{className:`page-head`,children:[(0,p.jsxs)(`div`,{className:`titles`,children:[(0,p.jsx)(`h1`,{children:`Alerts`}),(0,p.jsxs)(`span`,{className:`sub`,children:[`What needs attention now, checked every minute. Each clears by itself once its cause does.`,s.data?(0,p.jsxs)(p.Fragment,{children:[` Last checked `,(0,p.jsx)(u,{value:s.data.checked_at}),`.`]}):null]})]}),(0,p.jsx)(`span`,{className:`spacer`}),(0,p.jsx)(`button`,{id:`refresh-alerts`,type:`button`,className:`btn`,onClick:()=>void s.refetch(),children:`Check now`})]}),(0,p.jsxs)(`div`,{className:`minitabs alerts-tabs`,role:`tablist`,"aria-label":`Checks`,children:[C(`all`,`All`,x()),l.map(e=>C(e.id,e.label,x(e.kinds)))]}),S]})}export{y as AlertsPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,s as t,v as n,z as r}from"./client-ZHNiMG8H.js";import{A as i,D as a,N as o,c as s,d as c,k as l,s as u}from"./index
|
|
1
|
+
import{n as e,s as t,v as n,z as r}from"./client-ZHNiMG8H.js";import{A as i,D as a,N as o,c as s,d as c,k as l,s as u}from"./index-D3-9-G4i.js";import{t as d}from"./Time-BJyVzig6.js";import{t as f}from"./QueryInfo-DBmvcpR5.js";import{a as p}from"./reach-BAOLd75Y.js";import{i as m,t as h}from"./KeyActivity-mYv2bu9-.js";var g=r(),_={query:`named query`,collection:`collection`,connection:`connection`};function v({term:e,children:t}){return(0,g.jsxs)(`div`,{children:[(0,g.jsx)(`dt`,{children:e}),(0,g.jsx)(`dd`,{children:t})]})}function y({title:e,sub:t,children:n}){return(0,g.jsxs)(`section`,{className:`panel`,style:{marginTop:16},children:[(0,g.jsxs)(`div`,{className:`panel-bar`,children:[(0,g.jsx)(`strong`,{children:e}),t?(0,g.jsx)(`span`,{className:`dim`,children:t}):null]}),n]})}function b(){let{name:r=``}=o(),b=s(),x=u(),S=c(),C=m(r),{openDrawer:w}=l(),T=new Date().toISOString().slice(0,10),E=n({queryKey:[`apikey-history`,r],queryFn:async()=>t(await e.GET(`/api/v1/history`,{params:{query:{key:r,limit:50}}})).items,retry:!1});if(b.isPending)return(0,g.jsx)(a,{text:`Loading API key…`});let D=b.data?.[r];if(!D)return(0,g.jsxs)(g.Fragment,{children:[(0,g.jsx)(`div`,{className:`page-head`,children:(0,g.jsxs)(`div`,{className:`titles`,children:[(0,g.jsx)(i,{to:`/api-keys`,className:`sub`,children:`← API keys`}),(0,g.jsx)(`h1`,{children:r||`API key`})]})}),(0,g.jsx)(`div`,{className:`panel`,children:(0,g.jsxs)(`div`,{className:`empty`,children:[(0,g.jsx)(`strong`,{children:`No such API key`}),(0,g.jsx)(`span`,{children:`It may have been revoked and removed, or the name is wrong.`}),(0,g.jsx)(i,{to:`/api-keys`,className:`btn sm`,children:`Back to API keys`})]})})]});let O=!!(D.expires_at&&D.expires_at<T),k=D.active?O?`Expired`:`Active`:`Revoked`,A=(x.data??[]).map(e=>({q:e,via:p(D,e.name,e.collection,e.connection_name)})).filter(e=>e.via.length),j=(S.data??[]).map(e=>e.name),M=D.connections===`*`?j:D.connections??[],N=A.map(e=>e.q.connection_name).filter(e=>e!=null&&!M.includes(e)),P=[...new Set([...M,...N])].sort(),F=e=>(S.data??[]).find(t=>t.name===e)?.db??null,I=e=>w({title:e.name,kicker:`${e.collection||`uncollected`} · ${e.connection_name||`no connection`}`,content:(0,g.jsx)(f,{name:e.name,dbType:e.connection_name?F(e.connection_name):null})}),L=D.allowed_tables??[],R=D.allowed_ips??[],z=D.usage_this_period;return(0,g.jsxs)(g.Fragment,{children:[(0,g.jsxs)(`div`,{className:`page-head`,children:[(0,g.jsxs)(`div`,{className:`titles`,children:[(0,g.jsx)(i,{to:`/api-keys`,className:`sub`,children:`← API keys`}),(0,g.jsxs)(`h1`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,g.jsx)(`span`,{className:`dot ${D.active&&!O?`ok`:`off`}`,title:k}),r,D.example?(0,g.jsx)(`span`,{className:`tag`,children:`example`}):null]}),(0,g.jsxs)(`span`,{className:`sub`,children:[k,D.created_from_role?` · created from role ${D.created_from_role}`:``]})]}),(0,g.jsx)(`span`,{className:`spacer`}),(0,g.jsx)(i,{to:`/api-keys`,className:`btn sm`,children:`Back`})]}),(0,g.jsxs)(`dl`,{className:`meta`,children:[(0,g.jsx)(v,{term:`status`,children:k}),(0,g.jsx)(v,{term:`access`,children:D.allow_writes?`Read / write`:`Read-only`}),(0,g.jsx)(v,{term:`created`,children:(0,g.jsx)(d,{value:D.created_at})}),(0,g.jsx)(v,{term:`last used`,children:(0,g.jsx)(d,{value:D.last_used_at,fallback:`never`})}),(0,g.jsx)(v,{term:`expires`,children:D.expires_at?D.expires_at+(O?` (expired)`:``):`never`}),(0,g.jsx)(v,{term:`rate limit`,children:D.rate_limit||`server default`}),(0,g.jsx)(v,{term:`query timeout`,children:D.effective_limits?.query_timeout==null?`server default`:`${D.effective_limits.query_timeout}s`}),(0,g.jsx)(v,{term:`row limit (page)`,children:D.effective_limits?.max_rows?.page??`—`}),(0,g.jsx)(v,{term:`max concurrent`,children:D.effective_limits?.max_concurrent??`unlimited`}),(0,g.jsx)(v,{term:`allowed IPs`,children:R.length?R.join(`, `):`any`})]}),(0,g.jsx)(y,{title:`APIs it can reach`,sub:D.queries===`*`?`all saved queries`:`${A.length} saved ${A.length===1?`query`:`queries`}`,children:D.queries===`*`?(0,g.jsxs)(`div`,{className:`empty`,children:[(0,g.jsx)(`strong`,{children:`Every saved query`}),(0,g.jsx)(`span`,{children:`This key carries a wildcard query grant, so it can run any saved query.`})]}):A.length?(0,g.jsx)(`div`,{style:{overflowX:`auto`},children:(0,g.jsxs)(`table`,{className:`grid`,children:[(0,g.jsx)(`thead`,{children:(0,g.jsx)(`tr`,{children:[`API endpoint`,`Query`,`Connection`,`Collection`,`Granted via`,`Activity (30d)`].map(e=>(0,g.jsx)(`th`,{children:e},e))})}),(0,g.jsx)(`tbody`,{children:A.map(({q:e,via:t})=>(0,g.jsxs)(`tr`,{"data-query":e.name,children:[(0,g.jsx)(`td`,{className:`mono`,children:e.published_version?e.endpoint:(0,g.jsxs)(`span`,{className:`dim`,title:`No published version yet`,children:[e.endpoint,` (draft)`]})}),(0,g.jsx)(`td`,{children:(0,g.jsx)(`button`,{type:`button`,className:`as-link name`,title:`Show the SQL for ${e.name}`,onClick:()=>I(e),children:e.name})}),(0,g.jsx)(`td`,{className:`dim`,children:e.connection_name||`—`}),(0,g.jsx)(`td`,{className:`dim`,children:e.collection||`—`}),(0,g.jsx)(`td`,{children:t.map((e,t)=>(0,g.jsx)(`span`,{className:`tag`,style:{marginRight:4},children:e.kind===`query`?_.query:e.label},t))}),(0,g.jsx)(`td`,{className:`spark-cell`,children:(0,g.jsx)(h,{label:e.name,items:C.by[e.name],isPending:C.isPending,isError:C.isError})})]},e.name))})]})}):(0,g.jsxs)(`div`,{className:`empty`,children:[(0,g.jsx)(`strong`,{children:`No saved queries`}),(0,g.jsx)(`span`,{children:`This key reaches no saved query by name or collection. It may still run ad-hoc SQL on the connections below.`})]})}),(0,g.jsx)(y,{title:`Databases it can reach`,sub:D.connections===`*`?`all connections`:`${P.length} connection(s)`,children:P.length?(0,g.jsx)(`div`,{style:{overflowX:`auto`},children:(0,g.jsxs)(`table`,{className:`grid`,children:[(0,g.jsx)(`thead`,{children:(0,g.jsx)(`tr`,{children:[`Connection`,`Type`,`Reached by`].map(e=>(0,g.jsx)(`th`,{children:e},e))})}),(0,g.jsx)(`tbody`,{children:P.map(e=>(0,g.jsxs)(`tr`,{"data-conn":e,children:[(0,g.jsx)(`td`,{className:`name`,children:e}),(0,g.jsx)(`td`,{children:F(e)?(0,g.jsx)(`span`,{className:`tag`,children:F(e)}):(0,g.jsx)(`span`,{className:`dim`,children:`—`})}),(0,g.jsx)(`td`,{className:`dim`,children:D.connections===`*`?`all connections`:M.includes(e)?`direct grant (ad-hoc SQL)`:`saved query only`})]},e))})]})}):(0,g.jsxs)(`div`,{className:`empty`,children:[(0,g.jsx)(`strong`,{children:`No connections`}),(0,g.jsx)(`span`,{children:`This key cannot reach any connection directly or through a saved query.`})]})}),(0,g.jsxs)(y,{title:`Tables it can reach`,sub:L.length?`${L.length} allowed`:`all tables on reachable connections`,children:[L.length?(0,g.jsx)(`div`,{style:{padding:`12px 14px`,display:`flex`,flexWrap:`wrap`,gap:6},children:L.map(e=>(0,g.jsx)(`span`,{className:`tag mono`,children:e},e))}):(0,g.jsx)(`div`,{style:{padding:`12px 14px`},className:`dim`,children:`No table allow-list — this key may read every table on the connections it reaches.`}),D.allow_writes?(0,g.jsx)(`div`,{style:{padding:`0 14px 12px`},children:(0,g.jsxs)(`span`,{className:`tag`,children:[`writes: `,D.allowed_write_ops?.length?D.allowed_write_ops.join(`, `):`all operations`]})}):null,D.mask?.length?(0,g.jsx)(`div`,{style:{padding:`0 14px 12px`,display:`flex`,flexWrap:`wrap`,gap:6},children:D.mask.map((e,t)=>(0,g.jsxs)(`span`,{className:`tag`,title:`${e.column}: ${e.action}`,children:[`masked `,e.column]},t))}):null]}),(0,g.jsx)(y,{title:`Usage history`,sub:z?`${D.usage.queries} queries total · ${D.usage.rows.toLocaleString()} rows`:`${D.usage.queries} queries · ${D.usage.errors} failed · since this process started`,children:E.isPending?(0,g.jsx)(a,{text:`Loading usage…`}):E.isError?(0,g.jsx)(`div`,{style:{padding:`12px 14px`},className:`dim`,children:`Usage history isn’t available for this key (the run log may be off, or your role can’t read it).`}):E.data.length?(0,g.jsx)(`div`,{style:{overflowX:`auto`},children:(0,g.jsxs)(`table`,{className:`grid`,children:[(0,g.jsx)(`thead`,{children:(0,g.jsx)(`tr`,{children:[`When`,`What`,`Connection`,`Via`,`Rows`,`Duration`,`Result`].map(e=>(0,g.jsx)(`th`,{children:e},e))})}),(0,g.jsx)(`tbody`,{children:E.data.map((e,t)=>(0,g.jsxs)(`tr`,{children:[(0,g.jsx)(`td`,{style:{whiteSpace:`nowrap`},children:(0,g.jsx)(d,{value:e.executed_at})}),(0,g.jsx)(`td`,{className:`name`,children:e.kind===`adhoc`?(0,g.jsx)(`span`,{className:`dim`,children:`ad-hoc SQL`}):e.query}),(0,g.jsx)(`td`,{className:`dim`,children:e.connection_name||`—`}),(0,g.jsx)(`td`,{className:`dim`,children:e.transport||`—`}),(0,g.jsx)(`td`,{className:`mono`,children:e.rows??`—`}),(0,g.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:e.duration_ms==null?`—`:`${e.duration_ms} ms`}),(0,g.jsx)(`td`,{style:e.status===`error`?{color:`var(--danger)`}:void 0,children:e.status===`error`?(0,g.jsx)(`span`,{title:e.error??void 0,children:e.code||`error`}):`ok`})]},e.request_id??t))})]})}):(0,g.jsxs)(`div`,{className:`empty`,children:[(0,g.jsx)(`strong`,{children:`No runs recorded`}),(0,g.jsx)(`span`,{children:`This key hasn’t run a query yet, or its runs have aged out of the log.`})]})})]})}export{b as ApiKeyDetailPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as e}from"./client-ZHNiMG8H.js";import{A as t,D as n,c as r,l as i,u as a}from"./index
|
|
1
|
+
import{z as e}from"./client-ZHNiMG8H.js";import{A as t,D as n,c as r,l as i,u as a}from"./index-D3-9-G4i.js";import{t as o}from"./Time-BJyVzig6.js";import{t as s}from"./reach-BAOLd75Y.js";import{r as c,t as l}from"./KeyActivity-mYv2bu9-.js";import{i as u,n as d,o as f}from"./forms-jtkIvvrV.js";import{t as p}from"./icons-CFXE8hAz.js";import{t as m}from"./Managed-BYc6wyeG.js";var h=e();function g(e,t){let n=new Date(Date.now()+6048e5).toISOString().slice(0,10);return!!(e&&e>=t&&e<=n)}function _({usage:e}){let t=[`calls`,`rows`].map(t=>({what:t,used:e[t],limit:e[`${t}_limit`]})).filter(e=>e.limit!=null),n=t.reduce((e,t)=>t.used/t.limit>e.used/e.limit?t:e,t[0]),r=Math.min(1,n.used/n.limit),i=e.per===`day`?`today`:`this month`;return(0,h.jsxs)(`div`,{className:`quota-usage`,title:t.map(e=>`${e.used.toLocaleString()} of ${e.limit.toLocaleString()} ${e.what}`).join(` · `)+` ${i} · resets ${e.resets_at}`,children:[(0,h.jsxs)(`span`,{className:`dim`,children:[n.used.toLocaleString(),` / `,n.limit.toLocaleString(),` `,n.what,` `,i]}),(0,h.jsx)(`div`,{className:`quota-bar${r>=1?` full`:r>=.8?` near`:``}`,children:(0,h.jsx)(`span`,{style:{width:`${Math.round(r*100)}%`}})})]})}function v(e){if(!e.queries)return null;let t=[e.queries+(e.queries===1?` query`:` queries`)];return e.errors&&t.push(e.errors+` failed`),t.join(` · `)}function y(){let e=i(),y=r(),b=a(),x=c(),S=d(),C=u(),w=Object.keys(y.data??{}).sort(),T=new Date().toISOString().slice(0,10),E;return E=y.isPending?(0,h.jsx)(n,{text:`Loading API keys…`}):y.isError?(0,h.jsxs)(`div`,{className:`empty`,children:[(0,h.jsx)(`strong`,{children:`Couldn’t load API keys`}),(0,h.jsx)(`span`,{children:y.error.message}),(0,h.jsx)(`button`,{type:`button`,className:`btn sm`,onClick:()=>y.refetch(),children:`Retry`})]}):w.length?(0,h.jsx)(`div`,{style:{overflowX:`auto`},children:(0,h.jsxs)(`table`,{className:`grid`,children:[(0,h.jsx)(`thead`,{children:(0,h.jsx)(`tr`,{children:[`Name`,`Scope`,`Access`,`Rate limit`,`IPs`,`Expires`,`Last used`,`Usage`,`Activity (30d)`,``].map((e,t)=>(0,h.jsx)(`th`,{children:e},t))})}),(0,h.jsx)(`tbody`,{children:w.map(n=>{let r=y.data[n],i=!!(r.expires_at&&r.expires_at<T),a=!i&&g(r.expires_at,T),c=r.allowed_ips??[],u=v(r.usage);return(0,h.jsxs)(`tr`,{"data-name":n,children:[(0,h.jsx)(`td`,{children:(0,h.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,h.jsx)(`span`,{className:`dot ${r.active&&!i?`ok`:`off`}`,title:r.active?i?`Expired`:`Active`:`Revoked`}),(0,h.jsx)(`span`,{className:`name`,title:r.created_at?`Created `+r.created_at+(r.created_from_role?` from role `+r.created_from_role:``):void 0,children:n}),(0,h.jsx)(m,{kind:`api_keys`,name:n}),r.previous_expires_at?(0,h.jsx)(`span`,{className:`tag`,title:`Rotated: the old secret works until then too`,children:`rotating`}):null,r.mask?.length?(0,h.jsx)(`span`,{className:`tag`,title:r.mask.map(e=>`${e.column}: ${e.action}`).join(`, `),children:`masked`}):null]})}),(0,h.jsx)(`td`,{children:(0,h.jsx)(f,{entry:r,collections:b.data?.collections})}),(0,h.jsx)(s,{entry:r}),(0,h.jsx)(`td`,{className:r.rate_limit?`mono`:`mono dim`,style:{whiteSpace:`nowrap`},children:r.rate_limit||`server default`}),(0,h.jsx)(`td`,{className:`dim`,children:c.length?(0,h.jsx)(`span`,{title:c.join(`, `),children:c.length+(c.length===1?` IP`:` IPs`)}):(0,h.jsx)(`span`,{className:`dim`,children:`any`})}),(0,h.jsx)(`td`,{style:{whiteSpace:`nowrap`},children:r.expires_at?(0,h.jsx)(`span`,{style:i?{color:`var(--danger)`}:a?{color:`var(--warn)`}:void 0,children:r.expires_at+(i?` (expired)`:a?` (expires soon)`:``)}):(0,h.jsx)(`span`,{className:`dim`,children:`never`})}),(0,h.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,h.jsx)(o,{value:r.last_used_at,fallback:`never`})}),r.usage_this_period?(0,h.jsxs)(`td`,{style:{whiteSpace:`nowrap`},children:[u,(0,h.jsx)(_,{usage:r.usage_this_period})]}):u?(0,h.jsx)(`td`,{title:`${r.usage.rows}${r.usage.rows===1?` row`:` rows`} returned in total · since this process started`,style:{whiteSpace:`nowrap`},children:u}):(0,h.jsx)(`td`,{className:`dim`,style:{whiteSpace:`nowrap`},children:`No activity yet`}),(0,h.jsx)(`td`,{className:`spark-cell`,children:(0,h.jsx)(l,{label:n,items:x.by[n],isPending:x.isPending,isError:x.isError,to:`/api-keys/${encodeURIComponent(n)}`})}),(0,h.jsx)(`td`,{children:(0,h.jsxs)(`div`,{className:`actions`,children:[(0,h.jsx)(t,{to:`/api-keys/${encodeURIComponent(n)}`,className:`btn ghost sm`,title:`Full details of ${n}`,children:`Details`}),(0,h.jsx)(`button`,{hidden:!e(`access.write`),type:`button`,className:`btn ghost sm icon`,title:`Edit ${n}`,"aria-label":`Edit`,onClick:()=>S.editKey(n),children:(0,h.jsx)(p,{})}),(0,h.jsx)(`button`,{hidden:!e(`access.write`),type:`button`,className:`btn ghost sm danger`,onClick:()=>void C(n),children:`Revoke`})]})})]},n)})})]})}):(0,h.jsxs)(`div`,{className:`empty`,children:[(0,h.jsx)(`strong`,{children:`No scoped API keys yet`}),(0,h.jsx)(`span`,{children:`QUERYAPIGATE_API_KEY is a full-access admin key. Create a scoped one to limit a caller to specific connections, read-only.`}),(0,h.jsx)(`button`,{hidden:!e(`access.write`),type:`button`,className:`btn primary`,onClick:()=>S.newKey(),children:`New API key`})]}),(0,h.jsxs)(h.Fragment,{children:[(0,h.jsxs)(`div`,{className:`page-head`,children:[(0,h.jsxs)(`div`,{className:`titles`,children:[(0,h.jsx)(`h1`,{children:`API keys`}),(0,h.jsx)(`span`,{className:`sub`,id:`apikeys-sub`,children:(w.length?w.length+(w.length===1?` key. `:` keys. `):``)+`Scope each one to connections, collections or roles.`})]}),(0,h.jsx)(`span`,{className:`spacer`}),(0,h.jsx)(`button`,{hidden:!e(`access.write`),id:`new-apikey`,type:`button`,className:`btn primary`,onClick:()=>S.newKey(),children:`New API key`})]}),(0,h.jsx)(`div`,{id:`apikeys-table`,className:`panel`,children:E})]})}export{y as ApiKeysPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{B as e,H as t,R as n,n as r,s as i,t as a,v as o,z as s}from"./client-ZHNiMG8H.js";import{t as c}from"./useMutation-
|
|
1
|
+
import{B as e,H as t,R as n,n as r,s as i,t as a,v as o,z as s}from"./client-ZHNiMG8H.js";import{t as c}from"./useMutation-BbQqPorm.js";import{D as l,M as u,N as d,T as f,f as p,k as m}from"./index-D3-9-G4i.js";import{t as h}from"./Time-BJyVzig6.js";import{t as g}from"./CodeBox-Cwh5KHLp.js";var _=t(e(),1),v=s(),y={pending:`Waiting`,approved:`Approved - not run yet`,running:`Running`,done:`Done`,failed:`Failed`,rejected:`Rejected`,expired:`Expired`};function b(e){let t=p(`writes.approve`,`write requests`);return o({queryKey:[`write-requests`,e||`all`],queryFn:async()=>(await t.check(),i(await r.GET(`/api/v1/write-requests`,{params:{query:e?{status:e}:{}}})).items),enabled:t.ready,retry:!1})}var x=e=>JSON.stringify(e);function S({args:e}){let t=Object.entries(e);return t.length?(0,v.jsx)(`table`,{className:`grid approval-args`,children:(0,v.jsx)(`tbody`,{children:t.map(([e,t])=>(0,v.jsxs)(`tr`,{children:[(0,v.jsx)(`td`,{className:`mono`,children:e}),(0,v.jsx)(`td`,{className:`mono`,children:x(t)})]},e))})}):(0,v.jsx)(`span`,{className:`dim`,children:`none`})}function C({id:e}){let{closeDrawer:t,showError:s,toast:u}=m(),d=n(),[p,b]=(0,_.useState)(``),C=o({queryKey:[`write-requests`,`one`,e],queryFn:async()=>i(await r.GET(`/api/v1/write-requests/{id}`,{params:{path:{id:e}}})),retry:!1}),w=c({mutationFn:async t=>i(t?await r.POST(`/api/v1/write-requests/{id}/approve`,{params:{path:{id:e}}}):await r.POST(`/api/v1/write-requests/{id}/reject`,{params:{path:{id:e}},body:p.trim()?{reason:p.trim()}:{}})),onSuccess:e=>{s(``),d.invalidateQueries({queryKey:[`write-requests`]}),d.invalidateQueries({queryKey:[`alerts`]}),t(),u(e.status===`approved`?`Approved - ${e.caller} may run it now`:`Rejected`)},onError:e=>s(e.message,e instanceof a?{status:e.status}:{})});if(C.isPending)return(0,v.jsx)(l,{});if(C.isError)return(0,v.jsx)(`div`,{className:`hint`,children:C.error.message});let T=C.data,E=T.status===`pending`;return(0,v.jsxs)(`div`,{className:`form`,children:[(0,v.jsxs)(`p`,{className:`hint`,children:[(0,v.jsx)(`strong`,{children:T.caller}),` asked to run `,(0,v.jsx)(`strong`,{children:T.query}),` (version `,T.version,`) on`,` `,(0,v.jsx)(`span`,{className:`mono`,children:T.connection}),`. It changes at most `,T.max_affected_rows,` row`,T.max_affected_rows===1?``:`s`,`; past that it is rolled back.`]}),(0,v.jsx)(f,{label:`SQL`,children:(0,v.jsx)(g,{sql:T.sql})}),(0,v.jsx)(f,{label:`With exactly these arguments`,hint:`An approval is for these alone - no other write.`,children:(0,v.jsx)(S,{args:T.arguments})}),(0,v.jsxs)(`div`,{className:`hint`,children:[`Asked `,(0,v.jsx)(h,{value:T.created_at}),` · `,y[T.status]??T.status,E||T.status===`approved`?(0,v.jsxs)(v.Fragment,{children:[` `,`· expires `,(0,v.jsx)(h,{value:T.expires_at})]}):null,T.decided_by?` · ${T.status===`rejected`?`rejected`:`approved`} by ${T.decided_by}`:``,T.reason?` - ${T.reason}`:``]}),T.result?(0,v.jsx)(f,{label:`Result`,children:(0,v.jsx)(`span`,{className:`mono`,children:x(T.result)})}):null,E?(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(f,{id:`approval-reason`,label:`Reason`,hint:`Optional for a rejection - the agent is told.`,children:(0,v.jsx)(`textarea`,{id:`approval-reason`,rows:2,value:p,onChange:e=>b(e.target.value)})}),(0,v.jsxs)(`div`,{className:`form-actions`,children:[(0,v.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:w.isPending,onClick:()=>w.mutate(!0),children:`Approve`}),(0,v.jsx)(`button`,{type:`button`,className:`btn danger`,disabled:w.isPending,onClick:()=>w.mutate(!1),children:`Reject`}),(0,v.jsx)(`button`,{type:`button`,className:`btn`,onClick:t,children:`Close`})]})]}):(0,v.jsx)(`div`,{className:`form-actions`,children:(0,v.jsx)(`button`,{type:`button`,className:`btn`,onClick:t,children:`Close`})})]})}function w(){let{id:e}=d(),t=u(),{openDrawer:n}=m(),[r,i]=(0,_.useState)(`pending`),a=b(r),o=a.data??[],s=e=>n({title:`Approve a write`,kicker:e,wide:!0,content:(0,v.jsx)(C,{id:e})});(0,_.useEffect)(()=>{e&&(s(e),t(`/approvals`,{replace:!0}))},[e]);let c;return c=a.isPending?(0,v.jsx)(l,{text:`Loading write requests…`}):a.isError?(0,v.jsxs)(`div`,{className:`empty`,children:[(0,v.jsx)(`strong`,{children:`Couldn’t load write requests`}),(0,v.jsx)(`span`,{children:a.error.message})]}):o.length?(0,v.jsx)(`div`,{style:{overflowX:`auto`},children:(0,v.jsxs)(`table`,{className:`grid`,children:[(0,v.jsx)(`thead`,{children:(0,v.jsx)(`tr`,{children:[`Asked`,`Agent`,`Tool`,`Arguments`,r===`pending`?`Expires`:`Status`].map(e=>(0,v.jsx)(`th`,{children:e},e))})}),(0,v.jsx)(`tbody`,{children:o.map(e=>(0,v.jsxs)(`tr`,{"data-request":e.id,children:[(0,v.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,v.jsx)(h,{value:e.created_at,fallback:``})}),(0,v.jsx)(`td`,{className:`mono`,children:e.caller}),(0,v.jsx)(`td`,{children:(0,v.jsx)(`button`,{type:`button`,className:`agent-name`,onClick:()=>s(e.id),children:e.query})}),(0,v.jsx)(`td`,{className:`mono dim approval-args-cell`,children:x(e.arguments)}),(0,v.jsx)(`td`,{className:`dim`,style:{whiteSpace:`nowrap`},children:r===`pending`?(0,v.jsx)(h,{value:e.expires_at}):y[e.status]??e.status})]},e.id))})]})}):(0,v.jsxs)(`div`,{className:`empty`,children:[(0,v.jsx)(`strong`,{children:r===`pending`?`No writes are waiting for approval`:`No write requests yet`}),(0,v.jsx)(`span`,{children:`When an AI agent calls a write tool, its write waits here for a person to approve it - the agent is given a link to this screen.`})]}),(0,v.jsxs)(v.Fragment,{children:[(0,v.jsxs)(`div`,{className:`page-head`,children:[(0,v.jsxs)(`div`,{className:`titles`,children:[(0,v.jsx)(`h1`,{children:`Approvals`}),(0,v.jsx)(`span`,{className:`sub`,children:`Writes AI agents asked to make over MCP. Each runs only once a person approves it - with exactly the arguments shown.`})]}),(0,v.jsx)(`span`,{className:`spacer`}),(0,v.jsx)(`button`,{type:`button`,className:`btn`,onClick:()=>void a.refetch(),children:`Refresh`})]}),(0,v.jsxs)(`div`,{className:`panel`,style:{overflow:`hidden`},children:[(0,v.jsxs)(`div`,{className:`panel-bar`,children:[(0,v.jsxs)(`select`,{"aria-label":`Show`,value:r,onChange:e=>i(e.target.value),children:[(0,v.jsx)(`option`,{value:`pending`,children:`Waiting`}),(0,v.jsx)(`option`,{value:``,children:`All`}),(0,v.jsx)(`option`,{value:`done`,children:`Done`}),(0,v.jsx)(`option`,{value:`rejected`,children:`Rejected`}),(0,v.jsx)(`option`,{value:`expired`,children:`Expired`}),(0,v.jsx)(`option`,{value:`failed`,children:`Failed`})]}),(0,v.jsx)(`span`,{className:`spacer`}),(0,v.jsx)(`span`,{className:`panel-count`,children:a.data?`${o.length} request${o.length===1?``:`s`}`:``})]}),c]})]})}export{w as ApprovalsPage};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{B as e,H as t,n,s as r,v as i,z as a}from"./client-ZHNiMG8H.js";import{D as o,k as s}from"./index
|
|
1
|
+
import{B as e,H as t,n,s as r,v as i,z as a}from"./client-ZHNiMG8H.js";import{D as o,k as s}from"./index-D3-9-G4i.js";import{t as c}from"./Time-BJyVzig6.js";var l=t(e(),1),u=a();function d(e){return/^(create|load|save)/.test(e)?`ok`:/^(delete|unload|revoke)/.test(e)?`bad`:``}function f(e){if(e==null||e===``)return`none`;let t=e=>e&&typeof e==`object`?JSON.stringify(e):String(e);return Array.isArray(e)?e.length?e.map(t).join(`, `):`none`:t(e)}var p=e=>e==null||e===``||Array.isArray(e)&&!e.length,m=e=>!!e&&typeof e==`object`&&!Array.isArray(e)&&(`from`in e||`to`in e);function h({changes:e}){let t=e&&typeof e==`object`?e:null,n=t?Object.keys(t).sort():[],r=n.filter(e=>m(t[e])||!p(t[e]));return r.length?(0,u.jsx)(`div`,{children:r.map(e=>{let n=t[e];return(0,u.jsx)(`div`,{className:`mono`,style:{fontSize:12,color:`var(--ink-2)`},children:m(n)?`${e}: ${f(n.from)} → ${f(n.to)}`:`${e}: ${f(n)}`},e)})}):(0,u.jsx)(`span`,{className:`dim`,children:n.length?`no other fields set`:`—`})}function g(e){let t=new Blob([JSON.stringify(e,null,2)+`
|
|
2
2
|
`],{type:`application/json`}),n=document.createElement(`a`);n.href=URL.createObjectURL(t),n.download=`queryapigate-audit-log.json`,n.click(),setTimeout(()=>URL.revokeObjectURL(n.href),0)}function _(){let{toast:e}=s(),t=i({queryKey:[`audit`],queryFn:async()=>r(await n.GET(`/api/v1/audit`)),retry:!1}),[a,f]=(0,l.useState)(``),[p,m]=(0,l.useState)(``),_=t.data?.items??[],v=p.trim().toLowerCase(),y=_.filter(e=>(!a||e.action===a)&&(!v||[e.timestamp,e.actor,e.target].join(` `).toLowerCase().includes(v))),b=t.data?.actions??[],x,S=``;return t.isPending?x=(0,u.jsx)(o,{text:`Loading audit log…`}):t.isError?x=(0,u.jsxs)(`div`,{className:`empty`,children:[(0,u.jsx)(`strong`,{children:`Couldn’t load the audit log`}),(0,u.jsx)(`span`,{children:t.error.message}),(0,u.jsx)(`button`,{type:`button`,className:`btn sm`,onClick:()=>t.refetch(),children:`Retry`})]}):_.length?(S=y.length===_.length?_.length+(_.length===1?` entry`:` entries`):`${y.length} of ${_.length} entries`,x=y.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:[`Time`,`Actor`,`Action`,`Target`,`Changes`].map(e=>(0,u.jsx)(`th`,{children:e},e))})}),(0,u.jsx)(`tbody`,{children:y.map((e,t)=>(0,u.jsxs)(`tr`,{children:[(0,u.jsx)(`td`,{className:`mono dim`,style:{whiteSpace:`nowrap`},children:(0,u.jsx)(c,{value:e.timestamp,fallback:``})}),(0,u.jsx)(`td`,{className:`mono`,children:e.actor||`-`}),(0,u.jsx)(`td`,{children:(0,u.jsx)(`span`,{className:`tag act `+d(e.action),children:e.action||``})}),(0,u.jsx)(`td`,{children:(0,u.jsx)(`span`,{className:`name`,children:e.target||``})}),(0,u.jsx)(`td`,{children:(0,u.jsx)(h,{changes:e.changes})})]},t))})]})}):(0,u.jsx)(`div`,{className:`empty`,children:(0,u.jsx)(`span`,{children:`No entries match this filter.`})})):x=(0,u.jsxs)(`div`,{className:`empty`,children:[(0,u.jsx)(`strong`,{children:`No administrative changes recorded yet`}),(0,u.jsx)(`span`,{children:`Creating or changing a connection, saved query or API key will appear here.`})]}),(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:`Audit log`}),(0,u.jsx)(`span`,{className:`sub`,id:`auditlog-sub`,children:`Administrative actions, newest first.`+(t.data?` Keeps the last ${t.data.retention} entries.`:``)})]}),(0,u.jsx)(`span`,{className:`spacer`}),(0,u.jsx)(`button`,{id:`refresh-auditlog`,type:`button`,className:`btn`,onClick:()=>void t.refetch(),children:`Refresh`}),(0,u.jsx)(`button`,{id:`export-auditlog`,type:`button`,className:`btn`,onClick:()=>{g(y),e(`Exported `+y.length+(y.length===1?` entry`:` entries`))},children:`Export`})]}),(0,u.jsxs)(`div`,{className:`panel`,style:{overflow:`hidden`},children:[(0,u.jsxs)(`div`,{className:`panel-bar`,children:[(0,u.jsxs)(`select`,{id:`auditlog-action-filter`,"aria-label":`Action`,value:b.includes(a)?a:``,onChange:e=>f(e.target.value),children:[(0,u.jsx)(`option`,{value:``,children:`All actions`}),b.map(e=>(0,u.jsx)(`option`,{value:e,children:e},e))]}),(0,u.jsx)(`input`,{id:`auditlog-filter`,className:`search`,style:{width:260},type:`search`,placeholder:`Filter by actor, target, time…`,value:p,onChange:e=>m(e.target.value)}),(0,u.jsx)(`span`,{className:`spacer`}),(0,u.jsx)(`span`,{className:`panel-count`,id:`auditlog-count`,children:S})]}),(0,u.jsx)(`div`,{id:`auditlog-table`,children:x})]})]})}export{_ as AuditLogPage};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{B as e,H as t,R as n,c as r,n as i,r as a,s as o,v as s,z as c}from"./client-ZHNiMG8H.js";import{M as l,_ as u,k as d,s as f}from"./index
|
|
1
|
+
import{B as e,H as t,R as n,c as r,n as i,r as a,s as o,v as s,z as c}from"./client-ZHNiMG8H.js";import{M as l,_ as u,k as d,s as f}from"./index-D3-9-G4i.js";import{a as p,t as m}from"./KeyActivity-mYv2bu9-.js";import{i as h,n as g,t as _}from"./StatTile-Cft4kJPH.js";import{n as v,t as y}from"./Results-D-iAyzY7.js";var b=t(e(),1),x=c();function S(){return s({queryKey:[`cache`,`entries`],queryFn:async()=>o(await i.GET(`/api/v1/cache/entries`)).items,retry:!1})}function C(){let e=l(),t=n(),r=u(),i=g(),a=f(),o=p(),s=i.data??[],c=r.data?.find(e=>e.id===`cache`)?.rows[0]?.value??`…`,d=h(s,`queryapigate_cache_hits_total`),v=h(s,`queryapigate_cache_misses_total`),y=d+v,b=(a.data??[]).filter(e=>(e.cache_ttl??0)>0);return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`div`,{className:`page-head`,children:[(0,x.jsxs)(`div`,{className:`titles`,children:[(0,x.jsx)(`h1`,{children:`Caching`}),(0,x.jsxs)(`span`,{className:`sub`,children:[`The response cache backing saved queries' `,(0,x.jsx)(`code`,{children:`cache_ttl`}),` - see the Response cache row in Settings for the backend itself.`]})]}),(0,x.jsx)(`span`,{className:`spacer`}),(0,x.jsx)(`button`,{id:`refresh-caching`,type:`button`,className:`btn`,onClick:()=>{i.refetch(),t.invalidateQueries({queryKey:[`cache`,`entries`]})},children:`Refresh`})]}),(0,x.jsxs)(`div`,{id:`caching-body`,children:[(0,x.jsxs)(`div`,{className:`stat-tiles`,children:[(0,x.jsx)(_,{label:`Cache backend`,value:c}),(0,x.jsx)(_,{label:`Entries`,value:h(s,`queryapigate_cache_entries`)}),(0,x.jsx)(_,{label:`Hit rate`,value:y?(100*d/y).toFixed(1)+`%`:`—`}),(0,x.jsx)(_,{label:`Hits`,value:d}),(0,x.jsx)(_,{label:`Misses`,value:v})]}),(0,x.jsx)(`p`,{className:`sub-h`,style:{marginTop:14},children:`Saved queries with cache_ttl set`}),b.length?(0,x.jsx)(`div`,{className:`panel`,style:{overflowX:`auto`},children:(0,x.jsxs)(`table`,{className:`grid`,children:[(0,x.jsx)(`thead`,{children:(0,x.jsx)(`tr`,{children:[`Query`,`Collection`,`Connection`,`cache_ttl`,`Activity (30d)`].map((e,t)=>(0,x.jsx)(`th`,{className:t===3?`num`:``,children:e},e))})}),(0,x.jsx)(`tbody`,{children:b.map(t=>(0,x.jsxs)(`tr`,{children:[(0,x.jsx)(`td`,{children:(0,x.jsx)(`button`,{type:`button`,className:`target`,onClick:()=>e(`/queries/${encodeURIComponent(t.name)}`),children:t.name})}),(0,x.jsx)(`td`,{children:t.collection||(0,x.jsx)(`span`,{className:`dim`,children:`—`})}),(0,x.jsx)(`td`,{className:`mono`,children:t.connection_name||`—`}),(0,x.jsx)(`td`,{className:`mono num`,children:t.cache_ttl+`s`}),(0,x.jsx)(`td`,{className:`spark-cell`,children:(0,x.jsx)(m,{label:t.name,items:o.by[t.name],isPending:o.isPending,isError:o.isError})})]},t.name))})]})}):(0,x.jsx)(`div`,{className:`hint`,children:`No saved query declares a cache_ttl yet - set one on a read-only query to start caching its responses.`})]}),(0,x.jsx)(`div`,{id:`cache-entries-panel`,children:(0,x.jsx)(T,{})})]})}function w(e){return e<1024?e+` B`:(e/1024).toFixed(1)+` KB`}function T(){let e=n(),{openDrawer:t,showError:a,toast:o}=d(),s=S().data??[],c=()=>void e.invalidateQueries({queryKey:[`cache`,`entries`]}),l=e=>a(e.message),u=async()=>{if(window.confirm(`Evict all ${s.length} cache entries? The next call to each query will re-run for real.`))try{r(await i.DELETE(`/api/v1/cache/entries`)),o(`Cache cleared`),c()}catch(e){l(e)}},f=async e=>{try{r(await i.DELETE(`/api/v1/cache/entries/{key}`,{params:{path:{key:e}}})),o(`Entry deleted`),c()}catch(e){l(e)}};return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:12},children:[(0,x.jsxs)(`div`,{className:`set-head`,style:{marginTop:14},children:[(0,x.jsx)(`h2`,{children:`Cache entries`}),(0,x.jsx)(`span`,{children:`${s.length}${s.length===1?` live entry`:` live entries`} right now.`})]}),(0,x.jsx)(`span`,{className:`spacer`}),(0,x.jsx)(`button`,{type:`button`,className:`btn sm`,disabled:!s.length,onClick:()=>void u(),children:`Clear cache`})]}),s.length?(0,x.jsx)(`div`,{className:`panel`,style:{overflowX:`auto`,marginTop:8},children:(0,x.jsxs)(`table`,{className:`grid`,children:[(0,x.jsx)(`thead`,{children:(0,x.jsx)(`tr`,{children:[`Query`,`Version`,`Connection`,`Format`,`Content type`,`Size`,`TTL left`,``].map((e,t)=>(0,x.jsx)(`th`,{className:t>=4&&t<=6?`num`:``,children:e},t))})}),(0,x.jsx)(`tbody`,{children:s.map(e=>{let n=e.meta??{};return(0,x.jsxs)(`tr`,{children:[(0,x.jsx)(`td`,{children:(0,x.jsx)(`button`,{type:`button`,className:`target`,onClick:()=>t({title:n.name||`Cache entry`,kicker:e.key,content:(0,x.jsx)(D,{entry:e})}),children:n.name||e.key.slice(0,12)+`…`})}),(0,x.jsx)(`td`,{className:`mono`,children:n.version===void 0?`—`:`v`+n.version}),(0,x.jsx)(`td`,{className:`mono`,children:n.connection||`—`}),(0,x.jsx)(`td`,{className:`mono`,children:n.format||`—`}),(0,x.jsx)(`td`,{className:`mono num`,children:e.content_type}),(0,x.jsx)(`td`,{className:`mono num`,children:w(e.size_bytes)}),(0,x.jsx)(`td`,{className:`mono num`,children:Math.max(0,Math.round(e.ttl_remaining_s))+`s`}),(0,x.jsx)(`td`,{children:(0,x.jsx)(`button`,{type:`button`,className:`btn sm ghost`,onClick:()=>void f(e.key),children:`Delete`})})]},e.key)})})]})}):(0,x.jsx)(`div`,{className:`hint`,children:`Nothing cached right now - call a query with a cache_ttl set, then check back.`})]})}var E=e=>({ok:!1,status:0,statusText:``,contentType:``,headers:[],page:1,size:0,hasMore:!1,elapsed:0,format:``,error:e});function D({entry:e}){let{showError:t}=d(),n=e.meta?.name||`cache-entry`,r=e.meta?.format||`json`,[i,o]=(0,b.useState)(null);return(0,b.useEffect)(()=>{let i=!0,s=performance.now();return a(`/api/v1/cache/entries/${encodeURIComponent(e.key)}`).then(e=>v(e,{page:1,size:0,format:r,elapsed:Math.round(performance.now()-s),filename:n})).then(e=>{i&&(e.error&&t(e.error,{status:e.status}),o(e))},e=>i&&o(E(`Network error: `+e.message))),()=>{i=!1}},[e.key,r,n,t]),(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(`div`,{className:`hint`,children:`The exact cached body, served with its real Content-Type - what a caller receives on a hit.`}),(0,x.jsx)(y,{result:i,running:!i,filename:n,onPage:()=>{},onPageSize:()=>{},layout:`drawer`})]})}export{C as CachingPage};
|