diff --git a/web/packages/design/src/Checkbox/Checkbox.story.tsx b/web/packages/design/src/Checkbox/Checkbox.story.tsx new file mode 100644 index 0000000000000..48e767d2a48a3 --- /dev/null +++ b/web/packages/design/src/Checkbox/Checkbox.story.tsx @@ -0,0 +1,42 @@ +/* +Copyright 2022 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; + +import { Box } from 'design'; + +import { CheckboxWrapper, CheckboxInput } from './Checkbox'; + +export default { + title: 'Design/Checkbox', +}; + +export const Checkbox = () => ( + + + + Input 1 + + + + Input 2 + + + + Input 3 + + +); diff --git a/web/packages/design/src/Checkbox/Checkbox.tsx b/web/packages/design/src/Checkbox/Checkbox.tsx new file mode 100644 index 0000000000000..f3b3993e682b7 --- /dev/null +++ b/web/packages/design/src/Checkbox/Checkbox.tsx @@ -0,0 +1,42 @@ +/** + * Copyright 2022 Gravitational, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import styled from 'styled-components'; + +import { Flex } from 'design'; + +export const CheckboxWrapper = styled(Flex)` + padding: 8px; + margin-bottom: 4px; + width: 300px; + align-items: center; + border: 1px solid ${props => props.theme.colors.primary.light}; + border-radius: 8px; + + &.disabled { + pointer-events: none; + opacity: 0.5; + } +`; + +export const CheckboxInput = styled.input` + margin-right: 10px; + accent-color: ${props => props.theme.colors.secondary.main}; + + &:hover { + cursor: pointer; + } +`; diff --git a/web/packages/design/src/Checkbox/index.ts b/web/packages/design/src/Checkbox/index.ts new file mode 100644 index 0000000000000..a9c2d7725a19f --- /dev/null +++ b/web/packages/design/src/Checkbox/index.ts @@ -0,0 +1,17 @@ +/** + * Copyright 2022 Gravitational, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { CheckboxInput, CheckboxWrapper } from './Checkbox'; diff --git a/web/packages/teleterm/src/services/tshd/createClient.ts b/web/packages/teleterm/src/services/tshd/createClient.ts index 14fbd02faa270..80a8c3e6ecbc2 100644 --- a/web/packages/teleterm/src/services/tshd/createClient.ts +++ b/web/packages/teleterm/src/services/tshd/createClient.ts @@ -334,19 +334,29 @@ export default function createClient( }); }, - async getRequestableRoles(clusterUri: string) { - const req = new api.GetRequestableRolesRequest().setClusterUri( - clusterUri + async getRequestableRoles(params: types.GetRequestableRolesParams) { + const req = new api.GetRequestableRolesRequest() + .setClusterUri(params.rootClusterUri) + .setResourceIdsList( + params.resourceIds.map(({ id, clusterName, kind }) => { + const resourceId = new ResourceID(); + resourceId.setName(id); + resourceId.setClusterName(clusterName); + resourceId.setKind(kind); + return resourceId; + }) + ); + return new Promise( + (resolve, reject) => { + tshd.getRequestableRoles(req, (err, response) => { + if (err) { + reject(err); + } else { + resolve(response.toObject()); + } + }); + } ); - return new Promise((resolve, reject) => { - tshd.getRequestableRoles(req, (err, response) => { - if (err) { - reject(err); - } else { - resolve(response.toObject().rolesList); - } - }); - }); }, async addRootCluster(addr: string) { diff --git a/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts b/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts index fa32b6d2d5f95..2d9d988336c4b 100644 --- a/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts +++ b/web/packages/teleterm/src/services/tshd/fixtures/mocks.ts @@ -8,6 +8,7 @@ import { Gateway, GetDatabasesResponse, GetKubesResponse, + GetRequestableRolesParams, GetServersResponse, Kube, LoginLocalParams, @@ -19,6 +20,7 @@ import { TshAbortController, TshAbortSignal, TshClient, + GetRequestableRolesResponse, } from '../types'; import { AccessRequest } from '../v1/access_request_pb'; @@ -32,7 +34,9 @@ export class MockTshClient implements TshClient { getDatabases: (params: ServerSideParams) => Promise; listDatabaseUsers: (dbUri: string) => Promise; getAllServers: (clusterUri: string) => Promise; - getRequestableRoles: (clusterUri: string) => Promise; + getRequestableRoles: ( + params: GetRequestableRolesParams + ) => Promise; getServers: (params: ServerSideParams) => Promise; assumeRole: ( clusterUri: string, diff --git a/web/packages/teleterm/src/services/tshd/types.ts b/web/packages/teleterm/src/services/tshd/types.ts index c25f5f5d3d3b8..7055f4e8ee32e 100644 --- a/web/packages/teleterm/src/services/tshd/types.ts +++ b/web/packages/teleterm/src/services/tshd/types.ts @@ -26,6 +26,8 @@ export type AccessRequestReview = apiAccessRequest.AccessRequestReview.AsObject; export type GetServersResponse = apiService.GetServersResponse.AsObject; export type GetDatabasesResponse = apiService.GetDatabasesResponse.AsObject; export type GetKubesResponse = apiService.GetKubesResponse.AsObject; +export type GetRequestableRolesResponse = + apiService.GetRequestableRolesResponse.AsObject; // Available types are listed here: // https://github.com/gravitational/teleport/blob/v9.0.3/lib/defaults/defaults.go#L513-L530 // @@ -84,7 +86,9 @@ export type TshClient = { requestIds: string[], dropIds: string[] ) => Promise; - getRequestableRoles: (clusterUri: string) => Promise; + getRequestableRoles: ( + params: GetRequestableRolesParams + ) => Promise; getServers: (params: ServerSideParams) => Promise; getAccessRequests: (clusterUri: string) => Promise; getAccessRequest: ( @@ -198,6 +202,11 @@ export type CreateAccessRequestParams = { resourceIds: { kind: ResourceKind; clusterName: string; id: string }[]; }; +export type GetRequestableRolesParams = { + rootClusterUri: string; + resourceIds?: { kind: ResourceKind; clusterName: string; id: string }[]; +}; + export type AssumedRequest = { id: string; expires: Date; diff --git a/web/packages/teleterm/src/services/tshd/v1/service_pb.d.ts b/web/packages/teleterm/src/services/tshd/v1/service_pb.d.ts index a7470a5600333..238f72ae236ad 100644 --- a/web/packages/teleterm/src/services/tshd/v1/service_pb.d.ts +++ b/web/packages/teleterm/src/services/tshd/v1/service_pb.d.ts @@ -316,6 +316,11 @@ export class GetRequestableRolesRequest extends jspb.Message { getClusterUri(): string; setClusterUri(value: string): GetRequestableRolesRequest; + clearResourceIdsList(): void; + getResourceIdsList(): Array; + setResourceIdsList(value: Array): GetRequestableRolesRequest; + addResourceIds(value?: v1_access_request_pb.ResourceID, index?: number): v1_access_request_pb.ResourceID; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): GetRequestableRolesRequest.AsObject; @@ -330,6 +335,7 @@ export class GetRequestableRolesRequest extends jspb.Message { export namespace GetRequestableRolesRequest { export type AsObject = { clusterUri: string, + resourceIdsList: Array, } } @@ -339,6 +345,11 @@ export class GetRequestableRolesResponse extends jspb.Message { setRolesList(value: Array): GetRequestableRolesResponse; addRoles(value: string, index?: number): string; + clearApplicableRolesList(): void; + getApplicableRolesList(): Array; + setApplicableRolesList(value: Array): GetRequestableRolesResponse; + addApplicableRoles(value: string, index?: number): string; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): GetRequestableRolesResponse.AsObject; @@ -353,6 +364,7 @@ export class GetRequestableRolesResponse extends jspb.Message { export namespace GetRequestableRolesResponse { export type AsObject = { rolesList: Array, + applicableRolesList: Array, } } diff --git a/web/packages/teleterm/src/services/tshd/v1/service_pb.js b/web/packages/teleterm/src/services/tshd/v1/service_pb.js index 46cf8111f915a..11fdc62f5cd25 100644 --- a/web/packages/teleterm/src/services/tshd/v1/service_pb.js +++ b/web/packages/teleterm/src/services/tshd/v1/service_pb.js @@ -355,7 +355,7 @@ if (goog.DEBUG && !COMPILED) { * @constructor */ proto.teleport.terminal.v1.GetRequestableRolesRequest = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); + jspb.Message.initialize(this, opt_data, 0, -1, proto.teleport.terminal.v1.GetRequestableRolesRequest.repeatedFields_, null); }; goog.inherits(proto.teleport.terminal.v1.GetRequestableRolesRequest, jspb.Message); if (goog.DEBUG && !COMPILED) { @@ -3246,6 +3246,13 @@ proto.teleport.terminal.v1.AssumeRoleRequest.prototype.clearDropRequestIdsList = +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.teleport.terminal.v1.GetRequestableRolesRequest.repeatedFields_ = [2]; + if (jspb.Message.GENERATE_TO_OBJECT) { @@ -3277,7 +3284,9 @@ proto.teleport.terminal.v1.GetRequestableRolesRequest.prototype.toObject = funct */ proto.teleport.terminal.v1.GetRequestableRolesRequest.toObject = function(includeInstance, msg) { var f, obj = { - clusterUri: jspb.Message.getFieldWithDefault(msg, 1, "") + clusterUri: jspb.Message.getFieldWithDefault(msg, 1, ""), + resourceIdsList: jspb.Message.toObjectList(msg.getResourceIdsList(), + v1_access_request_pb.ResourceID.toObject, includeInstance) }; if (includeInstance) { @@ -3318,6 +3327,11 @@ proto.teleport.terminal.v1.GetRequestableRolesRequest.deserializeBinaryFromReade var value = /** @type {string} */ (reader.readString()); msg.setClusterUri(value); break; + case 2: + var value = new v1_access_request_pb.ResourceID; + reader.readMessage(value,v1_access_request_pb.ResourceID.deserializeBinaryFromReader); + msg.addResourceIds(value); + break; default: reader.skipField(); break; @@ -3354,6 +3368,14 @@ proto.teleport.terminal.v1.GetRequestableRolesRequest.serializeBinaryToWriter = f ); } + f = message.getResourceIdsList(); + if (f.length > 0) { + writer.writeRepeatedMessage( + 2, + f, + v1_access_request_pb.ResourceID.serializeBinaryToWriter + ); + } }; @@ -3375,13 +3397,51 @@ proto.teleport.terminal.v1.GetRequestableRolesRequest.prototype.setClusterUri = }; +/** + * repeated ResourceID resource_ids = 2; + * @return {!Array} + */ +proto.teleport.terminal.v1.GetRequestableRolesRequest.prototype.getResourceIdsList = function() { + return /** @type{!Array} */ ( + jspb.Message.getRepeatedWrapperField(this, v1_access_request_pb.ResourceID, 2)); +}; + + +/** + * @param {!Array} value + * @return {!proto.teleport.terminal.v1.GetRequestableRolesRequest} returns this +*/ +proto.teleport.terminal.v1.GetRequestableRolesRequest.prototype.setResourceIdsList = function(value) { + return jspb.Message.setRepeatedWrapperField(this, 2, value); +}; + + +/** + * @param {!proto.teleport.terminal.v1.ResourceID=} opt_value + * @param {number=} opt_index + * @return {!proto.teleport.terminal.v1.ResourceID} + */ +proto.teleport.terminal.v1.GetRequestableRolesRequest.prototype.addResourceIds = function(opt_value, opt_index) { + return jspb.Message.addToRepeatedWrapperField(this, 2, opt_value, proto.teleport.terminal.v1.ResourceID, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.teleport.terminal.v1.GetRequestableRolesRequest} returns this + */ +proto.teleport.terminal.v1.GetRequestableRolesRequest.prototype.clearResourceIdsList = function() { + return this.setResourceIdsList([]); +}; + + /** * List of repeated fields within this message type. * @private {!Array} * @const */ -proto.teleport.terminal.v1.GetRequestableRolesResponse.repeatedFields_ = [1]; +proto.teleport.terminal.v1.GetRequestableRolesResponse.repeatedFields_ = [1,2]; @@ -3414,7 +3474,8 @@ proto.teleport.terminal.v1.GetRequestableRolesResponse.prototype.toObject = func */ proto.teleport.terminal.v1.GetRequestableRolesResponse.toObject = function(includeInstance, msg) { var f, obj = { - rolesList: (f = jspb.Message.getRepeatedField(msg, 1)) == null ? undefined : f + rolesList: (f = jspb.Message.getRepeatedField(msg, 1)) == null ? undefined : f, + applicableRolesList: (f = jspb.Message.getRepeatedField(msg, 2)) == null ? undefined : f }; if (includeInstance) { @@ -3455,6 +3516,10 @@ proto.teleport.terminal.v1.GetRequestableRolesResponse.deserializeBinaryFromRead var value = /** @type {string} */ (reader.readString()); msg.addRoles(value); break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.addApplicableRoles(value); + break; default: reader.skipField(); break; @@ -3491,6 +3556,13 @@ proto.teleport.terminal.v1.GetRequestableRolesResponse.serializeBinaryToWriter = f ); } + f = message.getApplicableRolesList(); + if (f.length > 0) { + writer.writeRepeatedString( + 2, + f + ); + } }; @@ -3531,6 +3603,43 @@ proto.teleport.terminal.v1.GetRequestableRolesResponse.prototype.clearRolesList }; +/** + * repeated string applicable_roles = 2; + * @return {!Array} + */ +proto.teleport.terminal.v1.GetRequestableRolesResponse.prototype.getApplicableRolesList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 2)); +}; + + +/** + * @param {!Array} value + * @return {!proto.teleport.terminal.v1.GetRequestableRolesResponse} returns this + */ +proto.teleport.terminal.v1.GetRequestableRolesResponse.prototype.setApplicableRolesList = function(value) { + return jspb.Message.setField(this, 2, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + * @return {!proto.teleport.terminal.v1.GetRequestableRolesResponse} returns this + */ +proto.teleport.terminal.v1.GetRequestableRolesResponse.prototype.addApplicableRoles = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 2, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.teleport.terminal.v1.GetRequestableRolesResponse} returns this + */ +proto.teleport.terminal.v1.GetRequestableRolesResponse.prototype.clearApplicableRolesList = function() { + return this.setApplicableRolesList([]); +}; + + /** * List of repeated fields within this message type. diff --git a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts index 8dc561eb066e3..9f1da5c07c32d 100644 --- a/web/packages/teleterm/src/ui/services/clusters/clustersService.ts +++ b/web/packages/teleterm/src/ui/services/clusters/clustersService.ts @@ -15,6 +15,7 @@ import { NotificationsService } from 'teleterm/ui/services/notifications'; import { Cluster, CreateAccessRequestParams, + GetRequestableRolesParams, ReviewAccessRequestParams, ServerSideParams, } from 'teleterm/services/tshd/types'; @@ -399,13 +400,13 @@ export class ClustersService extends ImmutableStore { } } - async getRequestableRoles(rootClusterUri: string) { - const cluster = this.state.clusters.get(rootClusterUri); + async getRequestableRoles(params: GetRequestableRolesParams) { + const cluster = this.state.clusters.get(params.rootClusterUri); if (!cluster.connected) { return; } - return this.client.getRequestableRoles(rootClusterUri); + return this.client.getRequestableRoles(params); } getAssumedRequests(rootClusterUri: string) { diff --git a/web/packages/teleterm/src/ui/uri.ts b/web/packages/teleterm/src/ui/uri.ts index e5f94beb09db1..9bf344beb6d49 100644 --- a/web/packages/teleterm/src/ui/uri.ts +++ b/web/packages/teleterm/src/ui/uri.ts @@ -114,6 +114,11 @@ export const routing = { return appUri.startsWith(`${clusterUri}/apps/`); }, + isLeafCluster(clusterUri: string) { + const match = routing.parseClusterUri(clusterUri); + return match && Boolean(match.params.leafClusterId); + }, + belongsToProfile(clusterUri: string, resourceUri: string) { const rootClusterUri = this.ensureRootClusterUri(clusterUri); const resourceRootClusterUri = this.ensureRootClusterUri(resourceUri); diff --git a/web/packages/webapps.e b/web/packages/webapps.e index 1727ef51f467a..6454cf6456293 160000 --- a/web/packages/webapps.e +++ b/web/packages/webapps.e @@ -1 +1 @@ -Subproject commit 1727ef51f467a5af19a8ec2ffa5320318373820f +Subproject commit 6454cf64562937bb773d1562d0fe876fd97c6feb