From 2aa912589dabcb62a8f4782598f897a3a90e5bf7 Mon Sep 17 00:00:00 2001
From: Michelle Bergquist <michelle.bergquist@goteleport.com>
Date: Mon, 27 Jan 2025 20:31:03 -0700
Subject: [PATCH] Add enroll option for resource cards

---
 .../src/Integrations/IntegrationList.tsx      |   2 +-
 .../status/AwsOidc/Details/Agents.tsx         |   3 +
 .../status/AwsOidc/Details/Rules.tsx          | 134 ++++++++----------
 .../status/AwsOidc/EnrollCard.tsx             |  50 +++++++
 .../Integrations/status/AwsOidc/StatCard.tsx  |  17 +++
 web/packages/teleport/src/config.ts           |   4 +-
 .../teleport/src/generateResourcePath.ts      |   2 +
 .../src/services/integrations/integrations.ts |   4 +-
 8 files changed, 137 insertions(+), 79 deletions(-)
 create mode 100644 web/packages/teleport/src/Integrations/status/AwsOidc/EnrollCard.tsx

diff --git a/web/packages/teleport/src/Integrations/IntegrationList.tsx b/web/packages/teleport/src/Integrations/IntegrationList.tsx
index 4ae1ee07320f4..a99842ebc918b 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<IntegrationLike>) {
   }
 
   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/Details/Agents.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Agents.tsx
index dae7b472525e1..f0f8da56e941f 100644
--- a/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Agents.tsx
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Agents.tsx
@@ -18,6 +18,9 @@
 
 import Table, { LabelCell } from 'design/DataTable';
 
