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