initialize and pass api client to subviews

This commit is contained in:
Jake McDermott
2019-01-02 02:11:01 -05:00
parent a023df2c17
commit 8f4437e17e
7 changed files with 99 additions and 48 deletions

View File

@@ -38,8 +38,17 @@ class App extends Component {
config: {}, config: {},
error: false, error: false,
}; };
this.onLogout = this.onLogout.bind(this);
}; };
async onLogout () {
const { api } = this.props;
await api.logout();
window.location.replace('/#/login')
}
onNavToggle = () => { onNavToggle = () => {
this.setState(({ isNavOpen }) => ({ isNavOpen: !isNavOpen })); this.setState(({ isNavOpen }) => ({ isNavOpen: !isNavOpen }));
}; };

View File

@@ -1,33 +1,35 @@
import axios from 'axios'; import axios from 'axios';
import { const API_ROOT = '/api/';
API_CONFIG, const API_LOGIN = `${API_ROOT}login/`;
API_LOGIN, const API_LOGOUT = `${API_ROOT}logout/`;
API_ROOT, const API_V2 = `${API_ROOT}v2/`;
} from './endpoints'; const API_CONFIG = `${API_V2}config/`;
const API_ORGANIZATIONS = `${API_V2}organizations/`;
const CSRF_COOKIE_NAME = 'csrftoken'; const CSRF_COOKIE_NAME = 'csrftoken';
const CSRF_HEADER_NAME = 'X-CSRFToken'; const CSRF_HEADER_NAME = 'X-CSRFToken';
const LOGIN_CONTENT_TYPE = 'application/x-www-form-urlencoded'; const LOGIN_CONTENT_TYPE = 'application/x-www-form-urlencoded';
class APIClient { const defaultHttpAdapter = axios.create({
constructor () { xsrfCookieName: CSRF_COOKIE_NAME,
this.http = axios.create({ xsrfHeaderName: CSRF_HEADER_NAME,
xsrfCookieName: CSRF_COOKIE_NAME, });
xsrfHeaderName: CSRF_HEADER_NAME,
});
}
/* eslint class-methods-use-this: ["error", { "exceptMethods": ["getCookie"] }] */ class APIClient {
getCookie () { static getCookie () {
return document.cookie; return document.cookie;
} }
isAuthenticated () { constructor (httpAdapter = defaultHttpAdapter) {
let authenticated = false; this.http = httpAdapter;
}
const parsed = (`; ${this.getCookie()}`).split('; userLoggedIn='); isAuthenticated () {
const cookie = this.constructor.getCookie();
const parsed = (`; ${cookie}`).split('; userLoggedIn=');
let authenticated = false;
if (parsed.length === 2) { if (parsed.length === 2) {
authenticated = parsed.pop().split(';').shift() === 'true'; authenticated = parsed.pop().split(';').shift() === 'true';
@@ -36,10 +38,6 @@ class APIClient {
return authenticated; return authenticated;
} }
getRoot () {
return this.http.get(API_ROOT);
}
async login (username, password, redirect = API_CONFIG) { async login (username, password, redirect = API_CONFIG) {
const un = encodeURIComponent(username); const un = encodeURIComponent(username);
const pw = encodeURIComponent(password); const pw = encodeURIComponent(password);
@@ -49,12 +47,36 @@ class APIClient {
const headers = { 'Content-Type': LOGIN_CONTENT_TYPE }; const headers = { 'Content-Type': LOGIN_CONTENT_TYPE };
await this.http.get(API_LOGIN, { headers }); await this.http.get(API_LOGIN, { headers });
await this.http.post(API_LOGIN, data, { headers }); const response = await this.http.post(API_LOGIN, data, { headers });
return response;
} }
get = (endpoint, params = {}) => this.http.get(endpoint, { params }); logout () {
return this.http.get(API_LOGOUT);
}
post = (endpoint, data) => this.http.post(endpoint, data); getRoot () {
return this.http.get(API_ROOT);
}
getConfig () {
return this.http.get(API_CONFIG);
}
getOrganizations (params = {}) {
return this.http.get(API_ORGANIZATIONS, { params });
}
createOrganization (data) {
return this.http.post(API_ORGANIZATIONS, data);
}
getOrganizationDetails (id) {
const endpoint = `${API_ORGANIZATIONS}${id}/`;
return this.http.get(endpoint);
}
} }
export default new APIClient(); export default APIClient;

View File

@@ -18,7 +18,8 @@ import './app.scss';
import './components/Pagination/styles.scss'; import './components/Pagination/styles.scss';
import './components/DataListToolbar/styles.scss'; import './components/DataListToolbar/styles.scss';
import api from './api'; import APIClient from './api';
import App from './App'; import App from './App';
import Background from './components/Background'; import Background from './components/Background';
import Applications from './pages/Applications'; import Applications from './pages/Applications';
@@ -55,7 +56,7 @@ const language = (navigator.languages && navigator.languages[0])
|| navigator.userLanguage; || navigator.userLanguage;
const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0]; const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];
export async function main () { export async function main (api) {
const el = document.getElementById('app'); const el = document.getElementById('app');
// fetch additional config from server // fetch additional config from server
const { data } = await api.getRoot(); const { data } = await api.getRoot();
@@ -67,6 +68,7 @@ export async function main () {
path="/login" path="/login"
render={() => ( render={() => (
<Login <Login
api={api}
logo={custom_logo} logo={custom_logo}
loginInfo={custom_login_info} loginInfo={custom_login_info}
/> />
@@ -92,6 +94,7 @@ export async function main () {
<Route <Route
render={() => ( render={() => (
<App <App
api={api}
navLabel={i18n._(t`Primary Navigation`)} navLabel={i18n._(t`Primary Navigation`)}
routeGroups={[ routeGroups={[
{ {
@@ -244,6 +247,7 @@ export async function main () {
path={path} path={path}
render={({ match }) => ( render={({ match }) => (
<PageComponent <PageComponent
api={api}
match={match} match={match}
/> />
)} )}
@@ -262,4 +266,4 @@ export async function main () {
</HashRouter>, el); </HashRouter>, el);
}; };
export default main(); export default main(new APIClient());

View File

@@ -8,7 +8,6 @@ import {
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import towerLogo from '../../images/tower-logo-header.svg'; import towerLogo from '../../images/tower-logo-header.svg';
import api from '../api';
class AtLogin extends Component { class AtLogin extends Component {
constructor (props) { constructor (props) {
@@ -34,6 +33,7 @@ class AtLogin extends Component {
handleSubmit = async event => { handleSubmit = async event => {
const { username, password, loading } = this.state; const { username, password, loading } = this.state;
const { api } = this.props;
event.preventDefault(); event.preventDefault();
@@ -54,7 +54,7 @@ class AtLogin extends Component {
render () { render () {
const { username, password, isValidPassword } = this.state; const { username, password, isValidPassword } = this.state;
const { logo, alt } = this.props; const { api, alt, logo } = this.props;
const logoSrc = logo ? `data:image/jpeg;${logo}` : towerLogo; const logoSrc = logo ? `data:image/jpeg;${logo}` : towerLogo;
if (api.isAuthenticated()) { if (api.isAuthenticated()) {

View File

@@ -5,12 +5,31 @@ import OrganizationAdd from './views/Organization.add';
import OrganizationView from './views/Organization.view'; import OrganizationView from './views/Organization.view';
import OrganizationsList from './views/Organizations.list'; import OrganizationsList from './views/Organizations.list';
const Organizations = ({ match }) => ( export default ({ api, match }) => (
<Switch> <Switch>
<Route path={`${match.path}/add`} component={OrganizationAdd} /> <Route
<Route path={`${match.path}/:id`} component={OrganizationView} /> path={`${match.path}/add`}
<Route path={`${match.path}`} component={OrganizationsList} /> render={() => (
<OrganizationAdd
api={api}
/>
)}
/>
<Route
path={`${match.path}/:id`}
render={() => (
<OrganizationView
api={api}
/>
)}
/>
<Route
path={`${match.path}`}
render={() => (
<OrganizationsList
api={api}
/>
)}
/>
</Switch> </Switch>
); );
export default Organizations;

View File

@@ -2,16 +2,13 @@ import React, { Component, Fragment } from 'react';
import { i18nMark } from '@lingui/react'; import { i18nMark } from '@lingui/react';
import { import {
Switch, Switch,
Route Route,
withRouter,
} from 'react-router-dom'; } from 'react-router-dom';
import OrganizationBreadcrumb from '../components/OrganizationBreadcrumb'; import OrganizationBreadcrumb from '../components/OrganizationBreadcrumb';
import OrganizationDetail from '../components/OrganizationDetail'; import OrganizationDetail from '../components/OrganizationDetail';
import OrganizationEdit from '../components/OrganizationEdit'; import OrganizationEdit from '../components/OrganizationEdit';
import api from '../../../api';
import { API_ORGANIZATIONS } from '../../../endpoints';
class OrganizationView extends Component { class OrganizationView extends Component {
constructor (props) { constructor (props) {
super(props); super(props);
@@ -47,13 +44,15 @@ class OrganizationView extends Component {
async fetchOrganization () { async fetchOrganization () {
const { mounted } = this.state; const { mounted } = this.state;
const { api } = this.props;
if (mounted) { if (mounted) {
this.setState({ error: false, loading: true }); this.setState({ error: false, loading: true });
const { match } = this.props; const { match } = this.props;
const { parentBreadcrumbObj, organization } = this.state; const { parentBreadcrumbObj, organization } = this.state;
try { try {
const { data } = await api.get(`${API_ORGANIZATIONS}${match.params.id}/`); const { data } = await api.getOrganizationDetails(match.params.id);
if (organization === 'loading') { if (organization === 'loading') {
this.setState({ organization: data }); this.setState({ organization: data });
} }
@@ -118,4 +117,4 @@ class OrganizationView extends Component {
} }
} }
export default OrganizationView; export default withRouter(OrganizationView);

View File

@@ -17,9 +17,6 @@ import DataListToolbar from '../../../components/DataListToolbar';
import OrganizationListItem from '../components/OrganizationListItem'; import OrganizationListItem from '../components/OrganizationListItem';
import Pagination from '../../../components/Pagination'; import Pagination from '../../../components/Pagination';
import api from '../../../api';
import { API_ORGANIZATIONS } from '../../../endpoints';
import { import {
encodeQueryString, encodeQueryString,
parseQueryString, parseQueryString,
@@ -132,6 +129,7 @@ class Organizations extends Component {
} }
async fetchOrganizations (queryParams) { async fetchOrganizations (queryParams) {
const { api } = this.props;
const { page, page_size, order_by } = queryParams; const { page, page_size, order_by } = queryParams;
let sortOrder = 'ascending'; let sortOrder = 'ascending';
@@ -145,7 +143,7 @@ class Organizations extends Component {
this.setState({ error: false, loading: true }); this.setState({ error: false, loading: true });
try { try {
const { data } = await api.get(API_ORGANIZATIONS, queryParams); const { data } = await api.getOrganizations(queryParams);
const { count, results } = data; const { count, results } = data;
const pageCount = Math.ceil(count / page_size); const pageCount = Math.ceil(count / page_size);