From c14de3df0e838382419cec410f75f34f0deb52f5 Mon Sep 17 00:00:00 2001
From: Michelle Bergquist <11967646+michellescripts@users.noreply.github.com>
Date: Thu, 20 Feb 2025 10:56:12 -0700
Subject: [PATCH] [v17] aws oidc user tasks (#52262)
* Add navigation to individual aws resource rule tables (#49855)
* Add rules tables for aws resources
* review feedback
* Show user tasks for aws oidc integration (#50100)
* User task design updates (#52280)
* Set featurebox left padding
* Set side panel padding
* Stop modifying table contents
* Deep link open sidepanel
* PR feedback
* User task design updates (#52318)
* fix rebase imports
---
pnpm-lock.yaml | 663 +++++++++++++++++-
web/__mocks__/react-markdown.js | 7 +
web/packages/design/src/Tabs/Tabs.ts | 57 ++
web/packages/teleport/package.json | 3 +-
.../src/Integrations/IntegrationList.tsx | 2 +-
.../status/AwsOidc/AwsOidcDashboard.story.tsx | 158 ++---
.../status/AwsOidc/AwsOidcDashboard.test.tsx | 166 +++--
.../status/AwsOidc/AwsOidcDashboard.tsx | 67 +-
.../status/AwsOidc/AwsOidcHeader.tsx | 90 ++-
.../status/AwsOidc/AwsOidcRoutes.tsx | 16 +-
.../status/AwsOidc/AwsOidcTitle.test.tsx | 93 +++
.../status/AwsOidc/AwsOidcTitle.tsx | 84 +++
.../status/AwsOidc/Details/Agents.tsx | 106 +++
.../status/AwsOidc/Details/Details.story.tsx | 232 ++++++
.../status/AwsOidc/Details/Details.tsx | 58 ++
.../status/AwsOidc/Details/Rds.tsx | 105 +++
.../status/AwsOidc/Details/Rules.test.tsx | 98 +++
.../status/AwsOidc/Details/Rules.tsx | 115 +++
.../status/AwsOidc/EnrollCard.tsx | 61 ++
.../Integrations/status/AwsOidc/StatCard.tsx | 60 +-
.../status/AwsOidc/Tasks/SidePanel.tsx | 71 ++
.../status/AwsOidc/Tasks/Task.test.tsx | 199 ++++++
.../status/AwsOidc/Tasks/Task.tsx | 236 +++++++
.../status/AwsOidc/Tasks/TaskAlert.tsx | 77 ++
.../status/AwsOidc/Tasks/Tasks.story.tsx | 193 +++++
.../status/AwsOidc/Tasks/Tasks.test.tsx | 80 +++
.../status/AwsOidc/Tasks/Tasks.tsx | 225 ++++++
.../status/AwsOidc/Tasks/constants.ts | 22 +
.../Integrations/status/AwsOidc/helpers.ts | 34 +
.../makeAwsOidcStatusContextState.ts | 75 ++
.../makeIntegrationDiscoveryRule.ts | 34 +
.../testHelpers/mockAwsOidcStatusProvider.tsx | 9 +-
.../status/AwsOidc/useAwsOidcStatus.tsx | 2 +-
web/packages/teleport/src/config.ts | 89 +++
.../teleport/src/generateResourcePath.test.ts | 114 ++-
.../teleport/src/generateResourcePath.ts | 19 +-
.../integrations/integrations.test.ts | 98 +++
.../src/services/integrations/integrations.ts | 110 +++
.../src/services/integrations/types.ts | 173 +++++
39 files changed, 3847 insertions(+), 254 deletions(-)
create mode 100644 web/__mocks__/react-markdown.js
create mode 100644 web/packages/design/src/Tabs/Tabs.ts
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcTitle.test.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcTitle.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Details/Agents.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Details/Details.story.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Details/Details.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rds.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rules.test.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rules.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/EnrollCard.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/SidePanel.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Task.test.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Task.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/TaskAlert.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.story.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.test.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.tsx
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/constants.ts
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/helpers.ts
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/testHelpers/makeAwsOidcStatusContextState.ts
create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/testHelpers/makeIntegrationDiscoveryRule.ts
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a671c9ed815fa..135a96d9a6033 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -386,6 +386,9 @@ importers:
events:
specifier: 3.3.0
version: 3.3.0
+ react-markdown:
+ specifier: ^9.0.3
+ version: 9.1.0(@types/react@18.3.10)(react@18.3.1)
devDependencies:
'@gravitational/build':
specifier: workspace:*
@@ -474,7 +477,7 @@ importers:
version: 34.1.1
electron-builder:
specifier: ^25.1.8
- version: 25.1.8(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))
+ version: 25.1.8(electron-builder-squirrel-windows@25.1.8)
electron-vite:
specifier: ^2.3.0
version: 2.3.0(@swc/core@1.7.26)(vite@5.4.8(@types/node@22.7.4)(terser@5.31.1))
@@ -2562,6 +2565,9 @@ packages:
'@types/escodegen@0.0.6':
resolution: {integrity: sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==}
+ '@types/estree-jsx@1.0.5':
+ resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
+
'@types/estree@0.0.51':
resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==}
@@ -2586,6 +2592,9 @@ packages:
'@types/graceful-fs@4.1.8':
resolution: {integrity: sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw==}
+ '@types/hast@3.0.4':
+ resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
+
'@types/history@4.7.11':
resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==}
@@ -2623,6 +2632,9 @@ packages:
resolution: {integrity: sha512-xoBtGl5R9jeKUhc8ZqeYaRDx04qqJ10yhhXYGmJ4Jr8qKpvMsDQQrNUvF/wUJ4klOtmJeJM+p2Xo3zp9uaC3tw==}
deprecated: This is a stub types definition. keyv provides its own type definitions, so you do not need this installed.
+ '@types/mdast@4.0.4':
+ resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
+
'@types/mime@1.3.5':
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
@@ -2719,6 +2731,12 @@ packages:
'@types/triple-beam@1.3.5':
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
+ '@types/unist@2.0.11':
+ resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
+
+ '@types/unist@3.0.3':
+ resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
+
'@types/verror@1.10.5':
resolution: {integrity: sha512-9UjMCHK5GPgQRoNbqdLIAvAy0EInuiqbW0PBMtVP6B5B2HQJlvoJHM+KodPZMEjOa5VkSc+5LH7xy+cUzQdmHw==}
@@ -3191,6 +3209,9 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0
+ bail@2.0.2:
+ resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
+
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -3319,6 +3340,9 @@ packages:
caniuse-lite@1.0.30001651:
resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==}
+ ccount@2.0.1:
+ resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
+
chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
@@ -3343,6 +3367,18 @@ packages:
resolution: {integrity: sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==}
engines: {node: '>=12.20'}
+ character-entities-html4@2.1.0:
+ resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
+
+ character-entities-legacy@3.0.0:
+ resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
+
+ character-entities@2.0.2:
+ resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
+
+ character-reference-invalid@2.0.1:
+ resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
+
chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
@@ -3430,6 +3466,9 @@ packages:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
+ comma-separated-tokens@2.0.3:
+ resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
+
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -3678,6 +3717,9 @@ packages:
decimal.js@10.4.3:
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
+ decode-named-character-reference@1.0.2:
+ resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
+
decompress-response@6.0.0:
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
engines: {node: '>=10'}
@@ -3750,6 +3792,9 @@ packages:
detect-node@2.1.0:
resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
+ devlop@1.1.0:
+ resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+
diff-sequences@29.6.3:
resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -4151,6 +4196,9 @@ packages:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
+ estree-util-is-identifier-name@3.0.0:
+ resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==}
+
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
@@ -4192,6 +4240,9 @@ packages:
resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==}
engines: {node: '>= 0.10.0'}
+ extend@3.0.2:
+ resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+
extract-zip@2.0.1:
resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
engines: {node: '>= 10.17.0'}
@@ -4514,6 +4565,12 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
+ hast-util-to-jsx-runtime@2.3.3:
+ resolution: {integrity: sha512-pdpkP8YD4v+qMKn2lnKSiJvZvb3FunDmFYQvVOsoO08+eTNWdaWKPMrC5wwNICtU3dQWHhElj5Sf5jPEnv4qJg==}
+
+ hast-util-whitespace@3.0.0:
+ resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+
headers-polyfill@4.0.3:
resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==}
@@ -4552,6 +4609,9 @@ packages:
resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
engines: {node: '>=8'}
+ html-url-attributes@3.0.1:
+ resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
+
htmlparser2@3.10.1:
resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
@@ -4645,6 +4705,9 @@ packages:
ini@1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+ inline-style-parser@0.2.4:
+ resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
+
internal-slot@1.0.7:
resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
engines: {node: '>= 0.4'}
@@ -4664,6 +4727,12 @@ packages:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
+ is-alphabetical@2.0.1:
+ resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
+
+ is-alphanumerical@2.0.1:
+ resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
+
is-arguments@1.1.1:
resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
engines: {node: '>= 0.4'}
@@ -4712,6 +4781,9 @@ packages:
resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
engines: {node: '>= 0.4'}
+ is-decimal@2.0.1:
+ resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
+
is-docker@2.2.1:
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
engines: {node: '>=8'}
@@ -4740,6 +4812,9 @@ packages:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
+ is-hexadecimal@2.0.1:
+ resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
+
is-interactive@1.0.0:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'}
@@ -4769,6 +4844,10 @@ packages:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
engines: {node: '>=8'}
+ is-plain-obj@4.1.0:
+ resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
+ engines: {node: '>=12'}
+
is-plain-object@5.0.0:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'}
@@ -5251,6 +5330,9 @@ packages:
long@5.2.3:
resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==}
+ longest-streak@3.1.0:
+ resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
+
loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
@@ -5299,6 +5381,30 @@ packages:
resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==}
engines: {node: '>=10'}
+ mdast-util-from-markdown@2.0.2:
+ resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
+
+ mdast-util-mdx-expression@2.0.1:
+ resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==}
+
+ mdast-util-mdx-jsx@3.2.0:
+ resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==}
+
+ mdast-util-mdxjs-esm@2.0.1:
+ resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==}
+
+ mdast-util-phrasing@4.1.0:
+ resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
+
+ mdast-util-to-hast@13.2.0:
+ resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
+
+ mdast-util-to-markdown@2.1.2:
+ resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==}
+
+ mdast-util-to-string@4.0.0:
+ resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
+
media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
@@ -5320,6 +5426,69 @@ packages:
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
engines: {node: '>= 0.6'}
+ micromark-core-commonmark@2.0.2:
+ resolution: {integrity: sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==}
+
+ micromark-factory-destination@2.0.1:
+ resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==}
+
+ micromark-factory-label@2.0.1:
+ resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==}
+
+ micromark-factory-space@2.0.1:
+ resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==}
+
+ micromark-factory-title@2.0.1:
+ resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==}
+
+ micromark-factory-whitespace@2.0.1:
+ resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==}
+
+ micromark-util-character@2.1.1:
+ resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}
+
+ micromark-util-chunked@2.0.1:
+ resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==}
+
+ micromark-util-classify-character@2.0.1:
+ resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==}
+
+ micromark-util-combine-extensions@2.0.1:
+ resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==}
+
+ micromark-util-decode-numeric-character-reference@2.0.2:
+ resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==}
+
+ micromark-util-decode-string@2.0.1:
+ resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==}
+
+ micromark-util-encode@2.0.1:
+ resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}
+
+ micromark-util-html-tag-name@2.0.1:
+ resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==}
+
+ micromark-util-normalize-identifier@2.0.1:
+ resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==}
+
+ micromark-util-resolve-all@2.0.1:
+ resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==}
+
+ micromark-util-sanitize-uri@2.0.1:
+ resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}
+
+ micromark-util-subtokenize@2.0.4:
+ resolution: {integrity: sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==}
+
+ micromark-util-symbol@2.0.1:
+ resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}
+
+ micromark-util-types@2.0.1:
+ resolution: {integrity: sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==}
+
+ micromark@4.0.1:
+ resolution: {integrity: sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==}
+
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
@@ -5643,6 +5812,9 @@ packages:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
+ parse-entities@4.0.2:
+ resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
+
parse-json@5.2.0:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
@@ -5795,6 +5967,9 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ property-information@7.0.0:
+ resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==}
+
protobufjs@7.3.2:
resolution: {integrity: sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==}
engines: {node: '>=12.0.0'}
@@ -5907,6 +6082,12 @@ packages:
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+ react-markdown@9.1.0:
+ resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==}
+ peerDependencies:
+ '@types/react': '>=18'
+ react: '>=18'
+
react-router-dom@5.3.4:
resolution: {integrity: sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==}
peerDependencies:
@@ -5994,6 +6175,12 @@ packages:
resolution: {integrity: sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==}
engines: {node: '>=4'}
+ remark-parse@11.0.0:
+ resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
+
+ remark-rehype@11.1.1:
+ resolution: {integrity: sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==}
+
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
@@ -6261,6 +6448,9 @@ packages:
resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==}
engines: {node: '>= 8'}
+ space-separated-tokens@2.0.2:
+ resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+
spawn-wrap@2.0.0:
resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==}
engines: {node: '>=8'}
@@ -6351,6 +6541,9 @@ packages:
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+ stringify-entities@4.0.4:
+ resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
+
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
@@ -6386,6 +6579,9 @@ packages:
style-mod@4.1.2:
resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==}
+ style-to-object@1.0.8:
+ resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==}
+
styled-components@6.1.13:
resolution: {integrity: sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==}
engines: {node: '>= 16'}
@@ -6519,9 +6715,15 @@ packages:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
+ trim-lines@3.0.1:
+ resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+
triple-beam@1.3.0:
resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==}
+ trough@2.2.0:
+ resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
+
truncate-utf8-bytes@1.0.2:
resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==}
@@ -6659,6 +6861,9 @@ packages:
resolution: {integrity: sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==}
engines: {node: '>=4'}
+ unified@11.0.5:
+ resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
+
unique-filename@2.0.1:
resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -6667,6 +6872,21 @@ packages:
resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+ unist-util-is@6.0.0:
+ resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
+
+ unist-util-position@5.0.0:
+ resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
+
+ unist-util-stringify-position@4.0.0:
+ resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
+
+ unist-util-visit-parents@6.0.1:
+ resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
+
+ unist-util-visit@5.0.0:
+ resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
+
universalify@0.1.2:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
engines: {node: '>= 4.0.0'}
@@ -6751,6 +6971,12 @@ packages:
resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==}
engines: {node: '>=0.6.0'}
+ vfile-message@4.0.2:
+ resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
+
+ vfile@6.0.3:
+ resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
+
vite-plugin-wasm@3.3.0:
resolution: {integrity: sha512-tVhz6w+W9MVsOCHzxo6SSMSswCeIw4HTrXEi6qL3IRzATl83jl09JVO1djBqPSwfjgnpVHNLYcaMbaDX5WB/pg==}
peerDependencies:
@@ -7013,6 +7239,9 @@ packages:
zone.js@0.14.7:
resolution: {integrity: sha512-0w6DGkX2BPuiK/NLf+4A8FLE43QwBfuqz2dVgi/40Rj1WmqUskCqj329O/pwrqFJLG5X8wkeG2RhIAro441xtg==}
+ zwitch@2.0.4:
+ resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+
snapshots:
7zip-bin@5.2.0: {}
@@ -9539,6 +9768,10 @@ snapshots:
'@types/escodegen@0.0.6': {}
+ '@types/estree-jsx@1.0.5':
+ dependencies:
+ '@types/estree': 1.0.6
+
'@types/estree@0.0.51': {}
'@types/estree@1.0.6': {}
@@ -9572,6 +9805,10 @@ snapshots:
dependencies:
'@types/node': 20.16.10
+ '@types/hast@3.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
'@types/history@4.7.11': {}
'@types/http-cache-semantics@4.0.1': {}
@@ -9614,6 +9851,10 @@ snapshots:
dependencies:
keyv: 4.5.0
+ '@types/mdast@4.0.4':
+ dependencies:
+ '@types/unist': 3.0.3
+
'@types/mime@1.3.5': {}
'@types/minimatch@5.1.2': {}
@@ -9722,6 +9963,10 @@ snapshots:
'@types/triple-beam@1.3.5': {}
+ '@types/unist@2.0.11': {}
+
+ '@types/unist@3.0.3': {}
+
'@types/verror@1.10.5':
optional: true
@@ -10043,7 +10288,7 @@ snapshots:
app-builder-bin@5.0.0-alpha.10: {}
- app-builder-lib@25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)):
+ app-builder-lib@25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8):
dependencies:
'@develar/schema-utils': 2.6.5
'@electron/notarize': 2.5.0
@@ -10350,6 +10595,8 @@ snapshots:
babel-plugin-jest-hoist: 29.6.3
babel-preset-current-node-syntax: 1.0.1(@babel/core@7.25.2)
+ bail@2.0.2: {}
+
balanced-match@1.0.2: {}
bare-events@2.4.2:
@@ -10542,6 +10789,8 @@ snapshots:
caniuse-lite@1.0.30001651: {}
+ ccount@2.0.1: {}
+
chalk@2.4.2:
dependencies:
ansi-styles: 3.2.1
@@ -10564,6 +10813,14 @@ snapshots:
char-regex@2.0.1: {}
+ character-entities-html4@2.1.0: {}
+
+ character-entities-legacy@3.0.0: {}
+
+ character-entities@2.0.2: {}
+
+ character-reference-invalid@2.0.1: {}
+
chownr@2.0.0: {}
chromium-pickle-js@0.2.0: {}
@@ -10655,6 +10912,8 @@ snapshots:
dependencies:
delayed-stream: 1.0.0
+ comma-separated-tokens@2.0.3: {}
+
commander@2.20.3:
optional: true
@@ -10908,6 +11167,10 @@ snapshots:
decimal.js@10.4.3: {}
+ decode-named-character-reference@1.0.2:
+ dependencies:
+ character-entities: 2.0.2
+
decompress-response@6.0.0:
dependencies:
mimic-response: 3.1.0
@@ -10961,6 +11224,10 @@ snapshots:
detect-node@2.1.0:
optional: true
+ devlop@1.1.0:
+ dependencies:
+ dequal: 2.0.3
+
diff-sequences@29.6.3: {}
diffable-html@4.1.0:
@@ -10978,7 +11245,7 @@ snapshots:
dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8):
dependencies:
- app-builder-lib: 25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))
+ app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8)
builder-util: 25.1.7
builder-util-runtime: 9.2.10
fs-extra: 10.1.0
@@ -11064,7 +11331,7 @@ snapshots:
electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8):
dependencies:
- app-builder-lib: 25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))
+ app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8)
archiver: 5.3.2
builder-util: 25.1.7
fs-extra: 10.1.0
@@ -11073,9 +11340,9 @@ snapshots:
- dmg-builder
- supports-color
- electron-builder@25.1.8(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)):
+ electron-builder@25.1.8(electron-builder-squirrel-windows@25.1.8):
dependencies:
- app-builder-lib: 25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))
+ app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8)
builder-util: 25.1.7
builder-util-runtime: 9.2.10
chalk: 4.1.2
@@ -11355,7 +11622,7 @@ snapshots:
debug: 4.3.6
enhanced-resolve: 5.17.0
eslint: 8.57.0
- eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0)
+ eslint-module-utils: 2.8.2(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
fast-glob: 3.3.2
get-tsconfig: 4.8.0
is-bun-module: 1.1.0
@@ -11368,7 +11635,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -11380,7 +11647,7 @@ snapshots:
- supports-color
optional: true
- eslint-module-utils@2.8.2(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0):
+ eslint-module-utils@2.8.2(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -11406,7 +11673,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0)
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -11554,6 +11821,8 @@ snapshots:
estraverse@5.3.0: {}
+ estree-util-is-identifier-name@3.0.0: {}
+
estree-walker@2.0.2: {}
esutils@2.0.3: {}
@@ -11628,6 +11897,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ extend@3.0.2: {}
+
extract-zip@2.0.1:
dependencies:
debug: 4.3.7
@@ -11995,6 +12266,30 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ hast-util-to-jsx-runtime@2.3.3:
+ dependencies:
+ '@types/estree': 1.0.6
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.3
+ comma-separated-tokens: 2.0.3
+ devlop: 1.1.0
+ estree-util-is-identifier-name: 3.0.0
+ hast-util-whitespace: 3.0.0
+ mdast-util-mdx-expression: 2.0.1
+ mdast-util-mdx-jsx: 3.2.0
+ mdast-util-mdxjs-esm: 2.0.1
+ property-information: 7.0.0
+ space-separated-tokens: 2.0.2
+ style-to-object: 1.0.8
+ unist-util-position: 5.0.0
+ vfile-message: 4.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ hast-util-whitespace@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
headers-polyfill@4.0.3: {}
highlight-words-core@1.2.3: {}
@@ -12034,6 +12329,8 @@ snapshots:
html-tags@3.3.1: {}
+ html-url-attributes@3.0.1: {}
+
htmlparser2@3.10.1:
dependencies:
domelementtype: 1.3.1
@@ -12145,6 +12442,8 @@ snapshots:
ini@1.3.8: {}
+ inline-style-parser@0.2.4: {}
+
internal-slot@1.0.7:
dependencies:
es-errors: 1.3.0
@@ -12162,6 +12461,13 @@ snapshots:
ipaddr.js@1.9.1: {}
+ is-alphabetical@2.0.1: {}
+
+ is-alphanumerical@2.0.1:
+ dependencies:
+ is-alphabetical: 2.0.1
+ is-decimal: 2.0.1
+
is-arguments@1.1.1:
dependencies:
call-bind: 1.0.7
@@ -12211,6 +12517,8 @@ snapshots:
dependencies:
has-tostringtag: 1.0.2
+ is-decimal@2.0.1: {}
+
is-docker@2.2.1: {}
is-extglob@2.1.1: {}
@@ -12231,6 +12539,8 @@ snapshots:
dependencies:
is-extglob: 2.1.1
+ is-hexadecimal@2.0.1: {}
+
is-interactive@1.0.0: {}
is-lambda@1.0.1: {}
@@ -12249,6 +12559,8 @@ snapshots:
is-path-inside@3.0.3: {}
+ is-plain-obj@4.1.0: {}
+
is-plain-object@5.0.0: {}
is-potential-custom-element-name@1.0.1: {}
@@ -13054,6 +13366,8 @@ snapshots:
long@5.2.3: {}
+ longest-streak@3.1.0: {}
+
loose-envify@1.4.0:
dependencies:
js-tokens: 4.0.0
@@ -13117,6 +13431,95 @@ snapshots:
escape-string-regexp: 4.0.0
optional: true
+ mdast-util-from-markdown@2.0.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ decode-named-character-reference: 1.0.2
+ devlop: 1.1.0
+ mdast-util-to-string: 4.0.0
+ micromark: 4.0.1
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-decode-string: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.1
+ unist-util-stringify-position: 4.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdx-expression@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdx-jsx@3.2.0:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ ccount: 2.0.1
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ parse-entities: 4.0.2
+ stringify-entities: 4.0.4
+ unist-util-stringify-position: 4.0.0
+ vfile-message: 4.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-mdxjs-esm@2.0.1:
+ dependencies:
+ '@types/estree-jsx': 1.0.5
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ devlop: 1.1.0
+ mdast-util-from-markdown: 2.0.2
+ mdast-util-to-markdown: 2.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ mdast-util-phrasing@4.1.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ unist-util-is: 6.0.0
+
+ mdast-util-to-hast@13.2.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@ungap/structured-clone': 1.2.0
+ devlop: 1.1.0
+ micromark-util-sanitize-uri: 2.0.1
+ trim-lines: 3.0.1
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+
+ mdast-util-to-markdown@2.1.2:
+ dependencies:
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.3
+ longest-streak: 3.1.0
+ mdast-util-phrasing: 4.1.0
+ mdast-util-to-string: 4.0.0
+ micromark-util-classify-character: 2.0.1
+ micromark-util-decode-string: 2.0.1
+ unist-util-visit: 5.0.0
+ zwitch: 2.0.4
+
+ mdast-util-to-string@4.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+
media-typer@0.3.0: {}
memoize-one@6.0.0: {}
@@ -13129,6 +13532,139 @@ snapshots:
methods@1.1.2: {}
+ micromark-core-commonmark@2.0.2:
+ dependencies:
+ decode-named-character-reference: 1.0.2
+ devlop: 1.1.0
+ micromark-factory-destination: 2.0.1
+ micromark-factory-label: 2.0.1
+ micromark-factory-space: 2.0.1
+ micromark-factory-title: 2.0.1
+ micromark-factory-whitespace: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-chunked: 2.0.1
+ micromark-util-classify-character: 2.0.1
+ micromark-util-html-tag-name: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-subtokenize: 2.0.4
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.1
+
+ micromark-factory-destination@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.1
+
+ micromark-factory-label@2.0.1:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.1
+
+ micromark-factory-space@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-types: 2.0.1
+
+ micromark-factory-title@2.0.1:
+ dependencies:
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.1
+
+ micromark-factory-whitespace@2.0.1:
+ dependencies:
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.1
+
+ micromark-util-character@2.1.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.1
+
+ micromark-util-chunked@2.0.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-classify-character@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.1
+
+ micromark-util-combine-extensions@2.0.1:
+ dependencies:
+ micromark-util-chunked: 2.0.1
+ micromark-util-types: 2.0.1
+
+ micromark-util-decode-numeric-character-reference@2.0.2:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-decode-string@2.0.1:
+ dependencies:
+ decode-named-character-reference: 1.0.2
+ micromark-util-character: 2.1.1
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-encode@2.0.1: {}
+
+ micromark-util-html-tag-name@2.0.1: {}
+
+ micromark-util-normalize-identifier@2.0.1:
+ dependencies:
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-resolve-all@2.0.1:
+ dependencies:
+ micromark-util-types: 2.0.1
+
+ micromark-util-sanitize-uri@2.0.1:
+ dependencies:
+ micromark-util-character: 2.1.1
+ micromark-util-encode: 2.0.1
+ micromark-util-symbol: 2.0.1
+
+ micromark-util-subtokenize@2.0.4:
+ dependencies:
+ devlop: 1.1.0
+ micromark-util-chunked: 2.0.1
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.1
+
+ micromark-util-symbol@2.0.1: {}
+
+ micromark-util-types@2.0.1: {}
+
+ micromark@4.0.1:
+ dependencies:
+ '@types/debug': 4.1.12
+ debug: 4.3.7
+ decode-named-character-reference: 1.0.2
+ devlop: 1.1.0
+ micromark-core-commonmark: 2.0.2
+ micromark-factory-space: 2.0.1
+ micromark-util-character: 2.1.1
+ micromark-util-chunked: 2.0.1
+ micromark-util-combine-extensions: 2.0.1
+ micromark-util-decode-numeric-character-reference: 2.0.2
+ micromark-util-encode: 2.0.1
+ micromark-util-normalize-identifier: 2.0.1
+ micromark-util-resolve-all: 2.0.1
+ micromark-util-sanitize-uri: 2.0.1
+ micromark-util-subtokenize: 2.0.4
+ micromark-util-symbol: 2.0.1
+ micromark-util-types: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
micromatch@4.0.8:
dependencies:
braces: 3.0.3
@@ -13484,6 +14020,16 @@ snapshots:
dependencies:
callsites: 3.1.0
+ parse-entities@4.0.2:
+ dependencies:
+ '@types/unist': 2.0.11
+ character-entities-legacy: 3.0.0
+ character-reference-invalid: 2.0.1
+ decode-named-character-reference: 1.0.2
+ is-alphanumerical: 2.0.1
+ is-decimal: 2.0.1
+ is-hexadecimal: 2.0.1
+
parse-json@5.2.0:
dependencies:
'@babel/code-frame': 7.24.7
@@ -13612,6 +14158,8 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
+ property-information@7.0.0: {}
+
protobufjs@7.3.2:
dependencies:
'@protobufjs/aspromise': 1.1.2
@@ -13747,6 +14295,24 @@ snapshots:
react-is@18.3.1: {}
+ react-markdown@9.1.0(@types/react@18.3.10)(react@18.3.1):
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@types/react': 18.3.10
+ devlop: 1.1.0
+ hast-util-to-jsx-runtime: 2.3.3
+ html-url-attributes: 3.0.1
+ mdast-util-to-hast: 13.2.0
+ react: 18.3.1
+ remark-parse: 11.0.0
+ remark-rehype: 11.1.1
+ unified: 11.0.5
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
react-router-dom@5.3.4(react@18.3.1):
dependencies:
'@babel/runtime': 7.25.0
@@ -13893,6 +14459,23 @@ snapshots:
dependencies:
es6-error: 4.1.1
+ remark-parse@11.0.0:
+ dependencies:
+ '@types/mdast': 4.0.4
+ mdast-util-from-markdown: 2.0.2
+ micromark-util-types: 2.0.1
+ unified: 11.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ remark-rehype@11.1.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ mdast-util-to-hast: 13.2.0
+ unified: 11.0.5
+ vfile: 6.0.3
+
require-directory@2.1.1: {}
require-in-the-middle@7.3.0:
@@ -14187,6 +14770,8 @@ snapshots:
source-map@0.7.4: {}
+ space-separated-tokens@2.0.2: {}
+
spawn-wrap@2.0.0:
dependencies:
foreground-child: 2.0.0
@@ -14316,6 +14901,11 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
+ stringify-entities@4.0.4:
+ dependencies:
+ character-entities-html4: 2.1.0
+ character-entities-legacy: 3.0.0
+
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
@@ -14342,6 +14932,10 @@ snapshots:
style-mod@4.1.2: {}
+ style-to-object@1.0.8:
+ dependencies:
+ inline-style-parser: 0.2.4
+
styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@emotion/is-prop-valid': 1.2.2
@@ -14507,8 +15101,12 @@ snapshots:
tree-kill@1.2.2: {}
+ trim-lines@3.0.1: {}
+
triple-beam@1.3.0: {}
+ trough@2.2.0: {}
+
truncate-utf8-bytes@1.0.2:
dependencies:
utf8-byte-length: 1.0.4
@@ -14641,6 +15239,16 @@ snapshots:
unicode-property-aliases-ecmascript@2.0.0: {}
+ unified@11.0.5:
+ dependencies:
+ '@types/unist': 3.0.3
+ bail: 2.0.2
+ devlop: 1.1.0
+ extend: 3.0.2
+ is-plain-obj: 4.1.0
+ trough: 2.2.0
+ vfile: 6.0.3
+
unique-filename@2.0.1:
dependencies:
unique-slug: 3.0.0
@@ -14649,6 +15257,29 @@ snapshots:
dependencies:
imurmurhash: 0.1.4
+ unist-util-is@6.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-position@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-stringify-position@4.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+
+ unist-util-visit-parents@6.0.1:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.0
+
+ unist-util-visit@5.0.0:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-is: 6.0.0
+ unist-util-visit-parents: 6.0.1
+
universalify@0.1.2: {}
universalify@0.2.0: {}
@@ -14723,6 +15354,16 @@ snapshots:
extsprintf: 1.4.1
optional: true
+ vfile-message@4.0.2:
+ dependencies:
+ '@types/unist': 3.0.3
+ unist-util-stringify-position: 4.0.0
+
+ vfile@6.0.3:
+ dependencies:
+ '@types/unist': 3.0.3
+ vfile-message: 4.0.2
+
vite-plugin-wasm@3.3.0(vite@5.4.8(@types/node@22.7.4)(terser@5.31.1)):
dependencies:
vite: 5.4.8(@types/node@22.7.4)(terser@5.31.1)
@@ -15008,3 +15649,5 @@ snapshots:
zod@3.23.8: {}
zone.js@0.14.7: {}
+
+ zwitch@2.0.4: {}
diff --git a/web/__mocks__/react-markdown.js b/web/__mocks__/react-markdown.js
new file mode 100644
index 0000000000000..9bed155671a2c
--- /dev/null
+++ b/web/__mocks__/react-markdown.js
@@ -0,0 +1,7 @@
+// Manually mocks react-markdown for testing due to ES modules
+// https://jestjs.io/docs/manual-mocks
+function ReactMarkdown({ children }) {
+ return <>{children}>;
+}
+
+export default ReactMarkdown;
diff --git a/web/packages/design/src/Tabs/Tabs.ts b/web/packages/design/src/Tabs/Tabs.ts
new file mode 100644
index 0000000000000..cf2c1719a52f0
--- /dev/null
+++ b/web/packages/design/src/Tabs/Tabs.ts
@@ -0,0 +1,57 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { NavLink } from 'react-router-dom';
+import styled from 'styled-components';
+
+export const TabsContainer = styled.div`
+ position: relative;
+ display: flex;
+ gap: ${p => p.theme.space[5]}px;
+ align-items: center;
+ padding: 0 ${p => p.theme.space[5]}px;
+ border-bottom: 1px solid ${p => p.theme.colors.spotBackground[0]};
+`;
+
+export const TabContainer = styled(NavLink)<{ selected?: boolean }>`
+ padding: ${p => p.theme.space[1] + p.theme.space[2]}px
+ ${p => p.theme.space[2]}px;
+ position: relative;
+ cursor: pointer;
+ z-index: 2;
+ opacity: ${p => (p.selected ? 1 : 0.5)};
+ transition: opacity 0.3s linear;
+ color: ${p => p.theme.colors.text.main};
+ font-weight: 300;
+ font-size: 22px;
+ line-height: ${p => p.theme.space[5]}px;
+ white-space: nowrap;
+ text-decoration: none;
+
+ &:hover {
+ opacity: 1;
+ }
+`;
+
+export const TabBorder = styled.div`
+ position: absolute;
+ bottom: -1px;
+ background: ${p => p.theme.colors.brand};
+ height: 2px;
+ transition: all 0.3s cubic-bezier(0.19, 1, 0.22, 1);
+`;
diff --git a/web/packages/teleport/package.json b/web/packages/teleport/package.json
index d76bc3addb6d4..e4ef12e983162 100644
--- a/web/packages/teleport/package.json
+++ b/web/packages/teleport/package.json
@@ -39,7 +39,8 @@
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"create-react-class": "^15.6.3",
- "events": "3.3.0"
+ "events": "3.3.0",
+ "react-markdown": "^9.0.3"
},
"devDependencies": {
"@gravitational/build": "workspace:*",
diff --git a/web/packages/teleport/src/Integrations/IntegrationList.tsx b/web/packages/teleport/src/Integrations/IntegrationList.tsx
index 76de1610e11e7..cd8a0bdc92ecd 100644
--- a/web/packages/teleport/src/Integrations/IntegrationList.tsx
+++ b/web/packages/teleport/src/Integrations/IntegrationList.tsx
@@ -69,7 +69,7 @@ export function IntegrationList(props: Props) {
}
function getRowStyle(row: IntegrationLike): React.CSSProperties {
- if (row.kind !== 'okta') return;
+ if (row.kind !== 'okta' && row.kind !== IntegrationKind.AwsOidc) return;
return { cursor: 'pointer' };
}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.story.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.story.tsx
index 0540665408f80..06c54e9c99a3a 100644
--- a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.story.tsx
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.story.tsx
@@ -16,25 +16,37 @@
* along with this program. If not, see .
*/
-import { addHours } from 'date-fns';
-import React from 'react';
+import {
+ makeErrorAttempt,
+ makeProcessingAttempt,
+ makeSuccessAttempt,
+} from 'shared/hooks/useAsync';
+import cfg from 'teleport/config';
import { AwsOidcDashboard } from 'teleport/Integrations/status/AwsOidc/AwsOidcDashboard';
import { MockAwsOidcStatusProvider } from 'teleport/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider';
-import { AwsOidcStatusContextState } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';
-import {
- IntegrationKind,
- ResourceTypeSummary,
-} from 'teleport/services/integrations';
+import { IntegrationKind } from 'teleport/services/integrations';
+
+import { makeAwsOidcStatusContextState } from './testHelpers/makeAwsOidcStatusContextState';
export default {
title: 'Teleport/Integrations/AwsOidc',
};
+const setup = {
+ path: cfg.routes.integrationStatus,
+ initialEntries: [
+ cfg.getIntegrationStatusRoute(IntegrationKind.AwsOidc, 'oidc-int'),
+ ],
+};
+
// Loaded dashboard with data for each aws resource and a navigation header
export function Dashboard() {
return (
-
+
);
@@ -42,12 +54,23 @@ export function Dashboard() {
// Loaded dashboard with missing data for each aws resource and a navigation header
export function DashboardMissingData() {
+ const state = makeAwsOidcStatusContextState();
+ state.statsAttempt.data = undefined;
+ return (
+
+
+
+ );
+}
+
+// Loaded dashboard with missing data for each aws resource and a navigation header
+export function DashboardMissingSummary() {
const state = makeAwsOidcStatusContextState();
state.statsAttempt.data.awseks = undefined;
state.statsAttempt.data.awsrds = undefined;
state.statsAttempt.data.awsec2 = undefined;
return (
-
+
);
@@ -56,10 +79,10 @@ export function DashboardMissingData() {
// Loading screen
export function StatsProcessing() {
const props = makeAwsOidcStatusContextState({
- statsAttempt: { status: 'processing', data: null, statusText: '' },
+ statsAttempt: makeProcessingAttempt(),
});
return (
-
+
);
@@ -68,14 +91,10 @@ export function StatsProcessing() {
// No header, no loading indicator
export function IntegrationProcessing() {
const props = makeAwsOidcStatusContextState({
- integrationAttempt: {
- status: 'processing',
- data: null,
- statusText: '',
- },
+ integrationAttempt: makeProcessingAttempt(),
});
return (
-
+
);
@@ -84,32 +103,39 @@ export function IntegrationProcessing() {
// Loaded error message
export function StatsFailed() {
const props = makeAwsOidcStatusContextState({
- statsAttempt: {
- status: 'error',
- data: null,
- statusText: 'failed to get stats',
- error: {},
- },
+ statsAttempt: makeErrorAttempt(new Error('failed to get stats')),
});
return (
-
+
);
}
-// Loaded dashboard with data for each aws resource but no navigation header
+// Loaded error message
export function IntegrationFailed() {
const props = makeAwsOidcStatusContextState({
- integrationAttempt: {
- status: 'error',
- data: null,
- statusText: 'failed to get integration',
- error: {},
- },
+ integrationAttempt: makeErrorAttempt(
+ new Error('failed to get integration')
+ ),
});
return (
-
+
+
+
+ );
+}
+
+// Loaded error message
+export function BothFailed() {
+ const props = makeAwsOidcStatusContextState({
+ statsAttempt: makeErrorAttempt(new Error('failed to get stats')),
+ integrationAttempt: makeErrorAttempt(
+ new Error('failed to get integration')
+ ),
+ });
+ return (
+
);
@@ -118,10 +144,10 @@ export function IntegrationFailed() {
// Blank screen
export function StatsNoData() {
const props = makeAwsOidcStatusContextState({
- statsAttempt: { status: 'success', data: null, statusText: '' },
+ statsAttempt: makeSuccessAttempt(null),
});
return (
-
+
);
@@ -130,71 +156,11 @@ export function StatsNoData() {
// No header, no loading indicator
export function IntegrationNoData() {
const props = makeAwsOidcStatusContextState({
- integrationAttempt: {
- status: 'success',
- data: null,
- statusText: '',
- },
+ integrationAttempt: makeSuccessAttempt(null),
});
return (
-
+
);
}
-
-function makeAwsOidcStatusContextState(
- overrides: Partial = {}
-): AwsOidcStatusContextState {
- return Object.assign(
- {
- integrationAttempt: {
- status: 'success',
- statusText: '',
- data: {
- resourceType: 'integration',
- name: 'integration-one',
- kind: IntegrationKind.AwsOidc,
- spec: {
- roleArn: 'arn:aws:iam::111456789011:role/bar',
- },
- statusCode: 1,
- },
- },
- statsAttempt: {
- status: 'success',
- statusText: '',
- data: {
- name: 'integration-one',
- subKind: IntegrationKind.AwsOidc,
- awsoidc: {
- roleArn: 'arn:aws:iam::111456789011:role/bar',
- },
- awsec2: makeResourceTypeSummary(),
- awsrds: makeResourceTypeSummary(),
- awseks: makeResourceTypeSummary(),
- },
- },
- },
- overrides
- );
-}
-
-function makeResourceTypeSummary(
- overrides: Partial = {}
-): ResourceTypeSummary {
- return Object.assign(
- {
- rulesCount: Math.floor(Math.random() * 100),
- resourcesFound: Math.floor(Math.random() * 100),
- resourcesEnrollmentFailed: Math.floor(Math.random() * 100),
- resourcesEnrollmentSuccess: Math.floor(Math.random() * 100),
- discoverLastSync: addHours(
- new Date().getTime(),
- -Math.floor(Math.random() * 100)
- ),
- ecsDatabaseServiceCount: Math.floor(Math.random() * 100),
- },
- overrides
- );
-}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.test.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.test.tsx
index 580625fdacc74..753a13313fe1b 100644
--- a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.test.tsx
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.test.tsx
@@ -17,80 +17,84 @@
*/
import { within } from '@testing-library/react';
-import React from 'react';
+import { addHours } from 'date-fns';
import { render, screen } from 'design/utils/testing';
+import { makeSuccessAttempt } from 'shared/hooks/useAsync';
-import { addHours } from 'teleport/components/BannerList/useAlerts';
import { AwsOidcDashboard } from 'teleport/Integrations/status/AwsOidc/AwsOidcDashboard';
import { MockAwsOidcStatusProvider } from 'teleport/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider';
-import { IntegrationKind } from 'teleport/services/integrations';
+import {
+ IntegrationAwsOidc,
+ IntegrationKind,
+ IntegrationWithSummary,
+} from 'teleport/services/integrations';
test('renders header and stats cards', () => {
render(
({
+ resourceType: 'integration',
+ name: 'integration-one',
+ kind: IntegrationKind.AwsOidc,
+ spec: {
+ roleArn: 'arn:aws:iam::111456789011:role/bar',
},
- },
- statsAttempt: {
- status: 'success',
- statusText: '',
- data: {
- name: 'integration-one',
- subKind: IntegrationKind.AwsOidc,
- awsoidc: {
- roleArn: 'arn:aws:iam::111456789011:role/bar',
- },
- awsec2: {
- rulesCount: 24,
- resourcesFound: 12,
- resourcesEnrollmentFailed: 3,
- resourcesEnrollmentSuccess: 9,
- discoverLastSync: new Date().getTime(),
- ecsDatabaseServiceCount: 0, // irrelevant
- },
- awsrds: {
- rulesCount: 14,
- resourcesFound: 5,
- resourcesEnrollmentFailed: 5,
- resourcesEnrollmentSuccess: 0,
- discoverLastSync: addHours(new Date().getTime(), -4),
- ecsDatabaseServiceCount: 8, // relevant
- },
- awseks: {
- rulesCount: 33,
- resourcesFound: 3,
- resourcesEnrollmentFailed: 0,
- resourcesEnrollmentSuccess: 3,
- discoverLastSync: addHours(new Date().getTime(), -48),
- ecsDatabaseServiceCount: 0, // irrelevant
- },
+ statusCode: 1,
+ }),
+ statsAttempt: makeSuccessAttempt({
+ name: 'integration-one',
+ subKind: IntegrationKind.AwsOidc,
+ awsoidc: {
+ roleArn: 'arn:aws:iam::111456789011:role/bar',
},
- },
+ awsec2: {
+ rulesCount: 24,
+ resourcesFound: 12,
+ resourcesEnrollmentFailed: 3,
+ resourcesEnrollmentSuccess: 9,
+ discoverLastSync: new Date().getTime(),
+ ecsDatabaseServiceCount: 0, // irrelevant
+ },
+ awsrds: {
+ rulesCount: 14,
+ resourcesFound: 5,
+ resourcesEnrollmentFailed: 5,
+ resourcesEnrollmentSuccess: 0,
+ discoverLastSync: addHours(new Date().getTime(), -4).getTime(),
+ ecsDatabaseServiceCount: 8, // relevant
+ },
+ awseks: {
+ rulesCount: 33,
+ resourcesFound: 3,
+ resourcesEnrollmentFailed: 0,
+ resourcesEnrollmentSuccess: 3,
+ discoverLastSync: addHours(new Date().getTime(), -48).getTime(),
+ ecsDatabaseServiceCount: 0, // irrelevant
+ },
+ }),
}}
+ path=""
>
);
- expect(screen.getByRole('link', { name: 'back' })).toHaveAttribute(
+ const breadcrumbs = screen.getByTestId('aws-oidc-header');
+ expect(within(breadcrumbs).getByText('integration-one')).toBeInTheDocument();
+
+ const title = screen.getByTestId('aws-oidc-title');
+ expect(within(title).getByRole('link', { name: 'back' })).toHaveAttribute(
'href',
'/web/integrations'
);
- expect(screen.getByText('integration-one')).toBeInTheDocument();
- expect(screen.getByLabelText('status')).toHaveAttribute('kind', 'success');
- expect(screen.getByLabelText('status')).toHaveTextContent('Running');
+ expect(within(title).getByLabelText('status')).toHaveAttribute(
+ 'kind',
+ 'success'
+ );
+ expect(within(title).getByLabelText('status')).toHaveTextContent('Running');
+ expect(within(title).getByText('integration-one')).toBeInTheDocument();
const ec2 = screen.getByTestId('ec2-stats');
expect(within(ec2).getByTestId('sync')).toHaveTextContent(
@@ -137,3 +141,59 @@ test('renders header and stats cards', () => {
'Failed Clusters 0'
);
});
+
+test('renders enroll cards', () => {
+ const zeroCount = {
+ rulesCount: 0,
+ resourcesFound: 0,
+ resourcesEnrollmentFailed: 0,
+ resourcesEnrollmentSuccess: 0,
+ discoverLastSync: new Date().getTime(),
+ ecsDatabaseServiceCount: 0,
+ };
+
+ render(
+
+
+
+ );
+
+ expect(
+ within(screen.getByTestId('ec2-enroll')).getByRole('link', {
+ name: 'Enroll EC2',
+ })
+ ).toBeInTheDocument();
+ expect(
+ within(screen.getByTestId('rds-enroll')).getByRole('link', {
+ name: 'Enroll RDS',
+ })
+ ).toBeInTheDocument();
+ expect(
+ within(screen.getByTestId('eks-enroll')).getByRole('link', {
+ name: 'Enroll EKS',
+ })
+ ).toBeInTheDocument();
+});
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.tsx
index 491c93d3b0858..0e788054ff828 100644
--- a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.tsx
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.tsx
@@ -16,44 +16,77 @@
* along with this program. If not, see .
*/
-import React from 'react';
-
-import { Flex, H2, Indicator } from 'design';
+import { Box, Flex, H2, Indicator } from 'design';
import { Danger } from 'design/Alert';
import { FeatureBox } from 'teleport/components/Layout';
import { AwsOidcHeader } from 'teleport/Integrations/status/AwsOidc/AwsOidcHeader';
+import { AwsOidcTitle } from 'teleport/Integrations/status/AwsOidc/AwsOidcTitle';
import {
AwsResource,
StatCard,
} from 'teleport/Integrations/status/AwsOidc/StatCard';
+import { TaskAlert } from 'teleport/Integrations/status/AwsOidc/Tasks/TaskAlert';
import { useAwsOidcStatus } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';
export function AwsOidcDashboard() {
const { statsAttempt, integrationAttempt } = useAwsOidcStatus();
- if (statsAttempt.status == 'processing') {
- return ;
+ if (
+ statsAttempt.status === 'processing' ||
+ integrationAttempt.status === 'processing'
+ ) {
+ return (
+
+
+
+ );
+ }
+
+ if (integrationAttempt.status === 'error') {
+ return {integrationAttempt.statusText};
}
- if (statsAttempt.status == 'error') {
+
+ if (statsAttempt.status === 'error') {
return {statsAttempt.statusText};
}
- if (!statsAttempt.data) {
+
+ if (!statsAttempt.data || !integrationAttempt.data) {
return null;
}
- // todo (michellescripts) after routing, ensure this view can be sticky
const { awsec2, awseks, awsrds } = statsAttempt.data;
const { data: integration } = integrationAttempt;
return (
-
- {integration && }
- Auto-Enrollment
-
-
-
-
-
-
+ <>
+
+
+ {integration && (
+ <>
+
+
+ >
+ )}
+
+ Auto-Enrollment
+
+
+
+
+
+
+ >
);
}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcHeader.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcHeader.tsx
index f6efccd680aaf..851e05458142b 100644
--- a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcHeader.tsx
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcHeader.tsx
@@ -16,40 +16,92 @@
* along with this program. If not, see .
*/
-import React from 'react';
import { Link as InternalLink } from 'react-router-dom';
-import { ButtonIcon, Flex, Label, Text } from 'design';
-import { ArrowLeft } from 'design/Icon';
-import { HoverTooltip } from 'shared/components/ToolTip';
+import { ButtonText, Flex, Text } from 'design';
+import { Plugs } from 'design/Icon';
+import { HoverTooltip } from 'design/Tooltip';
import cfg from 'teleport/config';
-import { getStatusAndLabel } from 'teleport/Integrations/helpers';
-import { IntegrationAwsOidc } from 'teleport/services/integrations';
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+import { Integration } from 'teleport/services/integrations';
export function AwsOidcHeader({
integration,
+ resource,
+ tasks = false,
}: {
- integration: IntegrationAwsOidc;
+ integration: Integration;
+ resource?: AwsResource;
+ tasks?: boolean;
}) {
- const { status, labelKind } = getStatusAndLabel(integration);
+ const divider = (
+
+ /
+
+ );
+
return (
-
+
-
-
-
+
+
-
- {integration.name}
-
-
+ {!resource && !tasks ? (
+ <>
+ {divider}
+
+ {integration.name}
+
+ >
+ ) : (
+ <>
+ {divider}
+
+ {integration.name}
+
+ >
+ )}
+ {resource && (
+ <>
+ {divider}
+
+ {resource.toUpperCase()}
+
+ >
+ )}
+ {tasks && (
+ <>
+ {divider}
+
+ Pending Tasks
+
+ >
+ )}
);
}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcRoutes.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcRoutes.tsx
index 470c4bf32e482..82ea7afe8a73f 100644
--- a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcRoutes.tsx
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcRoutes.tsx
@@ -20,6 +20,8 @@ import React from 'react';
import { Route, Switch } from 'teleport/components/Router';
import cfg from 'teleport/config';
+import { Details } from 'teleport/Integrations/status/AwsOidc/Details/Details';
+import { Tasks } from 'teleport/Integrations/status/AwsOidc/Tasks/Tasks';
import { AwsOidcStatusProvider } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';
import { AwsOidcDashboard } from './AwsOidcDashboard';
@@ -29,7 +31,19 @@ export function AwsOidcRoutes() {
+
+ .
+ */
+import { within } from '@testing-library/react';
+import { MemoryRouter } from 'react-router';
+
+import { render, screen } from 'design/utils/testing';
+
+import { AwsOidcTitle } from 'teleport/Integrations/status/AwsOidc/AwsOidcTitle';
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+import {
+ IntegrationAwsOidc,
+ IntegrationKind,
+ IntegrationStatusCode,
+} from 'teleport/services/integrations';
+
+const testIntegration: IntegrationAwsOidc = {
+ kind: IntegrationKind.AwsOidc,
+ name: 'some-name',
+ resourceType: 'integration',
+ spec: {
+ roleArn: '',
+ issuerS3Bucket: '',
+ issuerS3Prefix: '',
+ },
+ statusCode: IntegrationStatusCode.Running,
+};
+
+test('renders with resource', () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByRole('link', { name: 'back' })).toHaveAttribute(
+ 'href',
+ '/web/integrations/status/aws-oidc/some-name'
+ );
+ expect(screen.getByText('EC2')).toBeInTheDocument();
+ expect(screen.queryByText('some-name')).not.toBeInTheDocument();
+ expect(
+ within(screen.getByLabelText('status')).getByText('Running')
+ ).toBeInTheDocument();
+});
+
+test('renders without resource', () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByRole('link', { name: 'back' })).toHaveAttribute(
+ 'href',
+ '/web/integrations'
+ );
+ expect(screen.getByText('some-name')).toBeInTheDocument();
+ expect(
+ within(screen.getByLabelText('status')).getByText('Running')
+ ).toBeInTheDocument();
+});
+
+test('renders tasks', () => {
+ render(
+
+
+
+ );
+
+ expect(screen.getByRole('link', { name: 'back' })).toHaveAttribute(
+ 'href',
+ '/web/integrations/status/aws-oidc/some-name'
+ );
+ expect(screen.getByText('Pending Tasks')).toBeInTheDocument();
+ expect(
+ within(screen.getByLabelText('status')).getByText('Running')
+ ).toBeInTheDocument();
+});
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcTitle.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcTitle.tsx
new file mode 100644
index 0000000000000..87d73a4627cc3
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcTitle.tsx
@@ -0,0 +1,84 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import { Link as InternalLink } from 'react-router-dom';
+
+import { ButtonIcon, Flex, Label, Text } from 'design';
+import { ArrowLeft } from 'design/Icon';
+import { HoverTooltip } from 'design/Tooltip';
+
+import cfg from 'teleport/config';
+import { getStatusAndLabel } from 'teleport/Integrations/helpers';
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+import { IntegrationAwsOidc } from 'teleport/services/integrations';
+
+export function AwsOidcTitle({
+ integration,
+ resource,
+ tasks,
+}: {
+ integration: IntegrationAwsOidc;
+ resource?: AwsResource;
+ tasks?: boolean;
+}) {
+ const { status, labelKind } = getStatusAndLabel(integration);
+ const content = getContent(integration, resource, tasks);
+
+ return (
+
+
+
+
+
+
+
+ {content.content}
+
+
+
+ );
+}
+
+function getContent(
+ integration: IntegrationAwsOidc,
+ resource?: AwsResource,
+ tasks?: boolean
+): { to: string; helper: string; content: string } {
+ if (resource) {
+ return {
+ to: cfg.getIntegrationStatusRoute(integration.kind, integration.name),
+ helper: 'Back to integration',
+ content: resource.toUpperCase(),
+ };
+ }
+
+ if (tasks) {
+ return {
+ to: cfg.getIntegrationStatusRoute(integration.kind, integration.name),
+ helper: 'Back to integration',
+ content: 'Pending Tasks',
+ };
+ }
+
+ return {
+ to: cfg.routes.integrations,
+ helper: 'Back to integrations',
+ content: integration.name,
+ };
+}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Agents.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Agents.tsx
new file mode 100644
index 0000000000000..f1495291228f6
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Agents.tsx
@@ -0,0 +1,106 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { useEffect, useState } from 'react';
+import { useParams } from 'react-router';
+
+import { Box, Flex, Indicator } from 'design';
+import { Danger } from 'design/Alert';
+import Table, { LabelCell } from 'design/DataTable';
+import { MultiselectMenu } from 'shared/components/Controls/MultiselectMenu';
+import { useAsync } from 'shared/hooks/useAsync';
+
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+import {
+ AWSOIDCDeployedDatabaseService,
+ awsRegionMap,
+ IntegrationKind,
+ integrationService,
+ Regions,
+} from 'teleport/services/integrations';
+
+export function Agents() {
+ const { name, resourceKind } = useParams<{
+ type: IntegrationKind;
+ name: string;
+ resourceKind: AwsResource;
+ }>();
+
+ const [regionFilter, setRegionFilter] = useState([]);
+ const [servicesAttempt, fetchServices] = useAsync(() => {
+ return integrationService.fetchAwsOidcDatabaseServices(
+ name,
+ resourceKind,
+ regionFilter
+ );
+ });
+
+ useEffect(() => {
+ fetchServices();
+ }, [regionFilter]);
+
+ if (servicesAttempt.status === 'processing') {
+ return (
+
+
+
+ );
+ }
+
+ return (
+ <>
+ {servicesAttempt.status === 'error' && (
+ {servicesAttempt.statusText}
+ )}
+ ({
+ value: r as Regions,
+ label: (
+
+ {awsRegionMap[r]}
+ {r}
+
+ ),
+ }))}
+ onChange={regions => setRegionFilter(regions)}
+ selected={regionFilter}
+ label="Region"
+ tooltip="Filter by region"
+ />
+
+ data={servicesAttempt.data?.services}
+ columns={[
+ {
+ key: 'name',
+ headerText: 'Service Name',
+ },
+ {
+ key: 'matchingLabels',
+ headerText: 'Tags',
+ render: ({ matchingLabels }) => (
+ `${l.name}:${l.value}`)}
+ />
+ ),
+ },
+ ]}
+ emptyText={`No ${resourceKind.toUpperCase()} agents`}
+ />
+ >
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Details.story.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Details.story.tsx
new file mode 100644
index 0000000000000..92c38f30c8f90
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Details.story.tsx
@@ -0,0 +1,232 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import { http, HttpResponse } from 'msw';
+
+import cfg from 'teleport/config';
+import { Details } from 'teleport/Integrations/status/AwsOidc/Details/Details';
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+import { MockAwsOidcStatusProvider } from 'teleport/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider';
+import { IntegrationKind } from 'teleport/services/integrations';
+
+import { makeAwsOidcStatusContextState } from '../testHelpers/makeAwsOidcStatusContextState';
+import { makeIntegrationDiscoveryRule } from '../testHelpers/makeIntegrationDiscoveryRule';
+
+export default {
+ title: 'Teleport/Integrations/AwsOidc/Details',
+};
+
+const integrationName = 'integration-story';
+
+// Empty ec2 details table
+export function EC2Empty() {
+ return (
+
+
+
+ );
+}
+
+// Populated ec2 details table
+export function EC2() {
+ return (
+
+
+
+ );
+}
+
+EC2.parameters = {
+ msw: {
+ handlers: [
+ http.get(
+ cfg.getIntegrationRulesUrl(integrationName, AwsResource.ec2),
+ () => {
+ return HttpResponse.json({
+ rules: rules,
+ nextKey: '1',
+ });
+ }
+ ),
+ ],
+ },
+};
+
+// Empty eks details table
+export function EKSEmpty() {
+ return (
+
+
+
+ );
+}
+
+// Populated eks details table
+export function EKS() {
+ return (
+
+
+
+ );
+}
+
+EKS.parameters = {
+ msw: {
+ handlers: [
+ http.get(
+ cfg.getIntegrationRulesUrl(integrationName, AwsResource.eks),
+ () => {
+ return HttpResponse.json({
+ rules: rules,
+ nextKey: '1',
+ });
+ }
+ ),
+ ],
+ },
+};
+
+// Empty rds details table
+export function RDSEmpty() {
+ return (
+
+
+
+ );
+}
+
+// Populated eks details table
+export function RDS() {
+ return (
+
+
+
+ );
+}
+
+RDS.parameters = {
+ msw: {
+ handlers: [
+ http.get(
+ cfg.getIntegrationRulesUrl(integrationName, AwsResource.rds),
+ () => {
+ return HttpResponse.json({
+ rules: rules,
+ nextKey: '1',
+ });
+ }
+ ),
+ http.post(
+ cfg.getAwsOidcDatabaseServices(integrationName, AwsResource.rds, []),
+ () => {
+ return HttpResponse.json({
+ services: [
+ {
+ name: 'dev-db',
+ matchingLabels: [{ name: 'region', value: 'us-west-2' }],
+ },
+ {
+ name: 'dev-db',
+ matchingLabels: [
+ { name: 'region', value: 'us-west-1' },
+ { name: '*', value: '*' },
+ ],
+ },
+ {
+ name: 'staging-db',
+ matchingLabels: [{ name: '*', value: '*' }],
+ },
+ ],
+ });
+ }
+ ),
+ ],
+ },
+};
+
+function getPath(resource: AwsResource) {
+ return cfg.getIntegrationStatusResourcesRoute(
+ IntegrationKind.AwsOidc,
+ integrationName,
+ resource
+ );
+}
+
+const rules = [
+ makeIntegrationDiscoveryRule({
+ region: 'us-west-2',
+ labelMatcher: [
+ { name: 'env', value: 'prod' },
+ { name: 'key', value: '123' },
+ ],
+ }),
+ makeIntegrationDiscoveryRule({
+ region: 'us-west-2',
+ labelMatcher: [
+ { name: 'env', value: 'prod' },
+ { name: 'key', value: '123' },
+ ],
+ }),
+ makeIntegrationDiscoveryRule({
+ region: 'us-west-2',
+ labelMatcher: [
+ { name: 'env', value: 'prod' },
+ { name: 'key', value: '123' },
+ ],
+ }),
+];
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Details.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Details.tsx
new file mode 100644
index 0000000000000..a8e11cbc53ec5
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Details.tsx
@@ -0,0 +1,58 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { useParams } from 'react-router';
+
+import { FeatureBox } from 'teleport/components/Layout';
+import { AwsOidcHeader } from 'teleport/Integrations/status/AwsOidc/AwsOidcHeader';
+import { AwsOidcTitle } from 'teleport/Integrations/status/AwsOidc/AwsOidcTitle';
+import { Rds } from 'teleport/Integrations/status/AwsOidc/Details/Rds';
+import { Rules } from 'teleport/Integrations/status/AwsOidc/Details/Rules';
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+import { TaskAlert } from 'teleport/Integrations/status/AwsOidc/Tasks/TaskAlert';
+import { useAwsOidcStatus } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';
+import { IntegrationKind } from 'teleport/services/integrations';
+
+export function Details() {
+ const { resourceKind } = useParams<{
+ type: IntegrationKind;
+ name: string;
+ resourceKind: AwsResource;
+ }>();
+
+ const { integrationAttempt } = useAwsOidcStatus();
+ const { data: integration } = integrationAttempt;
+ return (
+ <>
+ {integration && (
+
+ )}
+
+ <>
+ {integration && (
+ <>
+
+
+ >
+ )}
+ >
+ {resourceKind === AwsResource.rds ? : }
+
+ >
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rds.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rds.tsx
new file mode 100644
index 0000000000000..b46aadefd5ac1
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rds.tsx
@@ -0,0 +1,105 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { useEffect, useRef } from 'react';
+import { useLocation, useParams } from 'react-router';
+
+import { TabBorder, TabContainer, TabsContainer } from 'design/Tabs/Tabs';
+
+import cfg from 'teleport/config';
+import { Agents } from 'teleport/Integrations/status/AwsOidc/Details/Agents';
+import { Rules } from 'teleport/Integrations/status/AwsOidc/Details/Rules';
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+import { IntegrationKind } from 'teleport/services/integrations';
+
+export enum RdsTab {
+ Agents = 'agents',
+ Rules = 'rules',
+}
+
+export function Rds() {
+ const { type, name, resourceKind } = useParams<{
+ type: IntegrationKind;
+ name: string;
+ resourceKind: AwsResource;
+ }>();
+
+ const { search } = useLocation();
+ const searchParams = new URLSearchParams(search);
+ const tab = (searchParams.get('tab') as RdsTab) || RdsTab.Rules;
+
+ const borderRef = useRef(null);
+ const parentRef = useRef();
+
+ // todo (michellescripts) the following implementation mimics the implementation of tabs in
+ // e/web/teleport/src/AccessMonitoring/AccessMonitoring.tsx which is refactored/moved into a shared
+ // design component, web/packages/design/src/Tabs/Tabs.ts. When refactoring AccessMonitoring to use the shared
+ // component, consider updating both instances logic to be plain css
+ useEffect(() => {
+ if (!parentRef.current || !borderRef.current) {
+ return;
+ }
+
+ const activeElement = parentRef.current.querySelector(
+ `[data-tab-id="${tab}"]`
+ );
+
+ if (activeElement) {
+ const parentBounds = parentRef.current.getBoundingClientRect();
+ const activeBounds = activeElement.getBoundingClientRect();
+
+ const left = activeBounds.left - parentBounds.left;
+ const width = activeBounds.width;
+
+ borderRef.current.style.left = `${left}px`;
+ borderRef.current.style.width = `${width}px`;
+ }
+ }, [tab]);
+
+ return (
+ <>
+
+
+ Enrollment Rules
+
+
+ Agents
+
+
+
+ {tab === RdsTab.Rules && }
+ {tab === RdsTab.Agents && }
+ >
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rules.test.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rules.test.tsx
new file mode 100644
index 0000000000000..67d4b85c859ea
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rules.test.tsx
@@ -0,0 +1,98 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { within } from '@testing-library/react';
+import { MemoryRouter } from 'react-router';
+
+import { render, screen, waitFor } from 'design/utils/testing';
+
+import { Route } from 'teleport/components/Router';
+import cfg from 'teleport/config';
+import { Rules } from 'teleport/Integrations/status/AwsOidc/Details/Rules';
+import { integrationService } from 'teleport/services/integrations';
+
+import { makeIntegrationDiscoveryRule } from '../testHelpers/makeIntegrationDiscoveryRule';
+
+test('renders region & labels from response', async () => {
+ jest.spyOn(integrationService, 'fetchIntegrationRules').mockResolvedValue({
+ rules: [
+ makeIntegrationDiscoveryRule({
+ region: 'us-west-2',
+ labelMatcher: [
+ { name: 'env', value: 'prod' },
+ { name: 'key', value: '123' },
+ ],
+ }),
+ makeIntegrationDiscoveryRule({
+ region: 'us-east-2',
+ labelMatcher: [{ name: 'env', value: 'stage' }],
+ }),
+ makeIntegrationDiscoveryRule({
+ region: 'us-west-1',
+ labelMatcher: [{ name: 'env', value: 'test' }],
+ }),
+ makeIntegrationDiscoveryRule({
+ region: 'us-east-1',
+ labelMatcher: [{ name: 'env', value: 'dev' }],
+ }),
+ ],
+ nextKey: '',
+ });
+ render(
+
+ }
+ />
+
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText('env:prod')).toBeInTheDocument();
+ });
+
+ expect(getTableCellContents()).toEqual({
+ header: ['Region', 'Labels'],
+ rows: [
+ ['us-west-2', 'env:prodkey:123'],
+ ['us-east-2', 'env:stage'],
+ ['us-west-1', 'env:test'],
+ ['us-east-1', 'env:dev'],
+ ],
+ });
+
+ jest.clearAllMocks();
+});
+
+function getTableCellContents() {
+ const [header, ...rows] = screen.getAllByRole('row');
+ return {
+ header: within(header)
+ .getAllByRole('columnheader')
+ .map(cell => cell.textContent),
+ rows: rows.map(row =>
+ within(row)
+ .getAllByRole('cell')
+ .map(cell => cell.textContent)
+ ),
+ };
+}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rules.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rules.tsx
new file mode 100644
index 0000000000000..6fdbd8a1bebcc
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rules.tsx
@@ -0,0 +1,115 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { useEffect, useState } from 'react';
+import { useParams } from 'react-router';
+
+import { Flex } from 'design';
+import Table, { LabelCell } from 'design/DataTable';
+import { MultiselectMenu } from 'shared/components/Controls/MultiselectMenu';
+
+import { useServerSidePagination } from 'teleport/components/hooks';
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+import {
+ awsRegionMap,
+ IntegrationDiscoveryRule,
+ IntegrationKind,
+ integrationService,
+ Regions,
+} from 'teleport/services/integrations';
+
+export function Rules() {
+ const { name, resourceKind } = useParams<{
+ type: IntegrationKind;
+ name: string;
+ resourceKind: AwsResource;
+ }>();
+
+ const [regionFilter, setRegionFilter] = useState([]);
+ const serverSidePagination =
+ useServerSidePagination({
+ pageSize: 20,
+ fetchFunc: async () => {
+ const { rules, nextKey } =
+ await integrationService.fetchIntegrationRules(
+ name,
+ resourceKind,
+ regionFilter
+ );
+ return { agents: rules, nextKey };
+ },
+ clusterId: '',
+ params: {},
+ });
+
+ useEffect(() => {
+ serverSidePagination.fetch();
+ }, [regionFilter]);
+
+ return (
+ <>
+ ({
+ value: r as Regions,
+ label: (
+
+ {awsRegionMap[r]}
+ {r}
+
+ ),
+ }))}
+ onChange={regions => setRegionFilter(regions)}
+ selected={regionFilter}
+ label="Region"
+ tooltip="Filter by region"
+ />
+
+ data={serverSidePagination?.fetchedData?.agents}
+ columns={[
+ {
+ key: 'region',
+ headerText: 'Region',
+ },
+ {
+ key: 'labelMatcher',
+ headerText: getResourceTerm(resourceKind),
+ render: ({ labelMatcher }) => (
+ `${l.name}:${l.value}`)} />
+ ),
+ },
+ ]}
+ emptyText={`No ${resourceKind.toUpperCase()} rules`}
+ pagination={{ pageSize: serverSidePagination.pageSize }}
+ fetching={{
+ fetchStatus: serverSidePagination.fetchStatus,
+ onFetchNext: serverSidePagination.fetchNext,
+ onFetchPrev: serverSidePagination.fetchPrev,
+ }}
+ />
+ >
+ );
+}
+
+function getResourceTerm(resource: AwsResource): string {
+ switch (resource) {
+ case AwsResource.rds:
+ return 'Tags';
+ default:
+ return 'Labels';
+ }
+}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/EnrollCard.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/EnrollCard.tsx
new file mode 100644
index 0000000000000..c025683e1c75f
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/EnrollCard.tsx
@@ -0,0 +1,61 @@
+/**
+ * Teleport
+ * Copyright (C) 2025 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { Link as InternalLink } from 'react-router-dom';
+import styled from 'styled-components';
+
+import { ButtonSecondary, Card, Flex, H2, ResourceIcon } from 'design';
+
+import cfg from 'teleport/config';
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+
+export function EnrollCard({ resource }: { resource: AwsResource }) {
+ return (
+
+
+
+
+ {resource.toUpperCase()}
+
+
+ Enroll {resource.toUpperCase()}
+
+
+
+ );
+}
+
+const Enroll = styled(Card)`
+ width: 33%;
+ background-color: ${props => props.theme.colors.levels.surface};
+ padding: ${props => props.theme.space[3]}px;
+ border-radius: ${props => props.theme.radii[2]}px;
+ border: ${props => `1px solid ${props.theme.colors.levels.surface}`};
+
+ &:hover {
+ background-color: ${props => props.theme.colors.levels.elevated};
+ box-shadow: ${({ theme }) => theme.boxShadow[2]};
+ }
+`;
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/StatCard.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/StatCard.tsx
index 5a3a5173376d1..bd56adf338dfd 100644
--- a/web/packages/teleport/src/Integrations/status/AwsOidc/StatCard.tsx
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/StatCard.tsx
@@ -17,13 +17,19 @@
*/
import { formatDistanceStrict } from 'date-fns';
-import React from 'react';
+import { Link as InternalLink } from 'react-router-dom';
+import styled from 'styled-components';
import { Card, Flex, H2, Text } from 'design';
import * as Icons from 'design/Icon';
import { ResourceIcon } from 'design/ResourceIcon';
-import { ResourceTypeSummary } from 'teleport/services/integrations';
+import cfg from 'teleport/config';
+import { EnrollCard } from 'teleport/Integrations/status/AwsOidc/EnrollCard';
+import {
+ IntegrationKind,
+ ResourceTypeSummary,
+} from 'teleport/services/integrations';
export enum AwsResource {
ec2 = 'ec2',
@@ -32,22 +38,30 @@ export enum AwsResource {
}
type StatCardProps = {
+ name: string;
resource: AwsResource;
summary?: ResourceTypeSummary;
};
-export function StatCard({ resource, summary }: StatCardProps) {
+export function StatCard({ name, resource, summary }: StatCardProps) {
const updated = summary?.discoverLastSync
? new Date(summary?.discoverLastSync)
: undefined;
const term = getResourceTerm(resource);
+ if (!summary || !foundResource(summary)) {
+ return ;
+ }
+
return (
-
Enrollment Rules
{summary?.rulesCount || 0}
- {resource == AwsResource.rds && (
+ {resource === AwsResource.rds && (
Agents
{summary?.ecsDatabaseServiceCount || 0}
@@ -96,7 +110,7 @@ export function StatCard({ resource, summary }: StatCardProps) {
)}
-
+
);
}
@@ -111,3 +125,31 @@ function getResourceTerm(resource: AwsResource): string {
return 'Instances';
}
}
+
+function foundResource(resource: ResourceTypeSummary): boolean {
+ if (!resource || Object.keys(resource).length === 0) {
+ return false;
+ }
+
+ if (resource.ecsDatabaseServiceCount != 0) {
+ return true;
+ }
+
+ return resource.rulesCount != 0 || resource.resourcesFound != 0;
+}
+
+export const SelectCard = styled(Card)`
+ width: 33%;
+ background-color: ${props => props.theme.colors.levels.surface};
+ padding: ${props => props.theme.space[3]}px;
+ border-radius: ${props => props.theme.radii[2]}px;
+ border: ${props => `1px solid ${props.theme.colors.levels.surface}`};
+ cursor: pointer;
+ text-decoration: none;
+ color: ${props => props.theme.colors.text.main};
+
+ &:hover {
+ background-color: ${props => props.theme.colors.levels.elevated};
+ box-shadow: ${({ theme }) => theme.boxShadow[2]};
+ }
+`;
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/SidePanel.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/SidePanel.tsx
new file mode 100644
index 0000000000000..817baf3bb40f2
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/SidePanel.tsx
@@ -0,0 +1,71 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import { PropsWithChildren, ReactNode } from 'react';
+import styled from 'styled-components';
+
+import { ButtonIcon, Flex } from 'design';
+import { Cross } from 'design/Icon';
+
+export const SidePanel = ({
+ onClose,
+ header,
+ footer,
+ disabled = false,
+ children,
+}: PropsWithChildren & {
+ onClose: () => void;
+ header?: ReactNode;
+ footer?: ReactNode;
+ disabled?: boolean;
+}) => {
+ return (
+
+
+ {header}
+
+
+
+
+
+ {children}
+
+
+ {footer}
+
+
+ );
+};
+
+const Container = styled(Flex)`
+ flex-direction: column;
+
+ height: calc(100vh - ${props => props.theme.topBarHeight[1]}px);
+`;
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Task.test.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Task.test.tsx
new file mode 100644
index 0000000000000..2b7119122bf69
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Task.test.tsx
@@ -0,0 +1,199 @@
+/**
+ * Teleport
+ * Copyright (C) 2025 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { within } from '@testing-library/react';
+
+import { render, screen } from 'design/utils/testing';
+
+import { ContextProvider } from 'teleport/index';
+import { Task } from 'teleport/Integrations/status/AwsOidc/Tasks/Task';
+import { integrationService } from 'teleport/services/integrations';
+import TeleportContext from 'teleport/teleportContext';
+
+test('renders ec2 impacts', async () => {
+ const ctx = new TeleportContext();
+ jest.spyOn(integrationService, 'fetchUserTask').mockResolvedValue({
+ name: 'df4d8288-7106-5a50-bb50-4b5858e48ad5',
+ taskType: 'discover-ec2',
+ state: 'OPEN',
+ integration: '',
+ lastStateChange: '2025-02-11T20:32:19.482607921Z',
+ issueType: 'ec2-ssm-invocation-failure',
+ description:
+ 'Teleport failed to access the SSM Agent to auto enroll the instance.\nSome instances failed to communicate with the AWS Systems Manager service to execute the install script.\n\nUsually this happens when:\n\n**Missing policies**\n\nThe IAM Role used by the integration might be missing some required permissions.\nEnsure the following actions are allowed in the IAM Role used by the integration:\n- `ec2:DescribeInstances`\n- `ssm:DescribeInstanceInformation`\n- `ssm:GetCommandInvocation`\n- `ssm:ListCommandInvocations`\n- `ssm:SendCommand`\n\n**SSM Document is invalid**\n\nTeleport uses an SSM Document to run an installation script.\nIf the document is changed or removed, it might no longer work.',
+ discoverEks: undefined,
+ discoverRds: undefined,
+ discoverEc2: {
+ region: 'us-east-2',
+ accountId: undefined,
+ ssmDocument: undefined,
+ installerScript: undefined,
+ instances: {
+ 'i-016e32a5882f5ee81': {
+ instance_id: 'i-016e32a5882f5ee81',
+ name: undefined,
+ invocationUrl: undefined,
+ discoveryConfig: undefined,
+ discoveryGroup: undefined,
+ syncTime: undefined,
+ },
+ 'i-065818031835365cc': {
+ instance_id: 'i-065818031835365cc',
+ name: 'aws-test',
+ invocationUrl: undefined,
+ discoveryConfig: undefined,
+ discoveryGroup: undefined,
+ syncTime: undefined,
+ },
+ },
+ },
+ });
+
+ render(
+
+ {}} />
+
+ );
+
+ await screen.findByText('Details');
+
+ expect(getTableCellContents()).toEqual({
+ header: ['Instance ID', 'Instance Name'],
+ rows: [
+ ['i-016e32a5882f5ee81', ''],
+ ['i-065818031835365cc', 'aws-test'],
+ ],
+ });
+
+ jest.resetAllMocks();
+});
+
+test('renders eks impacts', async () => {
+ const ctx = new TeleportContext();
+ jest.spyOn(integrationService, 'fetchUserTask').mockResolvedValue({
+ name: 'df4d8288-7106-5a50-bb50-4b5858e48ad5',
+ taskType: 'discover-eks',
+ state: 'OPEN',
+ integration: 'integration-001',
+ lastStateChange: '2025-02-11T20:32:19.482607921Z',
+ issueType: 'eks-failure',
+ description:
+ 'Only EKS Clusters whose status is active can be automatically enrolled into teleport.\n',
+ discoverEc2: undefined,
+ discoverRds: undefined,
+ discoverEks: {
+ accountId: undefined,
+ region: undefined,
+ appAutoDiscover: false,
+ clusters: {
+ 'i-016e32a5882f5ee81': {
+ name: 'i-016e32a5882f5ee81',
+ discoveryConfig: undefined,
+ discoveryGroup: undefined,
+ syncTime: undefined,
+ },
+ 'i-065818031835365cc': {
+ name: 'i-065818031835365cc',
+ discoveryConfig: undefined,
+ discoveryGroup: undefined,
+ syncTime: undefined,
+ },
+ },
+ },
+ });
+
+ render(
+
+ {}} />
+
+ );
+
+ await screen.findByText('Details');
+
+ expect(getTableCellContents()).toEqual({
+ header: ['Name'],
+ rows: [['i-016e32a5882f5ee81'], ['i-065818031835365cc']],
+ });
+ jest.resetAllMocks();
+});
+
+test('renders rds impacts', async () => {
+ const ctx = new TeleportContext();
+ jest.spyOn(integrationService, 'fetchUserTask').mockResolvedValue({
+ name: 'df4d8288-7106-5a50-bb50-4b5858e48ad5',
+ taskType: 'discover-rds',
+ state: 'OPEN',
+ integration: 'integration-001',
+ lastStateChange: '2025-02-11T20:32:19.482607921Z',
+ issueType: 'rds-failure',
+ description:
+ 'The Teleport Database Service uses [IAM authentication](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html) to communicate with RDS.\n',
+ discoverEks: undefined,
+ discoverEc2: undefined,
+ discoverRds: {
+ accountId: undefined,
+ region: undefined,
+ databases: {
+ 'i-016e32a5882f5ee81': {
+ name: 'i-016e32a5882f5ee81',
+ isCluster: undefined,
+ engine: undefined,
+ discoveryConfig: undefined,
+ discoveryGroup: undefined,
+ syncTime: undefined,
+ },
+ 'i-065818031835365cc': {
+ name: 'i-065818031835365cc',
+ isCluster: undefined,
+ engine: undefined,
+ discoveryConfig: undefined,
+ discoveryGroup: undefined,
+ syncTime: undefined,
+ },
+ },
+ },
+ });
+
+ render(
+
+ {}} />
+
+ );
+
+ await screen.findByText('Details');
+
+ expect(getTableCellContents()).toEqual({
+ header: ['Name'],
+ rows: [['i-016e32a5882f5ee81'], ['i-065818031835365cc']],
+ });
+ jest.resetAllMocks();
+});
+
+function getTableCellContents() {
+ const [header, ...rows] = screen.getAllByRole('row');
+ return {
+ header: within(header)
+ .getAllByRole('columnheader')
+ .map(cell => cell.textContent),
+ rows: rows.map(row =>
+ within(row)
+ .getAllByRole('cell')
+ .map(cell => cell.textContent)
+ ),
+ };
+}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Task.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Task.tsx
new file mode 100644
index 0000000000000..d11267bf89f8e
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Task.tsx
@@ -0,0 +1,236 @@
+/**
+ * Teleport
+ * Copyright (C) 2025 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { PropsWithChildren, useEffect } from 'react';
+import ReactMarkdown from 'react-markdown';
+
+import { Alert, ButtonBorder, Flex, H2 } from 'design';
+import { Danger } from 'design/Alert';
+import Table from 'design/DataTable';
+import { TableColumn } from 'design/DataTable/types';
+import { H3, P2, Subtitle2 } from 'design/Text';
+import { useAsync } from 'shared/hooks/useAsync';
+import useAttempt from 'shared/hooks/useAttemptNext';
+
+import { getResourceType } from 'teleport/Integrations/status/AwsOidc/helpers';
+import {
+ DiscoverEc2,
+ DiscoverEc2Instance,
+ DiscoverEks,
+ DiscoverEksCluster,
+ DiscoverRds,
+ DiscoverRdsDatabase,
+ integrationService,
+ UserTaskDetail,
+} from 'teleport/services/integrations';
+
+import { AwsResource } from '../StatCard';
+import { SidePanel } from './SidePanel';
+
+export function Task({
+ name,
+ close,
+}: {
+ name: string;
+ close: (resolved: boolean) => void;
+}) {
+ const { attempt, setAttempt } = useAttempt('');
+
+ const [taskAttempt, fetchTask] = useAsync(() =>
+ integrationService.fetchUserTask(name)
+ );
+
+ useEffect(() => {
+ fetchTask();
+ }, []);
+
+ if (taskAttempt.status === 'error') {
+ return (
+ close(false)}>
+ {taskAttempt.statusText}
+
+ );
+ }
+
+ if (!taskAttempt.data) {
+ return null;
+ }
+
+ function resolve() {
+ setAttempt({ status: 'processing' });
+ integrationService
+ .resolveUserTask(name)
+ .then(() => {
+ setAttempt({ status: '', statusText: '' });
+ close(true);
+ })
+ .catch((err: Error) =>
+ setAttempt({ status: 'failed', statusText: err.message })
+ );
+ }
+
+ const impactedInstances = getImpactedInstances(taskAttempt.data);
+ const { resourceType, resource, impacts } = impactedInstances;
+ const table = makeImpactsTable(impactedInstances);
+
+ return (
+ close(false)}
+ header={{taskAttempt.data.issueType}
}
+ footer={
+
+ Mark as Resolved
+
+ }
+ disabled={attempt.status === 'processing'}
+ >
+ {attempt.status === 'failed' && (
+
+ Unable to resolve task
+
+ )}
+
+ {taskAttempt.data.integration}
+
+ {resourceType.toUpperCase()}
+ {resource.region}
+ Details
+ {taskAttempt.data.description}
+ Impacted instances ({Object.keys(impacts).length})
+
+
+ );
+}
+
+type TableInstance = {
+ instanceId?: string;
+ name: string;
+};
+
+function makeImpactsTable(instances: ImpactedInstances): {
+ columns: TableColumn[];
+ data: TableInstance[];
+} {
+ const { resourceType, impacts } = instances;
+ switch (resourceType) {
+ case AwsResource.ec2:
+ return {
+ columns: [
+ {
+ key: 'instanceId',
+ headerText: 'Instance ID',
+ },
+ {
+ key: 'name',
+ headerText: 'Instance Name',
+ },
+ ],
+ data: Object.keys(impacts).map(i => ({
+ instanceId: impacts[i].instance_id,
+ name: impacts[i].name,
+ })),
+ };
+ case AwsResource.eks:
+ case AwsResource.rds:
+ return {
+ columns: [
+ {
+ key: 'name',
+ headerText: 'Name',
+ },
+ ],
+ data: Object.keys(impacts).map(i => ({
+ name: impacts[i].name,
+ })),
+ };
+ default:
+ resourceType satisfies never;
+ }
+}
+
+type ImpactedInstances =
+ | {
+ resourceType: AwsResource.ec2;
+ resource: DiscoverEc2;
+ impacts: Record;
+ }
+ | {
+ resourceType: AwsResource.eks;
+ resource: DiscoverEks;
+ impacts: Record;
+ }
+ | {
+ resourceType: AwsResource.rds;
+ resource: DiscoverRds;
+ impacts: Record;
+ };
+
+function getImpactedInstances(task: UserTaskDetail): ImpactedInstances {
+ const resourceType = getResourceType(task.taskType);
+
+ switch (resourceType) {
+ case AwsResource.ec2:
+ return {
+ resourceType: resourceType,
+ resource: task.discoverEc2,
+ impacts: task.discoverEc2?.instances,
+ };
+ case AwsResource.eks:
+ return {
+ resourceType: resourceType,
+ resource: task.discoverEks,
+ impacts: task.discoverEks?.clusters,
+ };
+ case AwsResource.rds:
+ default:
+ return {
+ resourceType: resourceType,
+ resource: task.discoverRds,
+ impacts: task.discoverRds?.databases,
+ };
+ }
+}
+
+const Attribute = ({
+ title = '',
+ children,
+}: PropsWithChildren<{ title: string }>) => (
+
+ {title}:
+
+ {children || `N/A`}
+
+
+);
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/TaskAlert.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/TaskAlert.tsx
new file mode 100644
index 0000000000000..6e8a01b68c409
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/TaskAlert.tsx
@@ -0,0 +1,77 @@
+/**
+ * Teleport
+ * Copyright (C) 2025 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import { useEffect } from 'react';
+import { useHistory } from 'react-router';
+
+import { Alert } from 'design';
+import { ArrowForward, BellRinging } from 'design/Icon';
+import { useAsync } from 'shared/hooks/useAsync';
+
+import cfg from 'teleport/config';
+import { TaskState } from 'teleport/Integrations/status/AwsOidc/Tasks/constants';
+import {
+ IntegrationKind,
+ integrationService,
+} from 'teleport/services/integrations';
+
+export function TaskAlert({
+ name,
+ kind = IntegrationKind.AwsOidc,
+}: {
+ name: string;
+ kind?: IntegrationKind;
+}) {
+ const history = useHistory();
+ // todo (michellescripts) should we show the banner if there is an error
+ const [tasksAttempt, fetchTasks] = useAsync(() =>
+ integrationService.fetchIntegrationUserTasksList(name, TaskState.Open)
+ );
+
+ useEffect(() => {
+ fetchTasks();
+ }, []);
+
+ const pendingTasksCount =
+ (tasksAttempt.status === 'success' &&
+ tasksAttempt.data.items?.filter(t => t.state === TaskState.Open)
+ .length) ||
+ 0;
+
+ if (!pendingTasksCount) {
+ return null;
+ }
+
+ return (
+
+ Resolve Now
+
+ >
+ ),
+ onClick: () => history.push(cfg.getIntegrationTasksRoute(kind, name)),
+ }}
+ >
+ {pendingTasksCount} Pending Tasks
+
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.story.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.story.tsx
new file mode 100644
index 0000000000000..f69f2c295c415
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.story.tsx
@@ -0,0 +1,193 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { http, HttpResponse } from 'msw';
+
+import cfg from 'teleport/config';
+import { TaskState } from 'teleport/Integrations/status/AwsOidc/Tasks/constants';
+import { MockAwsOidcStatusProvider } from 'teleport/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider';
+import { IntegrationKind } from 'teleport/services/integrations';
+
+import { makeAwsOidcStatusContextState } from '../testHelpers/makeAwsOidcStatusContextState';
+import { Tasks } from './Tasks';
+
+export default {
+ title: 'Teleport/Integrations/AwsOidc/Tasks',
+};
+
+const integrationName = 'integration-story';
+
+// Empty tasks table
+export function TasksEmpty() {
+ return (
+
+
+
+ );
+}
+
+// Populated tasks table
+export function TaskView() {
+ return (
+
+
+
+ );
+}
+
+TaskView.parameters = {
+ msw: {
+ handlers: [
+ http.get(
+ cfg.getIntegrationUserTasksListUrl(integrationName, TaskState.Open),
+ () => {
+ return HttpResponse.json({
+ items: [
+ {
+ name: 'rds-detail',
+ taskType: 'discover-rds',
+ state: TaskState.Open,
+ issueType: 'rds-generic',
+ integration: integrationName,
+ lastStateChange: '2022-02-12T20:32:19.482607921Z',
+ },
+ {
+ name: 'ec2-detail',
+ taskType: 'discover-ec2',
+ state: TaskState.Open,
+ issueType: 'ec2-ssm-invocation-failure',
+ integration: integrationName,
+ lastStateChange: '2025-02-11T20:32:19.482607921Z',
+ },
+ {
+ name: 'ec2-detail',
+ taskType: 'discover-ec2',
+ state: TaskState.Open,
+ issueType: 'ec2-ssm-agent-not-registered',
+ integration: integrationName,
+ lastStateChange: '2025-02-11T20:32:13.61608091Z',
+ },
+ {
+ name: 'no match',
+ state: TaskState.Open,
+ issueType: 'side panel error',
+ integration: integrationName,
+ lastStateChange: '0',
+ },
+ {
+ name: 'eks-detail',
+ taskType: 'discover-eks',
+ state: TaskState.Open,
+ issueType: 'eks-failure',
+ integration: integrationName,
+ lastStateChange: '2025-02-11T20:32:13.61608091Z',
+ },
+ ],
+ nextKey: '1',
+ });
+ }
+ ),
+ http.get(cfg.getUserTaskUrl('ec2-detail'), () => {
+ return HttpResponse.json(ec2Detail);
+ }),
+ http.get(cfg.getUserTaskUrl('rds-detail'), () => {
+ return HttpResponse.json(rdsDetail);
+ }),
+ http.get(cfg.getUserTaskUrl('eks-detail'), () => {
+ return HttpResponse.json(eksDetail);
+ }),
+ ],
+ },
+};
+
+const ec2Detail = {
+ name: 'df4d8288-7106-5a50-bb50-4b5858e48ad5',
+ taskType: 'discover-ec2',
+ state: 'OPEN',
+ integration: integrationName,
+ lastStateChange: '2025-02-11T20:32:19.482607921Z',
+ issueType: 'ec2-ssm-invocation-failure',
+ description:
+ 'Teleport failed to access the SSM Agent to auto enroll the instance.\nSome instances failed to communicate with the AWS Systems Manager service to execute the install script.\n\nUsually this happens when:\n\n**Missing policies**\n\nThe IAM Role used by the integration might be missing some required permissions.\nEnsure the following actions are allowed in the IAM Role used by the integration:\n- `ec2:DescribeInstances`\n- `ssm:DescribeInstanceInformation`\n- `ssm:GetCommandInvocation`\n- `ssm:ListCommandInvocations`\n- `ssm:SendCommand`\n\n**SSM Document is invalid**\n\nTeleport uses an SSM Document to run an installation script.\nIf the document is changed or removed, it might no longer work.',
+ discoverEc2: {
+ region: 'us-east-2',
+ instances: {
+ 'i-016e32a5882f5ee81': {
+ instance_id: 'i-016e32a5882f5ee81',
+ },
+ 'i-065818031835365cc': {
+ instance_id: 'i-065818031835365cc',
+ name: 'aws-test',
+ },
+ },
+ },
+};
+
+const rdsDetail = {
+ name: 'df4d8288-7106-5a50-bb50-4b5858e48ad5',
+ taskType: 'discover-rds',
+ state: 'OPEN',
+ integration: integrationName,
+ lastStateChange: '2025-02-11T20:32:19.482607921Z',
+ issueType: 'rds-failure',
+ description:
+ 'The Teleport Database Service uses [IAM authentication](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html) to communicate with RDS.\n',
+ discoverRds: {
+ databases: {
+ 'i-016e32a5882f5ee81': {
+ name: 'i-016e32a5882f5ee81',
+ },
+ 'i-065818031835365cc': {
+ name: 'i-065818031835365cc',
+ },
+ },
+ },
+};
+
+const eksDetail = {
+ name: 'df4d8288-7106-5a50-bb50-4b5858e48ad5',
+ taskType: 'discover-eks',
+ state: 'OPEN',
+ integration: integrationName,
+ lastStateChange: '2025-02-11T20:32:19.482607921Z',
+ issueType: 'eks-failure',
+ description:
+ 'Only EKS Clusters whose status is active can be automatically enrolled into teleport.\n',
+ discoverEks: {
+ clusters: {
+ 'i-016e32a5882f5ee81': {
+ name: 'i-016e32a5882f5ee81',
+ },
+ 'i-065818031835365cc': {
+ name: 'i-065818031835365cc',
+ },
+ },
+ },
+};
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.test.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.test.tsx
new file mode 100644
index 0000000000000..e1806d98a897a
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.test.tsx
@@ -0,0 +1,80 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+import { createMemoryHistory } from 'history';
+import { Router } from 'react-router';
+
+import { render, screen, userEvent, waitFor } from 'design/utils/testing';
+
+import { ContextProvider } from 'teleport';
+import { Route } from 'teleport/components/Router';
+import cfg from 'teleport/config';
+import { Tasks } from 'teleport/Integrations/status/AwsOidc/Tasks/Tasks';
+import { makeAwsOidcStatusContextState } from 'teleport/Integrations/status/AwsOidc/testHelpers/makeAwsOidcStatusContextState';
+import { awsOidcStatusContext } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';
+import {
+ IntegrationKind,
+ integrationService,
+} from 'teleport/services/integrations';
+import TeleportContext from 'teleport/teleportContext';
+
+const integrationName = 'integration-test';
+
+test('deep links an open task', async () => {
+ const ctx = new TeleportContext();
+ jest
+ .spyOn(integrationService, 'fetchIntegrationUserTasksList')
+ .mockResolvedValue({
+ items: [
+ {
+ name: 'df4d8288-7106-5a50-bb50-4b5858e48ad5',
+ taskType: 'discover-rds',
+ state: 'OPEN',
+ integration: integrationName,
+ lastStateChange: '2025-02-11T20:32:19.482607921Z',
+ issueType: 'rds-failure',
+ },
+ ],
+ nextKey: 'next',
+ });
+
+ const history = createMemoryHistory({
+ initialEntries: [
+ cfg.getIntegrationTasksRoute(IntegrationKind.AwsOidc, integrationName),
+ ],
+ });
+ history.replace = jest.fn();
+
+ render(
+
+
+
+ } />
+
+
+
+ );
+
+ await screen.findAllByText('Pending Tasks');
+ await userEvent.click(screen.getByText('rds-failure'));
+
+ await waitFor(() =>
+ expect(history.replace).toHaveBeenCalledWith(
+ '/web/integrations/status/aws-oidc/integration-test/tasks?task=df4d8288-7106-5a50-bb50-4b5858e48ad5'
+ )
+ );
+});
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.tsx
new file mode 100644
index 0000000000000..52f435750a8a3
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/Tasks.tsx
@@ -0,0 +1,225 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { useEffect, useState } from 'react';
+import { useHistory } from 'react-router';
+import styled, { useTheme } from 'styled-components';
+
+import { ButtonBorder, Flex, Indicator } from 'design';
+import { Danger } from 'design/Alert';
+import Table, { Cell } from 'design/DataTable';
+import { Notification, NotificationItem } from 'shared/components/Notification';
+
+import { useServerSidePagination } from 'teleport/components/hooks';
+import { FeatureBox } from 'teleport/components/Layout';
+import { AwsOidcHeader } from 'teleport/Integrations/status/AwsOidc/AwsOidcHeader';
+import { AwsOidcTitle } from 'teleport/Integrations/status/AwsOidc/AwsOidcTitle';
+import { getResourceType } from 'teleport/Integrations/status/AwsOidc/helpers';
+import { TaskState } from 'teleport/Integrations/status/AwsOidc/Tasks/constants';
+import { Task } from 'teleport/Integrations/status/AwsOidc/Tasks/Task';
+import { useAwsOidcStatus } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';
+import { integrationService, UserTask } from 'teleport/services/integrations';
+
+export function Tasks() {
+ const theme = useTheme();
+ const history = useHistory();
+ const searchParams = new URLSearchParams(history.location.search);
+ const taskName = searchParams.get('task');
+ const [notification, setNotification] = useState();
+
+ const { integrationAttempt } = useAwsOidcStatus();
+ const { data: integration } = integrationAttempt;
+ const [selectedTask, setSelectedTask] = useState('');
+
+ const serverSidePagination = useServerSidePagination({
+ pageSize: 20,
+ fetchFunc: async () => {
+ const { items, nextKey } =
+ await integrationService.fetchIntegrationUserTasksList(
+ integration.name,
+ TaskState.Open
+ );
+ return { agents: items, nextKey };
+ },
+ clusterId: '',
+ params: {},
+ });
+
+ useEffect(() => {
+ serverSidePagination.fetch();
+ }, [integration]);
+
+ // use updated query params to set/unset the task side panel
+ useEffect(() => {
+ if (
+ taskName &&
+ taskName !== '' &&
+ serverSidePagination.fetchedData.agents &&
+ selectedTask === ''
+ ) {
+ setSelectedTask(taskName);
+ } else {
+ setSelectedTask('');
+ }
+ }, [taskName, serverSidePagination?.fetchedData]);
+
+ if (integrationAttempt.status === 'processing') {
+ return ;
+ }
+
+ if (serverSidePagination.attempt.status === 'processing') {
+ return ;
+ }
+
+ if (integrationAttempt.status === 'error') {
+ return {integrationAttempt.statusText};
+ }
+
+ if (serverSidePagination.attempt.status === 'failed') {
+ return {serverSidePagination.attempt.statusText};
+ }
+
+ function closeTask(resolved: boolean) {
+ if (resolved) {
+ // If there are multiple pages, we would rather refresh the table with X results rather than
+ // use modifyFetchedData to remove the item.
+ serverSidePagination.fetch();
+
+ setNotification({
+ content: {
+ description:
+ 'The task has been marked as resolved; it will reappear in the table if the issue persists after the next sync.',
+ },
+ severity: 'success',
+ id: taskName,
+ });
+ }
+ history.replace(history.location.pathname);
+ }
+
+ function openTask(task: UserTask) {
+ if (selectedTask != '') {
+ return;
+ }
+ const urlParams = new URLSearchParams();
+ urlParams.append('task', task.name);
+ history.replace(`${history.location.pathname}?${urlParams.toString()}`);
+ }
+
+ return (
+
+
+ {integration && (
+
+ )}
+
+ {integration && (
+
+ )}
+
+ data={serverSidePagination.fetchedData?.agents || []}
+ row={{
+ onClick: row => {
+ if (selectedTask === '') {
+ openTask(row);
+ }
+ },
+ getStyle: (row: UserTask) => {
+ if (selectedTask === '') {
+ return { cursor: 'pointer' };
+ }
+ if (row.name === selectedTask) {
+ return {
+ backgroundColor: theme.colors.interactive.tonal.primary[0],
+ };
+ }
+ },
+ }}
+ columns={[
+ {
+ key: 'taskType',
+ headerText: 'Type',
+ render: item => (
+ {getResourceType(item.taskType).toUpperCase()} |
+ ),
+ },
+ {
+ key: 'issueType',
+ headerText: 'Issue Details',
+ },
+ {
+ key: 'lastStateChange',
+ headerText: 'Timestamp (UTC)',
+ render: item => (
+ {new Date(item.lastStateChange).toISOString()} |
+ ),
+ },
+ {
+ altKey: 'action',
+ headerText: 'Actions',
+ render: item => (
+
+ openTask(item)}
+ disabled={selectedTask != ''}
+ size="small"
+ >
+ View
+
+ |
+ ),
+ },
+ ]}
+ emptyText={`No pending tasks`}
+ pagination={{
+ pageSize: serverSidePagination.pageSize,
+ pagerPosition: 'both',
+ }}
+ fetching={{
+ fetchStatus: serverSidePagination.fetchStatus,
+ onFetchNext: serverSidePagination.fetchNext,
+ onFetchPrev: serverSidePagination.fetchPrev,
+ }}
+ />
+ {notification && (
+
+ setNotification(undefined)}
+ minWidth="432px"
+ />
+
+ )}
+
+
+ {selectedTask && }
+
+ );
+}
+
+const NotificationContainer = styled.div`
+ position: absolute;
+ top: ${props => props.theme.space[10]}px;
+ right: ${props => props.theme.space[5]}px;
+`;
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/constants.ts b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/constants.ts
new file mode 100644
index 0000000000000..206d79101c536
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Tasks/constants.ts
@@ -0,0 +1,22 @@
+/**
+ * Teleport
+ * Copyright (C) 2025 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+export enum TaskState {
+ Open = 'OPEN',
+ Resolved = 'RESOLVED',
+}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/helpers.ts b/web/packages/teleport/src/Integrations/status/AwsOidc/helpers.ts
new file mode 100644
index 0000000000000..6ea5ec62a159c
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/helpers.ts
@@ -0,0 +1,34 @@
+/**
+ * Teleport
+ * Copyright (C) 2025 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+
+export function getResourceType(type: string): AwsResource {
+ switch (type) {
+ case 'ec2':
+ case 'discover-ec2':
+ return AwsResource.ec2;
+ case 'eks':
+ case 'discover-eks':
+ return AwsResource.eks;
+ case 'rds':
+ case 'discover-rds':
+ default:
+ return AwsResource.rds;
+ }
+}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/testHelpers/makeAwsOidcStatusContextState.ts b/web/packages/teleport/src/Integrations/status/AwsOidc/testHelpers/makeAwsOidcStatusContextState.ts
new file mode 100644
index 0000000000000..335236fa8a75f
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/testHelpers/makeAwsOidcStatusContextState.ts
@@ -0,0 +1,75 @@
+/**
+ * Teleport
+ * Copyright (C) 2025 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { addHours } from 'date-fns';
+
+import { makeSuccessAttempt } from 'shared/hooks/useAsync';
+
+import { AwsOidcStatusContextState } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';
+import {
+ IntegrationKind,
+ ResourceTypeSummary,
+} from 'teleport/services/integrations';
+
+export function makeAwsOidcStatusContextState(
+ overrides: Partial = {}
+): AwsOidcStatusContextState {
+ return Object.assign(
+ {
+ integrationAttempt: makeSuccessAttempt({
+ resourceType: 'integration',
+ name: 'integration-one',
+ kind: IntegrationKind.AwsOidc,
+ spec: {
+ roleArn: 'arn:aws:iam::111456789011:role/bar',
+ },
+ statusCode: 1,
+ }),
+ statsAttempt: makeSuccessAttempt({
+ name: 'integration-one',
+ subKind: IntegrationKind.AwsOidc,
+ awsoidc: {
+ roleArn: 'arn:aws:iam::111456789011:role/bar',
+ },
+ awsec2: makeResourceTypeSummary(),
+ awsrds: makeResourceTypeSummary(),
+ awseks: makeResourceTypeSummary(),
+ }),
+ },
+ overrides
+ );
+}
+
+function makeResourceTypeSummary(
+ overrides: Partial = {}
+): ResourceTypeSummary {
+ return Object.assign(
+ {
+ rulesCount: Math.floor(Math.random() * 100),
+ resourcesFound: Math.floor(Math.random() * 100),
+ resourcesEnrollmentFailed: Math.floor(Math.random() * 100),
+ resourcesEnrollmentSuccess: Math.floor(Math.random() * 100),
+ discoverLastSync: addHours(
+ new Date().getTime(),
+ -Math.floor(Math.random() * 100)
+ ),
+ ecsDatabaseServiceCount: Math.floor(Math.random() * 100),
+ },
+ overrides
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/testHelpers/makeIntegrationDiscoveryRule.ts b/web/packages/teleport/src/Integrations/status/AwsOidc/testHelpers/makeIntegrationDiscoveryRule.ts
new file mode 100644
index 0000000000000..231eed4c8c69c
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/testHelpers/makeIntegrationDiscoveryRule.ts
@@ -0,0 +1,34 @@
+/**
+ * Teleport
+ * Copyright (C) 2025 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { IntegrationDiscoveryRule } from 'teleport/services/integrations';
+
+export function makeIntegrationDiscoveryRule(
+ overrides: Partial = {}
+): IntegrationDiscoveryRule {
+ return Object.assign(
+ {
+ resourceType: '',
+ region: '',
+ labelMatcher: [],
+ discoveryConfig: '',
+ lastSync: 0,
+ },
+ overrides
+ );
+}
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider.tsx
index 0a513d3d0b57f..fa9f86bc2fc6d 100644
--- a/web/packages/teleport/src/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider.tsx
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider.tsx
@@ -20,6 +20,7 @@ import React from 'react';
import { MemoryRouter } from 'react-router';
import { ContextProvider } from 'teleport';
+import { Route } from 'teleport/components/Router';
import {
awsOidcStatusContext,
AwsOidcStatusContextState,
@@ -29,17 +30,21 @@ import { createTeleportContext } from 'teleport/mocks/contexts';
export const MockAwsOidcStatusProvider = ({
children,
value,
+ initialEntries,
+ path,
}: {
children?: React.ReactNode;
value: AwsOidcStatusContextState;
+ path: string;
+ initialEntries?: string[];
}) => {
const ctx = createTeleportContext();
return (
-
+
- {children}
+ <>{children}>} />
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/useAwsOidcStatus.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/useAwsOidcStatus.tsx
index 3593bd916a3d4..aaf0acfd8b9e1 100644
--- a/web/packages/teleport/src/Integrations/status/AwsOidc/useAwsOidcStatus.tsx
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/useAwsOidcStatus.tsx
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import React, { createContext, useContext, useEffect } from 'react';
+import { createContext, useContext, useEffect } from 'react';
import { useParams } from 'react-router';
import { Attempt, useAsync } from 'shared/hooks/useAsync';
diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts
index aab94e2591476..b055a011cf982 100644
--- a/web/packages/teleport/src/config.ts
+++ b/web/packages/teleport/src/config.ts
@@ -28,6 +28,8 @@ import {
} from 'shared/services';
import { mergeDeep } from 'shared/utils/highbar';
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+import { TaskState } from 'teleport/Integrations/status/AwsOidc/Tasks/constants';
import type { SortType } from 'teleport/services/agents';
import {
AwsOidcPolicyPreset,
@@ -204,6 +206,9 @@ const cfg = {
headlessSso: `/web/headless/:requestId`,
integrations: '/web/integrations',
integrationStatus: '/web/integrations/status/:type/:name',
+ integrationTasks: '/web/integrations/status/:type/:name/tasks',
+ integrationStatusResources:
+ '/web/integrations/status/:type/:name/resources/:resourceKind',
integrationEnroll: '/web/integrations/new/:type?',
locks: '/web/locks',
newLock: '/web/locks/new',
@@ -359,6 +364,15 @@ const cfg = {
integrationsPath: '/v1/webapi/sites/:clusterId/integrations/:name?',
integrationStatsPath:
'/v1/webapi/sites/:clusterId/integrations/:name/stats',
+ integrationRulesPath:
+ '/v1/webapi/sites/:clusterId/integrations/:name/discoveryrules?resourceType=:resourceType?&startKey=:startKey?&query=:query?&search=:search?&sort=:sort?&limit=:limit?®ions=:regions?',
+ awsOidcDatabaseServicesPath:
+ '/v1/webapi/sites/:clusterId/integrations/aws-oidc/:name/listdeployeddatabaseservices?resourceType=:resourceType?®ions=:regions?',
+ userTaskListByIntegrationPath:
+ '/v1/webapi/sites/:clusterId/usertask?integration=:name?&state=:state?',
+ userTaskPath: '/v1/webapi/sites/:clusterId/usertask/:name',
+ resolveUserTaskPath: '/v1/webapi/sites/:clusterId/usertask/:name/state',
+
thumbprintPath: '/v1/webapi/thumbprint',
pingAwsOidcIntegrationPath:
'/v1/webapi/sites/:clusterId/integrations/aws-oidc/:name/ping',
@@ -590,6 +604,22 @@ const cfg = {
return generatePath(cfg.routes.integrationStatus, { type, name });
},
+ getIntegrationStatusResourcesRoute(
+ type: PluginKind | IntegrationKind,
+ name: string,
+ resourceKind: AwsResource
+ ) {
+ return generatePath(cfg.routes.integrationStatusResources, {
+ type,
+ name,
+ resourceKind,
+ });
+ },
+
+ getIntegrationTasksRoute(type: PluginKind | IntegrationKind, name: string) {
+ return generatePath(cfg.routes.integrationTasks, { type, name });
+ },
+
getMsTeamsAppZipRoute(clusterId: string, plugin: string) {
return generatePath(cfg.api.msTeamsAppZipPath, { clusterId, plugin });
},
@@ -1082,6 +1112,59 @@ const cfg = {
});
},
+ getIntegrationRulesUrl(
+ name: string,
+ resourceType: AwsResource,
+ regions?: string[]
+ ) {
+ const clusterId = cfg.proxyCluster;
+ return generateResourcePath(cfg.api.integrationRulesPath, {
+ clusterId,
+ name,
+ resourceType,
+ regions,
+ });
+ },
+
+ getAwsOidcDatabaseServices(
+ name: string,
+ resourceType: AwsResource,
+ regions: string[]
+ ) {
+ const clusterId = cfg.proxyCluster;
+ return generateResourcePath(cfg.api.awsOidcDatabaseServicesPath, {
+ clusterId,
+ name,
+ resourceType,
+ regions,
+ });
+ },
+
+ getIntegrationUserTasksListUrl(name: string, state: TaskState) {
+ const clusterId = cfg.proxyCluster;
+ return generatePath(cfg.api.userTaskListByIntegrationPath, {
+ clusterId,
+ name,
+ state,
+ });
+ },
+
+ getUserTaskUrl(name: string) {
+ const clusterId = cfg.proxyCluster;
+ return generatePath(cfg.api.userTaskPath, {
+ clusterId,
+ name,
+ });
+ },
+
+ getResolveUserTaskUrl(name: string) {
+ const clusterId = cfg.proxyCluster;
+ return generatePath(cfg.api.resolveUserTaskPath, {
+ clusterId,
+ name,
+ });
+ },
+
getPingAwsOidcIntegrationUrl({
integrationName,
clusterId,
@@ -1466,6 +1549,12 @@ export interface UrlKubeResourcesParams {
kind: Omit;
}
+export interface UrlIntegrationParams {
+ name?: string;
+ resourceType?: string;
+ regions?: string[];
+}
+
export interface UrlDeployServiceIamConfigureScriptParams {
integrationName: string;
region: Regions;
diff --git a/web/packages/teleport/src/generateResourcePath.test.ts b/web/packages/teleport/src/generateResourcePath.test.ts
index cead245d24005..937a55c9599df 100644
--- a/web/packages/teleport/src/generateResourcePath.test.ts
+++ b/web/packages/teleport/src/generateResourcePath.test.ts
@@ -16,69 +16,107 @@
* along with this program. If not, see .
*/
-import cfg, { UrlKubeResourcesParams, UrlResourcesParams } from './config';
+import {
+ UrlIntegrationParams,
+ UrlKubeResourcesParams,
+ UrlResourcesParams,
+} from './config';
import generateResourcePath from './generateResourcePath';
-test('undefined params are set to empty string', () => {
+const fullParamPath =
+ '/v1/webapi/sites/:clusterId/:name/foo' +
+ '?kind=:kind?' +
+ '&kinds=:kinds?' +
+ '&kubeCluster=:kubeCluster?' +
+ '&kubeNamespace=:kubeNamespace?' +
+ '&limit=:limit?' +
+ '&pinnedOnly=:pinnedOnly?' +
+ '&query=:query?' +
+ '&resourceType=:resourceType?' +
+ '&search=:search?' +
+ '&searchAsRoles=:searchAsRoles?' +
+ '&sort=:sort?' +
+ '&startKey=:startKey?' +
+ '&includedResourceMode=:includedResourceMode?' +
+ '®ions=:regions?';
+
+test('undefined params are set to empty', () => {
expect(
- generateResourcePath(cfg.api.unifiedResourcesPath, { clusterId: 'cluster' })
+ generateResourcePath(fullParamPath, {
+ clusterId: 'some-cluster-id',
+ })
).toStrictEqual(
- '/v1/webapi/sites/cluster/resources?searchAsRoles=&limit=&startKey=&kinds=&query=&search=&sort=&pinnedOnly=&includedResourceMode='
+ '/v1/webapi/sites/some-cluster-id//foo?kind=&kinds=&kubeCluster=&kubeNamespace=&limit=&pinnedOnly=&query=&resourceType=&search=&searchAsRoles=&sort=&startKey=&includedResourceMode=®ions='
);
});
+type allParams = UrlResourcesParams &
+ UrlKubeResourcesParams &
+ UrlIntegrationParams;
+
test('defined params are set', () => {
- const unifiedParams: UrlResourcesParams = {
- query: 'query',
- search: 'search',
- sort: { fieldName: 'field', dir: 'DESC' },
+ const urlParams: allParams = {
+ includedResourceMode: 'all',
+ kind: 'some-kind',
+ kinds: ['app', 'db'],
+ kubeCluster: 'some-kube-cluster',
+ kubeNamespace: 'some-kube-namespace',
limit: 100,
- startKey: 'startkey',
- searchAsRoles: 'yes',
+ name: 'some-name',
pinnedOnly: true,
- includedResourceMode: 'all',
- kinds: ['app'],
+ query: 'some-query',
+ resourceType: 'some-resource-type',
+ search: 'some-search',
+ searchAsRoles: 'yes',
+ sort: { fieldName: 'sort-field', dir: 'DESC' },
+ startKey: 'some-start-key',
+ regions: ['us-west-2', 'af-south-1'],
};
expect(
- generateResourcePath(cfg.api.unifiedResourcesPath, {
- clusterId: 'cluster',
- ...unifiedParams,
+ generateResourcePath(fullParamPath, {
+ clusterId: 'some-cluster-id',
+ ...urlParams,
})
).toStrictEqual(
- '/v1/webapi/sites/cluster/resources?searchAsRoles=yes&limit=100&startKey=startkey&kinds=app&query=query&search=search&sort=field:desc&pinnedOnly=true&includedResourceMode=all'
+ '/v1/webapi/sites/some-cluster-id/some-name/foo?kind=some-kind&kinds=app&kinds=db&kubeCluster=some-kube-cluster&kubeNamespace=some-kube-namespace&limit=100&pinnedOnly=true&query=some-query&resourceType=some-resource-type&search=some-search&searchAsRoles=yes&sort=sort-field:desc&startKey=some-start-key&includedResourceMode=all®ions=us-west-2®ions=af-south-1'
);
});
-test('defined params but set to empty values are set to empty string', () => {
- const unifiedParams: UrlResourcesParams = {
- query: '',
- search: null,
- limit: 0,
- pinnedOnly: false,
+test('defined params but set to empty values are set to empty', () => {
+ const urlParams: allParams = {
+ includedResourceMode: null,
+ kind: '',
kinds: [],
+ kubeCluster: '',
+ kubeNamespace: '',
+ limit: 0,
+ name: '',
+ pinnedOnly: null,
+ query: '',
+ resourceType: '',
+ search: '',
+ searchAsRoles: '',
+ sort: null,
+ startKey: '',
+ regions: [],
};
expect(
- generateResourcePath(cfg.api.unifiedResourcesPath, {
- clusterId: 'cluster',
- ...unifiedParams,
+ generateResourcePath(fullParamPath, {
+ clusterId: 'some-cluster-id',
+ ...urlParams,
})
).toStrictEqual(
- '/v1/webapi/sites/cluster/resources?searchAsRoles=&limit=&startKey=&kinds=&query=&search=&sort=&pinnedOnly=&includedResourceMode='
+ '/v1/webapi/sites/some-cluster-id//foo?kind=&kinds=&kubeCluster=&kubeNamespace=&limit=&pinnedOnly=&query=&resourceType=&search=&searchAsRoles=&sort=&startKey=&includedResourceMode=®ions='
);
});
-test('defined kube related params are set', () => {
- const params: UrlKubeResourcesParams = {
- kind: 'namespace',
- kubeCluster: 'kubecluster',
- kubeNamespace: 'kubenamespace',
- };
+test('unknown key values are not set even if declared in path', () => {
+ let unknownParamPath = '/v1/webapi/sites/view?foo=:foo?&bar=:bar?&baz=:baz?';
expect(
- generateResourcePath(cfg.api.kubernetesResourcesPath, {
- clusterId: 'cluster',
- ...params,
+ generateResourcePath(unknownParamPath, {
+ foo: 'some-foo',
+ bar: 'some-bar',
+ baz: 'some-baz',
})
- ).toStrictEqual(
- '/v1/webapi/sites/cluster/kubernetes/resources?searchAsRoles=&limit=&startKey=&query=&search=&sort=&kubeCluster=kubecluster&kubeNamespace=kubenamespace&kind=namespace'
- );
+ ).toStrictEqual(unknownParamPath);
});
diff --git a/web/packages/teleport/src/generateResourcePath.ts b/web/packages/teleport/src/generateResourcePath.ts
index 908299cde6d88..d3245b2dea653 100644
--- a/web/packages/teleport/src/generateResourcePath.ts
+++ b/web/packages/teleport/src/generateResourcePath.ts
@@ -34,6 +34,8 @@ export default function generateResourcePath(
].dir.toLowerCase()}`;
} else if (param === 'kinds') {
processedParams[param] = (params[param] ?? []).join('&kinds=');
+ } else if (param === 'regions') {
+ processedParams[param] = (params[param] ?? []).join('®ions=');
} else
processedParams[param] = params[param]
? encodeURIComponent(params[param])
@@ -49,18 +51,23 @@ export default function generateResourcePath(
}
const output = path
+ // non-param
.replace(':clusterId', params.clusterId)
- .replace(':limit?', params.limit || '')
- .replace(':startKey?', params.startKey || '')
- .replace(':query?', processedParams.query || '')
- .replace(':search?', processedParams.search || '')
- .replace(':searchAsRoles?', processedParams.searchAsRoles || '')
- .replace(':sort?', processedParams.sort || '')
+ .replace(':name', params.name || '')
+ // param
.replace(':kind?', processedParams.kind || '')
.replace(':kinds?', processedParams.kinds || '')
.replace(':kubeCluster?', processedParams.kubeCluster || '')
.replace(':kubeNamespace?', processedParams.kubeNamespace || '')
+ .replace(':limit?', params.limit || '')
.replace(':pinnedOnly?', processedParams.pinnedOnly || '')
+ .replace(':query?', processedParams.query || '')
+ .replace(':resourceType?', params.resourceType || '')
+ .replace(':search?', processedParams.search || '')
+ .replace(':searchAsRoles?', processedParams.searchAsRoles || '')
+ .replace(':sort?', processedParams.sort || '')
+ .replace(':startKey?', params.startKey || '')
+ .replace(':regions?', processedParams.regions || '')
.replace(
':includedResourceMode?',
processedParams.includedResourceMode || ''
diff --git a/web/packages/teleport/src/services/integrations/integrations.test.ts b/web/packages/teleport/src/services/integrations/integrations.test.ts
index 1bb526752e29d..7b9b24accdf80 100644
--- a/web/packages/teleport/src/services/integrations/integrations.test.ts
+++ b/web/packages/teleport/src/services/integrations/integrations.test.ts
@@ -17,6 +17,8 @@
*/
import cfg from 'teleport/config';
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+import { TaskState } from 'teleport/Integrations/status/AwsOidc/Tasks/constants';
import api from 'teleport/services/api';
import { integrationService } from './integrations';
@@ -234,6 +236,102 @@ describe('fetchAwsDatabases() request body formatting', () => {
);
});
+test('fetch integration rules: fetchIntegrationRules()', async () => {
+ // test a valid response
+ jest.spyOn(api, 'get').mockResolvedValue({
+ rules: [
+ {
+ resourceType: 'eks',
+ region: 'us-west-2',
+ labelMatcher: [{ name: 'env', value: 'dev' }],
+ discoveryConfig: 'cfg',
+ lastSync: 1733782634,
+ },
+ ],
+ nextKey: 'some-key',
+ });
+
+ let response = await integrationService.fetchIntegrationRules(
+ 'name',
+ AwsResource.eks
+ );
+ expect(api.get).toHaveBeenCalledWith(
+ cfg.getIntegrationRulesUrl('name', AwsResource.eks, [])
+ );
+ expect(response).toEqual({
+ nextKey: 'some-key',
+ rules: [
+ {
+ resourceType: 'eks',
+ region: 'us-west-2',
+ labelMatcher: [{ name: 'env', value: 'dev' }],
+ discoveryConfig: 'cfg',
+ lastSync: 1733782634,
+ },
+ ],
+ });
+
+ // test null response
+ jest.spyOn(api, 'get').mockResolvedValue(null);
+
+ response = await integrationService.fetchIntegrationRules(
+ 'name',
+ AwsResource.eks
+ );
+ expect(response).toEqual({
+ nextKey: undefined,
+ rules: [],
+ });
+});
+
+test('fetch integration user task list: fetchIntegrationUserTasksList()', async () => {
+ // test a valid response
+ jest.spyOn(api, 'get').mockResolvedValue({
+ items: [
+ {
+ name: 'task-name',
+ taskType: 'task-type',
+ state: 'task-state',
+ issueType: 'issue-type',
+ integration: 'name',
+ },
+ ],
+ nextKey: 'some-key',
+ });
+
+ let response = await integrationService.fetchIntegrationUserTasksList(
+ 'name',
+ TaskState.Open
+ );
+ expect(api.get).toHaveBeenCalledWith(
+ cfg.getIntegrationUserTasksListUrl('name', TaskState.Open)
+ );
+ expect(response).toEqual({
+ nextKey: 'some-key',
+ items: [
+ {
+ name: 'task-name',
+ taskType: 'task-type',
+ state: 'task-state',
+ issueType: 'issue-type',
+ integration: 'name',
+ },
+ ],
+ });
+
+ // test null response
+ jest.spyOn(api, 'get').mockResolvedValue(null);
+
+ response = await integrationService.fetchIntegrationUserTasksList(
+ 'name',
+ TaskState.Open
+ );
+ expect(response).toEqual({
+ nextKey: undefined,
+ items: [],
+ });
+});
+
const nonAwsOidcIntegration = {
name: 'non-aws-oidc-integration',
subKind: 'abc',
diff --git a/web/packages/teleport/src/services/integrations/integrations.ts b/web/packages/teleport/src/services/integrations/integrations.ts
index 39a4ed25f5d45..6178a79cc8150 100644
--- a/web/packages/teleport/src/services/integrations/integrations.ts
+++ b/web/packages/teleport/src/services/integrations/integrations.ts
@@ -17,6 +17,8 @@
*/
import cfg from 'teleport/config';
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+import { TaskState } from 'teleport/Integrations/status/AwsOidc/Tasks/constants';
import api from 'teleport/services/api';
import { App } from '../apps';
@@ -27,8 +29,10 @@ import { withUnsupportedLabelFeatureErrorConversion } from '../version/unsupport
import {
AwsDatabaseVpcsResponse,
AwsOidcDeployDatabaseServicesRequest,
+ AWSOIDCDeployedDatabaseService,
AwsOidcDeployServiceRequest,
AwsOidcListDatabasesRequest,
+ AWSOIDCListDeployedDatabaseServiceResponse,
AwsOidcPingRequest,
AwsOidcPingResponse,
AwsRdsDatabase,
@@ -42,6 +46,7 @@ import {
Integration,
IntegrationCreateRequest,
IntegrationCreateResult,
+ IntegrationDiscoveryRules,
IntegrationKind,
IntegrationListResponse,
IntegrationStatusCode,
@@ -65,6 +70,9 @@ import {
SecurityGroup,
SecurityGroupRule,
Subnet,
+ UserTask,
+ UserTaskDetail,
+ UserTasksListResponse,
} from './types';
export const integrationService = {
@@ -493,8 +501,110 @@ export const integrationService = {
return resp;
});
},
+
+ fetchIntegrationRules(
+ name: string,
+ resourceType: AwsResource,
+ regions?: string[]
+ ): Promise {
+ return api
+ .get(cfg.getIntegrationRulesUrl(name, resourceType, regions))
+ .then(resp => {
+ return {
+ rules: resp?.rules || [],
+ nextKey: resp?.nextKey,
+ };
+ });
+ },
+
+ fetchAwsOidcDatabaseServices(
+ name: string,
+ resourceType: AwsResource,
+ regions: string[]
+ ): Promise {
+ return api
+ .post(cfg.getAwsOidcDatabaseServices(name, resourceType, regions), null)
+ .then(resp => {
+ return { services: makeDatabaseServices(resp) };
+ });
+ },
+
+ fetchIntegrationUserTasksList(
+ name: string,
+ state: TaskState
+ ): Promise {
+ return api
+ .get(cfg.getIntegrationUserTasksListUrl(name, state))
+ .then(resp => {
+ return {
+ items: resp?.items || [],
+ nextKey: resp?.nextKey,
+ };
+ });
+ },
+
+ fetchUserTask(name: string): Promise {
+ return api.get(cfg.getUserTaskUrl(name)).then(resp => {
+ return {
+ name: resp.name,
+ taskType: resp.taskType,
+ state: resp.state,
+ issueType: resp.issueType,
+ integration: resp.integration,
+ lastStateChange: resp.lastStateChange,
+ description: resp.description,
+ discoverEc2: {
+ instances: resp.discoverEc2?.instances,
+ accountId: resp.discoverEc2?.accountId,
+ region: resp.discoverEc2?.region,
+ ssmDocument: resp.discoverEc2?.ssmDocument,
+ installerScript: resp.discoverEc2?.installerScript,
+ },
+ discoverEks: {
+ clusters: resp.discoverEks?.instances,
+ accountId: resp.discoverEks?.accountId,
+ region: resp.discoverEks?.region,
+ appAutoDiscover: resp.discoverEks?.appAutoDiscover,
+ },
+ discoverRds: {
+ databases: resp.discoverRds?.instances,
+ accountId: resp.discoverRds?.accountId,
+ region: resp.discoverRds?.region,
+ },
+ };
+ });
+ },
+
+ resolveUserTask(name: string): Promise {
+ return api
+ .put(cfg.getResolveUserTaskUrl(name), {
+ state: TaskState.Resolved,
+ })
+ .then(resp => {
+ return {
+ name: resp.name,
+ taskType: resp.taskType,
+ state: resp.state,
+ issueType: resp.issueType,
+ integration: resp.integration,
+ lastStateChange: resp.lastStateChange,
+ };
+ });
+ },
};
+function makeDatabaseServices(json: any): AWSOIDCDeployedDatabaseService[] {
+ json = json ?? {};
+ const { services } = json;
+
+ return services?.map((service: AWSOIDCDeployedDatabaseService) => ({
+ name: service.name ?? '',
+ dashboardUrl: service.dashboardUrl ?? '',
+ validTeleportConfig: service.validTeleportConfig ?? false,
+ matchingLabels: service.matchingLabels ?? [],
+ }));
+}
+
export function makeIntegrations(json: any): Integration[] {
json = json || [];
return json.map(user => makeIntegration(user));
diff --git a/web/packages/teleport/src/services/integrations/types.ts b/web/packages/teleport/src/services/integrations/types.ts
index 6b31782967788..d4be092746a97 100644
--- a/web/packages/teleport/src/services/integrations/types.ts
+++ b/web/packages/teleport/src/services/integrations/types.ts
@@ -354,6 +354,159 @@ export type IntegrationWithSummary = {
awseks: ResourceTypeSummary;
};
+// IntegrationDiscoveryRules contains the list of discovery rules for a given Integration.
+export type IntegrationDiscoveryRules = {
+ // rules is the list of integration rules.
+ rules: IntegrationDiscoveryRule[];
+ // nextKey is the position to resume listing rules.
+ nextKey: string;
+};
+
+// UserTasksListResponse contains a list of UserTasks.
+// In case of exceeding the pagination limit (either via query param `limit` or the default 1000)
+// a `nextToken` is provided and should be used to obtain the next page (as a query param `startKey`)
+export type UserTasksListResponse = {
+ // items is a list of resources retrieved.
+ items: UserTask[];
+ // nextKey is the position to resume listing events.
+ nextKey: string;
+};
+
+// UserTask describes UserTask fields.
+// Used for listing User Tasks without receiving all the details.
+export type UserTask = {
+ // name is the UserTask name.
+ name: string;
+ // taskType identifies this task's type.
+ taskType: string;
+ // state is the state for the User Task.
+ state: string;
+ // issueType identifies this task's issue type.
+ issueType: string;
+ // integration is the Integration Name this User Task refers to.
+ integration: string;
+ // lastStateChange indicates when the current's user task state was last changed.
+ lastStateChange: string;
+};
+
+// UserTaskDetail contains all the details for a User Task.
+export type UserTaskDetail = UserTask & {
+ // description is a markdown document that explains the issue and how to fix it.
+ description: string;
+ // discoverEc2 contains the task details for the DiscoverEc2 tasks.
+ discoverEc2: DiscoverEc2;
+ // discoverEKS contains the task details for the DiscoverEKS tasks.
+ discoverEks: DiscoverEks;
+ // discoverRDS contains the task details for the DiscoverRDS tasks.
+ discoverRds: DiscoverRds;
+};
+
+// DiscoverEc2 contains the instances that failed to auto-enroll into the cluster.
+export type DiscoverEc2 = {
+ // instances maps an instance id to the result of enrolling that instance into teleport.
+ instances: Record;
+ // accountID is the AWS Account ID for the instances.
+ accountId: string;
+ // region is the AWS Region where Teleport failed to enroll EC2 instances.
+ region: string;
+ // ssmDocument is the Amazon Systems Manager SSM Document name that was used to install teleport on the instance.
+ // In Amazon console, the document is at:
+ // https://REGION.console.aws.amazon.com/systems-manager/documents/SSM_DOCUMENT/description
+ ssmDocument: string;
+ // installerScript is the Teleport installer script that was used to install teleport on the instance.
+ installerScript: string;
+};
+
+// DiscoverEc2Instance contains the result of enrolling an AWS EC2 Instance.
+export type DiscoverEc2Instance = {
+ // instanceID is the EC2 Instance ID that uniquely identifies the instance.
+ instance_id: string;
+ // name is the instance Name.
+ // Might be empty, if the instance doesn't have the Name tag.
+ name: string;
+ // invocationUrl is the url that points to the invocation.
+ // Empty if there was an error before installing the
+ invocationUrl: string;
+ // discoveryConfig is the discovery config name that originated this instance enrollment.
+ discoveryConfig: string;
+ // discoveryGroup is the DiscoveryGroup name that originated this task.
+ discoveryGroup: string;
+ // syncTime is the timestamp when the error was produced.
+ syncTime: number;
+};
+
+// DiscoverEks contains the clusters that failed to auto-enroll into the cluster.
+export type DiscoverEks = {
+ // clusters maps a cluster name to the result of enrolling that cluster into teleport.
+ clusters: Record;
+ // accountId is the AWS Account ID for the cluster.
+ accountId: string;
+ // region is the AWS Region where Teleport failed to enroll EKS Clusters.
+ region: string;
+ // appAutoDiscover indicates whether the Kubernetes agent should auto enroll HTTP services as Teleport Apps.
+ appAutoDiscover: boolean;
+};
+
+// DiscoverEksCluster contains the result of enrolling an AWS EKS Cluster.
+export type DiscoverEksCluster = {
+ // name is the cluster Name.
+ name: string;
+ // discoveryConfig is the discovery config name that originated this cluster enrollment.
+ discoveryConfig: string;
+ // discoveryGroup is the DiscoveryGroup name that originated this task.
+ discoveryGroup: string;
+ // syncTime is the timestamp when the error was produced.
+ syncTime: number;
+};
+
+// DiscoverRds contains the databases that failed to auto-enroll into teleport.
+export type DiscoverRds = {
+ // databases maps a database resource id to the result of enrolling that database into teleport.
+ // For RDS Aurora Clusters, this is the DBClusterIdentifier.
+ // For other RDS databases, this is the DBInstanceIdentifier.
+ databases: Record;
+ // accountId is the AWS Account ID for the database.
+ accountId: string;
+ // region is the AWS Region where Teleport failed to enroll RDS databases.
+ region: string;
+};
+
+// DiscoverRdsDatabase contains the result of enrolling an AWS RDS database.
+export type DiscoverRdsDatabase = {
+ // name is the database identifier.
+ // For RDS Aurora Clusters, this is the DBClusterIdentifier.
+ // For other RDS databases, this is the DBInstanceIdentifier.
+ name: string;
+ // isCluster indicates whether this database is a cluster or a single instance.
+ isCluster: boolean;
+ // engine indicates the engine name for this RDS.
+ // Eg, aurora-postgresql, postgresql
+ engine: string;
+ // discoveryConfig is the discovery config name that originated this database enrollment.
+ discoveryConfig: string;
+ // discoveryGroup is the DiscoveryGroup name that originated this task.
+ discoveryGroup: string;
+ // syncTime is the timestamp when the error was produced.
+ syncTime: number;
+};
+
+// IntegrationDiscoveryRule describes a discovery rule associated with an integration.
+export type IntegrationDiscoveryRule = {
+ // resourceType indicates the type of resource that this rule targets.
+ // This is the same value that is set in DiscoveryConfig.AWS..Types
+ // Example: ec2, rds, eks
+ resourceType: string;
+ // region where this rule applies to.
+ region: string;
+ // labelMatcher is the set of labels that are used to filter the resources before trying to auto-enroll them.
+ labelMatcher: Label[];
+ // discoveryConfig is the name of the DiscoveryConfig that created this rule.
+ discoveryConfig: string;
+ // lastSync contains the time when this rule was used.
+ // If empty, it indicates that the rule is not being used.
+ lastSync: number;
+};
+
// ResourceTypeSummary contains the summary of the enrollment rules and found resources by the integration.
export type ResourceTypeSummary = {
// rulesCount is the number of enrollment rules that are using this integration.
@@ -376,6 +529,26 @@ export type ResourceTypeSummary = {
ecsDatabaseServiceCount: number;
};
+// AWSOIDCListDeployedDatabaseServiceResponse is a list of Teleport Database Services that are deployed as ECS Services.
+export type AWSOIDCListDeployedDatabaseServiceResponse = {
+ // services are the ECS Services.
+ services: AWSOIDCDeployedDatabaseService[];
+};
+
+// AWSOIDCDeployedDatabaseService represents a Teleport Database Service that is deployed in Amazon ECS.
+export type AWSOIDCDeployedDatabaseService = {
+ // name is the ECS Service name.
+ name: string;
+ // dashboardUrl is the link to the ECS Service in Amazon Web Console.
+ dashboardUrl: string;
+ // validTeleportConfig returns whether this ECS Service has a valid Teleport Configuration for a deployed Database Service.
+ // ECS Services with non-valid configuration require the user to take action on them.
+ // No MatchingLabels are returned with an invalid configuration.
+ validTeleportConfig: boolean;
+ // matchingLabels are the labels that are used by the Teleport Database Service to know which databases it should proxy.
+ matchingLabels: Label[];
+};
+
// awsRegionMap maps the AWS regions to it's region name
// as defined in (omitted gov cloud regions):
// https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html