Migrate to patternfly 4
Change-Id: Ieb31bc739740c382d46a074f15ce7e9e94c1b480
This commit is contained in:
parent
3aa04800ef
commit
d42b04f049
2184
package-lock.json
generated
2184
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -2,17 +2,18 @@
|
|||||||
"name": "ara-web",
|
"name": "ara-web",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"homepage": "./",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@patternfly/patternfly-next": "1.0.105",
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
"patternfly": "^3.54.2",
|
|
||||||
"patternfly-react": "^2.17.3",
|
|
||||||
"react": "^16.5.1",
|
"react": "^16.5.1",
|
||||||
"react-dom": "^16.5.1",
|
"react-dom": "^16.5.1",
|
||||||
"react-redux": "^5.0.7",
|
"react-redux": "^5.0.7",
|
||||||
"react-router-dom": "^4.3.1",
|
"react-router-dom": "^4.3.1",
|
||||||
"react-scripts": "^1.1.5",
|
"react-scripts": "^1.1.5",
|
||||||
"redux": "^4.0.0",
|
"redux": "^4.0.0",
|
||||||
"redux-thunk": "^2.3.0"
|
"redux-thunk": "^2.3.0",
|
||||||
|
"styled-components": "^4.1.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
body {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
57
src/App.js
57
src/App.js
@ -2,57 +2,42 @@ import React, { Component } from "react";
|
|||||||
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
|
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
|
|
||||||
import "./App.css";
|
import "@patternfly/patternfly-next/patternfly.css";
|
||||||
import store from "./store";
|
import store from "./store";
|
||||||
import { setConfig } from "./config/configActions";
|
import { setConfig } from "./config/configActions";
|
||||||
import * as Containers from "./containers";
|
import * as Containers from "./containers";
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props);
|
isLoading: true
|
||||||
this.state = {
|
};
|
||||||
loading: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
setConfig({
|
setConfig({
|
||||||
apiURL: "http://localhost:8000",
|
apiURL: "http://localhost:8000"
|
||||||
ara_version: "1.0.0",
|
|
||||||
ansible_version: "2.6",
|
|
||||||
python_version: "3.6"
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
this.setState({ loading: false });
|
this.setState({ isLoading: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { isLoading } = this.state;
|
||||||
|
if (isLoading) return null;
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<Provider store={store}>
|
||||||
{this.state.loading ? (
|
<BrowserRouter>
|
||||||
<Containers.LoadingContainer />
|
<Switch>
|
||||||
) : (
|
<Redirect from="/" exact to="/playbooks" />
|
||||||
<Provider store={store}>
|
<Route
|
||||||
<BrowserRouter>
|
path="/playbooks"
|
||||||
<Switch>
|
exact
|
||||||
<Redirect from="/" exact to="/playbooks" />
|
component={Containers.PlaybooksContainer}
|
||||||
<Route
|
/>
|
||||||
path="/playbooks"
|
<Route component={Containers.Container404} />
|
||||||
exact
|
</Switch>
|
||||||
component={Containers.PlaybooksContainer}
|
</BrowserRouter>
|
||||||
/>
|
</Provider>
|
||||||
<Route
|
|
||||||
path="/about"
|
|
||||||
exact
|
|
||||||
component={Containers.AboutContainer}
|
|
||||||
/>
|
|
||||||
<Route component={Containers.Container404} />
|
|
||||||
</Switch>
|
|
||||||
</BrowserRouter>
|
|
||||||
</Provider>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
import React, { Component } from "react";
|
|
||||||
import { MainContainer } from "../containers";
|
|
||||||
|
|
||||||
export default class AboutContainer extends Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<MainContainer>
|
|
||||||
<p>AboutContainer</p>
|
|
||||||
</MainContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,3 @@
|
|||||||
export { default as Container404 } from "./layout/Container404";
|
export { default as Container404 } from "./layout/Container404";
|
||||||
export { default as LoadingContainer } from "./layout/LoadingContainer";
|
|
||||||
export { default as MainContainer } from "./layout/MainContainer";
|
export { default as MainContainer } from "./layout/MainContainer";
|
||||||
export { default as PlaybooksContainer } from "./playbooks/PlaybooksContainer";
|
export { default as PlaybooksContainer } from "./playbooks/PlaybooksContainer";
|
||||||
export { default as AboutContainer } from "./about/AboutContainer";
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
@ -1,8 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import "patternfly/dist/css/patternfly.min.css";
|
|
||||||
import "patternfly/dist/css/patternfly-additions.min.css";
|
|
||||||
import "./index.css";
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
|
||||||
ReactDOM.render(<App />, document.getElementById("root"));
|
ReactDOM.render(<App />, document.getElementById("root"));
|
||||||
|
@ -5,7 +5,15 @@ export default class Container404 extends Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<MainContainer>
|
<MainContainer>
|
||||||
<p>404</p>
|
<div className="pf-l-bullseye">
|
||||||
|
<div className="pf-l-bullseye__item">
|
||||||
|
<div className="pf-c-card">
|
||||||
|
<div className="pf-c-card__body">
|
||||||
|
<p>We are looking for your page...but we can't find it</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</MainContainer>
|
</MainContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import React, { Component } from "react";
|
|
||||||
|
|
||||||
export default class LoadingContainer extends Component {
|
|
||||||
render() {
|
|
||||||
return <div>loading...</div>;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +1,18 @@
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import Navbar from "./navigation/Navbar";
|
import Header from "./navigation/Header";
|
||||||
import { Grid, Row, Col } from "patternfly-react";
|
|
||||||
|
|
||||||
export default class MainContainer extends Component {
|
export default class MainContainer extends Component {
|
||||||
render() {
|
render() {
|
||||||
const { children } = this.props;
|
const { children } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="MainContent">
|
<div>
|
||||||
<Navbar />
|
<div className="pf-c-background-image" />
|
||||||
<Grid fluid>
|
<div className="pf-c-page" id="page-layout-horizontal-nav">
|
||||||
<Row>
|
<Header />
|
||||||
<Col xs={12}>{children}</Col>
|
<main role="main" className="pf-c-page__main">
|
||||||
</Row>
|
<section className="pf-c-page__main-section">{children}</section>
|
||||||
</Grid>
|
</main>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
72
src/layout/navigation/Header.js
Normal file
72
src/layout/navigation/Header.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { withRouter } from "react-router";
|
||||||
|
import logo from "./logo.svg";
|
||||||
|
|
||||||
|
const AraImg = styled.img`
|
||||||
|
height: 45px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export class NavLink extends Component {
|
||||||
|
render() {
|
||||||
|
const { to, className, location, children, ...rest } = this.props;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={to}
|
||||||
|
className={`${className} ${
|
||||||
|
location.pathname === to ? "pf-m-current" : ""
|
||||||
|
}`}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Header extends Component {
|
||||||
|
render() {
|
||||||
|
const { location } = this.props;
|
||||||
|
return (
|
||||||
|
<header role="banner" className="pf-c-page__header">
|
||||||
|
<div className="pf-c-page__header-brand">
|
||||||
|
<Link to="/playbooks" className="pf-c-page__header-brand-link">
|
||||||
|
<AraImg className="pf-c-brand" src={logo} alt="Ara Logo" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="pf-c-page__header-nav">
|
||||||
|
<nav
|
||||||
|
className="pf-c-nav"
|
||||||
|
id="page-layout-horizontal-nav-horizontal-nav"
|
||||||
|
aria-label="Horizontal Nav Example"
|
||||||
|
>
|
||||||
|
<ul className="pf-c-nav__horizontal-list">
|
||||||
|
<li className="pf-c-nav__item">
|
||||||
|
<NavLink
|
||||||
|
to="/playbooks"
|
||||||
|
className="pf-c-nav__link"
|
||||||
|
location={location}
|
||||||
|
>
|
||||||
|
Playbooks
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
<li className="pf-c-nav__item">
|
||||||
|
<a
|
||||||
|
href="https://ara.readthedocs.io/en/latest/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="pf-c-nav__link"
|
||||||
|
>
|
||||||
|
Documentation
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter(Header);
|
@ -1,15 +0,0 @@
|
|||||||
import React, { Component } from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
export default class NavLink extends Component {
|
|
||||||
render() {
|
|
||||||
const { id, to, location, children, ...rest } = this.props;
|
|
||||||
return (
|
|
||||||
<li className={location.pathname === to ? "active" : ""}>
|
|
||||||
<Link to={to} id={`navbar-navbar-primary__${id}-link`} {...rest}>
|
|
||||||
{children}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
import React, { Component } from "react";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { withRouter } from "react-router";
|
|
||||||
|
|
||||||
import logo from "./logo.svg";
|
|
||||||
import NavLink from "./NavLink";
|
|
||||||
|
|
||||||
export class Navbar extends Component {
|
|
||||||
render() {
|
|
||||||
const { location, config } = this.props;
|
|
||||||
const { ara_version, ansible_version, python_version } = config;
|
|
||||||
return (
|
|
||||||
<nav className="navbar navbar-default navbar-pf">
|
|
||||||
<div className="navbar-header">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="navbar-toggle"
|
|
||||||
data-toggle="collapse"
|
|
||||||
data-target=".navbar-collapse-1"
|
|
||||||
>
|
|
||||||
<span className="sr-only">Toggle navigation</span>
|
|
||||||
<span className="icon-bar" />
|
|
||||||
<span className="icon-bar" />
|
|
||||||
<span className="icon-bar" />
|
|
||||||
</button>
|
|
||||||
<Link
|
|
||||||
to="/playbooks"
|
|
||||||
id="navbar-navbar-header__playbooks-link"
|
|
||||||
className="navbar-brand"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={logo}
|
|
||||||
alt="ARA: Ansible Run Analysis"
|
|
||||||
width="81"
|
|
||||||
height="32"
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="collapse navbar-collapse navbar-collapse-1">
|
|
||||||
<ul className="nav navbar-nav navbar-utility">
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://ara.readthedocs.io/en/latest/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Documentation
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{ara_version ? (
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://github.com/openstack/ara"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<strong>ARA</strong> {ara_version}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
) : null}
|
|
||||||
{ansible_version ? (
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.ansible.com/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<strong>Ansible</strong> {ansible_version}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
) : null}
|
|
||||||
{python_version ? (
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.python.org/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<strong>Python</strong> {python_version}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
) : null}
|
|
||||||
</ul>
|
|
||||||
<ul className="nav navbar-nav navbar-primary">
|
|
||||||
<NavLink id="playbooks" to="/playbooks" location={location}>
|
|
||||||
Playbooks reports
|
|
||||||
</NavLink>
|
|
||||||
<NavLink id="about" to="/about" location={location}>
|
|
||||||
About
|
|
||||||
</NavLink>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
config: state.config
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withRouter(connect(mapStateToProps)(Navbar));
|
|
@ -1,9 +1,58 @@
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { Row, Col, ListView, Icon } from "patternfly-react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import PlaybookArgs from "./PlaybookArgs";
|
import PlaybookArgs from "./PlaybookArgs";
|
||||||
import PlaybookHosts from "./PlaybookHosts";
|
import PlaybookHosts from "./PlaybookHosts";
|
||||||
import PlaybookFiles from "./PlaybookFiles";
|
import PlaybookFiles from "./PlaybookFiles";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
function _getIconInfo(status) {
|
||||||
|
switch (status) {
|
||||||
|
case "running":
|
||||||
|
return {
|
||||||
|
title: "Playbook is in progress.",
|
||||||
|
icon: "fa-pause",
|
||||||
|
color: "blue"
|
||||||
|
};
|
||||||
|
case "completed":
|
||||||
|
return {
|
||||||
|
title: "Playbook has completed successfully.",
|
||||||
|
icon: "fa-check",
|
||||||
|
color: "green"
|
||||||
|
};
|
||||||
|
case "failed":
|
||||||
|
return {
|
||||||
|
title: "Playbook has failed with one or more errors.",
|
||||||
|
icon: "fa-warning",
|
||||||
|
color: "red"
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
title: "Playbook's status is unknown.",
|
||||||
|
icon: "fa-warning",
|
||||||
|
color: "red"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const IconWrapper = styled.i`
|
||||||
|
color: ${props => props.color};
|
||||||
|
`;
|
||||||
|
|
||||||
|
class StatusIcon extends Component {
|
||||||
|
render() {
|
||||||
|
const { status } = this.props;
|
||||||
|
const iconInfo = _getIconInfo(status);
|
||||||
|
return (
|
||||||
|
<IconWrapper
|
||||||
|
color={iconInfo.color}
|
||||||
|
className={`fas ${iconInfo.icon}`}
|
||||||
|
title={iconInfo.title}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const DataListCell = styled.div`
|
||||||
|
cursor: pointer;
|
||||||
|
`;
|
||||||
|
|
||||||
export default class Playbook extends Component {
|
export default class Playbook extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -17,120 +66,74 @@ export default class Playbook extends Component {
|
|||||||
_toggleExpanded = selection => {
|
_toggleExpanded = selection => {
|
||||||
this.setState(prevState => {
|
this.setState(prevState => {
|
||||||
if (selection === prevState.selection) {
|
if (selection === prevState.selection) {
|
||||||
return { expanded: !prevState.expanded };
|
return { expanded: !prevState.expanded, selection: null };
|
||||||
} else {
|
} else {
|
||||||
return { expanded: true, selection };
|
return { expanded: true, selection };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_getStatusMetadata(status) {
|
|
||||||
switch(status) {
|
|
||||||
case "running":
|
|
||||||
return {
|
|
||||||
"name": "running",
|
|
||||||
"title": "Playbook is in progress.",
|
|
||||||
"className": "pficon pficon-info list-view-pf-icon-md list-view-pf-icon-info"
|
|
||||||
}
|
|
||||||
case "completed":
|
|
||||||
return {
|
|
||||||
"name": "completed",
|
|
||||||
"title": "Playbook has completed successfully.",
|
|
||||||
"className": "pficon pficon-ok list-view-pf-icon-md list-view-pf-icon-success"
|
|
||||||
}
|
|
||||||
case "failed":
|
|
||||||
return {
|
|
||||||
"name": "failed",
|
|
||||||
"title": "Playbook has failed with one or more errors.",
|
|
||||||
"className": "pficon pficon-error-circle-o list-view-pf-icon-md list-view-pf-icon-danger"
|
|
||||||
}
|
|
||||||
case "unknown":
|
|
||||||
return {
|
|
||||||
"name": "unknown",
|
|
||||||
"title": "Playbook's status is unknown.",
|
|
||||||
"className": "pficon pficon-warning-triangle-o list-view-pf-icon-md list-view-pf-icon-warning"
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
"name": "unknown",
|
|
||||||
"title": "Playbook's status is unknown.",
|
|
||||||
"className": "pficon pficon-warning-triangle-o list-view-pf-icon-md list-view-pf-icon-warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { playbook } = this.props;
|
const { playbook } = this.props;
|
||||||
const { expanded, selection } = this.state;
|
const { expanded, selection } = this.state;
|
||||||
const status_metadata = this._getStatusMetadata(playbook.status);
|
|
||||||
const LeftIcon =
|
|
||||||
<ListView.Icon
|
|
||||||
name={status_metadata["name"]}
|
|
||||||
size="md"
|
|
||||||
title={status_metadata["title"]}
|
|
||||||
className={status_metadata["className"]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListView.Item
|
<ul className="pf-c-data-list pf-u-box-shadow-md">
|
||||||
checkboxInput={
|
<li
|
||||||
<Link to={`/playbooks/${playbook.id}`} className="navbar-brand">
|
className={`pf-c-data-list__item ${expanded ? "pf-m-expanded" : ""}`}
|
||||||
<Icon name="link" size="lg" />
|
>
|
||||||
</Link>
|
<div className="pf-c-data-list__check">
|
||||||
}
|
<StatusIcon status={playbook.status} />
|
||||||
leftContent={LeftIcon}
|
</div>
|
||||||
additionalInfo={[
|
<DataListCell className="pf-c-data-list__cell pf-m-flex-5">
|
||||||
<ListView.InfoItem key="args">
|
{playbook.file.path.split("/").slice(-1)[0]}
|
||||||
<ListView.Expand
|
</DataListCell>
|
||||||
expanded={expanded && selection === "args"}
|
<DataListCell
|
||||||
toggleExpanded={() => this._toggleExpanded("args")}
|
className="pf-c-data-list__cell pf-m-flex-1"
|
||||||
>
|
onClick={() => this._toggleExpanded("args")}
|
||||||
<Icon name="cogs" />
|
>
|
||||||
Arguments
|
<i
|
||||||
</ListView.Expand>
|
className={`fas fa-angle-${
|
||||||
</ListView.InfoItem>,
|
selection === "args" ? "down" : "right"
|
||||||
<ListView.InfoItem key="hosts">
|
} pf-u-mr-xs`}
|
||||||
<ListView.Expand
|
/>
|
||||||
expanded={expanded && selection === "hosts"}
|
<b>{Object.keys(playbook.arguments).length}</b> arguments
|
||||||
toggleExpanded={() => this._toggleExpanded("hosts")}
|
</DataListCell>
|
||||||
>
|
<DataListCell
|
||||||
<Icon name="server" />
|
className="pf-c-data-list__cell pf-m-flex-1"
|
||||||
<b>{playbook.hosts.length}</b> Hosts
|
onClick={() => this._toggleExpanded("hosts")}
|
||||||
</ListView.Expand>
|
>
|
||||||
</ListView.InfoItem>,
|
<i
|
||||||
<ListView.InfoItem key="files">
|
className={`fas fa-angle-${
|
||||||
<ListView.Expand
|
selection === "hosts" ? "down" : "right"
|
||||||
expanded={expanded && selection === "files"}
|
} pf-u-mr-xs`}
|
||||||
toggleExpanded={() => this._toggleExpanded("files")}
|
/>
|
||||||
>
|
<b>{playbook.hosts.length}</b> Hosts
|
||||||
<Icon name="folder-open" />
|
</DataListCell>
|
||||||
<b>{playbook.files.length}</b> Files
|
<DataListCell
|
||||||
</ListView.Expand>
|
className="pf-c-data-list__cell pf-m-flex-1"
|
||||||
</ListView.InfoItem>
|
onClick={() => this._toggleExpanded("files")}
|
||||||
]}
|
>
|
||||||
actions={
|
<i
|
||||||
<span>
|
className={`fas fa-angle-${
|
||||||
<Icon name="clock-o" size="lg" /> {Math.round(playbook.duration)} sec
|
selection === "files" ? "down" : "right"
|
||||||
</span>
|
} pf-u-mr-xs`}
|
||||||
}
|
/>
|
||||||
heading={
|
<b>{playbook.files.length}</b> Files
|
||||||
playbook.name
|
</DataListCell>
|
||||||
? playbook.name
|
<DataListCell className="pf-c-data-list__cell pf-u-text-align-right">
|
||||||
: playbook.file.path.split("/").slice(-1)[0]
|
<i className={`fas fa-clock pf-u-mr-xs`} />
|
||||||
}
|
{Math.round(playbook.duration)} sec
|
||||||
stacked={false}
|
</DataListCell>
|
||||||
compoundExpand
|
<section
|
||||||
compoundExpanded={expanded}
|
className="pf-c-data-list__expandable-content"
|
||||||
onCloseCompoundExpand={() => this.setState({ expanded: false })}
|
aria-label="Primary Content Details"
|
||||||
>
|
>
|
||||||
<Row>
|
|
||||||
<Col xs={12} sm={8} smOffset={2} md={6} mdOffset={3}>
|
|
||||||
{selection === "args" && <PlaybookArgs playbook={playbook} />}
|
{selection === "args" && <PlaybookArgs playbook={playbook} />}
|
||||||
{selection === "hosts" && <PlaybookHosts playbook={playbook} />}
|
{selection === "hosts" && <PlaybookHosts playbook={playbook} />}
|
||||||
{selection === "files" && <PlaybookFiles playbook={playbook} />}
|
{selection === "files" && <PlaybookFiles playbook={playbook} />}
|
||||||
</Col>
|
</section>
|
||||||
</Row>
|
</li>
|
||||||
</ListView.Item>
|
</ul>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,4 @@
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { OverlayTrigger, Tooltip, Icon } from "patternfly-react";
|
|
||||||
|
|
||||||
export class ParamatersHelpIcon extends Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<span style={{ float: "right" }}>
|
|
||||||
<OverlayTrigger
|
|
||||||
overlay={
|
|
||||||
<Tooltip id="tooltip">
|
|
||||||
<div>
|
|
||||||
<h3>Tips: Arguments</h3>
|
|
||||||
<hr />
|
|
||||||
<p>
|
|
||||||
This panel contains all the arguments and options passed to
|
|
||||||
the ansible-playbook command.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
You can control which arguments ARA should ignore with the{" "}
|
|
||||||
<code>ignored_arguments</code> configuration.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
placement="bottom"
|
|
||||||
>
|
|
||||||
<Icon name="question-circle" />
|
|
||||||
</OverlayTrigger>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class PlaybookArgs extends Component {
|
export default class PlaybookArgs extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -55,35 +24,28 @@ export default class PlaybookArgs extends Component {
|
|||||||
arg => arg.toLowerCase().indexOf(search.toLowerCase()) !== -1
|
arg => arg.toLowerCase().indexOf(search.toLowerCase()) !== -1
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className="table-response">
|
<div>
|
||||||
<div className="dataTables_header">
|
<div className="pf-l-grid pf-m-gutter pf-u-display-flex pf-u-align-items-center">
|
||||||
<div className="dataTables_filter">
|
<div className="pf-l-grid__item">
|
||||||
<input
|
<input
|
||||||
className="form-control"
|
className="pf-c-form-control"
|
||||||
placeholder="Search an argument"
|
placeholder="Search an argument"
|
||||||
type="search"
|
type="search"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={e => this.setState({ search: e.target.value })}
|
onChange={e => this.setState({ search: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="dataTables_info">
|
<div className="pf-l-grid__item">
|
||||||
Showing <b>{filteredArgs.length}</b> of{" "}
|
{`Showing ${filteredArgs.length} of ${args.length} args`}
|
||||||
<b>{args.length}</b> args
|
|
||||||
<ParamatersHelpIcon />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table className="table table-striped table-bordered table-hover">
|
|
||||||
<thead>
|
<table className="pf-c-table pf-m-compact pf-m-grid-md" role="grid">
|
||||||
<tr>
|
|
||||||
<th>Argument</th>
|
|
||||||
<th>Value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{filteredArgs.map((arg, i) => (
|
{filteredArgs.map((arg, i) => (
|
||||||
<tr key={i}>
|
<tr key={i}>
|
||||||
<td>{arg}</td>
|
<td className="pf-m-width-30">{arg}</td>
|
||||||
<td>{this._renderArg(playbook.arguments[arg])}</td>
|
<td className="pf-m-width-70">{this._renderArg(playbook.arguments[arg])}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { Button, Icon, Modal } from "patternfly-react";
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const ModalBox = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
`;
|
||||||
|
|
||||||
export default class PlaybookFiles extends Component {
|
export default class PlaybookFiles extends Component {
|
||||||
state = {
|
state = {
|
||||||
@ -16,48 +20,52 @@ export default class PlaybookFiles extends Component {
|
|||||||
const { playbook } = this.props;
|
const { playbook } = this.props;
|
||||||
const { showModal, filePath, fileContent } = this.state;
|
const { showModal, filePath, fileContent } = this.state;
|
||||||
return (
|
return (
|
||||||
<div className="table-response">
|
<div>
|
||||||
<Modal show={showModal} onHide={this.close} bsSize="large">
|
{showModal && (
|
||||||
<Modal.Header>
|
<ModalBox>
|
||||||
<button
|
<div
|
||||||
className="close"
|
className="pf-c-modal-box pf-m-lg"
|
||||||
onClick={this.close}
|
role="dialog"
|
||||||
aria-hidden="true"
|
aria-modal="true"
|
||||||
aria-label="Close"
|
aria-labelledby="modal-title"
|
||||||
|
aria-describedby="modal-description"
|
||||||
>
|
>
|
||||||
<Icon type="pf" name="close" />
|
<div className="pf-c-modal-box__close">
|
||||||
</button>
|
<button
|
||||||
<Modal.Title>{filePath}</Modal.Title>
|
className="pf-c-button pf-m-plain"
|
||||||
</Modal.Header>
|
aria-label="Close"
|
||||||
<Modal.Body>
|
onClick={this.close}
|
||||||
<pre>
|
>
|
||||||
<code>{fileContent}</code>
|
<i className="fas fa-times" aria-hidden="true" />
|
||||||
</pre>
|
</button>
|
||||||
</Modal.Body>
|
</div>
|
||||||
<Modal.Footer>
|
<header className="pf-c-modal-box__header">
|
||||||
<Button
|
<h1 className="pf-c-modal-box__header-title" id="modal-title">
|
||||||
bsStyle="default"
|
{filePath}
|
||||||
className="btn-cancel"
|
</h1>
|
||||||
onClick={this.close}
|
</header>
|
||||||
>
|
<div className="pf-c-modal-box__body" id="modal-description">
|
||||||
close
|
<pre>
|
||||||
</Button>
|
<code>{fileContent}</code>
|
||||||
</Modal.Footer>
|
</pre>
|
||||||
</Modal>
|
</div>
|
||||||
<table className="table table-striped table-bordered table-hover">
|
<footer className="pf-c-modal-box__footer">
|
||||||
<thead>
|
<button type="button" onClick={this.close}>
|
||||||
<tr>
|
close
|
||||||
<th>Name</th>
|
</button>
|
||||||
<th>Actions</th>
|
</footer>
|
||||||
</tr>
|
</div>
|
||||||
</thead>
|
</ModalBox>
|
||||||
|
)}
|
||||||
|
<table className="pf-c-table pf-m-compact pf-m-grid-md" role="grid">
|
||||||
<tbody>
|
<tbody>
|
||||||
{playbook.files.map(file => (
|
{playbook.files.map(file => (
|
||||||
<tr key={file.id}>
|
<tr key={file.id}>
|
||||||
<td>{file.path}</td>
|
<td>{file.path}</td>
|
||||||
<td className="text-center">
|
<td>
|
||||||
<Button
|
<button
|
||||||
bsStyle="primary"
|
type="button"
|
||||||
|
className="pf-c-button pf-m-secondary"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
this.setState({
|
this.setState({
|
||||||
showModal: true,
|
showModal: true,
|
||||||
@ -66,8 +74,8 @@ export default class PlaybookFiles extends Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Icon name="eye" />
|
See content
|
||||||
</Button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
@ -1,30 +1,4 @@
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { OverlayTrigger, Tooltip, Icon } from "patternfly-react";
|
|
||||||
|
|
||||||
export class HostsHelpIcon extends Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<span style={{ float: "right" }}>
|
|
||||||
<OverlayTrigger
|
|
||||||
overlay={
|
|
||||||
<Tooltip id="tooltip">
|
|
||||||
<div>
|
|
||||||
<h3>Tips: Hosts</h3>
|
|
||||||
<hr />
|
|
||||||
<p>
|
|
||||||
This panel contains all the hosts involved in the playbook.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
placement="bottom"
|
|
||||||
>
|
|
||||||
<Icon name="question-circle" />
|
|
||||||
</OverlayTrigger>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class PlaybookHosts extends Component {
|
export default class PlaybookHosts extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -41,35 +15,30 @@ export default class PlaybookHosts extends Component {
|
|||||||
host => host.name.toLowerCase().indexOf(search.toLowerCase()) !== -1
|
host => host.name.toLowerCase().indexOf(search.toLowerCase()) !== -1
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className="table-response">
|
<div>
|
||||||
<div className="dataTables_header">
|
<div className="pf-l-grid pf-m-gutter pf-u-display-flex pf-u-align-items-center">
|
||||||
<div className="dataTables_filter">
|
<div className="pf-l-grid__item">
|
||||||
<input
|
<input
|
||||||
className="form-control"
|
className="pf-c-form-control"
|
||||||
placeholder="Search a host"
|
placeholder="Search a host"
|
||||||
type="search"
|
type="search"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={e => this.setState({ search: e.target.value })}
|
onChange={e => this.setState({ search: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="dataTables_info">
|
<div className="pf-l-grid__item">
|
||||||
Showing <b>{filteredHosts.length}</b> of{" "}
|
{`Showing ${filteredHosts.length} of ${
|
||||||
<b>{playbook.hosts.length}</b> hosts
|
playbook.hosts.length
|
||||||
<HostsHelpIcon />
|
} hosts`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table className="table table-striped table-bordered table-hover">
|
|
||||||
<thead>
|
<table className="pf-c-table pf-m-compact pf-m-grid-md" role="grid">
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Alias</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{filteredHosts.map(host => (
|
{filteredHosts.map(host => (
|
||||||
<tr key={host.id}>
|
<tr key={host.id}>
|
||||||
<td>{host.name}</td>
|
<td className="pf-m-width-30">{host.name}</td>
|
||||||
<td>{host.alias}</td>
|
<td className="pf-m-width-70">{host.alias}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -1,24 +1,67 @@
|
|||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { ListView } from "patternfly-react";
|
import { isEmpty } from "lodash";
|
||||||
|
|
||||||
import { MainContainer } from "../containers";
|
import { MainContainer } from "../containers";
|
||||||
import { getPlaybooks } from "./playbooksActions";
|
import { getPlaybooks } from "./playbooksActions";
|
||||||
import Playbook from "./Playbook";
|
import Playbook from "./Playbook";
|
||||||
|
|
||||||
export class PlaybooksContainer extends Component {
|
export class PlaybooksContainer extends Component {
|
||||||
|
state = {
|
||||||
|
isLoading: true
|
||||||
|
};
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.getPlaybooks();
|
this.props
|
||||||
|
.getPlaybooks()
|
||||||
|
.catch(error => console.log(error))
|
||||||
|
.then(() => this.setState({ isLoading: false }));
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { playbooks } = this.props;
|
const { playbooks } = this.props;
|
||||||
|
const { isLoading } = this.state;
|
||||||
return (
|
return (
|
||||||
<MainContainer>
|
<MainContainer>
|
||||||
<ListView>
|
{isLoading && (
|
||||||
{playbooks.map(playbook => (
|
<div className="pf-l-bullseye">
|
||||||
<Playbook key={playbook.id} playbook={playbook} />
|
<div className="pf-l-bullseye__item">
|
||||||
))}
|
<div className="pf-c-card">
|
||||||
</ListView>
|
<div className="pf-c-card__body">loading</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isLoading && isEmpty(playbooks) && (
|
||||||
|
<div className="pf-l-bullseye">
|
||||||
|
<div className="pf-l-bullseye__item">
|
||||||
|
<div className="pf-c-card">
|
||||||
|
<div className="pf-c-card__body">
|
||||||
|
<div className="pf-c-empty-state">
|
||||||
|
<i
|
||||||
|
className="fas fa-cubes pf-c-empty-state__icon"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<h1 className="pf-c-title pf-m-lg">No playbooks</h1>
|
||||||
|
<p className="pf-c-empty-state__body">
|
||||||
|
There is no playbook available on this instance of Ara
|
||||||
|
</p>
|
||||||
|
<div className="pf-c-empty-state__action">
|
||||||
|
<a
|
||||||
|
href="https://ara.readthedocs.io/en/latest/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
See documentation
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{playbooks.map(playbook => (
|
||||||
|
<Playbook key={playbook.id} playbook={playbook} />
|
||||||
|
))}
|
||||||
</MainContainer>
|
</MainContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user