mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-04 13:48:56 +02:00
fix: make admin pages work for OSS and enterprise (#268)
* fix: make admin pages work for OSS and enterprise * fix: more admin tuning * fix: project mgm access
This commit is contained in:
parent
2a0acd3fb2
commit
b3436b5ae6
@ -93,6 +93,15 @@ Array [
|
|||||||
"title": "Sign out",
|
"title": "Sign out",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"component": [Function],
|
||||||
|
"hidden": false,
|
||||||
|
"icon": "album",
|
||||||
|
"layout": "main",
|
||||||
|
"path": "/admin",
|
||||||
|
"title": "Admin",
|
||||||
|
"type": "protected",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -260,39 +269,6 @@ Array [
|
|||||||
"title": "Projects",
|
"title": "Projects",
|
||||||
"type": "protected",
|
"type": "protected",
|
||||||
},
|
},
|
||||||
Object {
|
|
||||||
"component": [Function],
|
|
||||||
"layout": "main",
|
|
||||||
"parent": "/admin",
|
|
||||||
"path": "/admin/api",
|
|
||||||
"title": "API access",
|
|
||||||
"type": "protected",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"component": [Function],
|
|
||||||
"layout": "main",
|
|
||||||
"parent": "/admin",
|
|
||||||
"path": "/admin/users",
|
|
||||||
"title": "Users",
|
|
||||||
"type": "protected",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"component": [Function],
|
|
||||||
"layout": "main",
|
|
||||||
"parent": "/admin",
|
|
||||||
"path": "/admin/auth",
|
|
||||||
"title": "Authentication",
|
|
||||||
"type": "protected",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"component": [Function],
|
|
||||||
"hidden": true,
|
|
||||||
"icon": "album",
|
|
||||||
"layout": "main",
|
|
||||||
"path": "/admin",
|
|
||||||
"title": "Admin",
|
|
||||||
"type": "protected",
|
|
||||||
},
|
|
||||||
Object {
|
Object {
|
||||||
"component": [Function],
|
"component": [Function],
|
||||||
"layout": "main",
|
"layout": "main",
|
||||||
@ -389,5 +365,43 @@ Array [
|
|||||||
"title": "Log in",
|
"title": "Log in",
|
||||||
"type": "unprotected",
|
"type": "unprotected",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"component": [Function],
|
||||||
|
"layout": "main",
|
||||||
|
"parent": "/admin",
|
||||||
|
"path": "/admin/api",
|
||||||
|
"title": "API access",
|
||||||
|
"type": "protected",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": [Function],
|
||||||
|
"layout": "main",
|
||||||
|
"parent": "/admin",
|
||||||
|
"path": "/admin/users",
|
||||||
|
"title": "Users",
|
||||||
|
"type": "protected",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": Object {
|
||||||
|
"$$typeof": Symbol(react.memo),
|
||||||
|
"WrappedComponent": [Function],
|
||||||
|
"compare": null,
|
||||||
|
"type": [Function],
|
||||||
|
},
|
||||||
|
"layout": "main",
|
||||||
|
"parent": "/admin",
|
||||||
|
"path": "/admin/auth",
|
||||||
|
"title": "Authentication",
|
||||||
|
"type": "protected",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": [Function],
|
||||||
|
"hidden": false,
|
||||||
|
"icon": "album",
|
||||||
|
"layout": "main",
|
||||||
|
"path": "/admin",
|
||||||
|
"title": "Admin",
|
||||||
|
"type": "protected",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
@ -6,7 +6,7 @@ test('returns all defined routes', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('returns all baseRoutes', () => {
|
test('returns all baseRoutes', () => {
|
||||||
expect(baseRoutes.length).toEqual(11);
|
expect(baseRoutes.length).toEqual(12);
|
||||||
expect(baseRoutes).toMatchSnapshot();
|
expect(baseRoutes).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -213,41 +213,6 @@ export const routes = [
|
|||||||
layout: 'main',
|
layout: 'main',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Admin
|
|
||||||
{
|
|
||||||
path: '/admin/api',
|
|
||||||
parent: '/admin',
|
|
||||||
title: 'API access',
|
|
||||||
component: AdminApi,
|
|
||||||
type: 'protected',
|
|
||||||
layout: 'main',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/admin/users',
|
|
||||||
parent: '/admin',
|
|
||||||
title: 'Users',
|
|
||||||
component: AdminUsers,
|
|
||||||
type: 'protected',
|
|
||||||
layout: 'main',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/admin/auth',
|
|
||||||
parent: '/admin',
|
|
||||||
title: 'Authentication',
|
|
||||||
component: AdminAuth,
|
|
||||||
type: 'protected',
|
|
||||||
layout: 'main',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/admin',
|
|
||||||
title: 'Admin',
|
|
||||||
icon: 'album',
|
|
||||||
component: Admin,
|
|
||||||
hidden: true,
|
|
||||||
type: 'protected',
|
|
||||||
layout: 'main',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/tag-types/create',
|
path: '/tag-types/create',
|
||||||
parent: '/tag-types',
|
parent: '/tag-types',
|
||||||
@ -342,6 +307,40 @@ export const routes = [
|
|||||||
hidden: true,
|
hidden: true,
|
||||||
layout: 'standalone',
|
layout: 'standalone',
|
||||||
},
|
},
|
||||||
|
// Admin
|
||||||
|
{
|
||||||
|
path: '/admin/api',
|
||||||
|
parent: '/admin',
|
||||||
|
title: 'API access',
|
||||||
|
component: AdminApi,
|
||||||
|
type: 'protected',
|
||||||
|
layout: 'main',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/users',
|
||||||
|
parent: '/admin',
|
||||||
|
title: 'Users',
|
||||||
|
component: AdminUsers,
|
||||||
|
type: 'protected',
|
||||||
|
layout: 'main',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/auth',
|
||||||
|
parent: '/admin',
|
||||||
|
title: 'Authentication',
|
||||||
|
component: AdminAuth,
|
||||||
|
type: 'protected',
|
||||||
|
layout: 'main',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin',
|
||||||
|
title: 'Admin',
|
||||||
|
icon: 'album',
|
||||||
|
component: Admin,
|
||||||
|
hidden: false,
|
||||||
|
type: 'protected',
|
||||||
|
layout: 'main',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getRoute = path => routes.find(route => route.path === path);
|
export const getRoute = path => routes.find(route => route.path === path);
|
||||||
|
@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import HeaderTitle from '../../common/HeaderTitle';
|
import HeaderTitle from '../../common/HeaderTitle';
|
||||||
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender';
|
||||||
import { CREATE_PROJECT, DELETE_PROJECT } from '../../../permissions';
|
import { CREATE_PROJECT, DELETE_PROJECT, UPDATE_PROJECT } from '../../../permissions';
|
||||||
import { Icon, IconButton, List, ListItem, ListItemAvatar, ListItemText, Tooltip } from '@material-ui/core';
|
import { Icon, IconButton, List, ListItem, ListItemAvatar, ListItemText, Tooltip } from '@material-ui/core';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import ConfirmDialogue from '../../common/Dialogue';
|
import ConfirmDialogue from '../../common/Dialogue';
|
||||||
@ -36,6 +36,16 @@ const ProjectList = ({ projects, fetchProjects, removeProject, history, hasPermi
|
|||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const mgmAccessButton = project => (
|
||||||
|
<Tooltip title="Manage access">
|
||||||
|
<Link to={`/projects/${project.id}/access`} style={{ color: 'black' }}>
|
||||||
|
<IconButton aria-label="manage_access" >
|
||||||
|
<Icon>supervised_user_circle</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
|
||||||
const deleteProjectButton = project => (
|
const deleteProjectButton = project => (
|
||||||
<Tooltip title="Remove project">
|
<Tooltip title="Remove project">
|
||||||
<IconButton
|
<IconButton
|
||||||
@ -57,12 +67,16 @@ const ProjectList = ({ projects, fetchProjects, removeProject, history, hasPermi
|
|||||||
<Icon>folder_open</Icon>
|
<Icon>folder_open</Icon>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText primary={projectLink(project)} secondary={project.description} />
|
<ListItemText primary={projectLink(project)} secondary={project.description} />
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={hasPermission(UPDATE_PROJECT)}
|
||||||
|
show={mgmAccessButton(project)}
|
||||||
|
/>
|
||||||
<ConditionallyRender condition={hasPermission(DELETE_PROJECT)} show={deleteProjectButton(project)} />
|
<ConditionallyRender condition={hasPermission(DELETE_PROJECT)} show={deleteProjectButton(project)} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent headerContent={<HeaderTitle title="Projects (beta)" actions={addProjectButton()} />}>
|
<PageContent headerContent={<HeaderTitle title="Projects" actions={addProjectButton()} />}>
|
||||||
<List>
|
<List>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={projects.length > 0}
|
condition={projects.length > 0}
|
||||||
@ -93,8 +107,6 @@ ProjectList.propTypes = {
|
|||||||
removeProject: PropTypes.func.isRequired,
|
removeProject: PropTypes.func.isRequired,
|
||||||
history: PropTypes.object.isRequired,
|
history: PropTypes.object.isRequired,
|
||||||
hasPermission: PropTypes.func.isRequired,
|
hasPermission: PropTypes.func.isRequired,
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProjectList;
|
export default ProjectList;
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { fetchProjects, removeProject } from '../../../store/project/actions';
|
import { fetchProjects, removeProject } from '../../../store/project/actions';
|
||||||
import { hasPermission } from '../../../permissions';
|
import { hasPermission } from '../../../permissions';
|
||||||
import { RBAC } from '../../common/flags';
|
|
||||||
import ProjectList from './ProjectList';
|
import ProjectList from './ProjectList';
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const projects = state.projects.toJS();
|
const projects = state.projects.toJS();
|
||||||
const rbacEnabled = !!state.uiConfig.toJS().flags[RBAC];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
projects,
|
projects,
|
||||||
rbacEnabled,
|
|
||||||
hasPermission: hasPermission.bind(null, state.user.get('profile')),
|
hasPermission: hasPermission.bind(null, state.user.get('profile')),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { List, ListItem, IconButton, Icon, Paper, ListItemAvatar, ListItemText, Tooltip } from '@material-ui/core';
|
|
||||||
import { HeaderTitle, styles as commonStyles } from '../common';
|
|
||||||
import { CREATE_PROJECT, DELETE_PROJECT } from '../../permissions';
|
|
||||||
import ConditionallyRender from '../common/conditionally-render';
|
|
||||||
|
|
||||||
class ProjectListComponent extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
projects: PropTypes.array.isRequired,
|
|
||||||
fetchProjects: PropTypes.func.isRequired,
|
|
||||||
removeProject: PropTypes.func.isRequired,
|
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
hasPermission: PropTypes.func.isRequired,
|
|
||||||
rbacEnabled: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.fetchProjects();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeProject = (project, evt) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
this.props.removeProject(project);
|
|
||||||
};
|
|
||||||
|
|
||||||
projectLink = ({ id, name }) => (
|
|
||||||
<Link to={`/projects/edit/${id}`}>
|
|
||||||
<strong>{name}</strong>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
|
|
||||||
deleteProjectButton = project => (
|
|
||||||
<Tooltip title="Remove project">
|
|
||||||
<IconButton aria-label="delete" onClick={this.removeProject.bind(this, project)}>
|
|
||||||
<Icon>delete</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
|
|
||||||
projectList = () => {
|
|
||||||
const { projects, hasPermission } = this.props;
|
|
||||||
return projects.map((project, i) => (
|
|
||||||
<ListItem key={i}>
|
|
||||||
<ListItemAvatar>
|
|
||||||
<Icon>folder_open</Icon>
|
|
||||||
</ListItemAvatar>
|
|
||||||
<ListItemText primary={this.projectLink(project)} secondary={project.description} />
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={hasPermission(DELETE_PROJECT)}
|
|
||||||
show={this.deleteProjectButton(project)}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
addProjectButton = () => {
|
|
||||||
const { hasPermission } = this.props;
|
|
||||||
return (
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={hasPermission(CREATE_PROJECT)}
|
|
||||||
show={
|
|
||||||
<Tooltip title="Add new project">
|
|
||||||
<IconButton
|
|
||||||
aria-label="add-project"
|
|
||||||
onClick={() => this.props.history.push('/projects/create')}
|
|
||||||
>
|
|
||||||
<Icon>add</Icon>
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { projects } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Paper shadow={0} className={commonStyles.fullwidth}>
|
|
||||||
<HeaderTitle title="Projects (beta)" actions={this.addProjectButton()} />
|
|
||||||
<List>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={projects.length > 0}
|
|
||||||
show={this.projectList()}
|
|
||||||
elseShow={<ListItem>No projects defined</ListItem>}
|
|
||||||
/>
|
|
||||||
</List>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProjectListComponent;
|
|
@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import { Grid, Icon } from '@material-ui/core';
|
import { Paper, Icon, Tabs, Tab } from '@material-ui/core';
|
||||||
import PageContent from '../../component/common/PageContent/PageContent';
|
|
||||||
|
|
||||||
const navLinkStyle = {
|
const navLinkStyle = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -23,30 +22,36 @@ const iconStyle = {
|
|||||||
marginRight: '5px',
|
marginRight: '5px',
|
||||||
};
|
};
|
||||||
|
|
||||||
function AdminMenu() {
|
function AdminMenu({history}) {
|
||||||
|
const { location } = history;
|
||||||
|
const { pathname } = location;
|
||||||
return (
|
return (
|
||||||
<PageContent style={{ marginBottom: '1rem' }}>
|
<Paper style={{ marginBottom: '1rem' }}>
|
||||||
<Grid container justify={'center'}>
|
<Tabs centered value={pathname} >
|
||||||
<Grid item md={4}>
|
<Tab value="/admin/users" label={
|
||||||
<NavLink to="/admin/users" activeStyle={activeNavLinkStyle} style={navLinkStyle}>
|
<NavLink to="/admin/users" activeStyle={activeNavLinkStyle} style={navLinkStyle}>
|
||||||
<Icon style={iconStyle}>supervised_user_circle</Icon>
|
<Icon style={iconStyle}>supervised_user_circle</Icon>
|
||||||
Users
|
<span>Users</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</Grid>
|
}
|
||||||
<Grid item md={4}>
|
>
|
||||||
|
</Tab>
|
||||||
|
<Tab value="/admin/api" label={
|
||||||
<NavLink to="/admin/api" activeStyle={activeNavLinkStyle} style={navLinkStyle}>
|
<NavLink to="/admin/api" activeStyle={activeNavLinkStyle} style={navLinkStyle}>
|
||||||
<Icon style={iconStyle}>apps</Icon>
|
<Icon style={iconStyle}>apps</Icon>
|
||||||
API Access
|
API Access
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</Grid>
|
}>
|
||||||
<Grid item md={4}>
|
</Tab>
|
||||||
|
<Tab value="/admin/auth" label={
|
||||||
<NavLink to="/admin/auth" activeStyle={activeNavLinkStyle} style={navLinkStyle}>
|
<NavLink to="/admin/auth" activeStyle={activeNavLinkStyle} style={navLinkStyle}>
|
||||||
<Icon style={iconStyle}>lock</Icon>
|
<Icon style={iconStyle}>lock</Icon>
|
||||||
Authentication
|
Authentication
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</Grid>
|
}>
|
||||||
</Grid>
|
</Tab>
|
||||||
</PageContent>
|
</Tabs>
|
||||||
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
function ApiHowTo() {
|
|
||||||
return (
|
|
||||||
<div style={{ marginBottom: '1rem' }}>
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
backgroundColor: '#cfe5ff',
|
|
||||||
border: '2px solid #c4e1ff',
|
|
||||||
padding: '8px',
|
|
||||||
borderRadius: '5px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Read the{' '}
|
|
||||||
<a href="https://www.unleash-hosted.com/docs" target="_blank" rel="noreferrer">
|
|
||||||
Getting started guide
|
|
||||||
</a>{' '}
|
|
||||||
to learn how to connect to the Unleash API form your application or programmatically. <br /> <br />
|
|
||||||
Please note it can take up to 1 minute before a new API key is activated.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ApiHowTo;
|
|
@ -7,6 +7,7 @@ import { hasPermission } from '../../../permissions';
|
|||||||
export default connect(
|
export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
location: state.settings.toJS().location || {},
|
location: state.settings.toJS().location || {},
|
||||||
|
unleashUrl: state.uiConfig.toJS().unleashUrl,
|
||||||
keys: state.apiAdmin.toJS(),
|
keys: state.apiAdmin.toJS(),
|
||||||
hasPermission: permission => hasPermission(state.user.get('profile'), permission),
|
hasPermission: permission => hasPermission(state.user.get('profile'), permission),
|
||||||
}),
|
}),
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Icon, Table, TableHead, TableBody, TableRow, TableCell, IconButton } from '@material-ui/core';
|
import { Icon, Table, TableHead, TableBody, TableRow, TableCell, IconButton } from '@material-ui/core';
|
||||||
|
import { Alert } from '@material-ui/lab';
|
||||||
import { formatFullDateTimeWithLocale } from '../../../component/common/util';
|
import { formatFullDateTimeWithLocale } from '../../../component/common/util';
|
||||||
import CreateApiKey from './api-key-create';
|
import CreateApiKey from './api-key-create';
|
||||||
import Secret from './secret';
|
import Secret from './secret';
|
||||||
import ApiHowTo from './api-howto';
|
|
||||||
import ConditionallyRender from '../../../component/common/ConditionallyRender/ConditionallyRender';
|
import ConditionallyRender from '../../../component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import Dialogue from '../../../component/common/Dialogue/Dialogue';
|
import Dialogue from '../../../component/common/Dialogue/Dialogue';
|
||||||
|
|
||||||
function ApiKeyList({ location, fetchApiKeys, removeKey, addKey, keys, hasPermission }) {
|
function ApiKeyList({ location, fetchApiKeys, removeKey, addKey, keys, hasPermission, unleashUrl }) {
|
||||||
const [showDelete, setShowDelete] = useState(false);
|
const [showDelete, setShowDelete] = useState(false);
|
||||||
const [delKey, setDelKey] = useState(undefined);
|
const [delKey, setDelKey] = useState(undefined);
|
||||||
const deleteKey = async () => {
|
const deleteKey = async () => {
|
||||||
@ -25,7 +25,22 @@ function ApiKeyList({ location, fetchApiKeys, removeKey, addKey, keys, hasPermis
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ApiHowTo />
|
<Alert severity="info" >
|
||||||
|
<p>
|
||||||
|
Read the{' '}
|
||||||
|
<a href="https://docs.getunleash.io/docs" target="_blank" rel="noreferrer">
|
||||||
|
Getting started guide
|
||||||
|
</a>{' '}
|
||||||
|
to learn how to connect to the Unleash API form your application or programmatically.
|
||||||
|
Please note it can take up to 1 minute before a new API key is activated.
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<strong>API URL: </strong> <pre style={{display: 'inline'}}>{unleashUrl}/api/</pre>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<br />
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@ -93,6 +108,7 @@ ApiKeyList.propTypes = {
|
|||||||
removeKey: PropTypes.func.isRequired,
|
removeKey: PropTypes.func.isRequired,
|
||||||
addKey: PropTypes.func.isRequired,
|
addKey: PropTypes.func.isRequired,
|
||||||
keys: PropTypes.array.isRequired,
|
keys: PropTypes.array.isRequired,
|
||||||
|
unleashUrl: PropTypes.string,
|
||||||
hasPermission: PropTypes.func.isRequired,
|
hasPermission: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,9 +5,9 @@ import ApiKeyList from './api-key-list-container';
|
|||||||
import AdminMenu from '../admin-menu';
|
import AdminMenu from '../admin-menu';
|
||||||
import PageContent from '../../../component/common/PageContent/PageContent';
|
import PageContent from '../../../component/common/PageContent/PageContent';
|
||||||
|
|
||||||
const render = () => (
|
const render = ({history}) => (
|
||||||
<div>
|
<div>
|
||||||
<AdminMenu />
|
<AdminMenu history={history} />
|
||||||
<PageContent headerContent="API Access">
|
<PageContent headerContent="API Access">
|
||||||
<ApiKeyList />
|
<ApiKeyList />
|
||||||
</PageContent>
|
</PageContent>
|
||||||
|
56
frontend/src/page/admin/auth/authentication.jsx
Normal file
56
frontend/src/page/admin/auth/authentication.jsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import AdminMenu from '../admin-menu';
|
||||||
|
import { Alert } from '@material-ui/lab';
|
||||||
|
import GoogleAuth from './google-auth-container';
|
||||||
|
import SamlAuth from './saml-auth-container';
|
||||||
|
import TabNav from '../../../component/common/TabNav/TabNav';
|
||||||
|
import PageContent from '../../../component/common/PageContent/PageContent';
|
||||||
|
import ConditionallyRender from '../../../component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
|
function AdminAuthPage({ authenticationType, history }) {
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
label: 'SAML 2.0',
|
||||||
|
component: <SamlAuth />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Google',
|
||||||
|
component: <GoogleAuth />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<AdminMenu history={history} />
|
||||||
|
<PageContent headerContent="Authentication">
|
||||||
|
<ConditionallyRender condition={authenticationType === 'enterprise'}
|
||||||
|
show={
|
||||||
|
<TabNav tabData={tabs} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender condition={authenticationType === 'open-source'}
|
||||||
|
show={
|
||||||
|
<Alert severity="warning">
|
||||||
|
You are running the open-source version of Unleash. You have to use the Enterprise edition
|
||||||
|
in order configure Single Sign-on.</Alert>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender condition={authenticationType === 'custom'}
|
||||||
|
show={
|
||||||
|
<Alert severity="warning">You have decided to use custom authentication type. You have to use the Enterprise edition
|
||||||
|
in order configure Single Sign-on from the user interface.</Alert>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</PageContent>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminAuthPage.propTypes = {
|
||||||
|
match: PropTypes.object.isRequired,
|
||||||
|
history: PropTypes.object.isRequired,
|
||||||
|
authenticationType: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdminAuthPage;
|
@ -5,6 +5,7 @@ import { hasPermission } from '../../../permissions';
|
|||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
config: state.authAdmin.get('google'),
|
config: state.authAdmin.get('google'),
|
||||||
|
unleashUrl: state.uiConfig.toJS().unleashUrl,
|
||||||
hasPermission: permission => hasPermission(state.user.get('profile'), permission),
|
hasPermission: permission => hasPermission(state.user.get('profile'), permission),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, Grid, Switch, TextField, Typography } from '@material-ui/core';
|
import { Button, Grid, Switch, TextField } from '@material-ui/core';
|
||||||
|
import { Alert } from '@material-ui/lab';
|
||||||
import PageContent from '../../../component/common/PageContent/PageContent';
|
import PageContent from '../../../component/common/PageContent/PageContent';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
@ -9,7 +10,7 @@ const initialState = {
|
|||||||
unleashHostname: location.hostname,
|
unleashHostname: location.hostname,
|
||||||
};
|
};
|
||||||
|
|
||||||
function GoogleAuth({ config, getGoogleConfig, updateGoogleConfig, hasPermission }) {
|
function GoogleAuth({ config, getGoogleConfig, updateGoogleConfig, hasPermission, unleashUrl }) {
|
||||||
const [data, setData] = useState(initialState);
|
const [data, setData] = useState(initialState);
|
||||||
const [info, setInfo] = useState();
|
const [info, setInfo] = useState();
|
||||||
|
|
||||||
@ -58,14 +59,14 @@ function GoogleAuth({ config, getGoogleConfig, updateGoogleConfig, hasPermission
|
|||||||
<PageContent>
|
<PageContent>
|
||||||
<Grid container style={{ marginBottom: '1rem' }}>
|
<Grid container style={{ marginBottom: '1rem' }}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography variant="subtitle1">
|
<Alert severity="info">
|
||||||
Please read the{' '}
|
Please read the{' '}
|
||||||
<a href="https://www.unleash-hosted.com/docs/enterprise-authentication/google" target="_blank" rel="noreferrer">
|
<a href="https://www.unleash-hosted.com/docs/enterprise-authentication/google" target="_blank" rel="noreferrer">
|
||||||
documentation
|
documentation
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
to learn how to integrate with Google OAuth 2.0. <br />
|
to learn how to integrate with Google OAuth 2.0. <br />
|
||||||
Callback URL: <code>https://[unleash.hostname.com]/auth/google/callback</code>
|
Callback URL: <code>{unleashUrl}/auth/google/callback</code>
|
||||||
</Typography>
|
</Alert>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
@ -189,6 +190,7 @@ function GoogleAuth({ config, getGoogleConfig, updateGoogleConfig, hasPermission
|
|||||||
|
|
||||||
GoogleAuth.propTypes = {
|
GoogleAuth.propTypes = {
|
||||||
config: PropTypes.object,
|
config: PropTypes.object,
|
||||||
|
unleashUrl: PropTypes.string,
|
||||||
getGoogleConfig: PropTypes.func.isRequired,
|
getGoogleConfig: PropTypes.func.isRequired,
|
||||||
updateGoogleConfig: PropTypes.func.isRequired,
|
updateGoogleConfig: PropTypes.func.isRequired,
|
||||||
hasPermission: PropTypes.func.isRequired,
|
hasPermission: PropTypes.func.isRequired,
|
||||||
|
@ -1,36 +1,12 @@
|
|||||||
import React from 'react';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import component from './authentication';
|
||||||
import AdminMenu from '../admin-menu';
|
import { hasPermission } from '../../../permissions';
|
||||||
import GoogleAuth from './google-auth-container';
|
|
||||||
import SamlAuth from './saml-auth-container';
|
|
||||||
import TabNav from '../../../component/common/TabNav/TabNav';
|
|
||||||
import PageContent from '../../../component/common/PageContent/PageContent';
|
|
||||||
|
|
||||||
function AdminAuthPage() {
|
const mapStateToProps = state => ({
|
||||||
const tabs = [
|
authenticationType: state.uiConfig.toJS().authenticationType,
|
||||||
{
|
hasPermission: permission => hasPermission(state.user.get('profile'), permission),
|
||||||
label: 'SAML 2.0',
|
});
|
||||||
component: <SamlAuth />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Google',
|
|
||||||
component: <GoogleAuth />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
const Container = connect(mapStateToProps, { })(component);
|
||||||
<div>
|
|
||||||
<AdminMenu />
|
|
||||||
<PageContent headerContent="Authentication">
|
|
||||||
<TabNav tabData={tabs} />
|
|
||||||
</PageContent>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AdminAuthPage.propTypes = {
|
export default Container;
|
||||||
match: PropTypes.object.isRequired,
|
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdminAuthPage;
|
|
||||||
|
@ -5,6 +5,7 @@ import { hasPermission } from '../../../permissions';
|
|||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
config: state.authAdmin.get('saml'),
|
config: state.authAdmin.get('saml'),
|
||||||
|
unleashUrl: state.uiConfig.toJS().unleashUrl,
|
||||||
hasPermission: permission => hasPermission(state.user.get('profile'), permission),
|
hasPermission: permission => hasPermission(state.user.get('profile'), permission),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, Grid, Switch, TextField, Typography } from '@material-ui/core';
|
import { Button, Grid, Switch, TextField } from '@material-ui/core';
|
||||||
|
import { Alert } from '@material-ui/lab';
|
||||||
import PageContent from '../../../component/common/PageContent/PageContent';
|
import PageContent from '../../../component/common/PageContent/PageContent';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
@ -9,7 +10,7 @@ const initialState = {
|
|||||||
unleashHostname: location.hostname,
|
unleashHostname: location.hostname,
|
||||||
};
|
};
|
||||||
|
|
||||||
function SamlAuth({ config, getSamlConfig, updateSamlConfig, hasPermission }) {
|
function SamlAuth({ config, getSamlConfig, updateSamlConfig, hasPermission, unleashUrl }) {
|
||||||
const [data, setData] = useState(initialState);
|
const [data, setData] = useState(initialState);
|
||||||
const [info, setInfo] = useState();
|
const [info, setInfo] = useState();
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, hasPermission }) {
|
|||||||
}, [config]);
|
}, [config]);
|
||||||
|
|
||||||
if (!hasPermission('ADMIN')) {
|
if (!hasPermission('ADMIN')) {
|
||||||
return <span>You need admin privileges to access this section.</span>;
|
return <Alert severity="error">You need to be a root admin to access this section.</Alert>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateField = e => {
|
const updateField = e => {
|
||||||
@ -59,14 +60,14 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, hasPermission }) {
|
|||||||
<PageContent>
|
<PageContent>
|
||||||
<Grid container style={{ marginBottom: '1rem' }}>
|
<Grid container style={{ marginBottom: '1rem' }}>
|
||||||
<Grid item md={12}>
|
<Grid item md={12}>
|
||||||
<Typography variant="subtitle1">
|
<Alert severity="info">
|
||||||
Please read the{' '}
|
Please read the{' '}
|
||||||
<a href="https://www.unleash-hosted.com/docs/enterprise-authentication" target="_blank" rel="noreferrer">
|
<a href="https://www.unleash-hosted.com/docs/enterprise-authentication" target="_blank" rel="noreferrer">
|
||||||
documentation
|
documentation
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
to learn how to integrate with specific SAML 2.0 providers (Okta, Keycloak, etc). <br />
|
to learn how to integrate with specific SAML 2.0 providers (Okta, Keycloak, etc). <br />
|
||||||
Callback URL: <code>https://[unleash.hostname.com]/auth/saml/callback</code>
|
Callback URL: <code>{unleashUrl}/auth/saml/callback</code>
|
||||||
</Typography>
|
</Alert>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
@ -184,6 +185,7 @@ function SamlAuth({ config, getSamlConfig, updateSamlConfig, hasPermission }) {
|
|||||||
|
|
||||||
SamlAuth.propTypes = {
|
SamlAuth.propTypes = {
|
||||||
config: PropTypes.object,
|
config: PropTypes.object,
|
||||||
|
unleash: PropTypes.string,
|
||||||
getSamlConfig: PropTypes.func.isRequired,
|
getSamlConfig: PropTypes.func.isRequired,
|
||||||
updateSamlConfig: PropTypes.func.isRequired,
|
updateSamlConfig: PropTypes.func.isRequired,
|
||||||
hasPermission: PropTypes.func.isRequired,
|
hasPermission: PropTypes.func.isRequired,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/* eslint-disable no-alert */
|
/* eslint-disable no-alert */
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, Icon, IconButton, Table, TableBody, TableCell, TableHead, TableRow } from '@material-ui/core';
|
import { Button, Icon, IconButton, Table, TableBody, TableCell, TableHead, TableRow, Avatar } from '@material-ui/core';
|
||||||
import { formatFullDateTimeWithLocale } from '../../../../component/common/util';
|
import { formatDateWithLocale } from '../../../../component/common/util';
|
||||||
import AddUser from '../add-user-component';
|
import AddUser from '../add-user-component';
|
||||||
import ChangePassword from '../change-password-component';
|
import ChangePassword from '../change-password-component';
|
||||||
import UpdateUser from '../update-user-component';
|
import UpdateUser from '../update-user-component';
|
||||||
@ -78,26 +78,26 @@ function UsersList({
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Id</TableCell>
|
<TableCell></TableCell>
|
||||||
<TableCell>Created</TableCell>
|
<TableCell>Created</TableCell>
|
||||||
<TableCell>Username</TableCell>
|
|
||||||
<TableCell>Name</TableCell>
|
<TableCell>Name</TableCell>
|
||||||
<TableCell>Role</TableCell>
|
<TableCell>Username</TableCell>
|
||||||
<TableCell>{hasPermission('ADMIN') ? 'Action' : ''}</TableCell>
|
<TableCell align="center">Role</TableCell>
|
||||||
|
<TableCell align="right">{hasPermission('ADMIN') ? 'Action' : ''}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{users.map(item => (
|
{users.map(item => (
|
||||||
<TableRow key={item.id}>
|
<TableRow key={item.id}>
|
||||||
<TableCell>{item.id}</TableCell>
|
<TableCell><Avatar variant="rounded" alt={item.name} src={item.imageUrl} title={`${item.name || item.email || item.username} (id: ${item.id})`} /></TableCell>
|
||||||
<TableCell>{formatFullDateTimeWithLocale(item.createdAt, location.locale)}</TableCell>
|
<TableCell>{formatDateWithLocale(item.createdAt, location.locale)}</TableCell>
|
||||||
<TableCell style={{ textAlign: 'left' }}>{item.username || item.email}</TableCell>
|
|
||||||
<TableCell style={{ textAlign: 'left' }}>{item.name}</TableCell>
|
<TableCell style={{ textAlign: 'left' }}>{item.name}</TableCell>
|
||||||
<TableCell>{renderRole(item.rootRole)}</TableCell>
|
<TableCell style={{ textAlign: 'left' }}>{item.username || item.email}</TableCell>
|
||||||
|
<TableCell align="center">{renderRole(item.rootRole)}</TableCell>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={hasPermission('ADMIN')}
|
condition={hasPermission('ADMIN')}
|
||||||
show={
|
show={
|
||||||
<TableCell>
|
<TableCell align="right">
|
||||||
<IconButton aria-label="Edit" title="Edit" onClick={openUpdateDialog(item)}>
|
<IconButton aria-label="Edit" title="Edit" onClick={openUpdateDialog(item)}>
|
||||||
<Icon>edit</Icon>
|
<Icon>edit</Icon>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -4,9 +4,9 @@ import UsersList from './UsersList';
|
|||||||
import AdminMenu from '../admin-menu';
|
import AdminMenu from '../admin-menu';
|
||||||
import PageContent from '../../../component/common/PageContent/PageContent';
|
import PageContent from '../../../component/common/PageContent/PageContent';
|
||||||
|
|
||||||
const render = () => (
|
const render = ({history}) => (
|
||||||
<div>
|
<div>
|
||||||
<AdminMenu />
|
<AdminMenu history={history} />
|
||||||
<PageContent headerContent="Users">
|
<PageContent headerContent="Users">
|
||||||
<UsersList />
|
<UsersList />
|
||||||
</PageContent>
|
</PageContent>
|
||||||
|
Loading…
Reference in New Issue
Block a user