diff --git a/web/packages/design/src/assets/images/icons/plugins.svg b/web/packages/design/src/assets/images/icons/plugins.svg new file mode 100644 index 0000000000000..500602c500144 --- /dev/null +++ b/web/packages/design/src/assets/images/icons/plugins.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/packages/design/src/assets/images/icons/success.png b/web/packages/design/src/assets/images/icons/success.png new file mode 100644 index 0000000000000..764c95362ff9d Binary files /dev/null and b/web/packages/design/src/assets/images/icons/success.png differ diff --git a/web/packages/teleport/src/Integrations/IntegrationList.tsx b/web/packages/teleport/src/Integrations/IntegrationList.tsx index 9082a086c593f..0e414c1d73b80 100644 --- a/web/packages/teleport/src/Integrations/IntegrationList.tsx +++ b/web/packages/teleport/src/Integrations/IntegrationList.tsx @@ -22,8 +22,15 @@ import awsIcon from 'design/assets/images/icons/aws.svg'; import slackIcon from 'design/assets/images/icons/slack.svg'; import Table, { Cell } from 'design/DataTable'; import { MenuButton, MenuItem } from 'shared/components/MenuAction'; +import { ToolTipInfo } from 'shared/components/ToolTip'; -import type { Integration, Plugin } from 'teleport/services/integrations'; +import { + getStatusCodeDescription, + getStatusCodeTitle, + Integration, + IntegrationStatusCode, + Plugin, +} from 'teleport/services/integrations'; type Props = { list: IntegrationLike[]; @@ -75,12 +82,18 @@ export function IntegrationList(props: Props) { const StatusCell = ({ item }: { item: IntegrationLike }) => { const status = getStatus(item); + const statusDescription = getStatusCodeDescription(item.statusCode); return ( - {item.statusCode} + {getStatusCodeTitle(item.statusCode)} + {statusDescription && ( + + {statusDescription} + + )} ); @@ -106,17 +119,20 @@ enum Status { Error, } -function getStatus(item: IntegrationLike) { +function getStatus(item: IntegrationLike): Status | null { + if (item.resourceType !== 'plugin') { + return Status.Success; + } + switch (item.statusCode) { - case 'Running': + case IntegrationStatusCode.Unknown: + return null; + case IntegrationStatusCode.Running: return Status.Success; - - case 'Unauthorized': - case 'Unknown error': - return Status.Error; - - case 'Bot not invited to channel': + case IntegrationStatusCode.SlackNotInChannel: return Status.Warning; + default: + return Status.Error; } } diff --git a/web/packages/teleport/src/Integrations/fixtures.ts b/web/packages/teleport/src/Integrations/fixtures.ts index e29bc690fb4cd..24f8a35bbcb57 100644 --- a/web/packages/teleport/src/Integrations/fixtures.ts +++ b/web/packages/teleport/src/Integrations/fixtures.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { IntegrationStatusCode } from 'teleport/services/integrations'; + import type { Plugin, Integration } from 'teleport/services/integrations'; export const plugins: Plugin[] = [ @@ -22,7 +24,7 @@ export const plugins: Plugin[] = [ name: 'slack-default', details: `plugin running status`, kind: 'slack', - statusCode: 'Running', + statusCode: IntegrationStatusCode.Running, spec: {}, }, { @@ -30,7 +32,7 @@ export const plugins: Plugin[] = [ name: 'slack-secondary', details: `plugin unknown status`, kind: 'slack', - statusCode: 'Unknown', + statusCode: IntegrationStatusCode.Unknown, spec: {}, }, { @@ -38,15 +40,15 @@ export const plugins: Plugin[] = [ name: 'acmeco-default', details: `plugin unauthorized status`, kind: 'acmeco' as any, // unknown plugin, should handle gracefuly - statusCode: 'Unauthorized', + statusCode: IntegrationStatusCode.Unauthorized, spec: {}, }, { resourceType: 'plugin', name: 'slack', - details: 'plugin unknown error status', + details: 'plugin other error status', kind: 'slack', - statusCode: 'Unknown error', + statusCode: IntegrationStatusCode.OtherError, spec: {}, }, { @@ -54,7 +56,7 @@ export const plugins: Plugin[] = [ name: 'slack', details: '', kind: 'slack', - statusCode: 'Bot not invited to channel', + statusCode: IntegrationStatusCode.SlackNotInChannel, spec: {}, }, ]; @@ -64,14 +66,14 @@ export const integrations: Integration[] = [ resourceType: 'integration', name: 'aws', kind: 'aws-oidc', - statusCode: 'Running', + statusCode: IntegrationStatusCode.Running, spec: { roleArn: '' }, }, { resourceType: 'integration', name: 'some-integration-name', kind: '' as any, - statusCode: 'Running', + statusCode: IntegrationStatusCode.Running, spec: { roleArn: '' }, }, ]; diff --git a/web/packages/teleport/src/services/integrations/integrations.ts b/web/packages/teleport/src/services/integrations/integrations.ts index 894bd09f0533b..9e5e8fc8675d7 100644 --- a/web/packages/teleport/src/services/integrations/integrations.ts +++ b/web/packages/teleport/src/services/integrations/integrations.ts @@ -17,7 +17,7 @@ import api from 'teleport/services/api'; import cfg from 'teleport/config'; -import { Integration } from './types'; +import { Integration, IntegrationStatusCode } from './types'; export const integrationService = { fetchIntegration(clusterId: string, name: string): Promise { @@ -51,6 +51,6 @@ function makeIntegration(json: any): Integration { // integration resources together. As discussed, the only // supported status for integration is `Running` for now: // https://github.com/gravitational/teleport/pull/22556#discussion_r1158674300 - statusCode: 'Running', + statusCode: IntegrationStatusCode.Running, }; } diff --git a/web/packages/teleport/src/services/integrations/types.ts b/web/packages/teleport/src/services/integrations/types.ts index 3279d0e72b266..4973ea206f1c7 100644 --- a/web/packages/teleport/src/services/integrations/types.ts +++ b/web/packages/teleport/src/services/integrations/types.ts @@ -45,18 +45,44 @@ export type IntegrationSpecAwsOidc = { roleArn: string; }; -// IntegrationStatusCode must be in sync with the text values defined -// in the backend as these are used to determine the status color: -// https://github.com/gravitational/teleport.e/blob/1ebe50ce2fe608dc6dd24fef205fb9caaa216a46/lib/web/ui/plugins.go#L51 -export type IntegrationStatusCode = - | 'Unknown' - | 'Running' - | 'Unknown error' - | 'Unauthorized' - | 'Bot not invited to channel'; +export enum IntegrationStatusCode { + Unknown = 0, + Running = 1, + OtherError = 2, + Unauthorized = 3, + SlackNotInChannel = 10, +} + +export function getStatusCodeTitle(code: IntegrationStatusCode): string { + switch (code) { + case IntegrationStatusCode.Unknown: + return 'Unknown'; + case IntegrationStatusCode.Running: + return 'Running'; + case IntegrationStatusCode.Unauthorized: + return 'Unauthorized'; + case IntegrationStatusCode.SlackNotInChannel: + return 'Bot not invited to channel'; + default: + return 'Unknown error'; + } +} + +export function getStatusCodeDescription( + code: IntegrationStatusCode +): string | null { + switch (code) { + case IntegrationStatusCode.Unauthorized: + return 'The integration was denied access. This could be a result of revoked authorization on the 3rd party provider. Try removing and re-connecting the integration.'; + + case IntegrationStatusCode.SlackNotInChannel: + return 'The Slack integration must be invited to the default channel in order to receive access request notifications.'; + + default: + return null; + } +} export type Plugin = Integration<'plugin', PluginKind, PluginSpec>; -export type PluginSpec = { - statusDescription?: string; -}; +export type PluginSpec = Record; // currently no 'spec' fields exposed to the frontend export type PluginKind = 'slack';