Skip to content
This repository has been archived by the owner on Jun 6, 2024. It is now read-only.

Commit

Permalink
[Web Portal] New user management UI. (#2726)
Browse files Browse the repository at this point in the history
* New UI for user management

* refine code

* Resolve comments

* Reslove comments

* remove unused import

* Resolve comments

* Change modal content to react component
  • Loading branch information
mslichao authored May 16, 2019
1 parent 796c8e5 commit a774d4d
Show file tree
Hide file tree
Showing 23 changed files with 1,532 additions and 432 deletions.
10 changes: 8 additions & 2 deletions src/webportal/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
"jquery": true,
},
"extends": [
"eslint:recommended",
"eslint:recommended",
"plugin:react/recommended",
"google"
],
Expand All @@ -26,7 +26,13 @@ module.exports = {
},
"overrides": [
{
"files": ["**/*.jsx", "src/app/job/job-view/fabric/**/*.js", "src/app/components/**/*.js", "src/app/home/**/*.js"],
"files": [
"**/*.jsx",
"src/app/job/job-view/fabric/**/*.js",
"src/app/components/**/*.js",
"src/app/home/**/*.js",
"src/app/user/fabric/**/*.js",
],
"parser": "babel-eslint"
}
]
Expand Down
2 changes: 1 addition & 1 deletion src/webportal/config/webpack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const config = (env, argv) => ({
'home': './src/app/home/home.jsx',
'layout': './src/app/layout/layout.component.js',
'register': './src/app/user/user-register/user-register.component.js',
'userView': './src/app/user/user-view/user-view.component.js',
'userView': './src/app/user/fabric/user-view.jsx',
'batchRegister': './src/app/user/fabric/batch-register.jsx',
'changePassword': './src/app/user/change-password/change-password.component.js',
'dashboard': './src/app/dashboard/dashboard.component.js',
Expand Down
17 changes: 13 additions & 4 deletions src/webportal/src/app/components/loading.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import c from 'classnames';
import {isEqual, isNil} from 'lodash';
import {Spinner, SpinnerSize} from 'office-ui-fabric-react/lib/Spinner';
import React, {useLayoutEffect, useState} from 'react';
import PropTypes from 'prop-types';

import t from './tachyons.scss';

Expand All @@ -35,7 +36,7 @@ export const Loading = () => (

// min-height issue hack
// https://stackoverflow.com/questions/8468066/child-inside-parent-with-min-height-100-not-inheriting-height
export const SpinnerLoading = () => {
export const SpinnerLoading = ({label = 'Loading...'}) => {
const [style, setStyle] = useState({});
useLayoutEffect(() => {
function layout() {
Expand Down Expand Up @@ -67,19 +68,27 @@ export const SpinnerLoading = () => {
<div className={c(t.flex, t.itemsCenter, t.justifyCenter, t.fixed)} style={style}>
<div className={c(t.flex, t.itemsCenter)}>
<Spinner size={SpinnerSize.large} />
<div className={c(t.ml4, FontClassNames.xLarge)}>Loading...</div>
<div className={c(t.ml4, FontClassNames.xLarge)}>{label}</div>
</div>
</div>
);
};

export const MaskSpinnerLoading = () => (
SpinnerLoading.propTypes = {
label: PropTypes.string,
};

export const MaskSpinnerLoading = ({label = 'Loading...'}) => (
<div className={c(t.fixed, t.absoluteFill, ColorClassNames.whiteTranslucent40Background, t.z9999)}>
<div className={c(t.flex, t.itemsCenter, t.justifyCenter, t.h100)}>
<div className={c(t.flex, t.itemsCenter)}>
<Spinner size={SpinnerSize.large} />
<div className={c(t.ml4, FontClassNames.xLarge)}>Loading...</div>
<div className={c(t.ml4, FontClassNames.xLarge)}>{label}</div>
</div>
</div>
</div>
);

MaskSpinnerLoading.propTypes = {
label: PropTypes.string,
};
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function MessageBox(props) {
</div>
}
>
{text}
<span style={{whiteSpace: 'pre'}}>{text}</span>
</MessageBar>
</div>
</Modal >
Expand Down
92 changes: 92 additions & 0 deletions src/webportal/src/app/user/fabric/conn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// 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.

import config from '../../config/webportal.config';
import {checkToken} from '../user-auth/user-auth.component';

const fetchWrapper = async (...args) => {
const res = await fetch(...args);
const json = await res.json();
if (res.ok) {
return json;
} else {
throw new Error(json.message);
}
};

export const getAllUsersRequest = async () => {
const url = `${config.restServerUri}/api/v1/user`;
const token = checkToken();
return await fetchWrapper(url, {
headers: {
'Authorization': `Bearer ${token}`,
},
});
};

export const removeUserRequest = async (username) => {
const url = `${config.restServerUri}/api/v1/user`;
const token = checkToken();
return await fetchWrapper(url, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({username}),
});
};

export const updateUserVcRequest = async (username, virtualClusters) => {
const url = `${config.restServerUri}/api/v1/user/${username}/virtualClusters`;
const token = checkToken();
return await fetchWrapper(url, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({virtualClusters}),
});
};

export const updateUserAccountRequest = async (username, password, admin) => {
const url = `${config.restServerUri}/api/v1/user`;
const token = checkToken();
return await fetchWrapper(url, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
username,
password,
admin,
modify: true,
}),
});
};

export const updateUserGithubPATRequest = async (username, githubPAT) => {
const url = `${config.restServerUri}/api/v1/user/${username}/githubPAT`;
const token = checkToken();
return await fetchWrapper(url, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({githubPAT}),
});
};
40 changes: 40 additions & 0 deletions src/webportal/src/app/user/fabric/user-view.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// 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.

import 'core-js/stable';
import 'regenerator-runtime/runtime';
import 'whatwg-fetch';

import React from 'react';
import ReactDOM from 'react-dom';

import UserView from './userView';

const contentWrapper = document.getElementById('content-wrapper');

ReactDOM.render(<UserView />, contentWrapper);

document.getElementById('sidebar-menu--cluster-view--user-management').classList.add('active');

function layout() {
setTimeout(function() {
contentWrapper.style.height = contentWrapper.style.minHeight;
}, 10);
}

window.addEventListener('resize', layout);
window.addEventListener('load', layout);
21 changes: 21 additions & 0 deletions src/webportal/src/app/user/fabric/userView/Context.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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.

import React from 'react';

export default React.createContext({
});
100 changes: 100 additions & 0 deletions src/webportal/src/app/user/fabric/userView/Filter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// 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.

import {toBool} from './utils';

const LOCAL_STORAGE_KEY = 'pai-user-filter';

class Filter {
/**
* @param {Set<string>?} admins
* @param {Set<string>?} virtualClusters
*/
constructor(
keyword = '',
admins = new Set(),
virtualClusters = new Set(),
) {
this.keyword = keyword;
this.admins = admins;
this.virtualClusters = virtualClusters;
}

save() {
const content = JSON.stringify({
admins: Array.from(this.admins),
virtualClusters: Array.from(this.virtualClusters),
});
window.localStorage.setItem(LOCAL_STORAGE_KEY, content);
}

load() {
try {
const content = window.localStorage.getItem(LOCAL_STORAGE_KEY);
const {admins, virtualClusters} = JSON.parse(content);
if (Array.isArray(admins)) {
this.admins = new Set(admins);
}
if (Array.isArray(virtualClusters)) {
this.virtualClusters = new Set(virtualClusters);
}
} catch (e) {
window.localStorage.removeItem(LOCAL_STORAGE_KEY);
}
}

/**
* @param {any[]} users
*/
apply(users) {
const {keyword, admins, virtualClusters} = this;

const filters = [];
if (keyword !== '') {
filters.push(({username, virtualCluster}) => (
username.indexOf(keyword) > -1 ||
virtualCluster.indexOf(keyword) > -1
));
}
if (admins.size > 0) {
filters.push((user) => admins.has(user.admin));
}
if (virtualClusters.size > 0) {
filters.push(({virtualCluster, admin}) => {
if (toBool(admin)) {
return true;
}
if (virtualCluster) {
const vcs = virtualCluster.split(',');
for (let vc of virtualClusters) {
if (vcs.indexOf(vc) == -1) {
return false;
}
}
} else {
return false;
}
return true;
});
}
if (filters.length === 0) return users;

return users.filter((user) => filters.every((filter) => filter(user)));
}
}

export default Filter;
Loading

0 comments on commit a774d4d

Please sign in to comment.