From 0d96da5ddb9639b10323000046073272ada74a1b Mon Sep 17 00:00:00 2001 From: yiyione Date: Tue, 25 Aug 2020 19:12:36 +0800 Subject: [PATCH] Update vc and group experience for user (#4800) * Add group vc to user-profile * update * update webportal * update rest-server * update * add groups to vc card * update * update * update * update * update * update * fix --- .../src/app/components/copy-button.jsx | 8 +- .../src/app/home/home/groupDetailDialog.jsx | 123 +++++++ .../home/home/virtual-cluster-statistics.jsx | 323 +++++++++++------- src/webportal/src/app/user/fabric/conn.js | 4 + .../src/app/user/fabric/user-profile.jsx | 12 +- .../user/fabric/user-profile/storage-list.jsx | 7 +- 6 files changed, 342 insertions(+), 135 deletions(-) create mode 100644 src/webportal/src/app/home/home/groupDetailDialog.jsx diff --git a/src/webportal/src/app/components/copy-button.jsx b/src/webportal/src/app/components/copy-button.jsx index 4834ac6c98..414251f721 100644 --- a/src/webportal/src/app/components/copy-button.jsx +++ b/src/webportal/src/app/components/copy-button.jsx @@ -22,7 +22,7 @@ import { IconButton, FontSizes, TooltipHost } from 'office-ui-fabric-react'; const COPIED_TOOLTIP_CLOSE_DELAY = 1000; -const CopyButton = ({ value }) => { +const CopyButton = ({ value, hideTooltip, callback }) => { const ref = useRef(null); return (
@@ -35,9 +35,13 @@ const CopyButton = ({ value }) => { setTimeout(() => { ref.current && ref.current.dismiss(); }, COPIED_TOOLTIP_CLOSE_DELAY); + if (callback) { + callback(); + } }} />
+ ); + }, + }, + { + key: 'alias', + minWidth: 180, + maxWidth: 250, + name: 'Group alias', + isResizable: true, + onRender(group) { + return ( +
+
+ {group.externalName} +
+
+ { + setCopiedName(group.externalName); + }} + /> + +
+
+ ); + }, + }, + { + key: 'description', + minWidth: 180, + name: 'Description', + isResizable: true, + onRender(group) { + return ( +
+ {group.description} +
+ ); + }, + }, + ]} + disableSelectionZone + items={groupDetails.groups} + layoutMode={DetailsListLayoutMode.justified} + selectionMode={SelectionMode.none} + /> + + ); +} + +GroupDetailDialog.propTypes = { + groupDetails: PropTypes.object.isRequired, + setGroupDetails: PropTypes.func.isRequired, +}; diff --git a/src/webportal/src/app/home/home/virtual-cluster-statistics.jsx b/src/webportal/src/app/home/home/virtual-cluster-statistics.jsx index 9030465a27..0d6df5bdfc 100644 --- a/src/webportal/src/app/home/home/virtual-cluster-statistics.jsx +++ b/src/webportal/src/app/home/home/virtual-cluster-statistics.jsx @@ -1,19 +1,5 @@ -// Copyright (c) Microsoft Corporation -// All rights reserved. -// -// MIT License -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -// to permit persons to whom the Software is furnished to do so, subject to the following conditions: -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. import c from 'classnames'; import PropTypes from 'prop-types'; @@ -26,8 +12,9 @@ import { StackItem, FontClassNames, Link, + DefaultButton, } from 'office-ui-fabric-react'; -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import Card from '../../components/card'; import { UtilizationChart } from './utilization-chart'; @@ -37,6 +24,7 @@ import config from '../../config/webportal.config'; import t from '../../components/tachyons.scss'; import { ResourceBar } from './resource-bar'; +import GroupDetailDialog from './groupDetailDialog'; const getResouceUtilization = (used, guaranteed) => { if (Math.abs(guaranteed) < 1e-5) { @@ -45,118 +33,173 @@ const getResouceUtilization = (used, guaranteed) => { return used / guaranteed; }; +const getGrantedGroupsDescription = groups => { + const str = groups.map(group => group.groupname).join(); + if (str.length < 25) { + return str; + } else { + return str.substring(0, 22) + '...'; + } +}; + const isAdmin = cookies.get('admin') === 'true'; -const vcListColumns = [ - { - key: 'name', - minWidth: 45, - maxWidth: 100, - name: 'Name', - isResizable: true, - onRender(vc) { - return ( - - {isAdmin ? ( - - {vc.dedicated ? vc.name + ' (dedicated)' : vc.name} - - ) : ( -
- {vc.dedicated ? vc.name + ' (dedicated)' : vc.name} -
- )} -
- ); +const vcListColumns = colProps => { + const columns = [ + { + key: 'name', + minWidth: 45, + maxWidth: 100, + name: 'Name', + isResizable: true, + onRender(vc) { + return ( + + {isAdmin ? ( + + {vc.dedicated ? vc.name + ' (dedicated)' : vc.name} + + ) : ( +
+ {vc.dedicated ? vc.name + ' (dedicated)' : vc.name} +
+ )} +
+ ); + }, }, - }, - { - key: 'allocation', - minWidth: 80, - maxWidth: 132, - name: 'Allocation', - isResizable: true, - className: zeroPaddingClass, - onRender(vc) { - const { resourcesUsed, resourcesTotal } = vc; - const resourcesGuaranteed = vc.resourcesGuaranteed || resourcesTotal; + { + key: 'allocation', + minWidth: 80, + maxWidth: 132, + name: 'Allocation', + isResizable: true, + className: zeroPaddingClass, + onRender(vc) { + const { resourcesUsed, resourcesTotal } = vc; + const resourcesGuaranteed = vc.resourcesGuaranteed || resourcesTotal; - const resouceUtilization = Math.max( - getResouceUtilization(resourcesUsed.GPUs, resourcesGuaranteed.GPUs), - getResouceUtilization(resourcesUsed.memory, resourcesGuaranteed.memory), - getResouceUtilization(resourcesUsed.vCores, resourcesGuaranteed.vCores), - ); - return ( - - - - ); + const resouceUtilization = Math.max( + getResouceUtilization(resourcesUsed.GPUs, resourcesGuaranteed.GPUs), + getResouceUtilization( + resourcesUsed.memory, + resourcesGuaranteed.memory, + ), + getResouceUtilization( + resourcesUsed.vCores, + resourcesGuaranteed.vCores, + ), + ); + return ( + + + + ); + }, }, - }, - { - key: 'detail', - minWidth: 200, - name: 'Detail: Used / (Total - Bad)', - isResizable: true, - onRender(vc) { - const { resourcesUsed, resourcesTotal } = vc; - const resourcesGuaranteed = vc.resourcesGuaranteed || resourcesTotal; - return ( - - - - - - - - - - - - ); + { + key: 'detail', + minWidth: 100, + name: 'Detail: Used / (Total - Bad)', + isResizable: true, + onRender(vc) { + const { resourcesUsed, resourcesTotal } = vc; + const resourcesGuaranteed = vc.resourcesGuaranteed || resourcesTotal; + return ( + + + + + + + + + + + + ); + }, }, - }, -]; + ]; + + if (isAdmin && colProps.groups) { + columns.push({ + key: 'groups', + minWidth: 250, + name: 'Granted groups', + isResizable: true, + onRender(vc) { + const groups = []; + colProps.groups.forEach(group => { + const virtualClusters = group.extension.acls.virtualClusters; + if (virtualClusters && virtualClusters.includes(vc.name)) { + groups.push(group); + } + }); + return ( + +
+

{getGrantedGroupsDescription(groups)}

+
+ { + colProps.setGroupDetails({ vc, groups, hideDialog: false }); + }} + /> +
+ ); + }, + }); + } + + return columns; +}; export const VirtualClusterDetailsList = props => { const virtualClusters = props.virtualClusters; @@ -164,18 +207,34 @@ export const VirtualClusterDetailsList = props => { ...props, }; delete otherProps.virtualClusters; + delete otherProps.groups; const vcList = Object.entries(virtualClusters).map(([key, val]) => { return { name: key, ...val }; }); + const [groupDetails, setGroupDetails] = useState({ + hideDialog: true, + vc: { name: '' }, + groups: [], + }); + return ( - + <> + + + ); }; diff --git a/src/webportal/src/app/user/fabric/conn.js b/src/webportal/src/app/user/fabric/conn.js index afa48ce0f2..90c9fceee3 100644 --- a/src/webportal/src/app/user/fabric/conn.js +++ b/src/webportal/src/app/user/fabric/conn.js @@ -159,6 +159,10 @@ export const getTokenRequest = async () => { return wrapper(() => client.token.getTokens()); }; +export const getGroupsRequest = async () => { + return wrapper(() => client.group.getAllGroup()); +}; + export const revokeTokenRequest = async token => { await wrapper(() => client.token.deleteToken(token)); if (token === checkToken()) { diff --git a/src/webportal/src/app/user/fabric/user-profile.jsx b/src/webportal/src/app/user/fabric/user-profile.jsx index 5c871d5924..96f40fd597 100644 --- a/src/webportal/src/app/user/fabric/user-profile.jsx +++ b/src/webportal/src/app/user/fabric/user-profile.jsx @@ -20,6 +20,7 @@ import { updateUserPasswordRequest, updateUserEmailRequest, listStorageDetailRequest, + getGroupsRequest, } from './conn'; import t from '../../components/tachyons.scss'; @@ -58,6 +59,7 @@ const UserProfile = () => { const [virtualClusters, setVirtualClusters] = useState(null); const [tokens, setTokens] = useState(null); const [storageDetails, setStorageDetails] = useState(null); + const [groups, setGroups] = useState(null); const [processing, setProcessing] = useState(false); @@ -67,11 +69,13 @@ const UserProfile = () => { const vcPromise = getAllVcsRequest(); const tokenPromise = getTokenRequest(); const storageDetailPromise = listStorageDetailRequest(); + const groupsPromise = getGroupsRequest(); await Promise.all([ userPromise, vcPromise, tokenPromise, storageDetailPromise, + groupsPromise, ]).catch(err => { alert(err); throw err; @@ -96,6 +100,9 @@ const UserProfile = () => { const storageDetails = await storageDetailPromise; setStorageDetails(storageDetails); setLoading(false); + // group + const groups = await groupsPromise; + setGroups(groups); }; fetchData(); }, []); @@ -161,7 +168,10 @@ const UserProfile = () => { - + diff --git a/src/webportal/src/app/user/fabric/user-profile/storage-list.jsx b/src/webportal/src/app/user/fabric/user-profile/storage-list.jsx index 418d0ef75d..79a912e9ba 100644 --- a/src/webportal/src/app/user/fabric/user-profile/storage-list.jsx +++ b/src/webportal/src/app/user/fabric/user-profile/storage-list.jsx @@ -45,13 +45,18 @@ function getStorageServerUri(storageDetail) { ); case 'hdfs': return `hdfs://${data.namenode}:${data.port}`; + case 'unknown': + return ( + <> + {'Unknown'} + + ); default: throw new Error('Invalid storage server type'); } } const StorageList = ({ storageDetails }) => { - console.log(storageDetails); const [items, groups] = useMemo(() => { const items = []; const groups = [];