diff --git a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcHeader.tsx b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcHeader.tsx
index 83beb1314bd92..c7072f601ab5a 100644
--- a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcHeader.tsx
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcHeader.tsx
@@ -30,9 +30,11 @@ import { useHistory } from 'react-router';
export function AwsOidcHeader({
integration,
resource = undefined,
+ tasks = false,
}: {
integration: Integration;
resource?: AwsResource;
+ tasks?: boolean;
}) {
const history = useHistory();
const divider = (
@@ -90,6 +92,14 @@ export function AwsOidcHeader({
>
)}
+ {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 50d154d378e85..a04bf5f2947ff 100644
--- a/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcRoutes.tsx
+++ b/web/packages/teleport/src/Integrations/status/AwsOidc/AwsOidcRoutes.tsx
@@ -22,6 +22,7 @@ import cfg from 'teleport/config';
import { AwsOidcStatusProvider } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';
import { Details } from 'teleport/Integrations/status/AwsOidc/Details/Details';
+import {Tasks} from "teleport/Integrations/status/AwsOidc/Tasks/Tasks";
import { AwsOidcDashboard } from './AwsOidcDashboard';
@@ -35,6 +36,12 @@ export function AwsOidcRoutes() {
path={cfg.routes.integrationStatusResources}
component={Details}
/>
+
.
+ */
+
+import React, {useEffect} from 'react';
+import Table from 'design/DataTable';
+
+import {useAsync} from "shared/hooks/useAsync";
+
+import {useParams} from "react-router";
+
+import {Indicator} from "design";
+
+import {Danger} from "design/Alert";
+
+import { FeatureBox } from 'teleport/components/Layout';
+import { AwsOidcHeader } from 'teleport/Integrations/status/AwsOidc/AwsOidcHeader';
+import { useAwsOidcStatus } from 'teleport/Integrations/status/AwsOidc/useAwsOidcStatus';
+import {IntegrationKind, integrationService, UserTask} from "teleport/services/integrations";
+
+
+export function Tasks() {
+ const { name } = useParams<{
+ type: IntegrationKind;
+ name: string;
+ }>();
+
+ const { integrationAttempt } = useAwsOidcStatus();
+ const { data: integration } = integrationAttempt;
+
+ const [attempt, fetchTasks] = useAsync(() =>
+ integrationService.fetchIntegrationUserTasksList(name)
+ );
+
+ useEffect(() => {
+ fetchTasks();
+ }, []);
+
+ if (attempt.status == 'processing') {
+ return ;
+ }
+
+ if (attempt.status == 'error') {
+ return {attempt.statusText};
+ }
+
+ if (!attempt.data) {
+ return null;
+ }
+
+ return (
+
+ {integration && }
+ {/* todo (michellescripts) sync with Marco on timestamp field */}
+
+ data={attempt.data.items}
+ columns={[
+ {
+ key: 'taskType',
+ headerText: 'Type',
+ isSortable: true,
+ },
+ {
+ key: 'name',
+ headerText: 'Issue Details',
+ isSortable: true,
+ },
+ ]}
+ emptyText={`No pending tasks`}
+ isSearchable
+ />
+
+ );
+}
diff --git a/web/packages/teleport/src/config.ts b/web/packages/teleport/src/config.ts
index bc08ea147e425..1f6424ca9c795 100644
--- a/web/packages/teleport/src/config.ts
+++ b/web/packages/teleport/src/config.ts
@@ -199,6 +199,7 @@ 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?',
@@ -334,6 +335,8 @@ const cfg = {
'/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?',
+ userTaskListByIntegrationPath:
+ '/v1/webapi/sites/:clusterId/usertask?integration=:name',
thumbprintPath: '/v1/webapi/thumbprint',
pingAwsOidcIntegrationPath:
@@ -1026,6 +1029,14 @@ const cfg = {
});
},
+ getIntegrationUserTasksListUrl(name: string) {
+ const clusterId = cfg.proxyCluster;
+ return generatePath(cfg.api.userTaskListByIntegrationPath, {
+ clusterId,
+ name,
+ });
+ },
+
getPingAwsOidcIntegrationUrl({
integrationName,
clusterId,
diff --git a/web/packages/teleport/src/services/integrations/integrations.test.ts b/web/packages/teleport/src/services/integrations/integrations.test.ts
index a799dd90246c8..68637fa311120 100644
--- a/web/packages/teleport/src/services/integrations/integrations.test.ts
+++ b/web/packages/teleport/src/services/integrations/integrations.test.ts
@@ -276,6 +276,48 @@ test('fetch integration rules: fetchIntegrationRules()', async () => {
});
});
+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');
+ expect(api.get).toHaveBeenCalledWith(
+ cfg.getIntegrationUserTasksListUrl('name')
+ );
+ 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');
+ 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 f7fb6db4f4b2b..646418cc2e8a1 100644
--- a/web/packages/teleport/src/services/integrations/integrations.ts
+++ b/web/packages/teleport/src/services/integrations/integrations.ts
@@ -63,6 +63,7 @@ import {
AwsOidcPingRequest,
IntegrationWithSummary,
IntegrationDiscoveryRules,
+ UserTasksListResponse,
} from './types';
export const integrationService = {
@@ -446,6 +447,19 @@ export const integrationService = {
};
});
},
+
+ fetchIntegrationUserTasksList(
+ name: string,
+ ): Promise {
+ return api
+ .get(cfg.getIntegrationUserTasksListUrl(name))
+ .then(resp => {
+ return {
+ items: resp?.items || [],
+ nextKey: resp?.nextKey,
+ };
+ });
+ },
};
export function makeIntegrations(json: any): Integration[] {
diff --git a/web/packages/teleport/src/services/integrations/types.ts b/web/packages/teleport/src/services/integrations/types.ts
index d63cac5c85a96..4c1e49c8b0b39 100644
--- a/web/packages/teleport/src/services/integrations/types.ts
+++ b/web/packages/teleport/src/services/integrations/types.ts
@@ -306,6 +306,31 @@ export type IntegrationDiscoveryRules = {
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;
+};
+
// IntegrationDiscoveryRule describes a discovery rule associated with an integration.
export type IntegrationDiscoveryRule = {
// resourceType indicates the type of resource that this rule targets.