From 6d40ab975cefb91095f610b0f82ef9aebeda5947 Mon Sep 17 00:00:00 2001 From: Public Source Publisher Date: Thu, 1 Oct 2026 18:03:00 +0000 Subject: [PATCH] Publish curated ITWorx_MCP_Hub source from 6c7434368ce2a594005983c5a6601a31a639e7f1 --- PUBLIC_SOURCE_EXPORT.md | 2 +- PUBLIC_SOURCE_MANIFEST.json | 362 +++-- apps/web/package.json | 10 +- apps/web/playwright.config.ts | 10 +- apps/web/src/api.ts | 21 +- apps/web/src/app.tsx | 108 +- apps/web/src/components/CommandMenu.tsx | 235 +++- apps/web/src/components/DataTable.tsx | 21 +- apps/web/src/components/Modal.tsx | 80 +- apps/web/src/components/RecentToolRuns.tsx | 44 + apps/web/src/components/SchemaInputFields.tsx | 104 ++ apps/web/src/components/SessionMenu.tsx | 16 +- apps/web/src/components/modal-focus.ts | 114 ++ apps/web/src/contracts.ts | 67 +- apps/web/src/hooks.ts | 2 +- apps/web/src/main.ts | 8 +- apps/web/src/operator-data.ts | 41 + apps/web/src/pages/CatalogWorkflow.tsx | 22 +- apps/web/src/pages/ClientOnboarding.tsx | 339 ++++- apps/web/src/pages/ConnectorDetail.tsx | 135 ++ apps/web/src/pages/Dashboard.tsx | 55 +- apps/web/src/pages/Operations.tsx | 105 +- apps/web/src/pages/ProjectDetail.tsx | 203 ++- apps/web/src/pages/ResourcePage.tsx | 58 +- apps/web/src/pages/SettingsPage.tsx | 13 +- apps/web/src/pages/ToolReadinessModal.tsx | 238 +++- apps/web/src/preferences.ts | 7 + apps/web/src/rbac.ts | 2 + apps/web/src/routing.ts | 9 +- apps/web/src/schema-input.ts | 148 ++ apps/web/src/status-refresh.ts | 54 + apps/web/src/styles.css | 289 ++-- apps/web/src/styles/details.css | 130 ++ apps/web/src/styles/operator.css | 63 + apps/web/src/styles/shell.css | 190 +++ apps/web/src/styles/workflows.css | 99 ++ apps/web/src/tool-history.ts | 82 ++ apps/web/tests/api.test.ts | 28 +- apps/web/tests/contracts.test.ts | 4 + apps/web/tests/e2e/auth.spec.ts | 6 +- apps/web/tests/e2e/navigation.spec.ts | 3 +- apps/web/tests/e2e/operator-matrix.spec.ts | 194 +++ apps/web/tests/e2e/operator-review.spec.ts | 118 ++ apps/web/tests/e2e/operator-shell.spec.ts | 144 ++ apps/web/tests/e2e/operator-workflows.spec.ts | 329 +++++ apps/web/tests/e2e/visual-quality.spec.ts | 10 +- apps/web/tests/e2e/workflows.spec.ts | 14 +- apps/web/tests/operator-data.test.ts | 51 + apps/web/tests/preferences.test.ts | 16 + apps/web/tests/schema-input.test.ts | 91 ++ apps/web/tests/tool-history.test.ts | 61 + apps/web/vite.config.ts | 2 +- connectors/gitea/fake.py | 6 +- connectors/gitea/server.py | 190 +-- package.json | 9 + packages/connector_kit/gitea.py | 27 +- packages/hub_policy/audit.py | 10 +- packages/hub_policy/governance.py | 6 +- pnpm-lock.yaml | 842 ++++------- pyproject.toml | 3 +- scripts/validate_pack.py | 2 + scripts/verify.py | 6 + services/control_api/authz.py | 2 + services/control_api/catalog/loader.py | 5 +- services/control_api/clients/router.py | 47 +- .../control_api/persistence/machine_client.py | 10 +- services/runtime/client_service.py | 61 +- services/runtime/connector_gitea.py | 8 +- services/runtime/control_api.py | 1233 ++++++++++++++++- tests/e2e/harness.py | 33 +- tests/test_appops_connector.py | 21 +- tests/test_bootstrap.py | 17 + tests/test_catalog.py | 54 + tests/test_catalog_write_validation.py | 42 + tests/test_client_lifecycle_audit.py | 108 ++ tests/test_e2e_harness.py | 37 + tests/test_gitea_connector.py | 30 +- tests/test_machine_client_provisioning.py | 73 +- tests/test_operator_search.py | 194 +++ tests/test_operator_workspaces.py | 519 +++++++ tests/test_wp110_governance.py | 32 +- tests/test_wp230_authz.py | 2 + tests/test_wp230_policy_simulate.py | 33 +- tests/test_wp230_rbac.py | 12 +- tests/test_wp230_ui_completion.py | 16 +- uv.lock | 15 +- 86 files changed, 6784 insertions(+), 1478 deletions(-) create mode 100644 apps/web/src/components/RecentToolRuns.tsx create mode 100644 apps/web/src/components/SchemaInputFields.tsx create mode 100644 apps/web/src/components/modal-focus.ts create mode 100644 apps/web/src/operator-data.ts create mode 100644 apps/web/src/pages/ConnectorDetail.tsx create mode 100644 apps/web/src/preferences.ts create mode 100644 apps/web/src/schema-input.ts create mode 100644 apps/web/src/status-refresh.ts create mode 100644 apps/web/src/styles/details.css create mode 100644 apps/web/src/styles/operator.css create mode 100644 apps/web/src/styles/shell.css create mode 100644 apps/web/src/styles/workflows.css create mode 100644 apps/web/src/tool-history.ts create mode 100644 apps/web/tests/e2e/operator-matrix.spec.ts create mode 100644 apps/web/tests/e2e/operator-review.spec.ts create mode 100644 apps/web/tests/e2e/operator-shell.spec.ts create mode 100644 apps/web/tests/e2e/operator-workflows.spec.ts create mode 100644 apps/web/tests/operator-data.test.ts create mode 100644 apps/web/tests/preferences.test.ts create mode 100644 apps/web/tests/schema-input.test.ts create mode 100644 apps/web/tests/tool-history.test.ts create mode 100644 tests/test_catalog_write_validation.py create mode 100644 tests/test_client_lifecycle_audit.py create mode 100644 tests/test_e2e_harness.py create mode 100644 tests/test_operator_search.py create mode 100644 tests/test_operator_workspaces.py diff --git a/PUBLIC_SOURCE_EXPORT.md b/PUBLIC_SOURCE_EXPORT.md index fcb128a..4dee154 100644 --- a/PUBLIC_SOURCE_EXPORT.md +++ b/PUBLIC_SOURCE_EXPORT.md @@ -1,3 +1,3 @@ # Public source export -This source snapshot was generated from the private canonical repository at revision `931c7aca4392dc771dad004a65d54f2b1c430c37`. It contains no private Git history or operational evidence. Changes are published from the canonical repository. +This source snapshot was generated from the private canonical repository at revision `6c7434368ce2a594005983c5a6601a31a639e7f1`. It contains no private Git history or operational evidence. Changes are published from the canonical repository. diff --git a/PUBLIC_SOURCE_MANIFEST.json b/PUBLIC_SOURCE_MANIFEST.json index dd53d44..868d128 100644 --- a/PUBLIC_SOURCE_MANIFEST.json +++ b/PUBLIC_SOURCE_MANIFEST.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceRevision": "931c7aca4392dc771dad004a65d54f2b1c430c37", + "sourceRevision": "6c7434368ce2a594005983c5a6601a31a639e7f1", "files": [ { "path": ".dockerignore", @@ -49,13 +49,13 @@ }, { "path": "apps/web/package.json", - "sha256": "ab0984ea38b8c4f01d09694ebda943ae6aa7e477a62a9c382736ad1c70aa4a94", + "sha256": "a2720410e2436fa772af15d884717b12f0330d0b1dbf73c9496bb046072766d0", "bytes": 967 }, { "path": "apps/web/playwright.config.ts", - "sha256": "166983e0d59eccdec84161ade3e431f9dd63822a3d34f2130d7be60bf4c7cde2", - "bytes": 1854 + "sha256": "4b82bc4dfd9d022202f2f8266d02a4aa53a3cbf908d6b413ddb2484cfc61b701", + "bytes": 2140 }, { "path": "apps/web/public/favicon.svg", @@ -69,13 +69,13 @@ }, { "path": "apps/web/src/api.ts", - "sha256": "d0aebbda4a6fd5645ce019c1462df26c041f32992dfccd775ebf9ef688143c06", - "bytes": 4358 + "sha256": "6d0f9c3a36b206c4c970026132e8e447d29967dcaad89e788ac39a4b8ce06db1", + "bytes": 5572 }, { "path": "apps/web/src/app.tsx", - "sha256": "0a421dd05b1d9cb5d94a89f30086fb4af3af6b595ddc78b3bee3fa3281eeb97b", - "bytes": 8546 + "sha256": "fe1cc1fa47fa976872907183b6bdf35272c4927b6582c5d9c2bee303503e7f0b", + "bytes": 12190 }, { "path": "apps/web/src/bootstrap.ts", @@ -94,13 +94,13 @@ }, { "path": "apps/web/src/components/CommandMenu.tsx", - "sha256": "8dc002409163a9e0ac139fc802bc9807da79d3e0161a16a91927f8248a7efe42", - "bytes": 2329 + "sha256": "d8b66fee31037b2eb087c37a5d8db4eb26e6193bc30c6e0312825aa62bf6fbef", + "bytes": 11392 }, { "path": "apps/web/src/components/DataTable.tsx", - "sha256": "9fad5252ac45a4feb611e0209fccd46bef7a500f0cfaba46902bbb3a6c343895", - "bytes": 5553 + "sha256": "ddabe3b1f83d697d22615f76de34ebccf22e8ba237ae55302fe8ce06148e0e34", + "bytes": 6999 }, { "path": "apps/web/src/components/ErrorPanel.tsx", @@ -129,23 +129,38 @@ }, { "path": "apps/web/src/components/Modal.tsx", - "sha256": "96f10e6f24a58daf6041f0d93722c464a33a6f3226bb0065bf099f0f514d1aee", - "bytes": 2919 + "sha256": "fc049f55c84540de410bb3746455b2b8c5308fce42fc10b2165b6be411ab58fa", + "bytes": 2972 + }, + { + "path": "apps/web/src/components/RecentToolRuns.tsx", + "sha256": "f36bdb6dfe37d8973e86a731330da896a148c49ffadb8d22fd273d06f9e7380f", + "bytes": 2792 + }, + { + "path": "apps/web/src/components/SchemaInputFields.tsx", + "sha256": "b1b723140a07ab2fbaff375f706e119bf8a0621b9e4b7beb3c25396844c9fd02", + "bytes": 4785 }, { "path": "apps/web/src/components/SessionMenu.tsx", - "sha256": "08c9972cdc5d26731c5e7b8421882bc9854be9de89d876f7c91fb58004794285", - "bytes": 1184 + "sha256": "918b41bc5503255040b8a4907a7c320b1b1a889b425858a3810594bef618d9b9", + "bytes": 1294 }, { "path": "apps/web/src/components/StatusPill.tsx", "sha256": "de784a2c451c0dba4986125a5e6b794060b8bfa9b11ec9fe19b869b21a3b5e3b", "bytes": 298 }, + { + "path": "apps/web/src/components/modal-focus.ts", + "sha256": "2bc1fc1d53c1aed3a35e73027cd0eb49e1bbbda0045106ffb69b0d7a3dfa57a9", + "bytes": 4456 + }, { "path": "apps/web/src/contracts.ts", - "sha256": "b7c36d27a6011cd6c5660da72db0b6ff7700d7a80e8829d331f2351fd5f3d09b", - "bytes": 7629 + "sha256": "fc7a6e3a4349860d738b19e705b6f03967d66512cd6a81bf90202ccdca73061b", + "bytes": 10423 }, { "path": "apps/web/src/format.ts", @@ -154,19 +169,24 @@ }, { "path": "apps/web/src/hooks.ts", - "sha256": "f3474ce60c82458aead1aee26f7db8e668e98dea3f25e79848e68195bedb765b", - "bytes": 1436 + "sha256": "56b7e31485e60a9aba1725e6582d76a9a68708830482247970a3f0120a707042", + "bytes": 1432 }, { "path": "apps/web/src/main.ts", - "sha256": "c500632a52617540774ac856616edc585649678a475ec07d5d04d49272b8cc58", - "bytes": 438 + "sha256": "b7f471b3399e51f1dc8eca32e7ad5efc8a867937b693ed834a9fd354ab35aed5", + "bytes": 629 }, { "path": "apps/web/src/navigation.ts", "sha256": "c7d5d922ae27ded37a2f9e129e86dd04e3896b2caf31b157b71ea206be3ff8a8", "bytes": 1881 }, + { + "path": "apps/web/src/operator-data.ts", + "sha256": "dc63e2796ac5761769f3bfe6840f81dabb3d888395204f1138078b988cf1b380", + "bytes": 4742 + }, { "path": "apps/web/src/pages/AuthGateway.tsx", "sha256": "929b9a368bdc8a1c289022c21798374c5a65f3bad48abcbbf4524cc604df15d6", @@ -174,18 +194,23 @@ }, { "path": "apps/web/src/pages/CatalogWorkflow.tsx", - "sha256": "aba9390d623d2b6a1a3e2fc30ceea4434bf196765157c66df810f185438aafcf", - "bytes": 3325 + "sha256": "be520658fb1b87da9ac0d0af4e34865276c484ad4659d7c6df17164b6ce0e6b0", + "bytes": 4583 }, { "path": "apps/web/src/pages/ClientOnboarding.tsx", - "sha256": "6bc5c21c5655771d2b461bc6725b8516e4fe85c5fa9167aa2c27a9d1d043a4fb", - "bytes": 6758 + "sha256": "0a02c1f5b1f074571b8bb473aad16193d18b2ddbb4bebc84de3eb652c816918a", + "bytes": 21663 + }, + { + "path": "apps/web/src/pages/ConnectorDetail.tsx", + "sha256": "9794b70c2b31f5590c6c179354c8b118e4e235d265adc8346c6d970a372b2967", + "bytes": 7467 }, { "path": "apps/web/src/pages/Dashboard.tsx", - "sha256": "a265e25ad03f3822c0d459afa8d9574cde83b6024c233cb07b6c8ad7dc25a9cb", - "bytes": 10185 + "sha256": "37823853d25b8675ab97d8ed1b67c7d79e7e9fbe8ea3b2c5d009601414b93d29", + "bytes": 10452 }, { "path": "apps/web/src/pages/ForbiddenPanel.tsx", @@ -194,8 +219,8 @@ }, { "path": "apps/web/src/pages/Operations.tsx", - "sha256": "f197f902bd591a9accff51376ef7d8aae690b36962da0cb41774c6c786f122b6", - "bytes": 7941 + "sha256": "e4c90be00fa09a4db9385c2bf388af8c5c4f4810c9a938ddc6dc27cd93f7a1d0", + "bytes": 11932 }, { "path": "apps/web/src/pages/PolicySimulator.tsx", @@ -204,23 +229,28 @@ }, { "path": "apps/web/src/pages/ProjectDetail.tsx", - "sha256": "562e110e7fa170df08509fde06d8dcd5666f5731c0e8427eaa5330a42ee34b27", - "bytes": 6743 + "sha256": "70e84876db7ca02c1332afde9565a294cdc2313d09493bb4bcddf70ec4b249ae", + "bytes": 13104 }, { "path": "apps/web/src/pages/ResourcePage.tsx", - "sha256": "79e708edeae9d1d2978d8d195f1783403546bfdfaaadc5bc2eed0674db1ad99a", - "bytes": 9466 + "sha256": "2a092a436b7c91514d98e4f0104497362967a13e3d7343b6b85f2d39f4d3b9dd", + "bytes": 11672 }, { "path": "apps/web/src/pages/SettingsPage.tsx", - "sha256": "417ea7501e9608dd8aae9d06215eb50d80cb720ee95405c5a0952135ad8bae00", - "bytes": 2147 + "sha256": "83abb9907bfefd5572b9acdebdfcbc689afbdbf26df8114effdeaed610caa26a", + "bytes": 2169 }, { "path": "apps/web/src/pages/ToolReadinessModal.tsx", - "sha256": "9c8c81ec498b6b3b25d5cd58aa36709734a254f37ffceb2557c3276675479086", - "bytes": 9157 + "sha256": "75b09cb94ce445d374a3b54e89bb2504bac92d180af20affe3009e903dc7df0b", + "bytes": 11552 + }, + { + "path": "apps/web/src/preferences.ts", + "sha256": "298faf383f6a4414f98cc02e4530c2575380d1b028342a28cb2f0dad749ec67a", + "bytes": 558 }, { "path": "apps/web/src/rbac-context.tsx", @@ -229,23 +259,58 @@ }, { "path": "apps/web/src/rbac.ts", - "sha256": "d085bf7104c1c92c11cd50037c748d6ef0daa3c58a932a937a8a569908b3267c", - "bytes": 1621 + "sha256": "35f12f86889e39f766ab1eb96f88e144f59fae61e9dc81ab0a7a5239042d3ac9", + "bytes": 1683 }, { "path": "apps/web/src/routing.ts", - "sha256": "4eaf247684474b126ee96d11a1d559e20a6357a1508c39f8a3b2d6aa772773e6", - "bytes": 1149 + "sha256": "1941e048a034a5ac0e0d1d30e0759c2bc05ed58c0301fd436877e1eccd59f8bb", + "bytes": 1650 + }, + { + "path": "apps/web/src/schema-input.ts", + "sha256": "d363239aab90dc4c93747f645cc27757fb0f129a59c59700ecfea194d91a7cdf", + "bytes": 5990 + }, + { + "path": "apps/web/src/status-refresh.ts", + "sha256": "856a294fd4de301779b301c4e095d7a9500731f4923d1de7d1c0a02034677366", + "bytes": 2892 }, { "path": "apps/web/src/styles.css", - "sha256": "8f22fd7261c86abcab916a65d14e407db7d3d1d31116a776d866099ff75ddcc9", - "bytes": 64960 + "sha256": "e6caa3209994a4cd716a94e7b5e4b52888cbe16f8e3c38f5e9910f1c013d6ca5", + "bytes": 62320 + }, + { + "path": "apps/web/src/styles/details.css", + "sha256": "962d87e466c4e1873dd1d0ed80e81bcc40dfd042d7a0184a2b4d96ac8cf20a2f", + "bytes": 2611 + }, + { + "path": "apps/web/src/styles/operator.css", + "sha256": "f8c7d52769885843f83e08d2b89004826f84511e048b10f8539e1619aab61b70", + "bytes": 4297 + }, + { + "path": "apps/web/src/styles/shell.css", + "sha256": "543d658bad7791d0d4554c0a741fe07aee824515c5cc7a23e4ada58352dc2583", + "bytes": 5242 + }, + { + "path": "apps/web/src/styles/workflows.css", + "sha256": "66e08d736cbd7b532642d23d9bddad5351835640a06f572bdedbffb3a6773edf", + "bytes": 8978 + }, + { + "path": "apps/web/src/tool-history.ts", + "sha256": "dbfdd8063af2ce3b6d85b744a3b802648044d0656f8f5503848c3a6dcdff68bc", + "bytes": 2988 }, { "path": "apps/web/tests/api.test.ts", - "sha256": "79dc9e71d364f7d359ebdc1d1b5a3b80839c5c4ea400bd6029f1314f1c85951a", - "bytes": 5789 + "sha256": "f9b4bbb9d1337672568e7d27258c15fef52b697fab6a7a6dcdf60b0854d3b426", + "bytes": 6405 }, { "path": "apps/web/tests/bootstrap.test.ts", @@ -254,8 +319,8 @@ }, { "path": "apps/web/tests/contracts.test.ts", - "sha256": "13313c4a99971007d9c354e491ddfa23c5fbb9e09c9d40010d9288bc788a22b5", - "bytes": 1122 + "sha256": "c315bd4d881014d286f07c5baf71377792011b2ba107064768add1362c1fd38b", + "bytes": 1367 }, { "path": "apps/web/tests/e2e/accessibility.spec.ts", @@ -264,8 +329,8 @@ }, { "path": "apps/web/tests/e2e/auth.spec.ts", - "sha256": "e73c68a37faf2c96e061d814302c768fb94a449e275873e56e57262736b54ee8", - "bytes": 3513 + "sha256": "72abb30d223c4a847f8ce958c9c916801cf412e17b1a0c9ec56dde9448811d32", + "bytes": 3700 }, { "path": "apps/web/tests/e2e/fixtures.ts", @@ -274,8 +339,28 @@ }, { "path": "apps/web/tests/e2e/navigation.spec.ts", - "sha256": "693edec83b9d1569dbe60468921e15de559e5370fc4dda2a006b7acf3b206f06", - "bytes": 2747 + "sha256": "3a7fd93bdef6f25795720d7fe47e5ab600158d44043e559dfd7dd579d10c7c0b", + "bytes": 2862 + }, + { + "path": "apps/web/tests/e2e/operator-matrix.spec.ts", + "sha256": "31caf7cf64405304cbce4a653647f7d7ddd3915fe460b3469f49d1623ddda939", + "bytes": 10426 + }, + { + "path": "apps/web/tests/e2e/operator-review.spec.ts", + "sha256": "03cb44204c40bef2c8ab4e245a7374a8636ff32a15ff90b23c1022c0a76bd191", + "bytes": 7983 + }, + { + "path": "apps/web/tests/e2e/operator-shell.spec.ts", + "sha256": "cdefdbbada4610c631d292d1b6c9f5f15a3d57746c50c6f1c95a02bfbb0496ae", + "bytes": 7687 + }, + { + "path": "apps/web/tests/e2e/operator-workflows.spec.ts", + "sha256": "8a15c4305656d0068a4af4a8215b7b405055a85a1dfb55f745fae46d8e19bb44", + "bytes": 17752 }, { "path": "apps/web/tests/e2e/rbac.spec.ts", @@ -289,19 +374,39 @@ }, { "path": "apps/web/tests/e2e/visual-quality.spec.ts", - "sha256": "07ed212e6b8e9e253775672eac848b216bb71575139fecf079fb504ee4c902a7", - "bytes": 5553 + "sha256": "dc00e990afedf58ae57deec7e02b11b4a61681d45075bf7dbc2263e75ed8e352", + "bytes": 5685 }, { "path": "apps/web/tests/e2e/workflows.spec.ts", - "sha256": "83122d00735fef8adea67eeb7cb6e43b74f1bbb5e90c2650d63734723445fd36", - "bytes": 12525 + "sha256": "ccfe4e2e2f277ed6069d30376719ae7bcb94e8f1b2b2614d4612d9d409dfb4ad", + "bytes": 12984 + }, + { + "path": "apps/web/tests/operator-data.test.ts", + "sha256": "079b4b17a8adfcff3019e1519bedd5e619d34e34f5b9822c8a2ac4303add8718", + "bytes": 3371 + }, + { + "path": "apps/web/tests/preferences.test.ts", + "sha256": "b420285fe2d4c6d36aca301006ec034ba51af50f9b08858921a7152e999dac9e", + "bytes": 1066 }, { "path": "apps/web/tests/rbac.test.ts", "sha256": "e9a2827f96cc12550cb4cccdd30ab72efb15f5eb55ae0ac11fdb26e5134990bf", "bytes": 2661 }, + { + "path": "apps/web/tests/schema-input.test.ts", + "sha256": "269bc46ef8dc90e87ac100780e59abb36dff6d50cf7e508421431cfade9b8025", + "bytes": 3460 + }, + { + "path": "apps/web/tests/tool-history.test.ts", + "sha256": "d9f52f79abad7bb39a473dcc36c6d55838e6cb28aa234b0f881eb41a87381357", + "bytes": 2451 + }, { "path": "apps/web/tsconfig.json", "sha256": "b13b349ceb0a6062cf5509591e92e98cb4a1b26822c734e935e7d4e45935047e", @@ -309,8 +414,8 @@ }, { "path": "apps/web/vite.config.ts", - "sha256": "2497c38e866dbf9a64400811144a4e5afea5c099c048881c8abdaaf7051a5223", - "bytes": 277 + "sha256": "dd03015ec0252f3de6a62aa0c01fbc5803afd5f8294f202f0bc192cfdd6c3699", + "bytes": 309 }, { "path": "catalog/README.md", @@ -1639,8 +1744,8 @@ }, { "path": "connectors/gitea/fake.py", - "sha256": "94f7349380ab0695792c7308e0b08003661834801189b5005f934dc05d828eae", - "bytes": 30597 + "sha256": "e8012cd1b40e35f6947dcfb967ae83e3a8f85816c9a5c790704887223f093073", + "bytes": 30667 }, { "path": "connectors/gitea/lifecycle.py", @@ -1654,8 +1759,8 @@ }, { "path": "connectors/gitea/server.py", - "sha256": "d8c5be1efed22d99dc287a83fd78156e306afa731f6b233b4d076bb5736152b4", - "bytes": 227648 + "sha256": "be5f401d9bc75d531f1d082ba8893547fa752d1fad5c22069b165f3cea64db3b", + "bytes": 228031 }, { "path": "connectors/knowledge/__init__.py", @@ -2009,8 +2114,8 @@ }, { "path": "package.json", - "sha256": "9bd1cc5653ee2aac73b71005363817d687d016668de737e77b85a5e418d9a6c1", - "bytes": 379 + "sha256": "1f03209a7d913ba92b911cb2eddde44c34d64689761e5fdab8b3afc306e2d361", + "bytes": 583 }, { "path": "packages/README.md", @@ -2034,8 +2139,8 @@ }, { "path": "packages/connector_kit/gitea.py", - "sha256": "2a5af5c47531ba65002e11fc61893bc477aec9481b76da0e0e3b64b15ad52850", - "bytes": 50212 + "sha256": "118e657d06142f71db40741b08d25504e30f13754bf0f67fc5f45f865cb41e2e", + "bytes": 50270 }, { "path": "packages/connector_kit/gitea_resilient.py", @@ -2144,8 +2249,8 @@ }, { "path": "packages/hub_policy/audit.py", - "sha256": "171711bd6c6045b4fc73f25600ad96e7043d2fc9de4633a464c81e7481583b1d", - "bytes": 6191 + "sha256": "0dc272103d6cee37260b6c6cb8ef3b378b883ccdda9114627c0bee1dc4712ddb", + "bytes": 6233 }, { "path": "packages/hub_policy/engine.py", @@ -2154,8 +2259,8 @@ }, { "path": "packages/hub_policy/governance.py", - "sha256": "c600bfa0429bd12523b120462472bafe4338569e206e76532f54d18be41c16df", - "bytes": 5874 + "sha256": "643bac3aa11de19e58ce36bc61090aa472cfd47f5f7c028d9fa32b051035cd52", + "bytes": 5938 }, { "path": "packages/hub_policy/rate_limit.py", @@ -2239,8 +2344,8 @@ }, { "path": "pnpm-lock.yaml", - "sha256": "7f80ff769f89743192d6eff6a637a4c60cb5ceaa2bbece621a7a9ac03a7caf71", - "bytes": 90269 + "sha256": "eb333f963d85f9d33359a83eb5cbebe94a2042e5f1636778f49aa96fa5b5376a", + "bytes": 80522 }, { "path": "pnpm-workspace.yaml", @@ -2249,8 +2354,8 @@ }, { "path": "pyproject.toml", - "sha256": "f42fb743257f77af9066b85ce9a2eb633d3dfbddc10d67315a0d9f7352b4938d", - "bytes": 857 + "sha256": "561e2da3f0a6ef04eb8681f02c67e2c352c5cc3d90e9353237991ec2132fb316", + "bytes": 877 }, { "path": "scripts/README.md", @@ -2419,13 +2524,13 @@ }, { "path": "scripts/validate_pack.py", - "sha256": "36e33bab19294b0d0353fd6af80cf9db6de03d0440d2e4d5ac750deb97492e8d", - "bytes": 35777 + "sha256": "f77573eba9128c9920a293ed3f73e9f2307b2bc6a4505b6814ac3c950fe68712", + "bytes": 35865 }, { "path": "scripts/verify.py", - "sha256": "b2c39fb0f6ca2624be4f0f27db70b14f12689efb6d1b3ba3ced045712cdc4638", - "bytes": 13732 + "sha256": "da819db31ca891d8f9f2aaad42c02728b0dd8fa9bd2801a43f1fd0e947facca8", + "bytes": 14006 }, { "path": "scripts/verify_demo_token_flow.py", @@ -2504,8 +2609,8 @@ }, { "path": "services/control_api/authz.py", - "sha256": "f4f47704949be21fbe2d7d5a515a451bde79265292628e8c4f43bf1c652a4104", - "bytes": 5183 + "sha256": "4b363e26856c775762ab09a2a44f371021044dcc860ba80a42081ef7954bc881", + "bytes": 5270 }, { "path": "services/control_api/catalog/__init__.py", @@ -2514,8 +2619,8 @@ }, { "path": "services/control_api/catalog/loader.py", - "sha256": "6ba03597ef4b4dac2fb05b07516c5cf657622a239d38debb24482ba2f465956c", - "bytes": 13928 + "sha256": "61b5f6651b1a33d06d50d6fdbbf341cc21cb1f94ff2069651aa4f4463b3f68e2", + "bytes": 14060 }, { "path": "services/control_api/catalog/models.py", @@ -2549,8 +2654,8 @@ }, { "path": "services/control_api/clients/router.py", - "sha256": "7f51cfc3c87925a6286118773a446e2b4886e0fcda031f2e51e31bfc7a6f5373", - "bytes": 5785 + "sha256": "c1dbcb8decbf3aa04ac50bb727345288c1b32ec6a97e4708484707a38c83ae22", + "bytes": 7548 }, { "path": "services/control_api/clients/service.py", @@ -2629,8 +2734,8 @@ }, { "path": "services/control_api/persistence/machine_client.py", - "sha256": "ac8886cae645aaf87032aa74e59002c1036225a4f90c924aecb569a875e910dc", - "bytes": 6051 + "sha256": "2ec55f8bf1881e0f7b2726bcfa710e1df812ec79877574838b69570ed97326e4", + "bytes": 6384 }, { "path": "services/control_api/persistence/models.py", @@ -2744,8 +2849,8 @@ }, { "path": "services/runtime/client_service.py", - "sha256": "0dcb25a3c0cd5391409e40add822e48920a4560fb9fe182e901bf045876f547c", - "bytes": 8331 + "sha256": "73ff3a633fb0d622bacf19d6955a274f9072467e3a0a9a3456fd6432365e9058", + "bytes": 10709 }, { "path": "services/runtime/common.py", @@ -2759,8 +2864,8 @@ }, { "path": "services/runtime/connector_gitea.py", - "sha256": "c89874089d1bdde3020864e9222052d2470970ad7c4aa5677201cbfbc4a88eea", - "bytes": 37958 + "sha256": "5218c32351530d4b44cf99f61eac1543b0668de0a737a130f7d66a88cc0e2807", + "bytes": 37952 }, { "path": "services/runtime/connector_ingestion.py", @@ -2794,8 +2899,8 @@ }, { "path": "services/runtime/control_api.py", - "sha256": "348f126c7816d28cdd1c542a438621b332f8fdbfdd432dddfbbb15b83432778e", - "bytes": 55774 + "sha256": "5ea476237d69129634fa391ac3b5c2061bc889a4bbd83b98a1781e12c90539c2", + "bytes": 113286 }, { "path": "services/runtime/dataplane.py", @@ -2889,8 +2994,8 @@ }, { "path": "tests/e2e/harness.py", - "sha256": "c776c167b001a372906e9dca1e39a1a13a09a2fa88f8093b3cc02b99183eb98d", - "bytes": 6538 + "sha256": "a4edee80a05beeee8e07787c7bfa4607f3f27927c87d948051eea4d07ff30287", + "bytes": 7503 }, { "path": "tests/e2e/tls.py", @@ -2924,8 +3029,8 @@ }, { "path": "tests/test_appops_connector.py", - "sha256": "4e088d187b239cd1e8b81b7c5d72aed27eddae78491678564bfe2776d37913be", - "bytes": 9448 + "sha256": "93fea80c542f6c3bb6e490626c6b4b00db2a0238dfb0210c0edeb415abc1ca06", + "bytes": 9388 }, { "path": "tests/test_auth.py", @@ -2934,13 +3039,23 @@ }, { "path": "tests/test_bootstrap.py", - "sha256": "15dbc0d8e2499f10faf9e3eba79756fb8f4b77251fe5b5d6d5967c4dd91ef9a9", - "bytes": 10812 + "sha256": "48b59147ceb5bf46ade6327d2e1484fad2eaa6d76294654ad98bcfbb811b500c", + "bytes": 11767 }, { "path": "tests/test_catalog.py", - "sha256": "adbe584172170fa17fe21c0e6a1953729e9b83190845d8e9d053b0d2cef4b1c6", - "bytes": 16467 + "sha256": "1614357a0e3f15894f7bca4d0b7c67a6d71c13f6a372f15d8d2622abdcb34641", + "bytes": 18805 + }, + { + "path": "tests/test_catalog_write_validation.py", + "sha256": "785959baca7e28c8da6b363af7835f97fc94a3a4116dc7bf3052468876c2611d", + "bytes": 1487 + }, + { + "path": "tests/test_client_lifecycle_audit.py", + "sha256": "4a93dd3c4ade40e241c526a9ec116da2d4b5c32f91d3368e0b20c99e05e727a7", + "bytes": 4639 }, { "path": "tests/test_codex_bridge.py", @@ -3052,6 +3167,11 @@ "sha256": "fb4007bd62d722e17140ac41623f374f16fb599a41f8f3ac7ca2be18cb21cab5", "bytes": 2395 }, + { + "path": "tests/test_e2e_harness.py", + "sha256": "aba3682b8185ef4d0b1b28dc536b7cf106729fb5135f91cc790e019763335adb", + "bytes": 1516 + }, { "path": "tests/test_gitea_access_diagnostics.py", "sha256": "7b5c383119f017761551b28c5516eaa5b8bf5b0aeddf039d4fb4dd846c2824f3", @@ -3064,8 +3184,8 @@ }, { "path": "tests/test_gitea_connector.py", - "sha256": "c6389e924471d392f1a19cae48e9c409b349223b76faa34c20b9ae4c7894ede2", - "bytes": 128684 + "sha256": "dd61db44c16f1ecb2fcc2ac099900b3908fa7b5aa7ab397c40648795a4e2ecaa", + "bytes": 128560 }, { "path": "tests/test_gitea_merge_eligibility.py", @@ -3154,8 +3274,8 @@ }, { "path": "tests/test_machine_client_provisioning.py", - "sha256": "c1695cb3d087d58337d0abd14bdef3fec1c253648650c7a5ec98db9ac5e36aee", - "bytes": 6140 + "sha256": "f925d9624d81035f9131e4f4f25281d27b6c59f8bb1edfa2e2c7b7dcbafb6385", + "bytes": 8728 }, { "path": "tests/test_managed_validation_action_pins.py", @@ -3187,6 +3307,16 @@ "sha256": "a8f32e6569ed3fa947ba8b714128a86610f0a186bc7a0447b3497e41ba6c8e9f", "bytes": 1134 }, + { + "path": "tests/test_operator_search.py", + "sha256": "92a6e536600449d343477fa4b3866c5ba314cdc20e5bed992b19ac42fea8ecff", + "bytes": 6719 + }, + { + "path": "tests/test_operator_workspaces.py", + "sha256": "dbe4d6e42c85b0dbf674288096e6481fa0346c2b26300f67e89fc824794b07ef", + "bytes": 22774 + }, { "path": "tests/test_persistence.py", "sha256": "139f399570d6123d3fb5f6d8f52eb4a47d1a2f2f72f68fd7d2b12d4bcf5a85d2", @@ -3244,8 +3374,8 @@ }, { "path": "tests/test_wp110_governance.py", - "sha256": "dbb358f03c4b55060949dd728228862fba59a0e51bc45bc166b9d8062de59af4", - "bytes": 9314 + "sha256": "721200a16676a76ef239d3966ff0f4e71dc5d54f8b3571495966a2faf8dff329", + "bytes": 9480 }, { "path": "tests/test_wp140_clients.py", @@ -3294,18 +3424,18 @@ }, { "path": "tests/test_wp230_authz.py", - "sha256": "218bd70139767573ced7f210d230ddbd2cb5e3beafff7954f9b017efdf67a64f", - "bytes": 6696 + "sha256": "ba186a4cb0bea077feb240e6d738fa5fb907550d2b7ba45184d802c06a2b2995", + "bytes": 6746 }, { "path": "tests/test_wp230_policy_simulate.py", - "sha256": "f62386357ca2aadc0775fc7bba9393470ebdb85ff8f140f0f6c27fa994a1c74b", - "bytes": 20597 + "sha256": "1c8c6a9b8711824d03903844e70104522f494f08f05bb0c55287515db5efa4ff", + "bytes": 20697 }, { "path": "tests/test_wp230_rbac.py", - "sha256": "63665cb5c25c4ac13bfcd9378d5db12fa4ebac804c616970862f67c51a4902d9", - "bytes": 20973 + "sha256": "1b540347d8053040dbc8d7850fa163e3814f22c01f8619a19b4222bbff082ed8", + "bytes": 21361 }, { "path": "tests/test_wp230_session_store.py", @@ -3314,8 +3444,8 @@ }, { "path": "tests/test_wp230_ui_completion.py", - "sha256": "dacb89c17ea74db69c8cf28b3ff382f5e55d2f8952b93d4fdbd8e5aeaf7b596e", - "bytes": 10276 + "sha256": "45718516ff3b1aef1201c0eca71c0ebf828737ecfcbe8e462e570ed0c11964c7", + "bytes": 10132 }, { "path": "tests/test_wp270_functional_operations.py", @@ -3329,8 +3459,8 @@ }, { "path": "uv.lock", - "sha256": "4e8b857f3a4b8dee50b2148a208d887ce869ee2a6851734a8911d3ef61b7677c", - "bytes": 104010 + "sha256": "0157f642a8d62a7c0777989761bff66d0c09cb99338c1f2c2146f85bcfe3a908", + "bytes": 104059 } ] } diff --git a/apps/web/package.json b/apps/web/package.json index 5ed474a..a556b29 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -19,17 +19,17 @@ }, "devDependencies": { "@axe-core/playwright": "4.11.0", - "@playwright/test": "^1.62.1", - "@types/node": "^26.1.2", + "@playwright/test": "1.62.1", + "@types/node": "26.1.2", "@types/react": "19.1.10", "@types/react-dom": "19.1.7", "@typescript-eslint/parser": "8.39.0", "@vitejs/plugin-react": "4.7.0", - "@vitest/coverage-v8": "3.2.4", + "@vitest/coverage-v8": "4.1.11", "eslint": "9.35.0", "eslint-plugin-react-hooks": "5.2.0", "typescript": "5.9.2", - "vite": "7.1.3", - "vitest": "3.2.4" + "vite": "7.3.5", + "vitest": "4.1.11" } } diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts index dd76d6a..3402669 100644 --- a/apps/web/playwright.config.ts +++ b/apps/web/playwright.config.ts @@ -9,6 +9,7 @@ const DIST = path.join(WEB_DIR, "dist"); const WEB_PORT = 8780; const CONTROL_PORT = 8781; const OIDC_PORT = 8782; +const WORKER_PORT = 8783; export default defineConfig({ testDir: "./tests/e2e", @@ -43,7 +44,14 @@ export default defineConfig({ cwd: REPO_ROOT, }, { - command: `"${PYTHON}" -m tests.e2e.harness web --port ${WEB_PORT} --control-port ${CONTROL_PORT} --dist "${DIST}"`, + command: `"${PYTHON}" -m tests.e2e.harness worker --port ${WORKER_PORT}`, + url: `http://127.0.0.1:${WORKER_PORT}/health/ready`, + reuseExistingServer: false, + timeout: 90_000, + cwd: REPO_ROOT, + }, + { + command: `"${PYTHON}" -m tests.e2e.harness web --port ${WEB_PORT} --control-port ${CONTROL_PORT} --worker-port ${WORKER_PORT} --dist "${DIST}"`, url: `https://127.0.0.1:${WEB_PORT}/ready`, ignoreHTTPSErrors: true, reuseExistingServer: false, diff --git a/apps/web/src/api.ts b/apps/web/src/api.ts index 0c62a9b..c06e2ee 100644 --- a/apps/web/src/api.ts +++ b/apps/web/src/api.ts @@ -1,4 +1,4 @@ -import type { CatalogPreview, ClientActionResult, ClientOnboardingResult, DashboardSnapshot, ManagementApiClient, ManagementRole, OnboardingClientKind, OperationsCheck, OperationsSummary, PageQuery, PolicySimulation, ProjectDetailInfo, ResourceKind, ResourcePage, ToolDefinition, ToolInvocationResult, ToolReadinessResult } from "./contracts"; +import type { CatalogPreview, ClientActionResult, ClientOnboardingResult, ClientOnboardingStatus, ConnectorDetailInfo, DashboardSnapshot, ManagementApiClient, ManagementRole, OnboardingClientKind, OperationsCheck, OperationsSummary, PageQuery, PolicySimulation, ProjectDetailInfo, ResourceKind, ResourcePage, ToolDefinition, ToolInvocationResult, ToolReadinessResult } from "./contracts"; export class ApiError extends Error { readonly status: number; @@ -24,8 +24,9 @@ export async function logoutSession(): Promise { export function createApiClient(): ManagementApiClient { let csrfToken: string | null = null; return { session: async () => { const raw = await getJson<{ authenticated: boolean; csrf_token?: string; session?: { subject: string; client_id: string | null; kind: string; roles: string[]; expires_at: string } }>("/auth/session"); csrfToken = raw.csrf_token ?? null; return { authenticated: raw.authenticated, csrf_token: raw.csrf_token, session: raw.session ? { subject: raw.session.subject, clientId: raw.session.client_id, kind: raw.session.kind as "management_user" | "machine_client", roles: raw.session.roles as ManagementRole[], expiresAt: raw.session.expires_at } : undefined }; }, dashboard: () => getJson("/api/dashboard"), - resource: (kind: ResourceKind, query: PageQuery) => { const params = new URLSearchParams({ q: query.filter, limit: String(query.limit) }); if (query.cursor) params.set("cursor", query.cursor); return getJson(`/api/${kind}?${params.toString()}`); }, + resource: (kind: ResourceKind, query: PageQuery) => { const params = new URLSearchParams({ q: query.filter, limit: String(query.limit) }); if (query.cursor) params.set("cursor", query.cursor); if (query.project) params.set("project", query.project); if (query.environment) params.set("environment", query.environment); return getJson(`/api/${kind}?${params.toString()}`); }, projectDetail: (slug) => getJson(`/api/projects/${encodeURIComponent(slug)}`), + connectorDetail: (slug, scope) => { const params = new URLSearchParams(); if (scope) { params.set("project", scope.project); params.set("environment", scope.environment); } const value = params.toString(); const query = value ? `?${value}` : ""; return getJson(`/api/connectors/${encodeURIComponent(slug)}${query}`); }, simulatePolicy: (input) => postJson("/api/policies/simulate", input, csrfToken), testTool: (input) => postJson("/api/tools/test", input, csrfToken), toolDefinition: (canonicalName) => getJson(`/api/tool-definitions/${encodeURIComponent(canonicalName)}`), @@ -36,6 +37,20 @@ export function createApiClient(): ManagementApiClient { let csrfToken: string | applyCatalog: (input) => postJson("/api/catalog/apply", input, csrfToken), clientAction: async (id, action) => { await postJson(`/api/client-management/${encodeURIComponent(id)}/${action}`, {}, csrfToken); return { accepted: true, message: `Client ${action} completed.` }; }, createClient: (input: { name: string; project: string; environment: string; kind: OnboardingClientKind }) => postJson("/api/client-management", input, csrfToken), - generatedConfig: (id) => getJson<{ clientId: string; endpoint: string; scopes: string[] }>(`/api/client-management/${encodeURIComponent(id)}/config`), + generatedConfig: async (id) => { + const raw = await getJson(`/api/client-management/${encodeURIComponent(id)}/config`); + return { + clientId: raw.clientId, + kind: raw.kind, + protocolVersion: raw.protocolVersion, + endpoint: raw.endpoint, + transport: raw.transport, + scopes: raw.scopes, + ...(typeof raw.environmentVariable === "string" ? { environmentVariable: raw.environmentVariable } : {}), + ...(typeof raw.bridgeCommand === "string" ? { bridgeCommand: raw.bridgeCommand } : {}), + }; + }, + clientOnboardingStatus: (id) => getJson(`/api/client-management/${encodeURIComponent(id)}/onboarding-status`), + verifyClientOnboarding: (id) => postJson(`/api/client-management/${encodeURIComponent(id)}/verify`, {}, csrfToken), }; } export function safeApiMessage(error: unknown): string { return error instanceof ApiError ? error.message : "The control API returned no usable data."; } diff --git a/apps/web/src/app.tsx b/apps/web/src/app.tsx index 18a77c9..fb7fcda 100644 --- a/apps/web/src/app.tsx +++ b/apps/web/src/app.tsx @@ -1,10 +1,10 @@ import { useEffect, useMemo, useRef, useState } from "react"; -import type { DashboardSnapshot, ManagementApiClient, ManagementRole, SessionStatus } from "./contracts"; +import type { ClientOnboardingResult, DashboardSnapshot, ManagementApiClient, ManagementRole, SessionStatus } from "./contracts"; import { routeTitle } from "./contracts"; import { ApiError, createApiClient, safeApiMessage } from "./api"; import { RbacProvider } from "./rbac-context"; import { can } from "./rbac"; -import { basePath, resourceKind, usePath } from "./routing"; +import { basePath, connectorDetailId, resourceKind, usePath } from "./routing"; import { Brand } from "./components/Brand"; import { CommandMenu } from "./components/CommandMenu"; import { ErrorPanel } from "./components/ErrorPanel"; @@ -19,6 +19,8 @@ import { Operations } from "./pages/Operations"; import { ProjectDetail } from "./pages/ProjectDetail"; import { ResourcePage } from "./pages/ResourcePage"; import { SettingsPage } from "./pages/SettingsPage"; +import { ConnectorDetail } from "./pages/ConnectorDetail"; +import { focusableElements, isolateOutside, lockBodyScroll, trapTab } from "./components/modal-focus"; export function App({ api: apiProp }: { api?: ManagementApiClient }) { const [api] = useState(() => apiProp ?? createApiClient()); @@ -31,19 +33,24 @@ export function App({ api: apiProp }: { api?: ManagementApiClient }) { const [subject, setSubject] = useState(null); const [navOpen, setNavOpen] = useState(false); const [commandOpen, setCommandOpen] = useState(false); + const [createdClient, setCreatedClient] = useState(null); + const identitySubject = useRef(null); + identitySubject.current = session === "authenticated" ? subject : null; const navTriggerRef = useRef(null); - const title = useMemo(() => routeTitle(basePath(path)), [path]); + const navCloseRef = useRef(null); + const navSidebarRef = useRef(null); + const navBackdropRef = useRef(null); + const [compactNavigation, setCompactNavigation] = useState(() => window.matchMedia("(max-width: 840px)").matches); + const title = useMemo(() => connectorDetailId(path) ? "Connector detail" : routeTitle(basePath(path)), [path]); const clearIdentity = () => { setRoles([]); setSnapshot(null); setSubject(null); + setCreatedClient(null); }; - const closeNavigation = (restoreFocus = true) => { - setNavOpen(false); - if (restoreFocus) window.setTimeout(() => navTriggerRef.current?.focus(), 0); - }; + const closeNavigation = () => setNavOpen(false); const load = async () => { setSession("checking"); @@ -90,13 +97,45 @@ export function App({ api: apiProp }: { api?: ManagementApiClient }) { useEffect(() => { void load(); }, []); useEffect(() => { setNavOpen(false); }, [path]); useEffect(() => { - if (!navOpen) return; - const onKeyDown = (event: KeyboardEvent) => { - if (event.key === "Escape") closeNavigation(); + const media = window.matchMedia("(max-width: 840px)"); + const update = () => { + setCompactNavigation(media.matches); + if (!media.matches) setNavOpen(false); }; + media.addEventListener("change", update); + return () => media.removeEventListener("change", update); + }, []); + useEffect(() => { + const sidebar = navSidebarRef.current; + if (!navOpen || !compactNavigation || !sidebar) return; + const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null; + const backdrop = navBackdropRef.current; + const restoreIsolation = isolateOutside(sidebar, backdrop ? [backdrop] : []); + const restoreScroll = lockBodyScroll(); + const focusables = () => focusableElements(sidebar); + const onFocusIn = (event: FocusEvent) => { + if (event.target instanceof Node && sidebar.contains(event.target)) return; + (focusables()[0] ?? sidebar).focus(); + }; + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + event.preventDefault(); + setNavOpen(false); + } else { + trapTab(event, sidebar); + } + }; + navCloseRef.current?.focus(); + document.addEventListener("focusin", onFocusIn, true); document.addEventListener("keydown", onKeyDown); - return () => document.removeEventListener("keydown", onKeyDown); - }, [navOpen]); + return () => { + document.removeEventListener("focusin", onFocusIn, true); + document.removeEventListener("keydown", onKeyDown); + restoreIsolation(); + restoreScroll(); + if (previouslyFocused?.isConnected) previouslyFocused.focus(); + }; + }, [navOpen, compactNavigation]); useEffect(() => { if (session !== "authenticated") return; const onKeyDown = (event: KeyboardEvent) => { @@ -114,6 +153,9 @@ export function App({ api: apiProp }: { api?: ManagementApiClient }) { const kind = resourceKind(path); const detail = basePath(path).startsWith("/projects/"); + const connectorId = connectorDetailId(path); + const scopeParams = new URLSearchParams(path.split("?")[1] ?? ""); + const scopeKey = `${scopeParams.get("project") ?? ""}:${scopeParams.get("environment") ?? ""}`; const canReadAudit = can(roles, "audit.read"); const active = (href: string) => basePath(path) === href || (href !== "/" && basePath(path).startsWith(`${href}/`)); const authenticated = session === "authenticated"; @@ -125,27 +167,38 @@ export function App({ api: apiProp }: { api?: ManagementApiClient }) { : loadState === "error" ? void load()} /> : detail - ? - : kind - ? - : snapshot && basePath(path) === "/" - ? void load()} navigate={navigate} /> - : basePath(path) === "/operations" - ? - : basePath(path) === "/settings" - ? - : ; + ? + : connectorId + ? + : kind + ? { if (subject !== null && identitySubject.current === subject) setCreatedClient(client); }} /> + : snapshot && basePath(path) === "/" + ? void load()} navigate={navigate} /> + : basePath(path) === "/operations" + ? + : basePath(path) === "/settings" + ? + : ; return ( Skip to main content
{authenticated ? <> - {navOpen ? +