feat: user management

This commit is contained in:
LoadChange 2020-01-05 23:54:11 +08:00
parent 45c333cf37
commit c5443dc9a7
35 changed files with 1127 additions and 2120 deletions

View File

@ -26,7 +26,7 @@ module.exports = Object.assign({}, base, {
context: ['/'], context: ['/'],
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
target: 'http://localhost:8848', target: 'http://11.239.112.161:8848',
pathRewrite: {'^/v1' : '/nacos/v1'} pathRewrite: {'^/v1' : '/nacos/v1'}
}], }],
disableHostCheck: true, disableHostCheck: true,

View File

@ -27,55 +27,55 @@
}, },
"devDependencies": { "devDependencies": {
"@alifd/next-theme-loader": "^1.3.1", "@alifd/next-theme-loader": "^1.3.1",
"@babel/cli": "^7.2.3", "@babel/cli": "^7.7.7",
"@babel/core": "^7.2.2", "@babel/core": "^7.7.7",
"@babel/plugin-proposal-decorators": "^7.2.3", "@babel/plugin-proposal-decorators": "^7.7.4",
"@babel/preset-env": "^7.2.3", "@babel/preset-env": "^7.7.7",
"@babel/runtime": "^7.2.0", "@babel/runtime": "^7.7.7",
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.1",
"babel-loader": "^8.0.4", "babel-loader": "^8.0.4",
"babel-plugin-import": "^1.12.0", "babel-plugin-import": "^1.13.0",
"babel-preset-react-app": "^6.1.0", "babel-preset-react-app": "^9.1.0",
"clean-webpack-plugin": "^0.1.19", "clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^4.6.0", "copy-webpack-plugin": "^5.1.1 ",
"cross-env": "^5.2.0", "cross-env": "^6.0.3",
"css-loader": "^2.0.2", "css-loader": "^3.4.0",
"eslint": "^5.11.0", "eslint": "^6.8.0",
"eslint-config-ali": "^4.1.0", "eslint-config-ali": "^9.0.2",
"eslint-config-prettier": "^3.3.0", "eslint-config-prettier": "^6.8.0",
"eslint-loader": "^2.1.1", "eslint-loader": "^3.0.3",
"eslint-plugin-import": "^2.14.0", "eslint-plugin-import": "^2.14.0",
"eslint-plugin-prettier": "^3.0.0", "eslint-plugin-prettier": "^3.0.0",
"eslint-plugin-react": "^7.11.1", "eslint-plugin-react": "^7.17.0",
"file-loader": "^2.0.0", "file-loader": "^5.0.2",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"husky": "^1.1.4", "husky": "^3.1.0",
"lint-staged": "^8.0.4", "lint-staged": "^9.5.0",
"mini-css-extract-plugin": "^0.5.0", "mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.11.0", "node-sass": "^4.13.0",
"optimize-css-assets-webpack-plugin": "^5.0.1", "optimize-css-assets-webpack-plugin": "^5.0.3",
"prettier": "1.15.2", "prettier": "1.19.1",
"sass-loader": "^7.1.0", "sass-loader": "^8.0.0",
"style-loader": "^0.23.1", "style-loader": "^1.1.2",
"uglifyjs-webpack-plugin": "^2.1.0", "uglifyjs-webpack-plugin": "^2.2.0",
"url-loader": "^1.1.2", "url-loader": "^3.0.0",
"webpack": "^4.28.2", "webpack": "^4.41.4",
"webpack-cli": "^3.1.2", "webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.1.13" "webpack-dev-server": "^3.10.1"
}, },
"dependencies": { "dependencies": {
"@alifd/next": "^1.15.12", "@alifd/next": "^1.17.4",
"axios": "^0.18.0", "axios": "^0.18.0",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"moment": "^2.23.0", "moment": "^2.23.0",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"react": "^16.7.0", "react": "^16.12.0",
"react-dom": "^16.7.0", "react-dom": "^16.12.0",
"react-redux": "^5.1.1", "react-redux": "^7.1.3",
"react-router": "^4.3.1", "react-router": "^5.1.2",
"react-router-dom": "^4.3.1", "react-router-dom": "^5.1.2",
"react-router-redux": "^4.0.8", "react-router-redux": "^4.0.8",
"redux": "^4.0.1", "redux": "^4.0.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"yamljs": "^0.3.0" "yamljs": "^0.3.0"
} }

View File

@ -23,3 +23,9 @@ export const GET_STATE = 'GET_STATE';
export const GET_SUBSCRIBERS = 'GET_SUBSCRIBERS'; export const GET_SUBSCRIBERS = 'GET_SUBSCRIBERS';
export const REMOVE_SUBSCRIBERS = 'REMOVE_SUBSCRIBERS'; export const REMOVE_SUBSCRIBERS = 'REMOVE_SUBSCRIBERS';
export const UPDATE_USER = 'UPDATE_USER';
export const SIGN_IN = 'SIGN_IN';
export const USER_LIST = 'USER_LIST';
export const ROLE_LIST = 'ROLE_LIST';

View File

