mirror of
https://github.com/ansible/awx.git
synced 2026-05-20 07:17:40 -02:30
Nav and login updates
This commit is contained in:
220
src/App.jsx
220
src/App.jsx
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import {
|
import {
|
||||||
HashRouter as Router,
|
HashRouter as Router,
|
||||||
@@ -7,20 +7,29 @@ import {
|
|||||||
Redirect,
|
Redirect,
|
||||||
Switch,
|
Switch,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
BackgroundImage,
|
||||||
|
BackgroundImageSrc,
|
||||||
Brand,
|
Brand,
|
||||||
|
Button,
|
||||||
|
ButtonVariant,
|
||||||
Nav,
|
Nav,
|
||||||
NavList,
|
|
||||||
NavGroup,
|
NavGroup,
|
||||||
NavItem,
|
NavItem,
|
||||||
Page,
|
Page,
|
||||||
PageHeader,
|
PageHeader,
|
||||||
|
PageSection,
|
||||||
|
PageSectionVariants,
|
||||||
PageSidebar,
|
PageSidebar,
|
||||||
Title,
|
TextContent,
|
||||||
|
Text,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
ToolbarGroup,
|
ToolbarGroup,
|
||||||
ToolbarItem,
|
ToolbarItem
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
import { global_breakpoint_md as breakpointMd } from '@patternfly/react-tokens';
|
||||||
|
import { css } from '@patternfly/react-styles';
|
||||||
|
|
||||||
import api from './api';
|
import api from './api';
|
||||||
|
|
||||||
@@ -47,7 +56,6 @@ import Teams from './pages/Teams';
|
|||||||
import Templates from './pages/Templates';
|
import Templates from './pages/Templates';
|
||||||
import Users from './pages/Users';
|
import Users from './pages/Users';
|
||||||
|
|
||||||
|
|
||||||
const AuthenticatedRoute = ({ component: Component, ...rest }) => (
|
const AuthenticatedRoute = ({ component: Component, ...rest }) => (
|
||||||
<Route {...rest} render={props => (
|
<Route {...rest} render={props => (
|
||||||
api.isAuthenticated() ? (
|
api.isAuthenticated() ? (
|
||||||
@@ -61,11 +69,28 @@ const AuthenticatedRoute = ({ component: Component, ...rest }) => (
|
|||||||
)}/>
|
)}/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const UnauthenticatedRoute = ({ component: Component, ...rest }) => (
|
||||||
|
<Route {...rest} render={props => (
|
||||||
|
!api.isAuthenticated() ? (
|
||||||
|
<Component {...props}/>
|
||||||
|
) : (
|
||||||
|
<Redirect to={{
|
||||||
|
pathname: '/',
|
||||||
|
state: { from: props.location }
|
||||||
|
}}/>
|
||||||
|
)
|
||||||
|
)}/>
|
||||||
|
);
|
||||||
|
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = { activeItem: 'dashboard', isNavOpen: true };
|
this.state = {
|
||||||
|
activeItem: 'dashboard',
|
||||||
|
isNavOpen: (typeof window !== 'undefined' &&
|
||||||
|
window.innerWidth >= parseInt(breakpointMd.value, 10)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onNavToggle = () => {
|
onNavToggle = () => {
|
||||||
@@ -78,93 +103,107 @@ class App extends React.Component {
|
|||||||
this.setState({ activeItem: itemId });
|
this.setState({ activeItem: itemId });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onLogoClick = () => {
|
||||||
|
this.setState({ activeItem: "dashboard" });
|
||||||
|
}
|
||||||
|
|
||||||
|
onDevLogout = () => {
|
||||||
|
api.logout()
|
||||||
|
.then(() => {
|
||||||
|
this.setState({ activeItem: "dashboard" });
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { activeItem, isNavOpen } = this.state;
|
const { activeItem, isNavOpen } = this.state;
|
||||||
|
const { logo, loginInfo } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<Switch>
|
<Fragment>
|
||||||
<Route path="/login" component={Login} />
|
<BackgroundImage src={{
|
||||||
<AuthenticatedRoute component={() => (
|
[BackgroundImageSrc.lg]: '/assets/images/pfbg_1200.jpg',
|
||||||
<Page
|
[BackgroundImageSrc.md]: '/assets/images/pfbg_992.jpg',
|
||||||
header={(
|
[BackgroundImageSrc.md2x]: '/assets/images/pfbg_992@2x.jpg',
|
||||||
<PageHeader
|
[BackgroundImageSrc.sm]: '/assets/images/pfbg_768.jpg',
|
||||||
logo={<TowerLogo />}
|
[BackgroundImageSrc.sm2x]: '/assets/images/pfbg_768@2x.jpg',
|
||||||
toolbar={(
|
[BackgroundImageSrc.xl]: '/assets/images/pfbg_2000.jpg',
|
||||||
<Toolbar>
|
[BackgroundImageSrc.xs]: '/assets/images/pfbg_576.jpg',
|
||||||
<ToolbarGroup>
|
[BackgroundImageSrc.xs2x]: '/assets/images/pfbg_576@2x.jpg',
|
||||||
<ToolbarItem>Item 1</ToolbarItem>
|
[BackgroundImageSrc.filter]: '/assets/images/background-filter.svg'
|
||||||
</ToolbarGroup>
|
}} />
|
||||||
<ToolbarGroup>
|
<Switch>
|
||||||
<ToolbarItem>Item 2</ToolbarItem>
|
<UnauthenticatedRoute path="/login" component={() => <Login logo={logo} loginInfo={loginInfo} />} />
|
||||||
<ToolbarItem>Item 3</ToolbarItem>
|
<AuthenticatedRoute component={() => (
|
||||||
</ToolbarGroup>
|
<Page
|
||||||
</Toolbar>
|
header={(
|
||||||
|
<PageHeader
|
||||||
|
logo={<TowerLogo onClick={this.onLogoClick} />}
|
||||||
|
avatar={<i className="fas fa-user" onClick={this.onDevLogout}></i>}
|
||||||
|
showNavToggle
|
||||||
|
onNavToggle={this.onNavToggle}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
avatar="| avatar"
|
sidebar={(
|
||||||
showNavToggle
|
<PageSidebar
|
||||||
onNavToggle={this.onNavToggle}
|
isNavOpen={isNavOpen}
|
||||||
/>
|
nav={(
|
||||||
)}
|
<Nav onSelect={this.onNavSelect} aria-label="Primary Navigation">
|
||||||
sidebar={(
|
<NavGroup title="Views">
|
||||||
<PageSidebar
|
<NavItem to="#/home" itemId="dashboard" isActive={activeItem === 'dashboard'}>Dashboard</NavItem>
|
||||||
isNavOpen={isNavOpen}
|
<NavItem to="#/jobs" itemId="jobs" isActive={activeItem === 'jobs'}>Jobs</NavItem>
|
||||||
nav={(
|
<NavItem to="#/schedules" itemId="schedules" isActive={activeItem === 'schedules'}>Schedules</NavItem>
|
||||||
<Nav onSelect={this.onNavSelect} aria-label="Primary Navigation">
|
<NavItem to="#/portal" itemId="portal" isActive={activeItem === 'portal'}>My View</NavItem>
|
||||||
<NavGroup title="Views">
|
</NavGroup>
|
||||||
<NavItem to="#/home" itemId="dashboard" isActive={activeItem === 'dashboard'}>Dashboard</NavItem>
|
<NavGroup title="Resources">
|
||||||
<NavItem to="#/jobs" itemId="jobs" isActive={activeItem === 'jobs'}>Jobs</NavItem>
|
<NavItem to="#/templates" itemId="templates" isActive={activeItem === 'templates'}>Templates</NavItem>
|
||||||
<NavItem to="#/schedules" itemId="schedules" isActive={activeItem === 'schedules'}>Schedules</NavItem>
|
<NavItem to="#/credentials" itemId="credentials" isActive={activeItem === 'credentials'}>Credentials</NavItem>
|
||||||
<NavItem to="#/portal" itemId="portal" isActive={activeItem === 'portal'}>My View</NavItem>
|
<NavItem to="#/projects" itemId="projects" isActive={activeItem === 'projects'}>Projects</NavItem>
|
||||||
</NavGroup>
|
<NavItem to="#/inventories" itemId="inventories" isActive={activeItem === 'inventories'}>Inventories</NavItem>
|
||||||
<NavGroup title="Resources">
|
<NavItem to="#/inventory_scripts" itemId="inventory_scripts" isActive={activeItem === 'inventory_scripts'}>Inventory Scripts</NavItem>
|
||||||
<NavItem to="#/templates" itemId="templates" isActive={activeItem === 'templates'}>Templates</NavItem>
|
</NavGroup>
|
||||||
<NavItem to="#/credentials" itemId="credentials" isActive={activeItem === 'credentials'}>Credentials</NavItem>
|
<NavGroup title="Access">
|
||||||
<NavItem to="#/projects" itemId="projects" isActive={activeItem === 'projects'}>Projects</NavItem>
|
<NavItem to="#/organizations" itemId="organizations" isActive={activeItem === 'organizations'}>Organizations</NavItem>
|
||||||
<NavItem to="#/inventories" itemId="inventories" isActive={activeItem === 'inventories'}>Inventories</NavItem>
|
<NavItem to="#/users" itemId="users" isActive={activeItem === 'users'}>Users</NavItem>
|
||||||
<NavItem to="#/inventory_scripts" itemId="inventory_scripts" isActive={activeItem === 'inventory_scripts'}>Inventory Scripts</NavItem>
|
<NavItem to="#/teams" itemId="teams" isActive={activeItem === 'teams'}>Teams</NavItem>
|
||||||
</NavGroup>
|
</NavGroup>
|
||||||
<NavGroup title="Access">
|
<NavGroup title="Administration">
|
||||||
<NavItem to="#/organizations" itemId="organizations" isActive={activeItem === 'organizations'}>Organizations</NavItem>
|
<NavItem to="#/credential_types" itemId="credential_types" isActive={activeItem === 'credential_types'}>Credential Types</NavItem>
|
||||||
<NavItem to="#/users" itemId="users" isActive={activeItem === 'users'}>Users</NavItem>
|
<NavItem to="#/notification_templates" itemId="notification_templates" isActive={activeItem === 'notification_templates'}>Notifications</NavItem>
|
||||||
<NavItem to="#/teams" itemId="teams" isActive={activeItem === 'teams'}>Teams</NavItem>
|
<NavItem to="#/management_jobs" itemId="management_jobs" isActive={activeItem === 'management_jobs'}>Management Jobs</NavItem>
|
||||||
</NavGroup>
|
<NavItem to="#/instance_groups" itemId="instance_groups" isActive={activeItem === 'instance_groups'}>Instance Groups</NavItem>
|
||||||
<NavGroup title="Administration">
|
<NavItem to="#/applications" itemId="applications" isActive={activeItem === 'applications'}>Applications</NavItem>
|
||||||
<NavItem to="#/credential_types" itemId="credential_types" isActive={activeItem === 'credential_types'}>Credential Types</NavItem>
|
<NavItem to="#/settings" itemId="settings" isActive={activeItem === 'settings'}>Settings</NavItem>
|
||||||
<NavItem to="#/notification_templates" itemId="notification_templates" isActive={activeItem === 'notification_templates'}>Notifications</NavItem>
|
</NavGroup>
|
||||||
<NavItem to="#/management_jobs" itemId="management_jobs" isActive={activeItem === 'management_jobs'}>Management Jobs</NavItem>
|
</Nav>
|
||||||
<NavItem to="#/instance_groups" itemId="instance_groups" isActive={activeItem === 'instance_groups'}>Instance Groups</NavItem>
|
)}
|
||||||
<NavItem to="#/applications" itemId="applications" isActive={activeItem === 'applications'}>Applications</NavItem>
|
/>
|
||||||
<NavItem to="#/settings" itemId="settings" isActive={activeItem === 'settings'}>Settings</NavItem>
|
)}>
|
||||||
</NavGroup>
|
<Switch>
|
||||||
</Nav>
|
<Route exact path="/" component={() => (<Redirect to="/home" />)} />
|
||||||
)}
|
<Route path="/home" component={Dashboard} />
|
||||||
/>
|
<Route path="/jobs" component={Jobs} />
|
||||||
)}>
|
<Route path="/schedules" component={Schedules} />
|
||||||
<Switch>
|
<Route path="/portal" component={Portal} />
|
||||||
<Route exact path="/" component={() => (<Redirect to="/home" />)} />
|
<Route path="/templates" component={Templates} />
|
||||||
<Route path="/home" component={Dashboard} />
|
<Route path="/credentials" component={Credentials} />
|
||||||
<Route path="/jobs" component={Jobs} />
|
<Route path="/projects" component={Projects} />
|
||||||
<Route path="/schedules" component={Schedules} />
|
<Route path="/inventories" component={Inventories} />
|
||||||
<Route path="/portal" component={Portal} />
|
<Route path="/inventory_scripts" component={InventoryScripts} />
|
||||||
<Route path="/templates" component={Templates} />
|
<Route path="/organizations" component={Organizations} />
|
||||||
<Route path="/credentials" component={Credentials} />
|
<Route path="/users" component={Users} />
|
||||||
<Route path="/projects" component={Projects} />
|
<Route path="/teams" component={Teams} />
|
||||||
<Route path="/inventories" component={Inventories} />
|
<Route path="/credential_types" component={CredentialTypes} />
|
||||||
<Route path="/inventory_scripts" component={InventoryScripts} />
|
<Route path="/notification_templates" component={NotificationTemplates} />
|
||||||
<Route path="/organizations" component={Organizations} />
|
<Route path="/management_jobs" component={ManagementJobs} />
|
||||||
<Route path="/users" component={Users} />
|
<Route path="/instance_groups" component={InstanceGroups} />
|
||||||
<Route path="/teams" component={Teams} />
|
<Route path="/applications" component={Applications} />
|
||||||
<Route path="/credential_types" component={CredentialTypes} />
|
<Route path="/settings" component={Settings} />
|
||||||
<Route path="/notification_templates" component={NotificationTemplates} />
|
</Switch>
|
||||||
<Route path="/management_jobs" component={ManagementJobs} />
|
</Page>
|
||||||
<Route path="/instance_groups" component={InstanceGroups} />
|
)} />
|
||||||
<Route path="/applications" component={Applications} />
|
</Switch>
|
||||||
<Route path="/settings" component={Settings} />
|
</Fragment>
|
||||||
</Switch>
|
|
||||||
</Page>
|
|
||||||
)} />
|
|
||||||
</Switch>
|
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -172,4 +211,9 @@ class App extends React.Component {
|
|||||||
|
|
||||||
const el = document.getElementById('app');
|
const el = document.getElementById('app');
|
||||||
|
|
||||||
render(<App />, el);
|
api.getRoot()
|
||||||
|
.then(({ data }) => {
|
||||||
|
const { custom_logo, custom_login_info } = data;
|
||||||
|
|
||||||
|
render(<App logo={custom_logo} loginInfo={custom_login_info} />, el);
|
||||||
|
});
|
||||||
|
|||||||
29
src/api.js
29
src/api.js
@@ -2,6 +2,7 @@ import axios from 'axios';
|
|||||||
|
|
||||||
const API_ROOT = '/api/';
|
const API_ROOT = '/api/';
|
||||||
const API_LOGIN = `${API_ROOT}login/`;
|
const API_LOGIN = `${API_ROOT}login/`;
|
||||||
|
const API_LOGOUT = `${API_ROOT}logout/`;
|
||||||
const API_V2 = `${API_ROOT}v2/`;
|
const API_V2 = `${API_ROOT}v2/`;
|
||||||
const API_CONFIG = `${API_V2}config/`;
|
const API_CONFIG = `${API_V2}config/`;
|
||||||
const API_PROJECTS = `${API_V2}projects/`;
|
const API_PROJECTS = `${API_V2}projects/`;
|
||||||
@@ -12,7 +13,6 @@ const CSRF_HEADER_NAME = 'X-CSRFToken';
|
|||||||
|
|
||||||
class APIClient {
|
class APIClient {
|
||||||
constructor () {
|
constructor () {
|
||||||
this.authenticated = false; // temporary
|
|
||||||
this.http = axios.create({
|
this.http = axios.create({
|
||||||
xsrfCookieName: CSRF_COOKIE_NAME,
|
xsrfCookieName: CSRF_COOKIE_NAME,
|
||||||
xsrfHeaderName: CSRF_HEADER_NAME,
|
xsrfHeaderName: CSRF_HEADER_NAME,
|
||||||
@@ -20,7 +20,15 @@ class APIClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isAuthenticated () {
|
isAuthenticated () {
|
||||||
return this.authenticated;
|
let authenticated = false;
|
||||||
|
|
||||||
|
const parsed = (`; ${document.cookie}`).split('; userLoggedIn=');
|
||||||
|
|
||||||
|
if (parsed.length === 2) {
|
||||||
|
authenticated = parsed.pop().split(';').shift() === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
return authenticated;
|
||||||
}
|
}
|
||||||
|
|
||||||
login (username, password, redirect = API_CONFIG) {
|
login (username, password, redirect = API_CONFIG) {
|
||||||
@@ -32,16 +40,15 @@ class APIClient {
|
|||||||
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||||
|
|
||||||
return this.http.get(API_LOGIN, { headers })
|
return this.http.get(API_LOGIN, { headers })
|
||||||
.then(() => this.http.post(API_LOGIN, data, { headers }))
|
.then(() => this.http.post(API_LOGIN, data, { headers }));
|
||||||
.then(res => {
|
|
||||||
this.authenticated = true; // temporary
|
|
||||||
|
|
||||||
return res;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logout () {
|
logout () {
|
||||||
return this.http.delete(API_LOGIN);
|
return this.http.get(API_LOGOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig () {
|
||||||
|
return this.http.get(API_CONFIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
getProjects () {
|
getProjects () {
|
||||||
@@ -51,6 +58,10 @@ class APIClient {
|
|||||||
getOrganizations () {
|
getOrganizations () {
|
||||||
return this.http.get(API_ORGANIZATIONS);
|
return this.http.get(API_ORGANIZATIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRoot () {
|
||||||
|
return this.http.get(API_ROOT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new APIClient();
|
export default new APIClient();
|
||||||
|
|||||||
101
src/app.scss
101
src/app.scss
@@ -1,34 +1,95 @@
|
|||||||
|
//
|
||||||
|
// Header
|
||||||
|
//
|
||||||
|
|
||||||
.pf-l-page__header {
|
.pf-l-page__header {
|
||||||
--pf-l-page__header--MinHeight: 0px;
|
--pf-l-page__header--MinHeight: 0px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
background-color: #030303;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-l-page__header-brand {
|
.pf-l-page__header-brand {
|
||||||
--pf-l-page__header-brand--PaddingBottom: 0px;
|
align-self: center;
|
||||||
|
height: 60px;
|
||||||
|
max-width: 190px;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pf-l-page__header-tools {
|
||||||
|
align-self: center;
|
||||||
|
height: 60px;
|
||||||
|
padding-left: 190px;
|
||||||
|
|
||||||
|
.fa-user:hover {
|
||||||
|
// temporary dev logout
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-l-toolbar {
|
||||||
|
align-self: center;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-l-page__header-brand-link {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-l-page__header-brand-link img {
|
||||||
|
transform: scale(1.1, 1.1);
|
||||||
|
position: relative;
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-l-page__header-brand-toggle {
|
||||||
|
align-self: center;
|
||||||
|
position: relative;
|
||||||
|
right: 14px;
|
||||||
|
--pf-l-page__header-brand-link--MarginLeft: 0px;
|
||||||
|
--pf-l-page__header-brand-link--MarginLeft: 0px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
--pf-l-page__header-sidebar-toggle--FontSize: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Side Navigation
|
||||||
|
//
|
||||||
|
|
||||||
|
.pf-c-nav {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-nav__section {
|
||||||
|
--pf-c-nav__section--MarginTop: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-l-page__sidebar{
|
||||||
|
--pf-l-page__sidebar--Width--lg: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-nav__section + .pf-c-nav__section {
|
||||||
|
--pf-c-nav__section--MarginTop: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-nav__simple-list .pf-c-nav__link {
|
||||||
|
--pf-c-nav__simple-list-link--PaddingLeft: 24px;
|
||||||
|
--pf-c-nav__simple-list-link--PaddingBottom: 6px;
|
||||||
|
--pf-c-nav__simple-list-link--PaddingTop: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-c-nav__section-title {
|
||||||
|
--pf-c-nav__section-title--PaddingLeft: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Page
|
||||||
|
//
|
||||||
|
|
||||||
.pf-l-page__main-section {
|
.pf-l-page__main-section {
|
||||||
--pf-l-page__main-section--PaddingTop: 11px;
|
--pf-l-page__main-section--PaddingTop: 11px;
|
||||||
--pf-l-page__main-section--PaddingLeft: 11px;
|
--pf-l-page__main-section--PaddingLeft: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-nav__section + .pf-c-nav__section {
|
|
||||||
--pf-c-nav__section--MarginTop: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-l-page__header-brand-toggle {
|
|
||||||
padding-bottom: 4px;
|
|
||||||
padding-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-l-page__header-brand-link {
|
|
||||||
transform: scale(0.75, 0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-l-page__sidebar{
|
|
||||||
--pf-l-page__sidebar--Width--lg: 200px;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,69 +1,135 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Bullseye,
|
Brand,
|
||||||
Button,
|
Button,
|
||||||
TextInput
|
Level,
|
||||||
|
LevelItem,
|
||||||
|
Login,
|
||||||
|
LoginBox,
|
||||||
|
LoginBoxHeader,
|
||||||
|
LoginBoxBody,
|
||||||
|
LoginFooter,
|
||||||
|
LoginHeaderBrand,
|
||||||
|
TextInput,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
|
import TowerLogo from '../components/TowerLogo';
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
class Login extends Component {
|
const LOGIN_ERROR_MESSAGE = 'Invalid username or password. Please try again.';
|
||||||
state = {
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
redirect: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleUsernameChange = value => this.setState({ username: value });
|
class LoginPage extends Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
handlePasswordChange = value => this.setState({ password: value });
|
this.state = {
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
redirect: false,
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
this.unmounting = true; // todo: state management
|
||||||
|
}
|
||||||
|
|
||||||
|
safeSetState = obj => !this.unmounting && this.setState(obj);
|
||||||
|
|
||||||
|
handleUsernameChange = value => this.safeSetState({ username: value, error: '' });
|
||||||
|
|
||||||
|
handlePasswordChange = value => this.safeSetState({ password: value, error: '' });
|
||||||
|
|
||||||
handleSubmit = event => {
|
handleSubmit = event => {
|
||||||
const { username, password } = this.state;
|
const { username, password } = this.state;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.safeSetState({ loading: true });
|
||||||
|
|
||||||
api.login(username, password)
|
api.login(username, password)
|
||||||
.then(() => this.setState({ redirect: true }));
|
.then(() => this.safeSetState({ redirect: true }))
|
||||||
|
.catch(error => {
|
||||||
|
if (error.response.status === 401) {
|
||||||
|
this.safeSetState({ error: LOGIN_ERROR_MESSAGE });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.safeSetState({ loading: false });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { username, password, redirect } = this.state;
|
const { username, password, redirect, loading, error } = this.state;
|
||||||
|
const { logo, loginInfo } = this.props;
|
||||||
|
|
||||||
if (redirect) {
|
if (redirect) {
|
||||||
return (<Redirect to="/" />);
|
return (<Redirect to="/" />);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Bullseye>
|
<Login
|
||||||
<form onSubmit={this.handleSubmit}>
|
header={(
|
||||||
<div>
|
<LoginHeaderBrand>
|
||||||
<TextInput
|
{logo ? <Brand src={`data:image/jpeg;${logo}`} alt="logo brand" /> : <TowerLogo />}
|
||||||
aria-label="Username"
|
</LoginHeaderBrand>
|
||||||
name="username"
|
)}
|
||||||
type="text"
|
footer={<LoginFooter>{ loginInfo }</LoginFooter>}
|
||||||
onChange={this.handleUsernameChange}
|
>
|
||||||
value={username}
|
<LoginBox>
|
||||||
/>
|
<LoginBoxHeader>
|
||||||
</div>
|
Welcome to Ansible Tower! Please Sign In.
|
||||||
<div>
|
</LoginBoxHeader>
|
||||||
<TextInput
|
<LoginBoxBody>
|
||||||
aria-label="Password"
|
<form className="pf-c-form" onSubmit={this.handleSubmit}>
|
||||||
name="password"
|
<div className="pf-c-form__group" id="username">
|
||||||
type="password"
|
<label className="pf-c-form__label" htmlFor="username">
|
||||||
onChange={this.handlePasswordChange}
|
Username
|
||||||
value={password}
|
<span className="pf-c-form__label__required" aria-hidden="true">*</span>
|
||||||
/>
|
</label>
|
||||||
</div>
|
<TextInput
|
||||||
<Button type="submit">
|
autoComplete="off"
|
||||||
Login
|
aria-label="Username"
|
||||||
</Button>
|
name="username"
|
||||||
</form>
|
type="text"
|
||||||
</Bullseye>
|
isDisabled={loading}
|
||||||
|
value={username}
|
||||||
|
onChange={this.handleUsernameChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="pf-c-form__group">
|
||||||
|
<label className="pf-c-form__label" htmlFor="pw">
|
||||||
|
Password
|
||||||
|
<span className="pf-c-form__label__required" aria-hidden="true">*</span>
|
||||||
|
</label>
|
||||||
|
<TextInput
|
||||||
|
aria-label="Password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
isDisabled={loading}
|
||||||
|
value={password}
|
||||||
|
onChange={this.handlePasswordChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Level>
|
||||||
|
<LevelItem>
|
||||||
|
<p className="pf-c-form__helper-text pf-m-error" aria-live="polite">
|
||||||
|
{ error }
|
||||||
|
</p>
|
||||||
|
</LevelItem>
|
||||||
|
<LevelItem>
|
||||||
|
<Button type="submit" isDisabled={loading}>
|
||||||
|
Sign In
|
||||||
|
</Button>
|
||||||
|
</LevelItem>
|
||||||
|
</Level>
|
||||||
|
</form>
|
||||||
|
</LoginBoxBody>
|
||||||
|
</LoginBox>
|
||||||
|
</Login>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Login;
|
export default LoginPage;
|
||||||
|
|||||||
Reference in New Issue
Block a user