+{
+  /* TODO MBERG */
+}
 export function Agents() {
   return (
     <Table
diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rules.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rules.tsx
index ea0938b10afaf..100a2ca1d3329 100644
--- a/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rules.tsx
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/Details/Rules.tsx
@@ -1,3 +1,20 @@
+import React, { 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';
+
 /**
  * Teleport
  * Copyright (C) 2024 Gravitational, Inc.
@@ -16,21 +33,6 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-import { useEffect, useState } from 'react';
-import { useParams } from 'react-router';
-
-import Table, { LabelCell } from 'design/DataTable';
-import { SortType } from 'design/DataTable/types';
-import { SearchPanel } from 'shared/components/Search';
-
-import { useServerSidePagination } from 'teleport/components/hooks';
-import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
-import {
-  IntegrationDiscoveryRule,
-  IntegrationKind,
-  integrationService,
-} from 'teleport/services/integrations';
-
 export function Rules() {
   const { name, resourceKind } = useParams<{
     type: IntegrationKind;
@@ -38,83 +40,67 @@ export function Rules() {
     resourceKind: AwsResource;
   }>();
 
-  const [search, setSearch] = useState('');
-  const [sort, setSort] = useState<SortType>({
-    fieldName: 'region',
-    dir: 'ASC',
-  });
+  const [regionFilter, setRegionFilter] = useState<string[]>([]);
   const serverSidePagination =
     useServerSidePagination<IntegrationDiscoveryRule>({
       pageSize: 20,
-      fetchFunc: async (_, params) => {
+      fetchFunc: async () => {
         const { rules, nextKey } =
           await integrationService.fetchIntegrationRules(
             name,
             resourceKind,
-            params
+            regionFilter
           );
         return { agents: rules, nextKey };
       },
       clusterId: '',
-      params: { search, sort },
+      params: {},
     });
 
   useEffect(() => {
     serverSidePagination.fetch();
-  }, [search, sort]);
+  }, [regionFilter]);
 
   return (
-    <Table<IntegrationDiscoveryRule>
-      data={serverSidePagination.fetchedData.agents || undefined}
-      columns={[
-        {
-          key: 'region',
-          headerText: 'Region',
-          isSortable: true,
-        },
-        {
-          key: 'labelMatcher',
-          headerText: getResourceTerm(resourceKind),
-          isSortable: true,
-          onSort: (a, b) => {
-            const aStr = a.labelMatcher.toString();
-            const bStr = b.labelMatcher.toString();
-
-            if (aStr < bStr) {
-              return -1;
-            }
-            if (aStr > bStr) {
-              return 1;
-            }
-
-            return 0;
-          },
-          render: ({ labelMatcher }) => (
-            <LabelCell data={labelMatcher.map(l => `${l.name}:${l.value}`)} />
+    <>
+      <MultiselectMenu
+        options={Object.keys(awsRegionMap).map(r => ({
+          value: r as Regions,
+          label: (
+            <Flex justifyContent="space-between">
+              <div>{awsRegionMap[r]}&nbsp;&nbsp;</div>
+              <div>{r}</div>
+            </Flex>
           ),
-        },
-      ]}
-      emptyText={`No ${resourceKind} data`}
-      isSearchable
-      fetching={{
-        fetchStatus: serverSidePagination.fetchStatus,
-        onFetchNext: serverSidePagination.fetchNext,
-        onFetchPrev: serverSidePagination.fetchPrev,
-      }}
-      serversideProps={{
-        sort: sort,
-        setSort: setSort,
-        serversideSearchPanel: (
-          <SearchPanel
-            updateSearch={setSearch}
-            updateQuery={null}
-            hideAdvancedSearch={true}
-            filter={{ search }}
-            disableSearch={serverSidePagination.attempt.status === 'processing'}
-          />
-        ),
-      }}
-    />
+        }))}
+        onChange={regions => setRegionFilter(regions)}
+        selected={regionFilter}
+        label="Region"
+        tooltip="Filter by region"
+      />
+      <Table<IntegrationDiscoveryRule>
+        data={serverSidePagination.fetchedData.agents || undefined}
+        columns={[
+          {
+            key: 'region',
+            headerText: 'Region',
+          },
+          {
+            key: 'labelMatcher',
+            headerText: getResourceTerm(resourceKind),
+            render: ({ labelMatcher }) => (
+              <LabelCell data={labelMatcher.map(l => `${l.name}:${l.value}`)} />
+            ),
+          },
+        ]}
+        emptyText={`No ${resourceKind} data`}
+        fetching={{
+          fetchStatus: serverSidePagination.fetchStatus,
+          onFetchNext: serverSidePagination.fetchNext,
+          onFetchPrev: serverSidePagination.fetchPrev,
+        }}
+      />
+    </>
   );
 }
 
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..c5301daf7dc47
--- /dev/null
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/EnrollCard.tsx
@@ -0,0 +1,50 @@
+import { useHistory } from 'react-router';
+import styled from 'styled-components';
+
+import { ButtonBorder, Card, Flex, H2, ResourceIcon } from 'design';
+
+import cfg from 'teleport/config';
+import { AwsResource } from 'teleport/Integrations/status/AwsOidc/StatCard';
+
+type EnrollCardProps = {
+  resource: AwsResource;
+};
+
+export function EnrollCard({ resource }: EnrollCardProps) {
+  const history = useHistory();
+
+  const handleClick = () => {
+    history.push({
+      pathname: cfg.routes.discover,
+      state: { searchKeywords: resource },
+    });
+  };
+
+  // todo (michellescripts) update enroll design once ready
+  return (
+    <Enroll>
+      <Flex flexDirection="column" gap={4}>
+        <Flex alignItems="center" mb={2}>
+          <ResourceIcon name={resource} mr={2} width="32px" height="32px" />
+          <H2>{resource.toUpperCase()}</H2>
+        </Flex>
+        <ButtonBorder size="large" onClick={handleClick}>
+          Enroll {resource.toUpperCase()}
+        </ButtonBorder>
+      </Flex>
+    </Enroll>
+  );
+}
+
+const Enroll = styled(Card)`
+  width: 33%;
+  background-color: ${props => props.theme.colors.levels.surface};
+  padding: 12px;
+  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 83e3f3153db50..30a345c157f9e 100644
--- a/web/packages/teleport/src/Integrations/status/AwsOidc/StatCard.tsx
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/StatCard.tsx
@@ -24,6 +24,7 @@ import * as Icons from 'design/Icon';
 import { ResourceIcon } from 'design/ResourceIcon';
 
 import cfg from 'teleport/config';
+import { EnrollCard } from 'teleport/Integrations/status/AwsOidc/EnrollCard';
 import history from 'teleport/services/history';
 import {
   IntegrationKind,
@@ -48,6 +49,10 @@ export function StatCard({ name, resource, summary }: StatCardProps) {
     : undefined;
   const term = getResourceTerm(resource);
 
+  if (!foundResource(summary)) {
+    return <EnrollCard resource={resource} />;
+  }
+
   return (
     <SelectCard
       data-testid={`${resource}-stats`}
@@ -124,6 +129,18 @@ function getResourceTerm(resource: AwsResource): string {
   }
 }
 
+function foundResource(resource: ResourceTypeSummary): boolean {
+  if (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};
diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts
index fcb75700f2e46..157f1bffab5ad 100644
--- a/web/packages/teleport/src/config.ts
+++ b/web/packages/teleport/src/config.ts
@@ -1051,14 +1051,14 @@ const cfg = {
   getIntegrationRulesUrl(
     name: string,
     resourceType: AwsResource,
-    params?: UrlResourcesParams
+    regionFilter: string[]
   ) {
     const clusterId = cfg.proxyCluster;
     return generateResourcePath(cfg.api.integrationRulesPath, {
       clusterId,
       name,
       resourceType,
-      ...params,
+      regionFilter,
     });
   },
 
diff --git a/web/packages/teleport/src/generateResourcePath.ts b/web/packages/teleport/src/generateResourcePath.ts
index 25ac573dcb8c6..4900e40321bb0 100644
--- a/web/packages/teleport/src/generateResourcePath.ts
+++ b/web/packages/teleport/src/generateResourcePath.ts
@@ -65,6 +65,8 @@ export default function generateResourcePath(
     .replace(':searchAsRoles?', processedParams.searchAsRoles || '')
     .replace(':sort?', processedParams.sort || '')
     .replace(':startKey?', params.startKey || '')
+    //   todo mberg - marco hmw handle regions ?
+    .replace(':regionPrefix?', params.regionPrefix || '')
     .replace(
       ':includedResourceMode?',
       processedParams.includedResourceMode || ''
diff --git a/web/packages/teleport/src/services/integrations/integrations.ts b/web/packages/teleport/src/services/integrations/integrations.ts
index b8fa8721c8bc1..b5347178915a9 100644
--- a/web/packages/teleport/src/services/integrations/integrations.ts
+++ b/web/packages/teleport/src/services/integrations/integrations.ts
@@ -417,10 +417,10 @@ export const integrationService = {
   fetchIntegrationRules(
     name: string,
     resourceType: AwsResource,
-    params?: UrlResourcesParams
+    region: string[]
   ): Promise<IntegrationDiscoveryRules> {
     return api
-      .get(cfg.getIntegrationRulesUrl(name, resourceType, params))
+      .get(cfg.getIntegrationRulesUrl(name, resourceType, region))
       .then(resp => {
         return {
           rules: resp?.rules || [],