@ -12,9 +12,7 @@
*/ */
import projectConfig from './config'; import projectConfig from './config';
import moment from 'moment';
import $ from 'jquery'; import $ from 'jquery';
import i18DocObj from './i18ndoc';
const global = window; const global = window;
@ -205,120 +203,6 @@ const nacosUtils = (function(_global) {
}; };
})(global); })(global);
const aliwareIntl = (function(_global) {
/**
* 国际化构造方法
* @param {Object} options 配置信息
*/
function AliwareI18n(options) {
// let currentLocal = options.currentLocal || navigator.language || navigator.userLanguage;
const nowData = options.locals;
this.nowData = nowData;
this.setMomentLocale(this.currentLanguageCode);
}
let aliwareLocal = aliwareGetCookieByKeyName('aliyun_lang') || 'zh';
let aliwareLocalSite = aliwareGetCookieByKeyName('aliyun_country') || 'cn';
aliwareLocal = aliwareLocal.toLowerCase();
aliwareLocalSite = aliwareLocalSite.toLowerCase();
// 当前语言
AliwareI18n.prototype.currentLocal = aliwareLocal;
// 当前地区
AliwareI18n.prototype.currentSite = aliwareLocalSite;
// 当前语言-地区
AliwareI18n.prototype.currentLanguageCode =
aliwareGetCookieByKeyName('docsite_language') || `${aliwareLocal}-${aliwareLocalSite}`;
/**
* 通过key获取对应国际化文案
* @param {String} key 国际化key
*/
AliwareI18n.prototype.get = function(key) {
return this.nowData[key];
};
/**
* 修改国际化文案数据
* @param {String} local 语言信息
*/
AliwareI18n.prototype.changeLanguage = function(local) {
this.nowData = i18DocObj[local] || {};
};
/**
* 数字国际化
* @param {Number} num 数字
*/
AliwareI18n.prototype.intlNumberFormat = function(num) {
if (typeof Intl !== 'object' || typeof Intl.NumberFormat !== 'function') {
return num;
}
try {
return new Intl.NumberFormat(this.currentLanguageCode).format(num || 0);
} catch (error) {
return num;
}
};
/**
* 时间戳格式化
* @param {Number} num 时间戳
* @param {Object} initOption 配置信息
*/
AliwareI18n.prototype.intlTimeFormat = function(num = Date.now(), initOption = {}) {
try {
const date = Object.prototype.toString.call(num) === '[object Date]' ? num : new Date(num);
const options = Object.assign(
{},
{
// weekday: "short",
hour12: false,
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
},
initOption
);
return date.toLocaleDateString(this.currentLanguageCode, options);
} catch (error) {
return typeof moment === 'function' ? moment(num).format() : '--';
}
};
/**
* 获取当前时间格式
* @param {String} language 语言信息: zh/en
*/
AliwareI18n.prototype.getIntlTimeFormat = function(_language) {
const language = _language || aliwareLocal;
const langObj = {
zh: 'YYYY年M月D日 HH:mm:ss',
en: 'MMM D, YYYY, h:mm:ss A',
default: 'YYYY-MM-DD HH:mm:ss',
};
return langObj[language] ? langObj[language] : langObj.default;
};
/**
* 设置moment的locale
* @param {String} languageCode 语言信息: zh-ch/en-us
*/
AliwareI18n.prototype.setMomentLocale = function(languageCode) {
if (Object.prototype.toString.call(moment) === '[object Function]') {
moment.locale(languageCode || this.currentLanguageCode);
return true;
}
return false;
};
return new AliwareI18n({
currentLocal: `${aliwareLocal}`,
locals:
i18DocObj[AliwareI18n.prototype.currentLanguageCode] ||
i18DocObj['en-us'] ||
i18DocObj['zh-cn'] ||
{},
});
})(global);
/** /**
* 获取url中的参数 * 获取url中的参数
*/ */
@ -645,7 +529,6 @@ export {
nacosEvent, nacosEvent,
nacosUtils, nacosUtils,
aliwareGetCookieByKeyName, aliwareGetCookieByKeyName,
aliwareIntl,
getParams, getParams,
setParam, setParam,
setParams, setParams,

File diff suppressed because it is too large Load Diff

View File

@ -44,6 +44,9 @@ import ServiceList from './pages/ServiceManagement/ServiceList';
import ServiceDetail from './pages/ServiceManagement/ServiceDetail'; import ServiceDetail from './pages/ServiceManagement/ServiceDetail';
import SubscriberList from './pages/ServiceManagement/SubscriberList'; import SubscriberList from './pages/ServiceManagement/SubscriberList';
import ClusterNodeList from './pages/ClusterManagement/ClusterNodeList'; import ClusterNodeList from './pages/ClusterManagement/ClusterNodeList';
import UserManagement from './pages/AuthorityControl/UserManagement';
import PermissionsManagement from './pages/AuthorityControl/PermissionsManagement';
import RolesManagement from './pages/AuthorityControl/RolesManagement';
import Welcome from './pages/Welcome/Welcome'; import Welcome from './pages/Welcome/Welcome';
import reducers from './reducers'; import reducers from './reducers';
@ -65,10 +68,7 @@ const reducer = combineReducers({
const store = createStore( const store = createStore(
reducer, reducer,
compose( compose(applyMiddleware(thunk), window[REDUX_DEVTOOLS] ? window[REDUX_DEVTOOLS]() : f => f)
applyMiddleware(thunk),
window[REDUX_DEVTOOLS] ? window[REDUX_DEVTOOLS]() : f => f
)
); );
const MENU = [ const MENU = [
@ -89,12 +89,12 @@ const MENU = [
{ path: '/serviceDetail', component: ServiceDetail }, { path: '/serviceDetail', component: ServiceDetail },
{ path: '/subscriberList', component: SubscriberList }, { path: '/subscriberList', component: SubscriberList },
{ path: '/clusterManagement', component: ClusterNodeList }, { path: '/clusterManagement', component: ClusterNodeList },
{ path: '/userManagement', component: UserManagement },
{ path: '/rolesManagement', component: RolesManagement },
{ path: '/permissionsManagement', component: PermissionsManagement },
]; ];
@connect( @connect(state => ({ ...state.locale }), { changeLanguage })
state => ({ ...state.locale }),
{ changeLanguage }
)
class App extends React.Component { class App extends React.Component {
static propTypes = { static propTypes = {
locale: PropTypes.object, locale: PropTypes.object,

View File

@ -1217,10 +1217,6 @@ form.vertical-margin-lg .form-group {
border-color: #e0e0e0 !important; border-color: #e0e0e0 !important;
} }
.main-container {
padding: 10px;
}
.row-bg-green { .row-bg-green {
background-color: #e4fdda; background-color: #e4fdda;
} }

View File

@ -13,506 +13,122 @@
import React from 'react'; import React from 'react';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { ConfigProvider, Icon } from '@alifd/next';
import Header from './Header';
import $ from 'jquery';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { setParams } from '../globalLib'; import PropTypes from 'prop-types';
import { ConfigProvider, Icon, Menu } from '@alifd/next';
import Header from './Header';
import { getState } from '../reducers/base'; import { getState } from '../reducers/base';
import _menu from '../menu'; import getMenuData from './menu';
import './index.scss'; const { SubMenu, Item } = Menu;
@withRouter @withRouter
@connect( @connect(state => ({ ...state.locale, ...state.base }), { getState })
state => ({ ...state.locale, ...state.base }),
{ getState }
)
@ConfigProvider.config @ConfigProvider.config
class MainLayout extends React.Component { class MainLayout extends React.Component {
static displayName = 'MainLayout'; static displayName = 'MainLayout';
static propTypes = { static propTypes = {
history: PropTypes.object,
location: PropTypes.object,
locale: PropTypes.object, locale: PropTypes.object,
children: PropTypes.any, history: PropTypes.object,
version: PropTypes.any, version: PropTypes.any,
functionMode: PropTypes.any,
getState: PropTypes.func, getState: PropTypes.func,
functionMode: PropTypes.string,
}; };
constructor(props) {
super(props);
this.deepNav = [];
this.oneLevelNavArr = {}; // 平行导航map
this.state = {
navList: [..._menu.data],
leftBarClose: false,
showLink: null,
navRow: [],
noChild: false,
};
}
componentDidMount() { componentDidMount() {
this.props.getState(); this.props.getState();
this.refreshNav();
} }
goBack() { goBack() {
this.props.history.goBack(); this.props.history.goBack();
} }
nacosToggleNav(id, event) {
event.preventDefault();
const nowNav = document.getElementById(id);
const iconClass = nowNav.querySelector('.iconshow');
const subNav = nowNav.querySelector('.subnavlist');
const { classList } = iconClass;
let tmpClassName = 'iconshow ';
for (let i = 0; i < classList.length; i++) {
if (classList[i] === 'icon-arrow-down') {
subNav.style.display = 'none';
subNav.className += ' hidden';
tmpClassName += 'icon-arrow-right';
}
if (classList[i] === 'icon-arrow-right') {
tmpClassName += 'icon-arrow-down';
subNav.className = subNav.className.replace(/hidden/g, '');
subNav.style.display = 'block';
}
}
iconClass.className = tmpClassName;
}
/**
* Click the back button
* TODO: this.props.history.goBack(); ???
* @param url
*/
nacosGoBack(url) {
const params = window.location.hash.split('?')[1];
const urlArr = params.split('&') || [];
const queryParams = [];
for (let i = 0; i < urlArr.length; i++) {
if (
urlArr[i].split('=')[0] !== '_k' &&
urlArr[i].split('=')[0] !== 'dataId' &&
urlArr[i].split('=')[0] !== 'group'
) {
if (urlArr[i].split('=')[0] === 'searchDataId') {
queryParams.push(`dataId=${urlArr[i].split('=')[1]}`);
} else if (urlArr[i].split('=')[0] === 'searchGroup') {
queryParams.push(`group=${urlArr[i].split('=')[1]}`);
} else {
queryParams.push(urlArr[i]);
}
}
}
if (localStorage.getItem('namespace')) {
queryParams.push(`namespace=${localStorage.getItem('namespace')}`);
}
this.props.history.push(`/${url}?${queryParams.join('&')}`);
}
nacosEnterBack() {
document.getElementById('backarrow').style.color = '#09c';
}
nacosOutBack() {
document.getElementById('backarrow').style.color = '#546478';
}
nacosToggleLeftBar() {
if (!this.nacosOutDom) return;
if (!this.state.leftBarClose) {
// 关闭
this.nacosOutDom.className = 'viewFramework-product';
this.nacosLeftBarDom.style.width = 0;
this.nacosBodyDom.style.left = 0;
this.nacosToggleIconDom.style.left = 0;
} else {
this.nacosOutDom.className = 'viewFramework-product viewFramework-product-col-1';
this.nacosLeftBarDom.style.width = '180px';
this.nacosBodyDom.style.left = '180px';
this.nacosToggleIconDom.style.left = '160px';
}
this.setState({
leftBarClose: !this.state.leftBarClose,
});
}
navTo(url) { navTo(url) {
if (url !== '/configdetail' && url !== '/configeditor') { const { search } = this.props.location;
// 二级菜单不清空 this.props.history.push([url, search].join(''));
setParams({
dataId: '',
group: '',
});
}
const params = window.location.hash.split('?')[1];
const urlArr = params.split('&') || [];
const queryParams = [];
for (let i = 0; i < urlArr.length; i++) {
if (urlArr[i].split('=')[0] !== '_k') {
queryParams.push(urlArr[i]);
}
}
this.props.history.push(`${url}?${queryParams.join('&')}`);
} }
nacosSetSpecialNav(item) { isCurrentPath(url) {
item.children.forEach(_item => { const { location } = this.props;
const obj = _item; return url === location.pathname ? 'current-path' : undefined;
}
if (obj.dontUseChild === true) { defaultOpenKeys() {
obj.parentName = item.title; const MenuData = getMenuData(this.props.functionMode);
obj.parentId = item.id; for (let i = 0, len = MenuData.length; i < len; i++) {
obj.parentPath = `/${item.id}`; const { children } = MenuData[i];
this.deepNav.push(obj); if (children && children.filter(({ url }) => url === this.props.location.pathname).length) {
} return String(i);
if (_item.children) {
this.nacosSetSpecialNav(_item);
} }
}
}
isShowGoBack() {
const urls = [];
const MenuData = getMenuData(this.props.functionMode);
MenuData.forEach(item => {
if (item.url) urls.push(item.url);
if (item.children) item.children.forEach(({ url }) => urls.push(url));
}); });
} return !urls.includes(this.props.location.pathname);
nacosNavAct(serviceName, match, location) {
if (!match) {
const formatpath = location.pathname.substr(1); // 得到当前路径
const nowpathobj = this.oneLevelNavArr[formatpath]; // 根据平行导航匹配父类
if (nowpathobj) {
if (nowpathobj.parent === serviceName) {
// 如果父类等于当前的导航则高亮
return true;
}
}
return false;
}
return true;
}
nacosLoopNavDeeply(data, parentServiceName) {
// 深度遍历获取所有的导航数据
data.forEach(item => {
if (item) {
const navObj = item;
const _parentServiceName = item.serviceName;
navObj.parentServiceName = parentServiceName;
this.oneLevelNavArr[item.serviceName] = navObj; // 得到每一个层级的导航映射
if (item.children && item.children.length > 0) {
this.nacosLoopNavDeeply(item.children, _parentServiceName);
}
}
});
}
activeNav(id) {
if (this.preActNav) {
this.preActNav.removeClass('active');
}
const nowNav = $(`#${id}`);
nowNav.addClass('active');
this.preActNav = nowNav;
}
nacosLoopNav(data, _index = 0, parent) {
const { locale = {}, location = {} } = this.props;
const { pathname } = location;
let index = _index;
// 遍历导航只显示2级
const self = this;
return data.map(item => {
if (!item) return '';
index++;
if (item.dontUseChild === true) return '';
if (item.children && item.children.length > 0) {
if (item.isVirtual) {
// 如果是虚拟菜单需要增加展开箭头
const icon = item.isExtend ? (
<span className="icon-arrow-down iconshow" />
) : (
<span className="icon-arrow-right iconshow" />
);
const hiddenClass = item.isExtend ? '' : 'hidden';
return (
<li
style={{ display: item.enable ? 'block' : 'none' }}
key={`${item.serviceName}`}
data-spm-click={`gostr=/aliyun;locaid=${item.serviceName}`}
id={`${item.serviceName}`}
>
<div>
<a href="" onClick={this.nacosToggleNav.bind(this, item.serviceName)}>
<div className="nav-icon">{icon}</div>
<div className="nav-title">{locale[item.serviceName]}</div>
</a>
</div>
<ul className={`subnavlist ${hiddenClass}`}>
{self.nacosLoopNav(item.children, index)}
</ul>
</li>
);
} else {
return (
<li
className={pathname === `/${item.serviceName}` ? 'selected' : ''}
key={`${item.serviceName}`}
data-spm-click={`gostr=/aliyun;locaid=${item.serviceName}`}
onClick={this.navTo.bind(this, `/${item.serviceName}`)}
>
<a
href="javascript:;"
id={`${item.serviceName}`}
onClick={this.activeNav.bind(this, `nav${index}`)}
>
<div className="nav-icon" />
<div className="nav-title">{locale[item.serviceName]}</div>
</a>
</li>
);
}
}
return (
<li
className={pathname === `/${item.serviceName}` ? 'selected' : ''}
key={`${item.serviceName}`}
data-spm-click={`gostr=/aliyun;locaid=${item.serviceName}`}
onClick={this.navTo.bind(this, `/${item.serviceName}`)}
>
<a
href={'javascript:;'}
id={`${item.serviceName}`}
onClick={this.activeNav.bind(this, `nav${index}`)}
>
<div className="nav-icon" />
<div className="nav-title">{locale[item.serviceName]}</div>
</a>
</li>
);
});
}
nacosGetNav(navList) {
let navRow = ''; // 导航
if (navList.length > 0) {
navRow = <ul>{this.nacosLoopNav(navList)}</ul>;
this.nacosLoopNavDeeply(navList); // 深度遍历导航树获得平行map
}
return navRow;
}
renderNav() {
const { navList } = this.state;
this.nacosLeftBarDom = document.getElementById('viewFramework-product-navbar');
this.nacosBodyDom = document.getElementById('viewFramework-product-body');
this.nacosToggleIconDom = document.getElementById('viewFramework-product-navbar-collapse');
this.nacosOutDom = document.getElementById('page-header-mask');
const defaultNav = '/configurationManagement';
this.props.history.listen(location => {
if (this.preSimplePath && this.preSimplePath !== '/') {
if (location.pathname.indexOf(this.preSimplePath) !== -1) {
return;
}
}
const simplePath = window.location.hash.split('?')[0];
const navName = simplePath.substr('2');
this.preSimplePath = simplePath;
if (navName === '') {
this.props.history.push(defaultNav);
setTimeout(() => {
this.activeNav('configurationManagement');
});
return;
}
const nowNavObj = this.oneLevelNavArr[navName];
if (!nowNavObj) {
this.setState({
noChild: true,
});
return;
}
const { parentServiceName } = nowNavObj;
const parentNav = this.oneLevelNavArr[parentServiceName];
if (simplePath !== '/' && nowNavObj && parentNav && !parentNav.isVirtual) {
this.setState({
showLink: (
<div>
<Icon
type="arrow-left"
onClick={this.nacosGoBack.bind(this, parentServiceName)}
id={'backarrow'}
onMouseOver={this.nacosEnterBack.bind(this)}
onMouseLeave={this.nacosOutBack.bind(this)}
style={{
marginLeft: 77,
marginTop: 0,
fontWeight: 'bold',
cursor: 'pointer',
color: '#546478',
fontSize: '20px',
}}
/>
</div>
),
navRow: <ul>{this.nacosLoopNav([nowNavObj])}</ul>,
});
setTimeout(() => {
const navid = navName;
this.activeNav(navid);
});
} else {
this.setState({
showLink: null,
navRow: <ul>{this.nacosLoopNav(navList)}</ul>,
});
setTimeout(() => {
const navid = navName;
this.activeNav(navid);
});
}
});
}
refreshNav() {
const { navList } = this.state;
const { location, history, functionMode } = this.props;
const [configUrl, serviceUrl, clusterUrl] = [
'/configurationManagement',
'/serviceManagement',
'/clusterManagement',
];
this.setState(
{
navList: navList.map(item => {
if (
item.serviceName === 'configurationManagementVirtual' &&
(functionMode === null || functionMode === 'config')
) {
item.enable = true;
}
if (
item.serviceName === 'serviceManagementVirtual' &&
(functionMode === null || functionMode === 'naming')
) {
item.enable = true;
}
if (
item.serviceName === 'clusterManagementVirtual' &&
(functionMode === null || functionMode === 'cluster')
) {
item.enable = true;
}
return item;
}),
},
() => this.setState({ navRow: this.nacosGetNav(navList) }, () => this.renderNav())
);
if (functionMode === 'config' && location.pathname === serviceUrl) {
history.push(configUrl);
}
if (functionMode === 'naming' && location.pathname === configUrl) {
history.push(serviceUrl);
}
if (functionMode === 'cluster' && location.pathname === clusterUrl) {
history.push(clusterUrl);
}
}
componentWillReceiveProps() {
setTimeout(() => this.refreshNav());
} }
render() { render() {
const { locale = {}, version } = this.props; const { locale = {}, version, functionMode } = this.props;
const { nacosName, doesNotExist } = locale; const MenuData = getMenuData(functionMode);
const { showLink, navRow, leftBarClose, noChild } = this.state;
return ( return (
<div className="viewFramework-product" style={{ top: 66 }}> <>
<Header /> <Header />
<div <div className="main-container">
className="viewFramework-product-navbar" <div className="left-panel">
style={{ width: 180, marginLeft: 0 }} {this.isShowGoBack() ? (
id="viewFramework-product-navbar" <div className="go-back" onClick={() => this.goBack()}>
data-spm="acm_nav" <Icon type="arrow-left" />
>
<div className="viewFramework-product-navbar-removed">
<div>
<div className="product-nav-scene product-nav-main-scene">
{showLink ? (
<div className="product-nav-icon env" style={{ height: 80, paddingTop: 25 }}>
{showLink}
</div>
) : (
<div
style={{ textIndent: 0, display: !version ? 'none' : 'block' }}
className="product-nav-title"
title={nacosName}
>
<span>{nacosName}</span>
<span style={{ marginLeft: 5 }}>{version}</span>
</div>
)}
<div
className="product-nav-list"
style={{ position: 'relative', top: 0, height: '100%' }}
>
{navRow}
</div>
</div> </div>
</div>
</div>
</div>
<div
className="viewFramework-product-navbar-collapse"
id="viewFramework-product-navbar-collapse"
onClick={this.nacosToggleLeftBar.bind(this)}
>
<div className="product-navbar-collapse-inner">
<div className="product-navbar-collapse-bg" />
<div className="product-navbar-collapse">
{leftBarClose ? (
<span className="icon-collapse-right" style={{ display: 'block' }} />
) : (
<span className="icon-collapse-left" />
)}
</div>
</div>
</div>
<div
className="viewFramework-product-body"
style={{ marginLeft: 180 }}
id="viewFramework-product-body"
>
<div>
{!noChild ? (
<div>{this.props.children}</div>
) : ( ) : (
<div <>
style={{ <h1 className="nav-title">
height: 300, {locale.nacosName}
lineHeight: 300, <span>{version}</span>
textAlign: 'center', </h1>
fontSize: 18, <Menu
}} defaultOpenKeys={this.defaultOpenKeys()}
> className="nav-menu"
{doesNotExist} openMode="single"
</div> >
{MenuData.map((subMenu, idx) =>
subMenu.children ? (
<SubMenu key={String(idx)} label={locale[subMenu.key]}>
{subMenu.children.map((item, i) => (
<Item
key={[idx, i].join('-')}
onClick={() => this.navTo(item.url)}
className={this.isCurrentPath(item.url)}
>
{locale[item.key]}
</Item>
))}
</SubMenu>
) : (
<Item
key={idx}
className={['first-menu', this.isCurrentPath(subMenu.url)]
.filter(c => c)
.join(' ')}
onClick={() => this.navTo(subMenu.url)}
>
{locale[subMenu.key]}
</Item>
)
)}
</Menu>
</>
)} )}
</div> </div>
<div className="right-panel">{this.props.children}</div>
</div> </div>
</div> </>
); );
} }
} }

View File

@ -11,14 +11,6 @@
* limitations under the License. * limitations under the License.
*/ */
.header-container {
position: fixed;
left: 0;
top: 0;
width: 100%;
z-index: 1000;
background-color: #fff;
}
.header-container-primary { .header-container-primary {
background: #252a2f; background: #252a2f;
} }
@ -1396,5 +1388,62 @@ h6 {
} }
.product-nav-list li.selected a { .product-nav-list li.selected a {
background-color: #F4F6F8; background-color: #f4f6f8;
}
.main-container {
height: calc(100vh - 66px);
.left-panel,
.right-panel {
float: left;
height: 100%;
}
.left-panel {
width: 180px;
background-color: #eaedf1;
}
.right-panel {
width: calc(100% - 180px);
padding: 10px;
}
.nav-title {
margin: 0;
text-align: center;
font-size: 14px;
font-weight: bold;
line-height: 70px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
background-color: #d9dee4;
span {
margin-left: 5px;
}
}
.nav-menu {
padding: 0;
background: transparent;
border: 0;
line-height: 40px;
div.next-menu-item,
.first-menu > .next-menu-item-inner {
color: #333;
}
.next-menu-item-inner {
height: 40px;
color: #666;
}
.current-path {
background-color: #f2f3f7;
}
}
.go-back {
text-align: center;
color: rgb(84, 100, 120);
font-size: 20px;
font-weight: bold;
padding: 10px 0;
margin-top: 14px;
cursor: pointer;
}
} }

View File

@ -0,0 +1,61 @@
export default function(model) {
const configurationMenu = {
key: 'configurationManagementVirtual',
children: [
{
key: 'configurationManagement',
url: '/configurationManagement',
},
{
key: 'listeningToQuery',
url: '/listeningToQuery',
},
],
};
return [
model === 'naming' ? undefined : configurationMenu,
{
key: 'serviceManagementVirtual',
children: [
{
key: 'serviceManagement',
url: '/serviceManagement',
},
{
key: 'subscriberList',
url: '/subscriberList',
},
],
},
{
key: 'clusterManagementVirtual',
children: [
{
key: 'clusterManagement',
url: '/clusterManagement',
},
],
},
{
key: 'authorityControl',
children: [
{
key: 'userList',
url: '/userManagement',
},
{
key: 'roleManagement',
url: '/rolesManagement',
},
{
key: 'privilegeManagement',
url: '/permissionsManagement',
},
],
},
{
key: 'namespace',
url: '/namespace',
},
].filter(item => item);
}

View File

@ -50,6 +50,10 @@ const I18N_CONF = {
namespace: 'Namespace', namespace: 'Namespace',
clusterManagementVirtual: 'ClusterManagement', clusterManagementVirtual: 'ClusterManagement',
clusterManagement: 'Cluster Node List', clusterManagement: 'Cluster Node List',
authorityControl: 'Authority Control',
userList: 'User List',
roleManagement: 'Role Management',
privilegeManagement: 'Privilege Management',
}, },
Password: { Password: {
passwordNotConsistent: 'The passwords are not consistent', passwordNotConsistent: 'The passwords are not consistent',

View File

@ -50,6 +50,10 @@ const I18N_CONF = {
namespace: '命名空间', namespace: '命名空间',
clusterManagementVirtual: '集群管理', clusterManagementVirtual: '集群管理',
clusterManagement: '节点列表', clusterManagement: '节点列表',
authorityControl: '权限控制',
userList: '用户列表',
roleManagement: '角色管理',
privilegeManagement: '权限管理',
}, },
Password: { Password: {
passwordNotConsistent: '两次输入密码不一致', passwordNotConsistent: '两次输入密码不一致',

View File

@ -1,295 +0,0 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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.
*/
module.exports = {
data: [
{
enable: false,
isExtend: true,
name: '配置管理',
title: '配置管理',
isVirtual: true,
projectName: 'nacos',
serviceName: 'configurationManagementVirtual',
link: 'configurationManagementVirtual',
hasFusion: true,
template: '',
registerName: 'com.alibaba.nacos.page.configurationManagementVirtual',
useRouter: false,
id: 'com.alibaba.nacos.page.configurationManagementVirtual',
children: [
{
isExtend: false,
name: '配置列表',
title: '配置列表',
isVirtual: false,
projectName: 'nacos',
serviceName: 'configurationManagement',
link: 'configurationManagement',
hasFusion: true,
template: '',
dontUseChild: false,
registerName: 'com.alibaba.nacos.page.configurationManagement',
useRouter: false,
id: 'configurationManagement',
children: [
{
isExtend: false,
name: '配置详情',
title: '配置详情',
isVirtual: false,
projectName: 'nacos',
serviceName: 'configdetail',
link: 'Configdetail',
hasFusion: true,
template: '',
dontUseChild: false,
registerName: 'com.alibaba.nacos.page.configdetail',
useRouter: false,
id: 'configdetail',
},
{
isExtend: false,
name: '同步配置',
title: '同步配置',
isVirtual: false,
projectName: 'nacos',
serviceName: 'configsync',
link: 'configsync',
hasFusion: true,
template: '',
dontUseChild: true,
registerName: 'com.alibaba.nacos.page.configsync',
useRouter: false,
id: 'configsync',
},
{
isExtend: false,
name: '配置编辑',
title: '配置编辑',
isVirtual: false,
projectName: 'nacos',
serviceName: 'configeditor',
link: 'configeditor',
hasFusion: true,
template: '',
registerName: 'com.alibaba.nacos.page.configeditor',
useRouter: false,
id: 'configeditor',
},
{
isExtend: false,
name: '新建配置',
title: '新建配置',
isVirtual: false,
projectName: 'nacos',
serviceName: 'newconfig',
link: 'newconfig',
hasFusion: true,
template: '',
registerName: 'com.alibaba.nacos.page.newconfig',
useRouter: false,
id: 'newconfig',
},
],
},
{
isExtend: false,
name: '历史版本',
title: '历史版本',
isVirtual: false,
projectName: 'nacos',
children: [
{
isExtend: false,
name: '配置回滚',
title: '配置回滚',
isVirtual: false,
projectName: 'nacos',
serviceName: 'configRollback',
link: 'configRollback',
hasFusion: true,
template: '',
registerName: 'com.alibaba.nacos.page.configRollback',
useRouter: false,
id: 'configRollback',
},
{
isExtend: false,
name: '历史详情',
title: '历史详情',
isVirtual: false,
projectName: 'nacos',
serviceName: 'historyDetail',
link: 'historyDetail',
hasFusion: true,
template: '',
registerName: 'com.alibaba.nacos.page.historyDetail',
useRouter: false,
id: 'historyDetail',
},
],
serviceName: 'historyRollback',
link: 'historyRollback',
hasFusion: true,
template: '',
dontUseChild: false,
registerName: 'com.alibaba.nacos.page.historyRollback',
useRouter: false,
id: 'historyRollback',
},
{
isExtend: false,
name: '监听查询',
title: '监听查询',
isVirtual: false,
projectName: 'nacos',
serviceName: 'listeningToQuery',
link: 'listeningToQuery',
hasFusion: true,
template: '',
registerName: 'com.alibaba.nacos.page.listeningToQuery',
useRouter: false,
id: 'listeningToQuery',
},
],
},
{
enable: false,
isExtend: true,
name: '服务管理',
title: '服务管理',
isVirtual: true,
projectName: 'nacos',
serviceName: 'serviceManagementVirtual',
link: 'serviceManagementVirtual',
hasFusion: true,
template: '',
registerName: 'com.alibaba.nacos.page.serviceManagementVirtual',
useRouter: false,
id: 'com.alibaba.nacos.page.serviceManagementVirtual',
children: [
{
isExtend: false,
name: '服务列表',
title: '服务列表',
isVirtual: false,
projectName: 'nacos',
serviceName: 'serviceManagement',
link: 'serviceManagement',
hasFusion: true,
template: '',
registerName: 'com.alibaba.nacos.page.serviceManagement',
useRouter: false,
id: 'serviceManagement',
children: [
{
isExtend: true,
name: '服务详情',
title: '服务详情',
isVirtual: true,
projectName: 'nacos',
serviceName: 'serviceDetail',
link: 'serviceDetail',
hasFusion: true,
template: '',
registerName: 'com.alibaba.nacos.page.ServiceDetail',
useRouter: false,
id: 'serviceDetail',
},
],
},
{
isExtend: false,
name: '订阅者列表',
title: '订阅者列表',
isVirtual: false,
projectName: 'nacos',
serviceName: 'subscriberList',
link: 'subscriberList',
hasFusion: true,
template: '',
registerName: 'com.alibaba.nacos.page.subscriberList',
useRouter: false,
id: 'subscriberList',
children: [],
},
],
},
{
enable: true,
isExtend: false,
name: '命名空间',
title: '命名空间',
isVirtual: false,
projectName: 'nacos',
serviceName: 'namespace',
link: 'namespace',
hasFusion: true,
template: '',
dontUseChild: false,
registerName: 'com.alibaba.nacos.page.namespace',
useRouter: false,
id: 'namespace',
},
{
enable: true,
isExtend: false,
name: '修改密码',
title: '修改密码',
isVirtual: false,
projectName: 'nacos',
serviceName: 'password',
link: 'password',
hasFusion: true,
template: '',
dontUseChild: true,
registerName: 'com.alibaba.nacos.page.password',
useRouter: false,
id: 'password',
},
{
enable: false,
isExtend: true,
name: '集群管理',
title: '集群管理',
isVirtual: true,
projectName: 'nacos',
serviceName: 'clusterManagementVirtual',
link: 'clusterManagementVirtual',
hasFusion: true,
template: '',
registerName: 'com.alibaba.nacos.page.clusterManagementVirtual',
useRouter: false,
id: 'com.alibaba.nacos.page.clusterManagementVirtual',
children: [
{
isExtend: false,
name: '节点状态',
title: '节点状态',
isVirtual: false,
projectName: 'nacos',
serviceName: 'clusterManagement',
link: 'clusterManagement',
hasFusion: true,
template: '',
registerName: 'com.alibaba.nacos.page.clusterManagement',
useRouter: false,
id: 'clusterManagement',
},
],
},
],
defaultKey: 'configurationManagement',
projectName: 'nacos',
};

View File

@ -0,0 +1,47 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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 PropTypes from 'prop-types';
import {
Button,
Field,
Form,
Grid,
Input,
Loading,
Pagination,
Table,
ConfigProvider,
} from '@alifd/next';
import './PermissionsManagement.scss';
@ConfigProvider.config
class PermissionsManagement extends React.Component {
static displayName = 'UserManagement';
static propTypes = {
locale: PropTypes.object,
};
render() {
return (
<>
<h1>PermissionsManagement</h1>
</>
);
}
}
export default PermissionsManagement;

View File

@ -0,0 +1,12 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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.
*/

View File

@ -0,0 +1,16 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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 PermissionsManagement from './PermissionsManagement';
export default PermissionsManagement;

View File

@ -0,0 +1,7 @@
# 权限控制
> AuthorityControl
1. UserManagement => 用户管理
2. RolesManagement => 角色管理
3. PermissionsManagement => 权限管理

View File

@ -0,0 +1,96 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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 PropTypes from 'prop-types';
import {
Button,
Field,
Form,
Input,
Dialog,
Pagination,
Table,
ConfigProvider,
// Field,
} from '@alifd/next';
const FormItem = Form.Item;
const formItemLayout = {
labelCol: { fixedSpan: 3 },
wrapperCol: { span: 20 },
};
@ConfigProvider.config
class NewRole extends React.Component {
static displayName = 'NewRole';
field = new Field(this);
static propTypes = {
locale: PropTypes.object,
visible: PropTypes.bool,
};
check() {
const errors = {
role: '角色不能为空!',
username: '用户名不能为空!',
};
const vals = ['role', 'username'].map(key => {
const val = this.field.getValue(key);
if (!val) {
this.field.setError(key, errors[key]);
}
return val;
});
if (vals.filter(v => v).length === 2) {
return vals;
}
return null;
}
render() {
const { getError } = this.field;
const { visible, onOk, onCancel } = this.props;
return (
<>
<Dialog
title="绑定角色"
visible={visible}
onOk={() => {
const vals = this.check();
if (vals) {
onOk(vals).then(() => onCancel());
}
}}
onClose={onCancel}
onCancel={onCancel}
afterClose={() => this.field.reset()}
>
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
<FormItem label="角色名" required help={getError('role')}>
<Input name="role" trim placeholder="Please Enter Role" />
</FormItem>
<FormItem label="用户名" required help={getError('username')}>
<Input name="username" placeholder="Please Enter Username" />
</FormItem>
</Form>
</Dialog>
</>
);
}
}
export default NewRole;

View File

@ -0,0 +1,138 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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 PropTypes from 'prop-types';
import { Button, Field, Form, Input, Dialog, Pagination, Table, ConfigProvider } from '@alifd/next';
import { connect } from 'react-redux';
import { getRoles, createRole, deleteRole } from '../../../reducers/authority';
import RegionGroup from '../../../components/RegionGroup';
import NewRole from './NewRole';
import './RolesManagement.scss';
const FormItem = Form.Item;
const formItemLayout = {
labelCol: { fixedSpan: 3 },
wrapperCol: { span: 20 },
};
@connect(state => ({ roles: state.authority.roles }), { getRoles })
@ConfigProvider.config
class RolesManagement extends React.Component {
static displayName = 'RolesManagement';
static propTypes = {
locale: PropTypes.object,
roles: PropTypes.object,
getRoles: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
loading: true,
pageNo: 1,
pageSize: 9,
};
}
componentDidMount() {
this.getRoles();
}
getRoles() {
const { pageNo, pageSize } = this.state;
this.props
.getRoles({ pageNo, pageSize })
.then(() => {
if (this.state.loading) {
this.setState({ loading: false });
}
})
.catch(() => this.setState({ loading: false }));
}
colseCreateRole() {
this.setState({ createRoleVisible: false });
}
render() {
const { roles } = this.props;
const { loading, pageSize, pageNo, createRoleVisible, passwordResetUser } = this.state;
return (
<>
<RegionGroup left={'角色管理'} />
<div className="filter-panel">
<Button
type="primary"
className="create-user-btn"
onClick={() => this.setState({ createRoleVisible: true })}
>
绑定角色
</Button>
</div>
<Table dataSource={roles.pageItems} loading={loading} maxBodyHeight={476} fixedHeader>
<Table.Column title="角色名" dataIndex="role" />
<Table.Column title="用户名" dataIndex="username" />
<Table.Column
title="操作"
dataIndex="username"
cell={(value, index, record) => (
<>
<Button
type="primary"
warning
onClick={() =>
Dialog.confirm({
title: '确认',
content: '是否要删除该角色',
onOk: () =>
deleteRole(record).then(() =>
this.setState({ pageNo: 1 }, () => this.getRoles())
),
})
}
>
刪除
</Button>
</>
)}
/>
</Table>
{roles.totalCount > pageSize && (
<Pagination
className="users-pagination"
current={pageNo}
total={roles.totalCount}
pageSize={pageSize}
onChange={pageNo => this.setState({ pageNo }, () => this.getRoles())}
/>
)}
<NewRole
visible={createRoleVisible}
onOk={role =>
createRole(role).then(res => {
this.getRoles();
return res;
})
}
onCancel={() => this.colseCreateRole()}
/>
</>
);
}
}
export default RolesManagement;

View File

@ -0,0 +1,12 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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.
*/

View File

@ -0,0 +1,16 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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 RolesManagement from './RolesManagement';
export default RolesManagement;

View File

@ -0,0 +1,97 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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 PropTypes from 'prop-types';
import {
Button,
Field,
Form,
Input,
Dialog,
Pagination,
Table,
ConfigProvider,
// Field,
} from '@alifd/next';
import './UserManagement.scss';
const FormItem = Form.Item;
const formItemLayout = {
labelCol: { fixedSpan: 3 },
wrapperCol: { span: 20 },
};
@ConfigProvider.config
class NewUser extends React.Component {
static displayName = 'NewUser';
field = new Field(this);
static propTypes = {
locale: PropTypes.object,
visible: PropTypes.bool,
};
check() {
const errors = {
username: '用户名不能为空!',
password: '密码不能为空!',
};
const vals = ['username', 'password'].map(key => {
const val = this.field.getValue(key);
if (!val) {
this.field.setError(key, errors[key]);
}
return val;
});
if (vals.filter(v => v).length === 2) {
return vals;
}
return null;
}
render() {
const { getError } = this.field;
const { visible, onOk, onCancel } = this.props;
return (
<>
<Dialog
title="创建用户"
visible={visible}
onOk={() => {
const vals = this.check();
if (vals) {
onOk(vals).then(() => onCancel());
}
}}
onClose={onCancel}
onCancel={onCancel}
afterClose={() => this.field.reset()}
>
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
<FormItem label="用户名" required help={getError('username')}>
<Input name="username" trim placeholder="Please Enter Username" />
</FormItem>
<FormItem label="密码" required help={getError('password')}>
<Input name="password" htmlType="password" placeholder="Please Enter Password" />
</FormItem>
</Form>
</Dialog>
</>
);
}
}
export default NewUser;

View File

@ -0,0 +1,94 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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 PropTypes from 'prop-types';
import {
Button,
Field,
Form,
Input,
Dialog,
Pagination,
Table,
ConfigProvider,
// Field,
} from '@alifd/next';
import './UserManagement.scss';
const FormItem = Form.Item;
const formItemLayout = {
labelCol: { fixedSpan: 3 },
wrapperCol: { span: 20 },
};
@ConfigProvider.config
class PasswordReset extends React.Component {
static displayName = 'PasswordReset';
field = new Field(this);
static propTypes = {
locale: PropTypes.object,
visible: PropTypes.bool,
};
check() {
const errors = { password: '密码不能为空!' };
const vals = ['password'].map(key => {
const val = this.field.getValue(key);
if (!val) {
this.field.setError(key, errors[key]);
}
return val;
});
if (vals.filter(v => v).length === 1) {
return [this.props.username, ...vals];
}
return null;
}
render() {
const { getError } = this.field;
const { username, onOk, onCancel } = this.props;
return (
<>
<Dialog
title="密码重置"
visible={username}
onOk={() => {
const vals = this.check();
if (vals) {
onOk(vals).then(() => onCancel());
}
}}
onClose={onCancel}
onCancel={onCancel}
afterClose={() => this.field.reset()}
>
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
<FormItem label="用户名" required>
<p>{username}</p>
</FormItem>
<FormItem label="密码" required help={getError('password')}>
<Input name="password" htmlType="password" placeholder="Please Enter Password" />
</FormItem>
</Form>
</Dialog>
</>
);
}
}
export default PasswordReset;

View File

@ -0,0 +1,161 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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 PropTypes from 'prop-types';
import { Button, Field, Form, Input, Dialog, Pagination, Table, ConfigProvider } from '@alifd/next';
import { connect } from 'react-redux';
import { getUsers, createUser, deleteUser, passwordReset } from '../../../reducers/authority';
import RegionGroup from '../../../components/RegionGroup';
import NewUser from './NewUser';
import PasswordReset from './PasswordReset';
import './UserManagement.scss';
const FormItem = Form.Item;
const formItemLayout = {
labelCol: { fixedSpan: 3 },
wrapperCol: { span: 20 },
};
@connect(state => ({ users: state.authority.users }), { getUsers })
@ConfigProvider.config
class UserManagement extends React.Component {
static displayName = 'UserManagement';
static propTypes = {
locale: PropTypes.object,
users: PropTypes.object,
getUsers: PropTypes.func,
createUser: PropTypes.func,
};
constructor(props) {
super(props);
this.state = {
loading: true,
pageNo: 1,
pageSize: 9,
};
}
componentDidMount() {
this.getUsers();
}
getUsers() {
const { pageNo, pageSize } = this.state;
this.props
.getUsers({ pageNo, pageSize })
.then(() => {
if (this.state.loading) {
this.setState({ loading: false });
}
})
.catch(() => this.setState({ loading: false }));
}
colseCreateUser() {
this.setState({ createUserVisible: false });
}
render() {
const { users } = this.props;
const { loading, pageSize, pageNo, createUserVisible, passwordResetUser } = this.state;
return (
<>
<RegionGroup left={'用户管理'} />
<div className="filter-panel">
<Button
type="primary"
className="create-user-btn"
onClick={() => this.setState({ createUserVisible: true })}
>
创建用户
</Button>
</div>
<Table dataSource={users.pageItems} loading={loading} maxBodyHeight={476} fixedHeader>
<Table.Column title="用户名" dataIndex="username" />
<Table.Column
title="密码"
dataIndex="password"
cell={value => value.replace(/\S/g, '*')}
/>
<Table.Column
title="操作"
dataIndex="username"
cell={username => (
<>
<Button
type="primary"
onClick={() => this.setState({ passwordResetUser: username })}
>
修改
</Button>
&nbsp;&nbsp;&nbsp;
<Button
type="primary"
warning
onClick={() =>
Dialog.confirm({
title: '确认',
content: '是否要删除该用户',
onOk: () =>
deleteUser(username).then(() =>
this.setState({ pageNo: 1 }, () => this.getUsers())
),
})
}
>
刪除
</Button>
</>
)}
/>
</Table>
{users.totalCount > pageSize && (
<Pagination
className="users-pagination"
current={pageNo}
total={users.totalCount}
pageSize={pageSize}
onChange={pageNo => this.setState({ pageNo }, () => this.getUsers())}
/>
)}
<NewUser
visible={createUserVisible}
onOk={user =>
createUser(user).then(res => {
this.setState({ pageNo: 1 }, () => this.getUsers());
return res;
})
}
onCancel={() => this.colseCreateUser()}
/>
<PasswordReset
username={passwordResetUser}
onOk={user =>
passwordReset(user).then(res => {
this.getUsers();
return res;
})
}
onCancel={() => this.setState({ passwordResetUser: undefined })}
/>
</>
);
}
}
export default UserManagement;

View File

@ -0,0 +1,18 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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 '../authority.scss';
.users-pagination {
float: right;
margin-top: 20px;
}

View File

@ -0,0 +1,16 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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 UserManagement from './UserManagement';
export default UserManagement;

View File

@ -0,0 +1,17 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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.
*/
.filter-panel {
text-align: right;
padding: 10px 0;
}

View File

@ -1095,7 +1095,7 @@ class ConfigurationManagement extends React.Component {
render() { render() {
const { locale = {} } = this.props; const { locale = {} } = this.props;
return ( return (
<div> <>
<BatchHandle ref={ref => (this.batchHandle = ref)} /> <BatchHandle ref={ref => (this.batchHandle = ref)} />
<Loading <Loading
shape={'flower'} shape={'flower'}
@ -1107,7 +1107,7 @@ class ConfigurationManagement extends React.Component {
<div className={this.state.hasdash ? 'dash-page-container' : ''}> <div className={this.state.hasdash ? 'dash-page-container' : ''}>
<div <div
className={this.state.hasdash ? 'dash-left-container' : ''} className={this.state.hasdash ? 'dash-left-container' : ''}
style={{ position: 'relative', padding: 10 }} style={{ position: 'relative' }}
> >
<div style={{ display: this.inApp ? 'none' : 'block', marginTop: -15 }}> <div style={{ display: this.inApp ? 'none' : 'block', marginTop: -15 }}>
<RegionGroup <RegionGroup
@ -1359,7 +1359,7 @@ class ConfigurationManagement extends React.Component {
)} )}
</div> </div>
</Loading> </Loading>
</div> </>
); );
} }
} }

View File

@ -13,7 +13,4 @@
.next-pagination-size-selector{ .next-pagination-size-selector{
position: static !important; position: static !important;
} }
.next-overlay-inner{
top:154px !important;
}

View File

@ -183,7 +183,7 @@ class ListeningToQuery extends React.Component {
}, },
]; ];
return ( return (
<div style={{ padding: 10 }}> <>
<Loading <Loading
shape="flower" shape="flower"
style={{ position: 'relative' }} style={{ position: 'relative' }}
@ -320,7 +320,7 @@ class ListeningToQuery extends React.Component {
, ,
</div> </div>
</Loading> </Loading>
</div> </>
); );
} }
} }

