From c497ebdd579775e015f0f9135c16ae6286360597 Mon Sep 17 00:00:00 2001 From: Michelle Bergquist <11967646+michellescripts@users.noreply.github.com> Date: Tue, 10 Dec 2024 09:11:39 -0700 Subject: [PATCH] Show aws summary stats on dashboard (#49516) --- .../status/AwsOidc/AwsOidcDashboard.story.tsx | 186 ++++++++++++++++-- .../status/AwsOidc/AwsOidcDashboard.test.tsx | 87 +++++++- .../status/AwsOidc/AwsOidcDashboard.tsx | 33 +++- .../status/AwsOidc/AwsOidcHeader.tsx | 2 +- .../Integrations/status/AwsOidc/StatCard.tsx | 115 +++++++++++ .../status/AwsOidc/useAwsOidcStatus.tsx | 14 +- web/packages/teleport/src/config.ts | 10 + .../src/services/integrations/integrations.ts | 7 + .../src/services/integrations/types.ts | 35 ++++ 9 files changed, 461 insertions(+), 28 deletions(-) create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/StatCard.tsx 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 662b5b5206af5..ddaf1e6590ee9 100644 --- a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.story.tsx +++ b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.story.tsx @@ -18,34 +18,184 @@ import React from 'react'; +import { addHours } from 'date-fns'; + import { AwsOidcDashboard } from 'teleport/Integrations/status/AwsOidc/AwsOidcDashboard'; import { MockAwsOidcStatusProvider } from 'teleport/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider'; -import { IntegrationKind } from 'teleport/services/integrations'; +import { + IntegrationKind, + ResourceTypeSummary, +} from 'teleport/services/integrations'; +import { AwsOidcStatusContextState } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus'; export default { title: 'Teleport/Integrations/AwsOidc', }; +// Loaded dashboard with data for each aws resource and a navigation header export function Dashboard() { return ( - + + + + ); +} + +// Loaded dashboard with missing data for each aws resource and a navigation header +export function DashboardMissingData() { + const state = makeAwsOidcStatusContextState(); + state.statsAttempt.data.awseks = undefined; + state.statsAttempt.data.awsrds = undefined; + state.statsAttempt.data.awsec2 = undefined; + return ( + + + + ); +} + +// Loading screen +export function StatsProcessing() { + const props = makeAwsOidcStatusContextState({ + statsAttempt: { status: 'processing', data: null, statusText: '' }, + }); + return ( + + + + ); +} + +// No header, no loading indicator +export function IntegrationProcessing() { + const props = makeAwsOidcStatusContextState({ + integrationAttempt: { + status: 'processing', + data: null, + statusText: '', + }, + }); + return ( + ); } + +// Loaded error message +export function StatsFailed() { + const props = makeAwsOidcStatusContextState({ + statsAttempt: { + status: 'error', + data: null, + statusText: 'failed to get stats', + error: {}, + }, + }); + return ( + + + + ); +} + +// Loaded dashboard with data for each aws resource but no navigation header +export function IntegrationFailed() { + const props = makeAwsOidcStatusContextState({ + integrationAttempt: { + status: 'error', + data: null, + statusText: 'failed to get integration', + error: {}, + }, + }); + return ( + + + + ); +} + +// Blank screen +export function StatsNoData() { + const props = makeAwsOidcStatusContextState({ + statsAttempt: { status: 'success', data: null, statusText: '' }, + }); + return ( + + + + ); +} + +// No header, no loading indicator +export function IntegrationNoData() { + const props = makeAwsOidcStatusContextState({ + integrationAttempt: { + status: 'success', + data: null, + statusText: '', + }, + }); + 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 3a1a20866a359..76d89d80ce166 100644 --- a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.test.tsx +++ b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.test.tsx @@ -19,16 +19,20 @@ import React from 'react'; import { render, screen } from 'design/utils/testing'; +import { within } from '@testing-library/react'; + import { AwsOidcDashboard } from 'teleport/Integrations/status/AwsOidc/AwsOidcDashboard'; import { MockAwsOidcStatusProvider } from 'teleport/Integrations/status/AwsOidc/testHelpers/mockAwsOidcStatusProvider'; import { IntegrationKind } from 'teleport/services/integrations'; +import { addHours } from 'teleport/components/BannerList/useAlerts'; -test('renders header', () => { +test('renders header and stats cards', () => { render( { }, statusCode: 1, }, + }, + 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 + }, + }, }, }} > @@ -53,4 +91,49 @@ test('renders header', () => { expect(screen.getByText('integration-one')).toBeInTheDocument(); expect(screen.getByLabelText('status')).toHaveAttribute('kind', 'success'); expect(screen.getByLabelText('status')).toHaveTextContent('Running'); + + const ec2 = screen.getByTestId('ec2-stats'); + expect(within(ec2).getByTestId('sync')).toHaveTextContent( + 'Last Sync: 0 seconds ago' + ); + expect(within(ec2).getByTestId('rules')).toHaveTextContent( + 'Enrollment Rules 24' + ); + expect(within(ec2).queryByTestId('rds-agents')).not.toBeInTheDocument(); + expect(within(ec2).getByTestId('enrolled')).toHaveTextContent( + 'Enrolled Instances 9' + ); + expect(within(ec2).getByTestId('failed')).toHaveTextContent( + 'Failed Instances 3' + ); + + const rds = screen.getByTestId('rds-stats'); + expect(within(rds).getByTestId('sync')).toHaveTextContent( + 'Last Sync: 4 hours ago' + ); + expect(within(rds).getByTestId('rules')).toHaveTextContent( + 'Enrollment Rules 14' + ); + expect(within(rds).getByTestId('rds-agents')).toHaveTextContent('Agents 8'); + expect(within(rds).getByTestId('enrolled')).toHaveTextContent( + 'Enrolled Databases 0' + ); + expect(within(rds).getByTestId('failed')).toHaveTextContent( + 'Failed Databases 5' + ); + + const eks = screen.getByTestId('eks-stats'); + expect(within(eks).getByTestId('sync')).toHaveTextContent( + 'Last Sync: 2 days ago' + ); + expect(within(eks).getByTestId('rules')).toHaveTextContent( + 'Enrollment Rules 33' + ); + expect(within(eks).queryByTestId('rds-agents')).not.toBeInTheDocument(); + expect(within(eks).getByTestId('enrolled')).toHaveTextContent( + 'Enrolled Clusters 3' + ); + expect(within(eks).getByTestId('failed')).toHaveTextContent( + 'Failed Clusters 0' + ); }); diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.tsx index 98f5faa122bd4..e755de5bc75f8 100644 --- a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.tsx +++ b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcDashboard.tsx @@ -18,18 +18,43 @@ import React from 'react'; +import { Flex, H2, Indicator } from 'design'; + +import { Danger } from 'design/Alert'; + import { AwsOidcHeader } from 'teleport/Integrations/status/AwsOidc/AwsOidcHeader'; import { useAwsOidcStatus } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus'; import { FeatureBox } from 'teleport/components/Layout'; +import { + AwsResource, + StatCard, +} from 'teleport/Integrations/status/AwsOidc/StatCard'; -// todo (michellescripts) after routing, ensure this view can be sticky export function AwsOidcDashboard() { - const { attempt } = useAwsOidcStatus(); + const { statsAttempt, integrationAttempt } = useAwsOidcStatus(); + + if (statsAttempt.status == 'processing') { + return ; + } + if (statsAttempt.status == 'error') { + return {statsAttempt.statusText}; + } + if (!statsAttempt.data) { + return null; + } + // todo (michellescripts) after routing, ensure this view can be sticky + const { awsec2, awseks, awsrds } = statsAttempt.data; + const { data: integration } = integrationAttempt; return ( - {attempt.data && } - Status for integration type aws-oidc is not supported + {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 773efe7beb610..e2c58ab85b50a 100644 --- a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcHeader.tsx +++ b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcHeader.tsx @@ -40,7 +40,7 @@ export function AwsOidcHeader({ integration }: { integration: Integration }) { - + {integration.name}