You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ea...@apache.org on 2019/10/07 15:48:11 UTC
[qpid-dispatch] 03/03: Overview detail pages mostly working
This is an automated email from the ASF dual-hosted git repository.
eallen pushed a commit to branch eallen-DISPATCH-1385
in repository https://gitbox.apache.org/repos/asf/qpid-dispatch.git
commit 4d273db81efc3906d3a1fc4dd3aee2e09492b68c
Author: Ernest Allen <ea...@redhat.com>
AuthorDate: Mon Oct 7 11:47:52 2019 -0400
Overview detail pages mostly working
---
console/react/src/App.css | 61 +++-
console/react/src/App.js | 1 +
console/react/src/amqp/management.js | 5 +
console/react/src/amqp/utilities.js | 32 ++-
console/react/src/connect-form.js | 26 +-
console/react/src/connectPage.js | 55 ++--
console/react/src/layout.js | 253 ++++++++--------
console/react/src/overview/addressesTable.js | 161 -----------
console/react/src/overview/connectionsTable.js | 95 ------
.../react/src/overview/dashboard/dashboardPage.js | 48 ++--
.../react/src/overview/dataSources/addressData.js | 191 +++++++++++++
.../src/overview/dataSources/connectionData.js | 169 +++++++++++
.../{linksTable.js => dataSources/linkData.js} | 131 +++++----
.../{routersTable.js => dataSources/routerData.js} | 68 +++--
console/react/src/overview/detailsTablePage.js | 218 ++++++++++++++
console/react/src/overview/entityData.js | 32 +++
console/react/src/overview/overviewTable.js | 318 +++++++++++++++++++++
console/react/src/overview/overviewTableBase.js | 213 --------------
console/react/src/overview/overviewTablePage.js | 102 ++++---
console/react/src/overview/tableToolbar.jsx | 19 ++
console/react/src/qdrService.js | 5 +-
21 files changed, 1409 insertions(+), 794 deletions(-)
diff --git a/console/react/src/App.css b/console/react/src/App.css
index 1f7a1f0..5601c16 100644
--- a/console/react/src/App.css
+++ b/console/react/src/App.css
@@ -378,10 +378,16 @@ div.state-container button.pf-c-clipboard-copy__group-copy {
padding-right: 0 !important;
}
.overview-header {
- padding: 0.5em;
- font-size: 5em;
+ padding: 2em;
text-align: left;
}
+.overview-header.details {
+ padding: 1em 2em;
+}
+.overview-header.details .pf-c-breadcrumb__item {
+ font-size: 1.125em;
+ margin-bottom: 1em;
+}
.fill-card {
width: 100%;
}
@@ -834,13 +840,16 @@ div.qdrChord .legend-text {
}
.overview-charts-page .pf-c-card__header.pf-c-title {
- text-align: left;
font-size: 22px;
}
+.dashboard-header {
+ display: flex;
+}
.overview-charts-page div.time-period {
font-size: 18px;
color: #888;
+ text-align: left;
}
.chart-container {
@@ -871,8 +880,7 @@ div.qdrChord .legend-text {
}
.duration-tabs {
- position: absolute;
- right: 1.5em;
+ margin-left: auto;
}
.duration-tabs li {
@@ -911,3 +919,46 @@ div.qdrChord .legend-text {
.table-toolbar .pf-l-toolbar__item {
margin-right: 0 !important;
}
+
+.overview-header .pf-c-content {
+ display: flex;
+}
+
+.overview-loading {
+ margin-left: auto;
+ color: #999;
+ border: 0;
+ font-size: 14px;
+ padding: 0;
+ background-color: white;
+}
+
+.pf-c-button.pf-m-primary.link-button,
+.overview-header.details .pf-c-breadcrumb__item.link-button {
+ border: 0;
+ background-color: transparent;
+ color: blue;
+ font-weight: bold;
+ white-space: normal;
+}
+
+.pf-c-button.pf-m-primary.link-button:hover,
+.overview-header.details .pf-c-breadcrumb__item.link-button:hover {
+ text-decoration: underline;
+}
+
+.noWrap {
+ white-space: nowrap;
+}
+
+.link-dir-in,
+.link-dir-out {
+ padding-right: 0.5em;
+}
+.link-dir-in {
+ color: red;
+}
+
+.link-dir-out {
+ color: blue;
+}
diff --git a/console/react/src/App.js b/console/react/src/App.js
index d686a5d..91f09e7 100644
--- a/console/react/src/App.js
+++ b/console/react/src/App.js
@@ -1,5 +1,6 @@
import React, { Component } from "react";
import "@patternfly/patternfly/patternfly.css";
+import "@patternfly/patternfly/patternfly-addons.css";
import "patternfly/dist/css/patternfly.css";
import "@patternfly/patternfly/components/Nav/nav.css";
diff --git a/console/react/src/amqp/management.js b/console/react/src/amqp/management.js
index 0bdb748..19ca359 100644
--- a/console/react/src/amqp/management.js
+++ b/console/react/src/amqp/management.js
@@ -27,6 +27,11 @@ export class Management {
getSchema(callback) {
var self = this;
return new Promise(function(resolve, reject) {
+ if (self.connection.schema) {
+ if (callback) callback(self.connection.schema);
+ resolve(self.connection.schema);
+ return;
+ }
self.connection.sendMgmtQuery("GET-SCHEMA").then(
function(responseAndContext) {
var response = responseAndContext.response;
diff --git a/console/react/src/amqp/utilities.js b/console/react/src/amqp/utilities.js
index a42247e..0814b34 100644
--- a/console/react/src/amqp/utilities.js
+++ b/console/react/src/amqp/utilities.js
@@ -19,7 +19,7 @@ var ddd = typeof window === "undefined" ? require("d3") : d3;
var utils = {
isAConsole: function(properties, connectionId, nodeType, key) {
- return this.isConsole({
+ return utils.isConsole({
properties: properties,
connectionId: connectionId,
nodeType: nodeType,
@@ -73,7 +73,7 @@ var utils = {
};
let results = [];
for (let i = 0; i < entity.results.length; i++) {
- let f = filter(this.flatten(entity.attributeNames, entity.results[i]));
+ let f = filter(utils.flatten(entity.attributeNames, entity.results[i]));
if (f) results.push(f);
}
return results;
@@ -235,6 +235,34 @@ var utils = {
const url = document.createElement("a");
url.setAttribute("href", fullUrl);
return url;
+ },
+ Icap: s => s[0].toUpperCase() + s.slice(1, s.length - 1),
+
+ // get last token in string that looks like "/fooo/baaar/baaaz"
+ entityFromProps: props => {
+ if (props && props.location && props.location.pathname) {
+ return props.location.pathname.split("/").slice(-1)[0];
+ }
+ return "";
+ },
+
+ formatAttributes: (record, entityType) => {
+ for (const attrib in record) {
+ const schemaAttrib = entityType.attributes[attrib];
+ if (schemaAttrib) {
+ if (schemaAttrib.type === "integer") {
+ record[attrib] = utils.pretty(record[attrib]);
+ } else if (record[attrib] === null) {
+ record[attrib] = "";
+ } else if (schemaAttrib.type === "map") {
+ record[attrib] = JSON.stringify(record[attrib], null, 2);
+ } else {
+ record[attrib] = String(record[attrib]);
+ }
+ }
+ }
+ return record;
}
};
+
export { utils };
diff --git a/console/react/src/connect-form.js b/console/react/src/connect-form.js
index 4735b4c..6ef9a74 100644
--- a/console/react/src/connect-form.js
+++ b/console/react/src/connect-form.js
@@ -20,14 +20,11 @@ import {
TextInput,
ActionGroup,
Button,
- ButtonVariant,
TextContent,
Text,
TextVariants
} from "@patternfly/react-core";
-import { PowerOffIcon } from "@patternfly/react-icons";
-
class ConnectForm extends React.Component {
constructor(props) {
super(props);
@@ -37,9 +34,7 @@ class ConnectForm extends React.Component {
value1: "",
value2: "",
value3: "",
- value4: "",
- formVisible: !this.props.buttonHidden,
- buttonVisible: this.props.buttonHidden ? false : true
+ value4: ""
};
this.handleTextInputChange1 = value1 => {
this.setState({ value1 });
@@ -57,11 +52,11 @@ class ConnectForm extends React.Component {
handleConnect = () => {
this.toggleDrawerHide();
- this.props.handleConnect();
+ this.props.handleConnect(this.props.fromPath);
};
toggleDrawerHide = () => {
- this.setState({ formVisible: !this.state.formVisible });
+ this.props.handleConnectCancel();
};
render() {
@@ -69,20 +64,7 @@ class ConnectForm extends React.Component {
return (
<div>
- <Button
- id="notificationButton"
- onClick={this.toggleDrawerHide}
- aria-label="Notifications actions"
- variant={ButtonVariant.plain}
- className={this.state.buttonVisible ? "" : "hidden"}
- >
- <PowerOffIcon />
- </Button>
- <div
- className={
- this.state.formVisible ? "connect-modal" : "connect-modal hidden"
- }
- >
+ <div className="connect-modal">
<div className="">
<Form isHorizontal>
<TextContent className="connect-title">
diff --git a/console/react/src/connectPage.js b/console/react/src/connectPage.js
index 50b0e9b..cd537d1 100644
--- a/console/react/src/connectPage.js
+++ b/console/react/src/connectPage.js
@@ -10,40 +10,43 @@ import ConnectForm from "./connect-form";
class ConnectPage extends React.Component {
constructor(props) {
super(props);
- this.state = {};
+ this.state = { showForm: true };
}
+ handleConnectCancel = () => {
+ this.setState({ showForm: false });
+ };
render() {
+ const { showForm } = this.state;
+ const { from } = this.props.location.state || { from: { pathname: "/" } };
return (
- <React.Fragment>
- <PageSection
- variant={PageSectionVariants.light}
- className="connect-page"
- >
- <div className="left-content">
- <TextContent>
- <Text component="h1" className="console-banner">
- Apache Qpid Dispatch Console
- </Text>
- </TextContent>
- <TextContent>
- <Text component="p">
- The console provides limited information about the clients that
- are attached to the router network and is therefore more
- appropriate for administrators needing to know the layout and
- health of the router network.
- </Text>
- </TextContent>
- </div>
- </PageSection>
- <PageSection>
+ <PageSection variant={PageSectionVariants.light} className="connect-page">
+ {showForm ? (
<ConnectForm
prefix="form"
handleConnect={this.props.handleConnect}
- buttonHidden={true}
+ handleConnectCancel={this.handleConnectCancel}
+ fromPath={from.pathname}
/>
- </PageSection>
- </React.Fragment>
+ ) : (
+ <React.Fragment />
+ )}
+ <div className="left-content">
+ <TextContent>
+ <Text component="h1" className="console-banner">
+ Apache Qpid Dispatch Console
+ </Text>
+ </TextContent>
+ <TextContent>
+ <Text component="p">
+ The console provides limited information about the clients that
+ are attached to the router network and is therefore more
+ appropriate for administrators needing to know the layout and
+ health of the router network.
+ </Text>
+ </TextContent>
+ </div>
+ </PageSection>
);
}
}
diff --git a/console/react/src/layout.js b/console/react/src/layout.js
index afae49e..e81ee11 100644
--- a/console/react/src/layout.js
+++ b/console/react/src/layout.js
@@ -26,7 +26,6 @@ import {
DropdownToggle,
DropdownItem,
DropdownSeparator,
- KebabToggle,
Page,
PageHeader,
SkipToContent,
@@ -40,14 +39,23 @@ import {
PageSidebar
} from "@patternfly/react-core";
+import {
+ BrowserRouter as Router,
+ Switch,
+ Route,
+ Link,
+ Redirect
+} from "react-router-dom";
+
import accessibleStyles from "@patternfly/patternfly/utilities/Accessibility/accessibility.css";
import spacingStyles from "@patternfly/patternfly/utilities/Spacing/spacing.css";
import { css } from "@patternfly/react-styles";
-import { BellIcon, CogIcon } from "@patternfly/react-icons";
-import ConnectForm from "./connect-form";
+import { BellIcon, CogIcon, PowerOffIcon } from "@patternfly/react-icons";
+//import ConnectForm from "./connect-form";
import ConnectPage from "./connectPage";
import DashboardPage from "./overview/dashboard/dashboardPage";
import OverviewTablePage from "./overview/overviewTablePage";
+import DetailsTablePage from "./overview/detailsTablePage";
import TopologyPage from "./topology/qdrTopology";
import MessageFlowPage from "./chord/qdrChord";
import { QDRService } from "./qdrService";
@@ -58,23 +66,16 @@ class PageLayout extends React.Component {
super(props);
this.state = {
connected: false,
+ connectPath: "",
isDropdownOpen: false,
- isKebabDropdownOpen: false,
activeGroup: "overview",
- activeItem: "dashboard"
+ activeItem: "dashboard",
+ detailInfo: null,
+ detailMeta: null
};
this.tables = ["routers", "addresses", "links", "connections", "logs"];
/*
- connections: [
- { title: "name", displayName: "host" },
- { title: "container" },
- { title: "role" },
- { title: "dir" },
- { title: "security" },
- { title: "authentication" },
- { title: "close" }
- ],
logs: [
{ title: "Module" },
{ title: "Notice" },
@@ -91,7 +92,7 @@ class PageLayout extends React.Component {
}
setLocation = where => {
- //console.log(`setLocation to ${where}`);
+ //this.setState({ connectPath: where })
};
onDropdownToggle = isDropdownOpen => {
@@ -106,49 +107,55 @@ class PageLayout extends React.Component {
});
};
- onKebabDropdownToggle = isKebabDropdownOpen => {
- this.setState({
- isKebabDropdownOpen
- });
- };
-
- onKebabDropdownSelect = event => {
- this.setState({
- isKebabDropdownOpen: !this.state.isKebabDropdownOpen
- });
- };
-
- handleConnect = event => {
+ handleConnect = connectPath => {
this.service
.connect({ address: "localhost", port: 5673, reconnect: true })
.then(
r => {
- //console.log(r);
+ this.setState({
+ connected: true,
+ connectPath
+ });
},
e => {
console.log(e);
}
);
- this.setState({
- connected: true
- });
};
+ handleConnectCancel = () => {};
onNavSelect = result => {
this.setState({
activeItem: result.itemId,
- activeGroup: result.groupId
+ activeGroup: result.groupId,
+ connectPath: ""
});
};
icap = s => s.charAt(0).toUpperCase() + s.slice(1);
- render() {
- const {
- isDropdownOpen,
- isKebabDropdownOpen,
+ showDetailTable = (_value, detailInfo, activeItem, detailMeta) => {
+ this.setState({
+ activeGroup: "detailsTable",
activeItem,
- activeGroup
- } = this.state;
+ detailInfo,
+ detailMeta,
+ connectPath: "/details"
+ });
+ };
+
+ BreadcrumbSelected = connectPath => {
+ this.setState({
+ connectPath
+ });
+ };
+
+ toggleConnectForm = event => {
+ console.log("taggleConnectForm called with event.target");
+ console.log(event.target);
+ };
+
+ render() {
+ const { isDropdownOpen, activeItem, activeGroup } = this.state;
const PageNav = (
<Nav onSelect={this.onNavSelect} aria-label="Nav" className="pf-m-dark">
@@ -164,7 +171,7 @@ class PageLayout extends React.Component {
itemId="dashboard"
isActive={activeItem === "dashboard"}
>
- Dashboard
+ <Link to="/dashboard">Dashboard</Link>
</NavItem>
{this.tables.map(t => {
return (
@@ -174,7 +181,7 @@ class PageLayout extends React.Component {
isActive={activeItem === { t }}
key={t}
>
- {this.icap(t)}
+ <Link to={`/overview/${t}`}>{this.icap(t)}</Link>
</NavItem>
);
})}
@@ -189,62 +196,42 @@ class PageLayout extends React.Component {
itemId="topology"
isActive={activeItem === "topology"}
>
- Topology
+ <Link to="/topology">Topology</Link>
</NavItem>
<NavItem
groupId="visualizations"
itemId="flow"
isActive={activeItem === "flow"}
>
- Message flow
+ <Link to="/flow">Message flow</Link>
</NavItem>
</NavExpandable>
<NavExpandable
title="Details"
- groupId="grp-3"
- isActive={activeGroup === "grp-3"}
+ groupId="detailsGroup"
+ isActive={activeGroup === "detailsGroup"}
>
<NavItem
- groupId="grp-3"
- itemId="grp-3_itm-1"
- isActive={activeItem === "grp-3_itm-1"}
+ groupId="detailsGroup"
+ itemId="entities"
+ isActive={activeItem === "entities"}
>
- Entities
+ <Link to="/entities">Entities</Link>
</NavItem>
<NavItem
- groupId="grp-3"
- itemId="grp-3_itm-2"
- isActive={activeItem === "grp-3_itm-2"}
+ groupId="detailsGroup"
+ itemId="schema"
+ isActive={activeItem === "schema"}
>
- Schema
+ <Link to="/schema">Schema</Link>
</NavItem>
</NavExpandable>
</NavList>
</Nav>
);
- const kebabDropdownItems = [
- <DropdownItem key="notif">
- <BellIcon /> Notifications
- </DropdownItem>,
- <DropdownItem key="sett">
- <CogIcon /> Settings
- </DropdownItem>
- ];
const userDropdownItems = [
- <DropdownItem key="link">Link</DropdownItem>,
<DropdownItem component="button" key="action">
- Action
- </DropdownItem>,
- <DropdownItem isDisabled key="dis">
- Disabled Link
- </DropdownItem>,
- <DropdownItem isDisabled component="button" key="button">
- Disabled Action
- </DropdownItem>,
- <DropdownSeparator key="sep0" />,
- <DropdownItem key="sep">Separated Link</DropdownItem>,
- <DropdownItem component="button" key="sep1">
- Separated Action
+ Logout
</DropdownItem>
];
const PageToolbar = (
@@ -256,41 +243,27 @@ class PageLayout extends React.Component {
)}
>
<ToolbarItem>
- <ConnectForm prefix="toolbar" handleConnect={this.handleConnect} />
- </ToolbarItem>
- <ToolbarItem>
<Button
- id="default-example-uid-01"
- aria-label="Notifications actions"
+ id="connectButton"
+ onClick={this.toggleConnectForm}
+ aria-label="Toggle Connect Form"
variant={ButtonVariant.plain}
>
- <BellIcon />
+ <PowerOffIcon />
</Button>
</ToolbarItem>
<ToolbarItem>
<Button
- id="default-example-uid-02"
- aria-label="Settings actions"
+ id="default-example-uid-01"
+ aria-label="Notifications actions"
variant={ButtonVariant.plain}
>
- <CogIcon />
+ <BellIcon />
</Button>
</ToolbarItem>
</ToolbarGroup>
<ToolbarGroup>
<ToolbarItem
- className={css(accessibleStyles.hiddenOnLg, spacingStyles.mr_0)}
- >
- <Dropdown
- isPlain
- position="right"
- onSelect={this.onKebabDropdownSelect}
- toggle={<KebabToggle onToggle={this.onKebabDropdownToggle} />}
- isOpen={isKebabDropdownOpen}
- dropdownItems={kebabDropdownItems}
- />
- </ToolbarItem>
- <ToolbarItem
className={css(
accessibleStyles.screenReader,
accessibleStyles.visibleOnMd
@@ -322,54 +295,82 @@ class PageLayout extends React.Component {
showNavToggle
/>
);
- const Sidebar = <PageSidebar nav={PageNav} className="pf-m-dark" />;
const pageId = "main-content-page-layout-expandable-nav";
const PageSkipToContent = (
<SkipToContent href={`#${pageId}`}>Skip to Content</SkipToContent>
);
- const activeItemToPage = () => {
- if (this.state.activeGroup === "overview") {
- if (this.state.activeItem === "dashboard") {
- return <DashboardPage service={this.service} />;
- }
- return (
- <OverviewTablePage
- entity={this.state.activeItem}
- service={this.service}
- />
- );
- } else if (this.state.activeGroup === "visualizations") {
- if (this.state.activeItem === "topology") {
- return <TopologyPage service={this.service} />;
- } else {
- return <MessageFlowPage service={this.service} />;
- }
+
+ const sidebar = PageNav => {
+ if (this.state.connected) {
+ return <PageSidebar nav={PageNav} className="pf-m-dark" />;
}
- //console.log("using overview charts page");
- return <DashboardPage service={this.service} />;
+ return <React.Fragment />;
};
- if (!this.state.connected) {
- return (
- <Page header={Header} skipToContent={PageSkipToContent}>
- <ConnectPage handleConnect={this.handleConnect} />
- </Page>
- );
- }
+ // don't allow access to this component unless we are logged in
+ const PrivateRoute = ({ component: Component, path: rpath, ...more }) => (
+ <Route
+ path={rpath}
+ {...(more.exact ? "exact" : "")}
+ render={props =>
+ this.state.connected ? (
+ <Component service={this.service} {...props} {...more} />
+ ) : (
+ <Redirect
+ to={{ pathname: "/login", state: { from: props.location } }}
+ />
+ )
+ }
+ />
+ );
+
+ // When we need to display a different component(page),
+ // we render a <Redirect> object
+ const redirectAfterConnect = () => {
+ let { connectPath } = this.state;
+ if (connectPath !== "") {
+ if (connectPath === "/login") connectPath = "/";
+ return <Redirect to={connectPath} />;
+ }
+ return <React.Fragment />;
+ };
return (
- <React.Fragment>
+ <Router>
+ {redirectAfterConnect()}
<Page
header={Header}
- sidebar={Sidebar}
+ sidebar={sidebar(PageNav)}
isManagedSidebar
skipToContent={PageSkipToContent}
>
- {activeItemToPage()}
+ <Switch>
+ <PrivateRoute path="/" exact component={DashboardPage} />
+ <PrivateRoute path="/dashboard" exact component={DashboardPage} />
+ <PrivateRoute
+ path="/overview/:entity"
+ component={OverviewTablePage}
+ />
+ <PrivateRoute path="/details" component={DetailsTablePage} />
+ <PrivateRoute path="/topology" component={TopologyPage} />
+ <PrivateRoute path="/flow" component={MessageFlowPage} />
+ <Route
+ path="/login"
+ render={props => (
+ <ConnectPage {...props} handleConnect={this.handleConnect} />
+ )}
+ />
+ </Switch>
</Page>
- </React.Fragment>
+ </Router>
);
}
}
export default PageLayout;
+
+/* <ToolbarItem>
+ <ConnectForm prefix="toolbar" handleConnect={this.handleConnect} />
+ </ToolbarItem>
+
+ */
diff --git a/console/react/src/overview/addressesTable.js b/console/react/src/overview/addressesTable.js
deleted file mode 100644
index b69345c..0000000
--- a/console/react/src/overview/addressesTable.js
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements. See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership. The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied. See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-
-import { sortable } from "@patternfly/react-table";
-
-import OverviewTableBase from "./overviewTableBase";
-
-class AddressesTable extends OverviewTableBase {
- constructor(props) {
- super(props);
- this.fields = [
- { title: "Address", field: "address", transforms: [sortable] },
- { title: "Class", field: "class" },
- { title: "Phase", field: "phase" },
- { title: "In-proc", field: "inproc" },
- { title: "Local", field: "local" },
- { title: "Remote", field: "remote" },
- { title: "In", field: "in" },
- { title: "Out", field: "out" }
- ];
- }
-
- doFetch = (page, perPage) => {
- return new Promise(resolve => {
- var addr_phase = addr => {
- if (!addr) return "-";
- if (addr[0] === "M") return addr[1];
- return "";
- };
- var prettyVal = val => {
- return this.props.service.utilities.pretty(val || "-");
- };
- let addressFields = [];
- let addressObjs = {};
- // send the requests for all connection and router info for all routers
- this.props.service.management.topology.fetchAllEntities(
- { entity: "router.address" },
- nodes => {
- for (let node in nodes) {
- let response = nodes[node]["router.address"];
- response.results.forEach(result => {
- let address = this.props.service.utilities.flatten(
- response.attributeNames,
- result
- );
-
- var addNull = (oldVal, newVal) => {
- if (oldVal != null && newVal != null) return oldVal + newVal;
- if (oldVal != null) return oldVal;
- return newVal;
- };
-
- let uid = address.identity;
- let identity = this.props.service.utilities.identity_clean(uid);
-
- if (
- !addressObjs[
- this.props.service.utilities.addr_text(identity) +
- this.props.service.utilities.addr_class(identity)
- ]
- )
- addressObjs[
- this.props.service.utilities.addr_text(identity) +
- this.props.service.utilities.addr_class(identity)
- ] = {
- address: this.props.service.utilities.addr_text(identity),
- class: this.props.service.utilities.addr_class(identity),
- phase: addr_phase(identity),
- inproc: address.inProcess,
- local: address.subscriberCount,
- remote: address.remoteCount,
- in: address.deliveriesIngress,
- out: address.deliveriesEgress,
- thru: address.deliveriesTransit,
- toproc: address.deliveriesToContainer,
- fromproc: address.deliveriesFromContainer,
- uid: uid
- };
- else {
- let sumObj =
- addressObjs[
- this.props.service.utilities.addr_text(identity) +
- this.props.service.utilities.addr_class(identity)
- ];
- sumObj.inproc = addNull(sumObj.inproc, address.inProcess);
- sumObj.local = addNull(sumObj.local, address.subscriberCount);
- sumObj.remote = addNull(sumObj.remote, address.remoteCount);
- sumObj["in"] = addNull(sumObj["in"], address.deliveriesIngress);
- sumObj.out = addNull(sumObj.out, address.deliveriesEgress);
- sumObj.thru = addNull(sumObj.thru, address.deliveriesTransit);
- sumObj.toproc = addNull(
- sumObj.toproc,
- address.deliveriesToContainer
- );
- sumObj.fromproc = addNull(
- sumObj.fromproc,
- address.deliveriesFromContainer
- );
- }
- });
- }
- for (let obj in addressObjs) {
- addressObjs[obj].inproc = prettyVal(addressObjs[obj].inproc);
- addressObjs[obj].local = prettyVal(addressObjs[obj].local);
- addressObjs[obj].remote = prettyVal(addressObjs[obj].remote);
- addressObjs[obj]["in"] = prettyVal(addressObjs[obj]["in"]);
- addressObjs[obj].out = prettyVal(addressObjs[obj].out);
- addressObjs[obj].thru = prettyVal(addressObjs[obj].thru);
- addressObjs[obj].toproc = prettyVal(addressObjs[obj].toproc);
- addressObjs[obj].fromproc = prettyVal(addressObjs[obj].fromproc);
- addressFields.push(addressObjs[obj]);
- }
- if (addressFields.length === 0) return;
- // update the grid's data
- addressFields.sort((a, b) => {
- return a.address + a["class"] < b.address + b["class"]
- ? -1
- : a.address + a["class"] > b.address + b["class"]
- ? 1
- : 0;
- });
- addressFields[0].title = addressFields[0].address;
- for (let i = 1; i < addressFields.length; ++i) {
- // if this address is the same as the previous address, add a class to the display titles
- if (addressFields[i].address === addressFields[i - 1].address) {
- addressFields[i - 1].title =
- addressFields[i - 1].address +
- " (" +
- addressFields[i - 1]["class"] +
- ")";
- addressFields[i].title =
- addressFields[i].address +
- " (" +
- addressFields[i]["class"] +
- ")";
- } else addressFields[i].title = addressFields[i].address;
- }
- resolve(this.slice(addressFields, page, perPage));
- }
- );
- });
- };
-}
-
-export default AddressesTable;
diff --git a/console/react/src/overview/connectionsTable.js b/console/react/src/overview/connectionsTable.js
deleted file mode 100644
index 1ee140f..0000000
--- a/console/react/src/overview/connectionsTable.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements. See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership. The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied. See the License for the
-specific language governing permissions and limitations
-under the License.
-*/
-
-import { sortable } from "@patternfly/react-table";
-import OverviewTableBase from "./overviewTableBase";
-
-class LinksTable extends OverviewTableBase {
- constructor(props) {
- super(props);
- this.fields = [
- { title: "Host", field: "name", transforms: [sortable] },
- { title: "Container", field: "container", transforms: [sortable] },
- { title: "Role", field: "role", transforms: [sortable] },
- { title: "Dir", field: "dir", transforms: [sortable] },
- { title: "Security", field: "security", transforms: [sortable] },
- {
- title: "Authentication",
- field: "authentication",
- transforms: [sortable]
- },
- { title: "Close", field: "close" }
- ];
- }
- doFetch = (page, perPage) => {
- return new Promise(resolve => {
- this.props.service.management.topology.fetchAllEntities(
- { entity: "connection" },
- nodes => {
- // we have all the data now in the nodes object
- let connectionFields = [];
- for (let node in nodes) {
- const response = nodes[node]["connection"];
- for (let i = 0; i < response.results.length; i++) {
- const result = response.results[i];
- const connection = this.props.service.utilities.flatten(
- response.attributeNames,
- result
- );
- let auth = "no_auth";
- let sasl = connection.sasl;
- if (connection.isAuthenticated) {
- auth = sasl;
- if (sasl === "ANONYMOUS") auth = "anonymous-user";
- else {
- if (sasl === "GSSAPI") sasl = "Kerberos";
- if (sasl === "EXTERNAL") sasl = "x.509";
- auth = connection.user + "(" + connection.sslCipher + ")";
- }
- }
-
- let sec = "no-security";
- if (connection.isEncrypted) {
- if (sasl === "GSSAPI") sec = "Kerberos";
- else
- sec = connection.sslProto + "(" + connection.sslCipher + ")";
- }
-
- let host = connection.host;
- let connField = {
- host: host,
- security: sec,
- authentication: auth,
- routerId: node,
- uid: host + connection.container + connection.identity
- };
- response.attributeNames.forEach(function(attribute, i) {
- connField[attribute] = result[i];
- });
- connectionFields.push(connField);
- }
- }
- resolve(this.slice(connectionFields, page, perPage));
- }
- );
- });
- };
-}
-
-export default LinksTable;
diff --git a/console/react/src/overview/dashboard/dashboardPage.js b/console/react/src/overview/dashboard/dashboardPage.js
index e3b853c..9feefcd 100644
--- a/console/react/src/overview/dashboard/dashboardPage.js
+++ b/console/react/src/overview/dashboard/dashboardPage.js
@@ -51,32 +51,34 @@ class DashboardPage extends React.Component {
<StackItem>
<Card>
<CardHeader>
- <div>Router network statistics</div>
+ <div className="dashboard-header">
+ <div>Router network statistics</div>
+ <div className="duration-tabs">
+ <nav className="pf-c-nav" aria-label="Local">
+ <ul className="pf-c-nav__tertiary-list">
+ <li
+ onClick={() => this.setTimePeriod(60)}
+ className={`pf-c-nav__item ${
+ this.state.timePeriod === 60 ? "selected" : ""
+ }`}
+ >
+ Min
+ </li>
+ <li
+ onClick={() => this.setTimePeriod(60 * 60)}
+ className={`pf-c-nav__item ${
+ this.state.timePeriod === 60 ? "" : "selected"
+ }`}
+ >
+ Hour
+ </li>
+ </ul>
+ </nav>
+ </div>
+ </div>
<div className="time-period">
For the past {this.timePeriodString()}
</div>
- <div className="duration-tabs">
- <nav className="pf-c-nav" aria-label="Local">
- <ul className="pf-c-nav__tertiary-list">
- <li
- onClick={() => this.setTimePeriod(60)}
- className={`pf-c-nav__item ${
- this.state.timePeriod === 60 ? "selected" : ""
- }`}
- >
- Min
- </li>
- <li
- onClick={() => this.setTimePeriod(60 * 60)}
- className={`pf-c-nav__item ${
- this.state.timePeriod === 60 ? "" : "selected"
- }`}
- >
- Hour
- </li>
- </ul>
- </nav>
- </div>
</CardHeader>
<CardBody>
<ThroughputChart
diff --git a/console/react/src/overview/dataSources/addressData.js b/console/react/src/overview/dataSources/addressData.js
new file mode 100644
index 0000000..ee581f2
--- /dev/null
+++ b/console/react/src/overview/dataSources/addressData.js
@@ -0,0 +1,191 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+class AddressData {
+ constructor(service) {
+ this.service = service;
+ this.fields = [
+ { title: "Address", field: "address" },
+ { title: "Class", field: "class" },
+ { title: "Phase", field: "phase" },
+ { title: "In-proc", field: "inproc", numeric: true },
+ { title: "Local", field: "local", numeric: true },
+ { title: "Remote", field: "remote", numeric: true },
+ { title: "In", field: "in", numeric: true },
+ { title: "Out", field: "out", numeric: true }
+ ];
+ this.detailName = "Address";
+ this.detailField = "title";
+ this.hideFields = ["title"];
+ }
+
+ fetchRecord = (currentRecord, schema) => {
+ return new Promise(resolve => {
+ this.service.management.topology.fetchEntities(
+ currentRecord.nodeId,
+ [{ entity: "router.address" }],
+ data => {
+ const record = data[currentRecord.nodeId]["router.address"];
+ const identityIndex = record.attributeNames.indexOf("identity");
+ const result = record.results.find(
+ r => r[identityIndex] === currentRecord.uid
+ );
+ let address = this.service.utilities.flatten(
+ record.attributeNames,
+ result
+ );
+ address = this.service.utilities.formatAttributes(
+ address,
+ schema.entityTypes["router.address"]
+ );
+ resolve(address);
+ }
+ );
+ });
+ };
+
+ doFetch = (page, perPage) => {
+ return new Promise(resolve => {
+ var addr_phase = addr => {
+ if (!addr) return "-";
+ if (addr[0] === "M") return addr[1];
+ return "";
+ };
+ let addressFields = [];
+ let addressObjs = {};
+ var addNull = (oldVal, newVal) => {
+ if (oldVal != null && newVal != null) return oldVal + newVal;
+ if (oldVal != null) return oldVal;
+ return newVal;
+ };
+ // send the requests to get the router.address records for every router
+ this.service.management.topology.fetchAllEntities(
+ { entity: "router.address" },
+ nodes => {
+ // each router is a node in nodes
+ for (let node in nodes) {
+ let response = nodes[node]["router.address"];
+ // response is an array of router.address records for this node/router
+ response.results.forEach(result => {
+ // result is a single address record for this node/router
+ let address = this.service.utilities.flatten(
+ response.attributeNames,
+ result
+ );
+ // address is now an object with attribute names as keys and their values
+ let uid = address.identity;
+ let identity = this.service.utilities.identity_clean(uid);
+
+ // if this is the 1st time we've seen this address:class
+ if (
+ !addressObjs[
+ this.service.utilities.addr_text(identity) +
+ this.service.utilities.addr_class(identity)
+ ]
+ ) {
+ // create a new addressObjs record
+ addressObjs[
+ this.service.utilities.addr_text(identity) +
+ this.service.utilities.addr_class(identity)
+ ] = {
+ address: this.service.utilities.addr_text(identity),
+ class: this.service.utilities.addr_class(identity),
+ phase: addr_phase(identity),
+ inproc: address.inProcess,
+ local: address.subscriberCount,
+ remote: address.remoteCount,
+ in: address.deliveriesIngress,
+ out: address.deliveriesEgress,
+ thru: address.deliveriesTransit,
+ toproc: address.deliveriesToContainer,
+ fromproc: address.deliveriesFromContainer,
+ identity: identity,
+ nodeId: node,
+ uid: uid
+ };
+ } else {
+ // we've seen this address:class before. add the values
+ // into the addressObjs
+ let sumObj =
+ addressObjs[
+ this.service.utilities.addr_text(identity) +
+ this.service.utilities.addr_class(identity)
+ ];
+ sumObj.inproc = addNull(sumObj.inproc, address.inProcess);
+ sumObj.local = addNull(sumObj.local, address.subscriberCount);
+ sumObj.remote = addNull(sumObj.remote, address.remoteCount);
+ sumObj["in"] = addNull(sumObj["in"], address.deliveriesIngress);
+ sumObj.out = addNull(sumObj.out, address.deliveriesEgress);
+ sumObj.thru = addNull(sumObj.thru, address.deliveriesTransit);
+ sumObj.toproc = addNull(
+ sumObj.toproc,
+ address.deliveriesToContainer
+ );
+ sumObj.fromproc = addNull(
+ sumObj.fromproc,
+ address.deliveriesFromContainer
+ );
+ }
+ });
+ }
+ // At this point we have created and summed all the address records.
+ for (let obj in addressObjs) {
+ addressFields.push(addressObjs[obj]);
+ }
+
+ // Two records that have the same address
+ // are differenciated by adding a class to both records' title.
+ // To do this we need to sort the array by address:class
+ addressFields.sort((a, b) => {
+ return a.address + a["class"] < b.address + b["class"]
+ ? -1
+ : a.address + a["class"] > b.address + b["class"]
+ ? 1
+ : 0;
+ });
+
+ // Loop through the sorted array to find records with the same address.
+ // Construct a title field that has "address (class)" for records with
+ // duplicate addresses, and just the address for unique records
+ if (addressFields.length) {
+ addressFields[0].title = addressFields[0].address;
+ for (let i = 1; i < addressFields.length; ++i) {
+ // if this address is the same as the previous address, add a class to the display titles
+ if (addressFields[i].address === addressFields[i - 1].address) {
+ addressFields[i - 1].title =
+ addressFields[i - 1].address +
+ " (" +
+ addressFields[i - 1]["class"] +
+ ")";
+ addressFields[i].title =
+ addressFields[i].address +
+ " (" +
+ addressFields[i]["class"] +
+ ")";
+ } else addressFields[i].title = addressFields[i].address;
+ }
+ }
+ resolve({ data: addressFields, page, perPage });
+ }
+ );
+ });
+ };
+}
+
+export default AddressData;
diff --git a/console/react/src/overview/dataSources/connectionData.js b/console/react/src/overview/dataSources/connectionData.js
new file mode 100644
index 0000000..4e69543
--- /dev/null
+++ b/console/react/src/overview/dataSources/connectionData.js
@@ -0,0 +1,169 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+import React from "react";
+import { Button } from "@patternfly/react-core";
+
+class ConnectionClose extends React.Component {
+ closeConnection = () => {
+ const record = this.props.extraInfo.rowData.data;
+ this.props.service.management.connection
+ .sendMethod(
+ record.nodeId,
+ "connection",
+ { adminStatus: "deleted", identity: record.identity },
+ "UPDATE",
+ { adminStatus: "deleted" }
+ )
+ .then(results => {
+ let statusCode =
+ results.context.message.application_properties.statusCode;
+ if (statusCode < 200 || statusCode >= 300) {
+ console.log(
+ `error ${record.name} ${results.context.message.application_properties.statusDescription}`
+ );
+ } else {
+ console.log(
+ `success ${record.name} ${results.context.message.application_properties.statusDescription}`
+ );
+ }
+ });
+ };
+
+ render() {
+ if (this.props.extraInfo.rowData.data.role === "normal") {
+ return (
+ <Button className="link-button" onClick={this.closeConnection}>
+ Close
+ </Button>
+ );
+ } else {
+ return <React.Fragment />;
+ }
+ }
+}
+
+class ConnectionData {
+ constructor(service) {
+ this.service = service;
+ this.fields = [
+ {
+ title: "Host",
+ field: "name"
+ },
+ { title: "Container", field: "container" },
+ { title: "Role", field: "role" },
+ { title: "Dir", field: "dir" },
+ { title: "Security", field: "security" },
+ {
+ title: "Authentication",
+ field: "authentication"
+ },
+ {
+ title: "",
+ noSort: true,
+ formatter: ConnectionClose
+ }
+ ];
+ this.detailEntity = "connection";
+ this.detailName = "Connection";
+ }
+
+ fetchRecord = (currentRecord, schema) => {
+ return new Promise(resolve => {
+ this.service.management.topology.fetchEntities(
+ currentRecord.nodeId,
+ [{ entity: "connection" }],
+ data => {
+ const record = data[currentRecord.nodeId]["connection"];
+ const identityIndex = record.attributeNames.indexOf("identity");
+ const result = record.results.find(
+ r => r[identityIndex] === currentRecord.identity
+ );
+ let connection = this.service.utilities.flatten(
+ record.attributeNames,
+ result
+ );
+ connection = this.service.utilities.formatAttributes(
+ connection,
+ schema.entityTypes["connection"]
+ );
+ resolve(connection);
+ }
+ );
+ });
+ };
+
+ doFetch = (page, perPage) => {
+ return new Promise(resolve => {
+ this.service.management.topology.fetchAllEntities(
+ { entity: "connection" },
+ nodes => {
+ // we have all the data now in the nodes object
+ let connectionFields = [];
+ for (let node in nodes) {
+ const response = nodes[node]["connection"];
+ for (let i = 0; i < response.results.length; i++) {
+ const result = response.results[i];
+ const connection = this.service.utilities.flatten(
+ response.attributeNames,
+ result
+ );
+ let auth = "no_auth";
+ let sasl = connection.sasl;
+ if (connection.isAuthenticated) {
+ auth = sasl;
+ if (sasl === "ANONYMOUS") auth = "anonymous-user";
+ else {
+ if (sasl === "GSSAPI") sasl = "Kerberos";
+ if (sasl === "EXTERNAL") sasl = "x.509";
+ auth = connection.user + "(" + connection.sslCipher + ")";
+ }
+ }
+
+ let sec = "no-security";
+ if (connection.isEncrypted) {
+ if (sasl === "GSSAPI") sec = "Kerberos";
+ else
+ sec = connection.sslProto + "(" + connection.sslCipher + ")";
+ }
+
+ let host = connection.host;
+ let connField = {
+ host: host,
+ security: sec,
+ authentication: auth,
+ nodeId: node,
+ uid: host + connection.container + connection.identity,
+ identity: connection.identity
+ };
+ response.attributeNames.forEach(function(attribute, i) {
+ connField[attribute] = result[i];
+ });
+ connectionFields.push(connField);
+ }
+ }
+ resolve({ data: connectionFields, page, perPage });
+ }
+ );
+ });
+ };
+}
+
+export default ConnectionData;
diff --git a/console/react/src/overview/linksTable.js b/console/react/src/overview/dataSources/linkData.js
similarity index 61%
rename from console/react/src/overview/linksTable.js
rename to console/react/src/overview/dataSources/linkData.js
index d528bc3..5b2bf50 100644
--- a/console/react/src/overview/linksTable.js
+++ b/console/react/src/overview/dataSources/linkData.js
@@ -17,46 +17,90 @@ specific language governing permissions and limitations
under the License.
*/
-import { sortable } from "@patternfly/react-table";
-import OverviewTableBase from "./overviewTableBase";
+import React from "react";
-class LinksTable extends OverviewTableBase {
- constructor(props) {
- super(props);
+const LinkDir = ({ value }) => (
+ <span>
+ <i
+ className={`link-dir-${value} fa fa-arrow-circle-${
+ value === "in" ? "right" : "left"
+ }`}
+ ></i>
+ {value}
+ </span>
+);
+
+class LinkData {
+ constructor(service) {
+ this.service = service;
this.fields = [
- { title: "Link", field: "name" },
- { title: "Type", field: "linkType", transforms: [sortable] },
- { title: "Dir", field: "linkDir" },
+ { title: "Link", field: "link", noWrap: true },
+ { title: "Type", field: "linkType", noWrap: true },
+ { title: "Dir", field: "linkDir", formatter: LinkDir },
{ title: "Admin status", field: "adminStatus" },
{ title: "Oper status", field: "operStatus" },
- { title: "Deliveries", field: "deliveryCount" },
- { title: "Rate", field: "rate" },
- { title: "Delayed 1 sec", field: "delayed1Sec" },
- { title: "Delayed 10 secs", field: "delayed10Sec" },
- { title: "Outstanding", field: "outstanding" },
- { title: "Address", field: "owningAddr", transforms: [sortable] }
+ {
+ title: "Deliveries",
+ field: "deliveryCount",
+ noWrap: true,
+ numeric: true
+ },
+ { title: "Rate", field: "rate", numeric: true },
+ {
+ title: "Delayed 1 sec",
+ field: "delayed1Sec",
+ numeric: true
+ },
+ {
+ title: "Delayed 10 secs",
+ field: "delayed10Sec",
+ numeric: true
+ },
+ {
+ title: "Outstanding",
+ field: "outstanding",
+ numeric: true
+ },
+ { title: "Address", field: "owningAddr" }
];
+ this.detailEntity = "router.link";
+ this.detailName = "Link";
}
+
+ fetchRecord = (currentRecord, schema) => {
+ return new Promise(resolve => {
+ this.service.management.topology.fetchEntities(
+ currentRecord.nodeId,
+ [{ entity: "router.link" }],
+ data => {
+ const record = data[currentRecord.nodeId]["router.link"];
+ const identityIndex = record.attributeNames.indexOf("identity");
+ const result = record.results.find(
+ r => r[identityIndex] === currentRecord.identity
+ );
+ let link = this.service.utilities.flatten(
+ record.attributeNames,
+ result
+ );
+ link = this.service.utilities.formatAttributes(
+ link,
+ schema.entityTypes["router.link"]
+ );
+ resolve(link);
+ }
+ );
+ });
+ };
+
doFetch = (page, perPage) => {
return new Promise(resolve => {
- this.props.service.management.topology.fetchAllEntities(
+ this.service.management.topology.fetchAllEntities(
{ entity: "router.link" },
nodes => {
// we have all the data now in the nodes object
let linkFields = [];
- const now = new Date();
- var prettyVal = value => {
- return typeof value === "undefined"
- ? "-"
- : this.props.service.utilities.pretty(value);
- };
- var uncounts = link => {
- return this.props.service.utilities.pretty(
- link.undeliveredCount + link.unsettledCount
- );
- };
var getLinkName = (node, link) => {
- let namestr = this.props.service.utilities.nameFromId(node);
+ let namestr = this.service.utilities.nameFromId(node);
return `${namestr}:${link.identity}`;
};
var fixAddress = link => {
@@ -100,7 +144,7 @@ class LinksTable extends OverviewTableBase {
const response = nodes[node]["router.link"];
for (let i = 0; i < response.results.length; i++) {
const result = response.results[i];
- const link = this.props.service.utilities.flatten(
+ const link = this.service.utilities.flatten(
response.attributeNames,
result
);
@@ -109,48 +153,37 @@ class LinksTable extends OverviewTableBase {
linkFields.push({
link: linkName,
- title: linkName,
- outstanding: uncounts(link),
- operStatus: link.operStatus,
+ linkType: link.linkType,
+ linkDir: link.linkDir,
adminStatus: link.adminStatus,
+ operStatus: link.operStatus,
+ deliveryCount: link.deliveryCount,
+ rate: link.settleRate,
+ delayed1Sec: link.deliveriesDelayed1Sec,
+ delayed10Sec: link.deliveriesDelayed10Sec,
+ outstanding: link.undeliveredCount + link.unsettledCount,
owningAddr: addresses[0],
- acceptedCount: prettyVal(link.acceptedCount),
- modifiedCount: prettyVal(link.modifiedCount),
- presettledCount: prettyVal(link.presettledCount),
- rejectedCount: prettyVal(link.rejectedCount),
- releasedCount: prettyVal(link.releasedCount),
- deliveryCount: prettyVal(link.deliveryCount),
-
- rate: prettyVal(link.settleRate),
- deliveriesDelayed10Sec: prettyVal(link.deliveriesDelayed10Sec),
- deliveriesDelayed1Sec: prettyVal(link.deliveriesDelayed1Sec),
capacity: link.capacity,
undeliveredCount: link.undeliveredCount,
unsettledCount: link.unsettledCount,
rawAddress: addresses[1],
- rawDeliveryCount: link.deliveryCount,
name: link.name,
- linkName: link.linkName,
connectionId: link.connectionId,
- linkDir: link.linkDir,
- linkType: link.linkType,
peer: link.peer,
type: link.type,
- uid: linkName,
- timestamp: now,
nodeId: node,
identity: link.identity
});
}
}
- resolve(this.slice(linkFields, page, perPage));
+ resolve({ data: linkFields, page, perPage });
}
);
});
};
}
-export default LinksTable;
+export default LinkData;
diff --git a/console/react/src/overview/routersTable.js b/console/react/src/overview/dataSources/routerData.js
similarity index 54%
rename from console/react/src/overview/routersTable.js
rename to console/react/src/overview/dataSources/routerData.js
index b846c31..67c015e 100644
--- a/console/react/src/overview/routersTable.js
+++ b/console/react/src/overview/dataSources/routerData.js
@@ -17,24 +17,57 @@ specific language governing permissions and limitations
under the License.
*/
-import { sortable } from "@patternfly/react-table";
-import OverviewTableBase from "./overviewTableBase";
-
-class RoutersTable extends OverviewTableBase {
- constructor(props) {
- super(props);
+class RouterData {
+ constructor(service) {
+ this.service = service;
this.fields = [
- { title: "Router", field: "name", transforms: [sortable] },
+ { title: "Router", field: "name" },
{ title: "Area", field: "area" },
{ title: "Mode", field: "mode" },
- { title: "Addresses", field: "addrCount" },
- { title: "Links", field: "linkCount" },
- { title: "External connections", field: "connections" }
+ {
+ title: "Addresses",
+ field: "addrCount",
+ numeric: true
+ },
+ {
+ title: "Links",
+ field: "linkCount",
+ numeric: true
+ },
+ {
+ title: "External connections",
+ field: "connections",
+ numeric: true
+ }
];
+ this.detailEntity = "router";
+ this.detailName = "Router";
}
+
+ fetchRecord = (currentRecord, schema) => {
+ return new Promise(resolve => {
+ this.service.management.topology.fetchEntities(
+ currentRecord.nodeId,
+ [{ entity: "router" }],
+ results => {
+ const record = results[currentRecord.nodeId].router;
+ let router = this.service.utilities.flatten(
+ record.attributeNames,
+ record.results[0]
+ );
+ router = this.service.utilities.formatAttributes(
+ router,
+ schema.entityTypes.router
+ );
+ resolve(router);
+ }
+ );
+ });
+ };
+
doFetch = (page, perPage) => {
return new Promise(resolve => {
- this.props.service.management.topology.fetchAllEntities(
+ this.service.management.topology.fetchAllEntities(
[{ entity: "connection", attrs: ["role"] }, { entity: "router" }],
nodes => {
// we have all the data now in the nodes object
@@ -42,26 +75,27 @@ class RoutersTable extends OverviewTableBase {
for (let node in nodes) {
let connections = 0;
for (let i = 0; i < nodes[node]["connection"].results.length; ++i) {
- // we only requested "role" so it will be at [0]
+ // we only requested "role" so it will be at results[0]
if (nodes[node]["connection"].results[i][0] !== "inter-router")
++connections;
}
let routerRow = {
- connections: connections,
+ connections,
nodeId: node,
- id: this.props.service.utilities.nameFromId(node)
+ id: this.service.utilities.nameFromId(node)
};
nodes[node]["router"].attributeNames.forEach((routerAttr, i) => {
- if (routerAttr !== "routerId" && routerAttr !== "id")
+ if (routerAttr !== "id") {
routerRow[routerAttr] = nodes[node]["router"].results[0][i];
+ }
});
allRouterFields.push(routerRow);
}
- resolve(this.slice(allRouterFields, page, perPage));
+ resolve({ data: allRouterFields, page, perPage });
}
);
});
};
}
-export default RoutersTable;
+export default RouterData;
diff --git a/console/react/src/overview/detailsTablePage.js b/console/react/src/overview/detailsTablePage.js
new file mode 100644
index 0000000..d945455
--- /dev/null
+++ b/console/react/src/overview/detailsTablePage.js
@@ -0,0 +1,218 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+import React from "react";
+import { PageSection, PageSectionVariants } from "@patternfly/react-core";
+import {
+ Stack,
+ StackItem,
+ TextContent,
+ Text,
+ TextVariants,
+ Breadcrumb,
+ BreadcrumbItem
+} from "@patternfly/react-core";
+
+import {
+ cellWidth,
+ Table,
+ TableHeader,
+ TableBody,
+ TableVariant
+} from "@patternfly/react-table";
+import { Card, CardBody } from "@patternfly/react-core";
+import { Redirect } from "react-router-dom";
+import { dataMap } from "./entityData";
+
+class DetailTablesPage extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ columns: [
+ { title: "Attribute", transforms: [cellWidth(20)] },
+ {
+ title: "Value",
+ transforms: [cellWidth("max")],
+ props: { className: "pf-u-text-align-left" }
+ }
+ ],
+ rows: [],
+ redirect: false,
+ redirectState: { page: 1 },
+ redirectPath: "/dashboard",
+ lastUpdated: new Date()
+ };
+ // if we get to this page and we don't have a props.location.state.entity
+ // then redirect back to the dashboard.
+ // this can happen if we get here from a bookmark or browser refresh
+ this.entity =
+ this.props &&
+ this.props.location &&
+ this.props.location.state &&
+ this.props.location.state.entity;
+ if (!dataMap[this.entity]) {
+ this.state.redirect = true;
+ } else {
+ this.dataSource = new dataMap[this.entity](this.props.service);
+ }
+ }
+
+ componentDidMount = () => {
+ this.props.service.management.getSchema().then(schema => {
+ this.schema = schema;
+ this.timer = setInterval(this.update, 5000);
+ this.update();
+ });
+ };
+
+ componentWillUnmount = () => {
+ if (this.timer) {
+ clearInterval(this.timer);
+ }
+ };
+
+ update = () => {
+ this.mapRows().then(
+ rows => {
+ this.setState({ rows, lastUpdated: new Date() });
+ },
+ error => {
+ console.log(`detailsTablePage: ${error}`);
+ }
+ );
+ };
+
+ toString = val => {
+ return val === null ? "" : String(val);
+ };
+
+ mapRows = () => {
+ return new Promise((resolve, reject) => {
+ const rows = [];
+ if (!this.dataSource) {
+ reject("no data source");
+ }
+ this.dataSource
+ .fetchRecord(this.props.location.state.currentRecord, this.schema)
+ .then(data => {
+ for (const attribute in data) {
+ if (
+ !this.dataSource.hideFields ||
+ this.dataSource.hideFields.indexOf(attribute) === -1
+ ) {
+ rows.push({
+ cells: [attribute, this.toString(data[attribute])]
+ });
+ }
+ }
+ resolve(rows);
+ });
+ });
+ };
+
+ icap = s => s.charAt(0).toUpperCase() + s.slice(1);
+
+ parentItem = () => {
+ // if we have a specific field that should be used
+ // as the record's title, return it
+ if (this.dataSource.detailField) {
+ return this.props.location.state.currentRecord[
+ this.dataSource.detailField
+ ];
+ }
+ // otherwise return the 1st field
+ return this.props.location.state.value;
+ };
+
+ breadcrumbSelected = () => {
+ this.setState({
+ redirect: true,
+ redirectPath: `/overview/${this.entity}`,
+ redirectState: this.props.location.state
+ });
+ };
+
+ render() {
+ if (this.state.redirect) {
+ return (
+ <Redirect
+ to={{
+ pathname: this.state.redirectPath,
+ state: this.state.redirectState
+ }}
+ />
+ );
+ }
+
+ return (
+ <React.Fragment>
+ <PageSection
+ variant={PageSectionVariants.light}
+ className="overview-table-page"
+ >
+ <Stack>
+ <StackItem className="overview-header details">
+ <Breadcrumb>
+ <BreadcrumbItem
+ className="link-button"
+ onClick={() =>
+ this.breadcrumbSelected(`/overview/${this.entity}`)
+ }
+ >
+ {this.icap(this.entity)}
+ </BreadcrumbItem>
+ <BreadcrumbItem isActive>{this.parentItem()}</BreadcrumbItem>
+ </Breadcrumb>
+
+ <TextContent>
+ <Text className="overview-title" component={TextVariants.h1}>
+ {`${
+ this.dataSource.detailName
+ } ${this.parentItem()} attributes`}
+ </Text>
+ <Text className="overview-loading" component={TextVariants.pre}>
+ {`Updated ${this.props.service.utilities.strDate(
+ this.state.lastUpdated
+ )}`}
+ </Text>
+ </TextContent>
+ </StackItem>
+ <StackItem className="overview-table">
+ <Card>
+ <CardBody>
+ <Table
+ cells={this.state.columns}
+ rows={this.state.rows}
+ variant={TableVariant.compact}
+ aria-label={this.entity}
+ >
+ <TableHeader />
+ <TableBody />
+ </Table>
+ </CardBody>
+ </Card>
+ </StackItem>
+ </Stack>
+ </PageSection>
+ </React.Fragment>
+ );
+ }
+}
+
+export default DetailTablesPage;
diff --git a/console/react/src/overview/entityData.js b/console/react/src/overview/entityData.js
new file mode 100644
index 0000000..f625328
--- /dev/null
+++ b/console/react/src/overview/entityData.js
@@ -0,0 +1,32 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+import RouterData from "./dataSources/routerData";
+import AddressData from "./dataSources/addressData";
+import LinkData from "./dataSources/linkData";
+import ConnectionData from "./dataSources/connectionData";
+
+const dataMap = {
+ routers: RouterData,
+ connections: ConnectionData,
+ links: LinkData,
+ addresses: AddressData
+};
+
+export { RouterData, AddressData, LinkData, ConnectionData, dataMap };
diff --git a/console/react/src/overview/overviewTable.js b/console/react/src/overview/overviewTable.js
new file mode 100644
index 0000000..2d77f20
--- /dev/null
+++ b/console/react/src/overview/overviewTable.js
@@ -0,0 +1,318 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+import React from "react";
+import {
+ sortable,
+ SortByDirection,
+ Table,
+ TableHeader,
+ TableBody,
+ TableVariant
+} from "@patternfly/react-table";
+import { Button, Pagination } from "@patternfly/react-core";
+import { Redirect } from "react-router-dom";
+import TableToolbar from "./tableToolbar";
+import { dataMap } from "./entityData";
+
+// If the breadcrumb on the details page was used to return to this page,
+// we will have saved state info in props.location.state
+const propFromLocation = (props, which, defaultValue) =>
+ props &&
+ props.location &&
+ props.location.state &&
+ typeof props.location.state[which] !== "undefined"
+ ? props.location.state[which]
+ : defaultValue;
+
+class OverviewTable extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ sortBy: propFromLocation(props, "sortBy", {
+ index: 0,
+ direction: SortByDirection.asc
+ }),
+ filterBy: propFromLocation(props, "filterBy", {}),
+ perPage: propFromLocation(props, "perPage", 10),
+ total: 1,
+ page: propFromLocation(props, "page", 1),
+ columns: [],
+ allRows: [],
+ rows: [],
+ redirect: false,
+ redirectState: {}
+ };
+ this.entity = this.props.service.utilities.entityFromProps(props);
+ if (!dataMap[this.entity]) {
+ this.state.redirect = true;
+ } else {
+ this.dataSource = new dataMap[this.entity](this.props.service);
+ }
+ }
+
+ componentDidMount = () => {
+ this.mounted = true;
+ if (!this.dataSource) return;
+ // initialize the columns and get the data
+ this.dataSource.fields.forEach(f => {
+ if (!f.noSort) f.transforms = [sortable];
+ f.cellFormatters = [];
+ if (f.numeric) {
+ f.cellFormatters.push(this.prettier);
+ }
+ if (f.noWrap) {
+ f.cellFormatters.push(this.noWrap);
+ }
+ if (f.formatter) {
+ f.cellFormatters.push((value, extraInfo) =>
+ this.formatter(f.formatter, value, extraInfo)
+ );
+ }
+ });
+ if (!this.dataSource.fields[0].cellFormatters)
+ this.dataSource.fields[0].cellFormatters = [];
+ this.dataSource.fields[0].cellFormatters.push(this.detailLink);
+
+ this.setState({ columns: this.dataSource.fields }, () => {
+ this.update();
+ this.timer = setInterval(this.update, 5000);
+ });
+ };
+
+ componentWillUnmount = () => {
+ this.mounted = false;
+ clearInterval(this.timer);
+ };
+
+ update = () => {
+ this.fetch(this.state.page, this.state.perPage);
+ };
+
+ fetch = (page, perPage) => {
+ // get the data. Note: The current page number might change if
+ // the number of rows is less than before
+ this.dataSource.doFetch(page, perPage).then(results => {
+ const sliced = this.slice(results.data, results.page, results.perPage);
+ // if fetch was called and the component was unmounted before
+ // the results arrived, don't call setState
+ if (!this.mounted) return;
+ const { rows, page, total, allRows } = sliced;
+ this.setState({
+ rows,
+ page,
+ perPage,
+ total,
+ allRows
+ });
+ this.props.lastUpdated(new Date());
+ });
+ };
+
+ detailLink = (value, extraInfo) => {
+ return (
+ <Button
+ className="link-button"
+ onClick={() => this.detailClick(value, extraInfo)}
+ >
+ {value}
+ </Button>
+ );
+ };
+
+ noWrap = (value, extraInfo) => {
+ return <span className="noWrap">{value}</span>;
+ };
+
+ prettier = (value, extraInfo) => {
+ return typeof value === "undefined"
+ ? "-"
+ : this.props.service.utilities.pretty(value);
+ };
+
+ formatter = (Component, value, extraInfo) => {
+ return (
+ <Component
+ value={value}
+ extraInfo={extraInfo}
+ service={this.props.service}
+ />
+ );
+ };
+
+ detailClick = (value, extraInfo) => {
+ this.setState({
+ redirect: true,
+ redirectState: {
+ value: extraInfo.rowData.cells[0],
+ currentRecord: extraInfo.rowData.data,
+ entity: this.entity,
+ page: this.state.page,
+ sortBy: this.state.sortBy,
+ filterBy: this.state.filterBy,
+ perPage: this.state.perPage
+ }
+ });
+ };
+
+ onSort = (_event, index, direction) => {
+ this.setState({ sortBy: { index, direction } }, () => {
+ const { allRows, page, perPage } = this.state;
+ let rows = this.filter(allRows);
+ rows = this.sort(rows);
+ rows = this.page(rows, rows.length, page, perPage);
+ this.setState({ rows });
+ });
+ };
+
+ renderPagination(variant = "top") {
+ const { page, perPage, total } = this.state;
+ return (
+ <Pagination
+ itemCount={total}
+ page={page}
+ perPage={perPage}
+ onSetPage={(_evt, value) => this.onSetPage(value)}
+ onPerPageSelect={(_evt, value) => this.onPerPageSelect(value)}
+ variant={variant}
+ />
+ );
+ }
+
+ onSetPage = value => {
+ this.fetch(value, this.state.perPage);
+ };
+ onPerPageSelect = value => {
+ this.fetch(1, value);
+ };
+ handleChangeFilterValue = (field, value) => {
+ this.setState({ filterBy: { field, value } }, this.update);
+ };
+
+ field2Row = field => ({
+ cells: this.dataSource.fields.map(f => field[f.field]),
+ data: field
+ });
+
+ cellIndex = field => {
+ return this.dataSource.fields.findIndex(f => {
+ return f.title === field;
+ });
+ };
+
+ filter = rows => {
+ const filterField = this.state.filterBy.field;
+ const filterValue = this.state.filterBy.value;
+ if (
+ typeof filterField !== "undefined" &&
+ typeof filterValue !== "undefined" &&
+ filterValue !== ""
+ ) {
+ const cellIndex = this.cellIndex(filterField);
+ rows = rows.filter(r => {
+ return r.cells[cellIndex].includes(filterValue);
+ });
+ }
+ return rows;
+ };
+
+ page = (rows, total, page, perPage) => {
+ const newPages = Math.ceil(total / perPage);
+ page = Math.min(page, newPages);
+ const start = perPage * (page - 1);
+ const end = Math.min(start + perPage, rows.length);
+ return rows.slice(start, end);
+ };
+
+ slice = (fields, page, perPage) => {
+ let allRows = fields.map(f => this.field2Row(f));
+ let rows = this.filter(allRows);
+ const total = rows.length;
+ rows = this.sort(rows);
+ rows = this.page(rows, total, page, perPage);
+ return { rows, page, total, allRows };
+ };
+
+ sort = rows => {
+ const { index, direction } = this.state.sortBy;
+ if (typeof index === "undefined" || typeof direction === "undefined") {
+ return rows;
+ }
+
+ if (this.dataSource.fields[index].numeric) {
+ rows.sort((a, b) => {
+ if (direction === SortByDirection.desc)
+ return a > b ? -1 : a < b ? 1 : 0;
+ return a < b ? -1 : a > b ? 1 : 0;
+ });
+ } else {
+ rows.sort((a, b) => {
+ return a.cells[index] < b.cells[index]
+ ? -1
+ : a.cells[index] > b.cells[index]
+ ? 1
+ : 0;
+ });
+ if (direction === SortByDirection.desc) {
+ rows = rows.reverse();
+ }
+ }
+ return rows;
+ };
+
+ render() {
+ if (this.state.redirect) {
+ return (
+ <Redirect
+ to={{
+ pathname: "/details",
+ state: this.state.redirectState
+ }}
+ />
+ );
+ }
+ return (
+ <React.Fragment>
+ <TableToolbar
+ total={this.state.total}
+ page={this.state.page}
+ perPage={this.state.perPage}
+ onSetPage={this.onSetPage}
+ onPerPageSelect={this.onPerPageSelect}
+ fields={this.dataSource.fields}
+ handleChangeFilterValue={this.handleChangeFilterValue}
+ />
+ <Table
+ cells={this.state.columns}
+ rows={this.state.rows}
+ aria-label={this.entity}
+ sortBy={this.state.sortBy}
+ onSort={this.onSort}
+ variant={TableVariant.compact}
+ >
+ <TableHeader />
+ <TableBody />
+ </Table>
+ {this.renderPagination("bottom")}
+ </React.Fragment>
+ );
+ }
+}
+
+export default OverviewTable;
diff --git a/console/react/src/overview/overviewTableBase.js b/console/react/src/overview/overviewTableBase.js
deleted file mode 100644
index 54625ce..0000000
--- a/console/react/src/overview/overviewTableBase.js
+++ /dev/null
@@ -1,213 +0,0 @@
-import React from "react";
-import {
- SortByDirection,
- Table,
- TableHeader,
- TableBody
-} from "@patternfly/react-table";
-import { Pagination, Title } from "@patternfly/react-core";
-
-import TableToolbar from "./tableToolbar";
-
-class OverviewTableBase extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- sortBy: {},
- filterBy: {},
- perPage: 10,
- total: 1,
- page: 1,
- loading: true,
- columns: [],
- allRows: [],
- rows: [
- {
- cells: ["QDR.A", "0", "interior", "1", "2", "3"]
- },
- {
- cells: [
- {
- title: <div>QDR.B</div>,
- props: { title: "hover title", colSpan: 3 }
- },
- "2",
- "3",
- "4"
- ]
- },
- {
- cells: [
- "QDR.C",
- "0",
- "interior",
- "3",
- {
- title: "four",
- props: { textCenter: false }
- },
- "5"
- ]
- }
- ]
- };
- }
-
- componentDidMount() {
- this.mounted = true;
- console.log("overviewTable componentDidMount");
- // initialize the columns and get the data
- this.setState({ columns: this.fields }, () => {
- this.update();
- this.timer = setInterval(this.upate, 5000);
- });
- }
-
- componentWillUnmount = () => {
- this.mounted = false;
- clearInterval(this.timer);
- };
-
- update = () => {
- this.fetch(this.state.page, this.state.perPage);
- };
-
- fetch = (page, perPage) => {
- this.setState({ loading: true });
- // doFetch is defined in the derived class
- this.doFetch(page, perPage).then(sliced => {
- // if fetch was called and the component was unmounted before
- // the results arrived, don't call setState
- if (!this.mounted) return;
- const { rows, page, total, allRows } = sliced;
- this.setState({
- rows,
- loading: false,
- page,
- perPage,
- total,
- allRows
- });
- });
- };
-
- onSort = (_event, index, direction) => {
- const rows = this.sort(this.state.allRows, index, direction);
- this.setState({ rows, page: 1, sortBy: { index, direction } });
- };
-
- renderPagination(variant = "top") {
- const { page, perPage, total } = this.state;
- return (
- <Pagination
- itemCount={total}
- page={page}
- perPage={perPage}
- onSetPage={(_evt, value) => this.onSetPage(value)}
- onPerPageSelect={(_evt, value) => this.onPerPageSelect(value)}
- variant={variant}
- />
- );
- }
-
- onSetPage = value => {
- this.fetch(value, this.state.perPage);
- };
- onPerPageSelect = value => {
- this.fetch(1, value);
- };
- handleChangeFilterValue = (field, value) => {
- this.setState({ filterBy: { field, value } }, this.update);
- console.log(`handleChangeFilterValue(${field}, ${value})`);
- };
-
- field2Row = field => ({
- cells: this.fields.map(f => field[f.field])
- });
-
- cellIndex = field => {
- return this.fields.findIndex(f => {
- return f.title === field;
- });
- };
- slice = (fields, page, perPage) => {
- const filterField = this.state.filterBy.field;
- const filterValue = this.state.filterBy.value;
- let rows = fields.map(f => this.field2Row(f));
- if (
- typeof filterField !== "undefined" &&
- typeof filterValue !== "undefined" &&
- filterValue !== ""
- ) {
- const cellIndex = this.cellIndex(filterField);
- rows = rows.filter(r => {
- return r.cells[cellIndex].includes(filterValue);
- });
- }
- rows = this.sort(rows);
- const total = rows.length;
- const newPages = Math.ceil(total / perPage);
- page = Math.min(page, newPages);
- const start = perPage * (page - 1);
- const end = Math.min(start + perPage, rows.length);
- const slicedRows = rows.slice(start, end);
- return { rows: slicedRows, page, total, allRows: rows };
- };
-
- sort = rows => {
- if (
- typeof this.state.index === "undefined" ||
- typeof this.state.direction === "undefined"
- ) {
- return rows;
- }
- rows.sort((a, b) =>
- a.cells[this.sate.index] < b.cells[this.sate.index]
- ? -1
- : a.cells[this.sate.index] > b.cells[this.sate.index]
- ? 1
- : 0
- );
- if (this.sate.direction === SortByDirection.desc) {
- rows = rows.reverse();
- }
- return rows;
- };
-
- render() {
- const { loading } = this.state;
- return (
- <React.Fragment>
- <TableToolbar
- total={this.state.total}
- page={this.state.page}
- perPage={this.state.perPage}
- onSetPage={this.onSetPage}
- onPerPageSelect={this.onPerPageSelect}
- fields={this.fields}
- handleChangeFilterValue={this.handleChangeFilterValue}
- />
- {!loading && (
- <Table
- cells={this.state.columns}
- rows={this.state.rows}
- aria-label={this.props.entity}
- sortBy={this.state.sortBy}
- onSort={this.onSort}
- >
- <TableHeader />
- <TableBody />
- </Table>
- )}
- {this.renderPagination("bottom")}
- {loading && (
- <center>
- <Title size="3xl">Please wait while loading data</Title>
- </center>
- )}
- </React.Fragment>
- );
- }
-}
-
-export default OverviewTableBase;
diff --git a/console/react/src/overview/overviewTablePage.js b/console/react/src/overview/overviewTablePage.js
index 92adfd2..8e19a65 100644
--- a/console/react/src/overview/overviewTablePage.js
+++ b/console/react/src/overview/overviewTablePage.js
@@ -1,3 +1,22 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
import React from "react";
import { PageSection, PageSectionVariants } from "@patternfly/react-core";
import {
@@ -9,68 +28,45 @@ import {
} from "@patternfly/react-core";
import { Card, CardBody } from "@patternfly/react-core";
-import RoutersTable from "./routersTable";
-import AddressesTable from "./addressesTable";
-import LinksTable from "./linksTable";
-import ConnectionsTable from "./connectionsTable";
+import OverviewTable from "./overviewTable";
class OverviewTablePage extends React.Component {
constructor(props) {
super(props);
- this.state = {};
+ this.state = { loading: false, lastUpdated: new Date() };
}
- whichTable = () => {
- if (this.props.entity === "routers") {
- return (
- <RoutersTable entity={this.props.entity} service={this.props.service} />
- );
- }
- if (this.props.entity === "addresses") {
- return (
- <AddressesTable
- entity={this.props.entity}
- service={this.props.service}
- />
- );
- }
- if (this.props.entity === "links") {
- return (
- <LinksTable entity={this.props.entity} service={this.props.service} />
- );
- }
- if (this.props.entity === "connections") {
- return (
- <ConnectionsTable
- entity={this.props.entity}
- service={this.props.service}
- />
- );
- }
+ lastUpdated = lastUpdated => {
+ this.setState({ lastUpdated });
};
render() {
return (
- <React.Fragment>
- <PageSection
- variant={PageSectionVariants.light}
- className="overview-table-page"
- >
- <Stack>
- <StackItem className="overview-header">
- <TextContent>
- <Text className="overview-title" component={TextVariants.h1}>
- {this.props.entity}
- </Text>
- </TextContent>
- </StackItem>
- <StackItem className="overview-table">
- <Card>
- <CardBody>{this.whichTable()}</CardBody>
- </Card>
- </StackItem>
- </Stack>
- </PageSection>
- </React.Fragment>
+ <PageSection
+ variant={PageSectionVariants.light}
+ className="overview-table-page"
+ >
+ <Stack>
+ <StackItem className="overview-header">
+ <TextContent>
+ <Text className="overview-title" component={TextVariants.h1}>
+ {this.props.service.utilities.entityFromProps(this.props)}
+ </Text>
+ <Text className="overview-loading" component={TextVariants.pre}>
+ {`Updated ${this.props.service.utilities.strDate(
+ this.state.lastUpdated
+ )}`}
+ </Text>
+ </TextContent>
+ </StackItem>
+ <StackItem className="overview-table">
+ <Card>
+ <CardBody>
+ <OverviewTable {...this.props} lastUpdated={this.lastUpdated} />
+ </CardBody>
+ </Card>
+ </StackItem>
+ </Stack>
+ </PageSection>
);
}
}
diff --git a/console/react/src/overview/tableToolbar.jsx b/console/react/src/overview/tableToolbar.jsx
index 79154d0..e0fe006 100644
--- a/console/react/src/overview/tableToolbar.jsx
+++ b/console/react/src/overview/tableToolbar.jsx
@@ -1,3 +1,22 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
import React from "react";
import {
Dropdown,
diff --git a/console/react/src/qdrService.js b/console/react/src/qdrService.js
index 55a6cc9..1d3fbd9 100644
--- a/console/react/src/qdrService.js
+++ b/console/react/src/qdrService.js
@@ -52,8 +52,9 @@ export class QDRService {
self.onDisconnect.bind(self)
);
- self.management.getSchema().then(() => {
- //console.log("got schema after connection");
+ self.management.getSchema().then(schema => {
+ console.log("got schema after connection");
+ console.log(schema);
self.management.topology.setUpdateEntities([]);
//console.log("requesting a topology");
self.management.topology
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org