Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions web/packages/design/src/assets/images/icons/plugins.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 26 additions & 10 deletions web/packages/teleport/src/Integrations/IntegrationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<IntegrationLike> = {
list: IntegrationLike[];
Expand Down Expand Up @@ -75,12 +82,18 @@ export function IntegrationList(props: Props<IntegrationLike>) {

const StatusCell = ({ item }: { item: IntegrationLike }) => {
const status = getStatus(item);
const statusDescription = getStatusCodeDescription(item.statusCode);

return (
<Cell>
<Flex alignItems="center">
<StatusLight status={status} />
{item.statusCode}
{getStatusCodeTitle(item.statusCode)}
{statusDescription && (
<Box mx="1">
<ToolTipInfo>{statusDescription}</ToolTipInfo>
</Box>
)}
</Flex>
</Cell>
);
Expand All @@ -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;
}
}

Expand Down
18 changes: 10 additions & 8 deletions web/packages/teleport/src/Integrations/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [
Expand All @@ -22,39 +24,39 @@ export const plugins: Plugin[] = [
name: 'slack-default',
details: `plugin running status`,
kind: 'slack',
statusCode: 'Running',
statusCode: IntegrationStatusCode.Running,
spec: {},
},
{
resourceType: 'plugin',
name: 'slack-secondary',
details: `plugin unknown status`,
kind: 'slack',
statusCode: 'Unknown',
statusCode: IntegrationStatusCode.Unknown,
spec: {},
},
{
resourceType: '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: {},
},
{
resourceType: 'plugin',
name: 'slack',
details: '',
kind: 'slack',
statusCode: 'Bot not invited to channel',
statusCode: IntegrationStatusCode.SlackNotInChannel,
spec: {},
},
];
Expand All @@ -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: '' },
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -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<Integration> {
Expand Down Expand Up @@ -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,
};
}
50 changes: 38 additions & 12 deletions web/packages/teleport/src/services/integrations/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, never>; // currently no 'spec' fields exposed to the frontend
export type PluginKind = 'slack';