View File

@ -301,7 +301,7 @@ class NameSpace extends React.Component {
namespaceOperation, namespaceOperation,
} = locale; } = locale;
return ( return (
<div style={{ padding: 10 }} className="clearfix"> <>
<RegionGroup left={namespace} /> <RegionGroup left={namespace} />
<div className="fusion-demo"> <div className="fusion-demo">
<Loading <Loading
@ -348,7 +348,7 @@ class NameSpace extends React.Component {
<EditorNameSpace ref={this.editgroup} getNameSpaces={this.getNameSpaces.bind(this)} /> <EditorNameSpace ref={this.editgroup} getNameSpaces={this.getNameSpaces.bind(this)} />
</Loading> </Loading>
</div> </div>
</div> </>
); );
} }
} }

View File

@ -24,15 +24,8 @@ class Welcome extends React.Component {
render() { render() {
const { functionMode } = this.props; const { functionMode } = this.props;
return ( const path = functionMode === 'naming' ? 'serviceManagement' : 'configurationManagement';
<div> return <>{functionMode !== '' && <Redirect to={`/${path}`} />}</>;
{functionMode !== '' && (
<Redirect
to={`/${functionMode === 'naming' ? 'serviceManagement' : 'configurationManagement'}`}
/>
)}
</div>
);
} }
} }

View File

@ -0,0 +1,96 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
* 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 { Message } from '@alifd/next';
import request from '../utils/request';
import { UPDATE_USER, SIGN_IN, USER_LIST, ROLE_LIST } from '../constants';
const initialState = {
users: {
totalCount: 0,
pageNumber: 1,
pagesAvailable: 1,
pageItems: [],
},
roles: {},
};
const successMsg = res => {
if (res.code === 200) {
Message.success(res.message);
}
return res;
};
/**
* 用户列表
* @param {*} params
*/
const getUsers = params => dispatch =>
request.get('v1/auth/users', { params }).then(data => dispatch({ type: USER_LIST, data }));
/**
* 创建用户
* @param {*} param0
*/
const createUser = ([username, password]) =>
request.post('v1/auth/users', { username, password }).then(res => successMsg(res));
/**
* 删除用户
* @param {*} username
*/
const deleteUser = username =>
request.delete('v1/auth/users', { params: { username } }).then(res => successMsg(res));
/**
* 重置密码
* @param {*} param0
*/
const passwordReset = ([username, newPassword]) =>
request.put('v1/auth/users', { username, newPassword }).then(res => successMsg(res));
/**
* 角色列表
* @param {*} params
*/
const getRoles = params => dispatch =>
request.get('v1/auth/roles', { params }).then(data => dispatch({ type: ROLE_LIST, data }));
/**
* 创建角色
* @param {*} param0
*/
const createRole = ([role, username]) =>
request.post('v1/auth/roles', { role, username }).then(res => successMsg(res));
/**
* 删除角色
* @param {*} param0
*/
const deleteRole = role =>
request.delete('v1/auth/roles', { params: role }).then(res => successMsg(res));
export default (state = initialState, action) => {
switch (action.type) {
case USER_LIST:
return { ...state, users: { ...action.data } };
case ROLE_LIST:
return { ...state, roles: { ...action.data } };
default:
return state;
}
};
export { getUsers, createUser, deleteUser, passwordReset, getRoles, createRole, deleteRole };

View File

@ -14,5 +14,6 @@
import locale from './locale'; import locale from './locale';
import base from './base'; import base from './base';
import subscribers from './subscribers'; import subscribers from './subscribers';
import authority from './authority';
export default { locale, base, subscribers }; export default { locale, base, subscribers, authority };

View File

@ -1,4 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import qs from 'qs';
import { Message } from '@alifd/next'; import { Message } from '@alifd/next';
// import { SUCCESS_RESULT_CODE } from '../constants'; // import { SUCCESS_RESULT_CODE } from '../constants';
@ -7,6 +8,21 @@ const API_GENERAL_ERROR_MESSAGE = 'Request error, please try again later!';
const request = () => { const request = () => {
const instance = axios.create(); const instance = axios.create();
instance.interceptors.request.use(
function(config) {
if (['post', 'put'].includes(config.method)) {
config.data = qs.stringify(config.data);
config.headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
}
return config;
},
function(error) {
return Promise.reject(error);
}
);
instance.interceptors.response.use( instance.interceptors.response.use(
response => { response => {
const { success, resultCode, resultMessage = API_GENERAL_ERROR_MESSAGE } = response.data; const { success, resultCode, resultMessage = API_GENERAL_ERROR_MESSAGE } = response.data;