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/26 17:26:02 UTC
[qpid-dispatch] branch eallen-DISPATCH-1385 updated: Using pf4
topology wrapper component
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
The following commit(s) were added to refs/heads/eallen-DISPATCH-1385 by this push:
new 885a457 Using pf4 topology wrapper component
885a457 is described below
commit 885a457005ebe55225938b6c5324641b8352c082
Author: Ernest Allen <ea...@redhat.com>
AuthorDate: Sat Oct 26 13:25:42 2019 -0400
Using pf4 topology wrapper component
---
console/react/src/App.css | 118 +++++++++-
console/react/src/App.js | 2 +-
console/react/src/amqp/topology.js | 4 -
console/react/src/connect-form.js | 75 +++---
console/react/src/connectPage.js | 6 +-
...xtMenuComponent.jsx => contextMenuComponent.js} | 2 +
console/react/src/layout.js | 205 +++++++++-------
console/react/src/overview/dataSources/logsData.js | 72 +++++-
console/react/src/overview/logDetails.js | 100 ++++++--
console/react/src/overview/overviewTable.js | 11 +-
console/react/src/qdrGlobals.js | 1 -
console/react/src/qdrService.js | 3 +-
console/react/src/topology/contextMenu.js | 80 +++++++
console/react/src/topology/legend.js | 3 +-
console/react/src/topology/legendComponent.js | 134 +++--------
console/react/src/topology/map.js | 55 ++++-
.../{mapLegendComponent.jsx => mapComponent.js} | 0
console/react/src/topology/nodes.js | 5 +-
console/react/src/topology/svgUtils.js | 111 ++++-----
console/react/src/topology/topoUtils.js | 10 +-
console/react/src/topology/topologyPage.js | 41 ++++
console/react/src/topology/topologyToolbar.js | 129 ++++++++++
.../topology/{qdrTopology.js => topologyViewer.js} | 259 +++++++++------------
console/react/src/topology/traffic.js | 2 +
24 files changed, 940 insertions(+), 488 deletions(-)
diff --git a/console/react/src/App.css b/console/react/src/App.css
index eb0bfb2..9c45680 100644
--- a/console/react/src/App.css
+++ b/console/react/src/App.css
@@ -130,6 +130,13 @@ g.selected line {
stroke: #fff;
}
+#topologyPage {
+ padding: 0;
+}
+
+#topologyPage .pf-l-stack__item {
+ border-bottom: 1px solid #aaa;
+}
path.hittarget {
stroke-width: 15px;
stroke: transparent;
@@ -370,9 +377,14 @@ div.state-container button.pf-c-clipboard-copy__group-copy {
.overview-charts-page,
.overview-table {
background-color: #eaeaea !important;
- padding: 3em;
+ padding: 1.5em;
+}
+.overview-table {
+ height: 100%;
+}
+.overview-table .pf-c-card__body {
+ padding-left: 1em;
}
-
.overview-table-page {
padding-left: 0 !important;
padding-right: 0 !important;
@@ -392,11 +404,18 @@ div.state-container button.pf-c-clipboard-copy__group-copy {
width: 100%;
}
+.qdrTopology {
+ width: 100%;
+ height: 100%;
+ background-color: white;
+}
.qdrTopology dl.pf-c-accordion,
.qdrChord dl.pf-c-accordion {
+ /*
position: absolute;
width: 20em;
right: 1em;
+ */
padding-top: 0;
padding-bottom: 0;
margin-top: 1em;
@@ -683,6 +702,14 @@ path.empty {
border-color: black;
}
+.topology-toolbar-item .pf-c-popover__content > button,
+.topology-toolbar-item .pf-c-popover__content .pf-c-title.pf-m-xl {
+ display: none;
+}
+
+.topology-toolbar-item .pf-c-popover__content {
+ padding: 2em 2em 0.5em;
+}
.qdrPopup {
position: absolute;
z-index: 200;
@@ -696,7 +723,7 @@ path.empty {
}
#popover-div {
- position: absolute;
+ position: fixed;
z-index: 200;
border-radius: 4px;
border: 1px solid gray;
@@ -797,9 +824,14 @@ div.qdrChord .legend-text {
font-weight: bold;
}
+.colored-dot span.colored-dot-dot > span {
+ padding-top: 5px;
+}
+
.colored-dot span.colored-dot-text {
margin-left: 0.5em;
color: black;
+ font-size: 16px;
}
.qdrTopology .addresses .legend-line,
@@ -807,12 +839,31 @@ div.qdrChord .legend-text {
margin-left: 1.5em;
}
-.qdrTopology .pf-c-options-menu__toggle {
- display: none;
+@media (min-width: 768px) {
+ div.qdrTopology .pf-topology-side-bar.shown {
+ width: calc(100% - 330px);
+ max-width: 330px;
+ }
+ div.qdrTopology .pf-topology-container__with-sidebar .pf-topology-content {
+ /*
+ width: 180px;
+ min-width: calc(100% - 330px);
+ */
+ width: 100%;
+ height: 100%;
+ background-color: white;
+ }
}
-.context-menu {
+.diagram {
+ width: 100%;
position: absolute;
+ top: 0;
+ height: 100%;
+}
+
+.context-menu {
+ position: fixed;
background-color: white;
border-top: 1px solid #888;
border-left: 1px solid #888;
@@ -931,6 +982,7 @@ div.qdrChord .legend-text {
font-size: 14px;
padding: 0;
background-color: white;
+ overflow: hidden;
}
.pf-c-button.pf-m-primary.link-button,
@@ -940,6 +992,7 @@ div.qdrChord .legend-text {
color: blue;
font-weight: bold;
white-space: normal;
+ padding-left: 0;
}
.pf-c-button.pf-m-primary.link-button:hover,
@@ -967,3 +1020,56 @@ div.qdrChord .legend-text {
.overview-table tr {
border-bottom-color: #eaeaea !important;
}
+
+.log-record {
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+.log-message {
+ background-color: #eaeaea;
+ width: 100%;
+}
+
+div#topologyLegend {
+ position: absolute;
+ bottom: 2em;
+ right: 1em;
+ padding: 0;
+ width: 14em;
+}
+
+div#topologyLegend header {
+ padding: 0.5em 1em;
+ background-color: #efefef;
+ display: flex;
+ justify-content: space-between;
+}
+
+div#topologyLegend .pf-c-modal-box__body {
+ padding: 1em;
+}
+
+div#topologyLegend .pf-c-title {
+ font-size: unset;
+ padding-top: 0.25em;
+}
+
+div.qdrTopology .topology-toolbar-button {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ color: black;
+
+ background-color: rgba(0, 0, 0, 0);
+ border: 1px solid #eaeaea;
+ border-bottom: 1px solid #212121;
+}
+
+div.qdrTopology .topology-toolbar-button i {
+ margin-left: 16px;
+ margin-right: 8px;
+}
diff --git a/console/react/src/App.js b/console/react/src/App.js
index 91f09e7..f896d4c 100644
--- a/console/react/src/App.js
+++ b/console/react/src/App.js
@@ -12,7 +12,7 @@ class App extends Component {
render() {
return (
- <div className="App">
+ <div className="App pf-m-redhat-font">
<PageLayout />
</div>
);
diff --git a/console/react/src/amqp/topology.js b/console/react/src/amqp/topology.js
index 8afa75a..98b03da 100644
--- a/console/react/src/amqp/topology.js
+++ b/console/react/src/amqp/topology.js
@@ -141,10 +141,6 @@ class Topology {
}
get() {
- if (typeof this.getCounter === "undefined") {
- this.getCounter = 0;
- }
- console.log(`topology: get - ${this.getCounter++}`);
return new Promise(
function(resolve, reject) {
this.connection.sendMgmtQuery("GET-MGMT-NODES").then(
diff --git a/console/react/src/connect-form.js b/console/react/src/connect-form.js
index 7f0073a..678be78 100644
--- a/console/react/src/connect-form.js
+++ b/console/react/src/connect-form.js
@@ -25,34 +25,47 @@ import {
TextVariants
} from "@patternfly/react-core";
+const CONNECT_KEY = "QDRSettings";
+
class ConnectForm extends React.Component {
constructor(props) {
super(props);
this.state = {
- value: "please choose",
- value1: "",
- value2: "",
- value3: "",
- value4: ""
- };
- this.handleTextInputChange1 = value1 => {
- this.setState({ value1 });
- };
- this.handleTextInputChange2 = value2 => {
- this.setState({ value2 });
- };
- this.handleTextInputChange3 = value3 => {
- this.setState({ value3 });
- };
- this.handleTextInputChange4 = value4 => {
- this.setState({ value4 });
+ address: "",
+ port: "",
+ username: "",
+ password: ""
};
}
+ componentDidMount = () => {
+ let savedValues = localStorage.getItem(CONNECT_KEY);
+ if (!savedValues) {
+ savedValues = {
+ address: "localhost",
+ port: "5673",
+ username: "",
+ password: ""
+ };
+ } else {
+ savedValues = JSON.parse(savedValues);
+ }
+ this.setState(savedValues);
+ };
+ handleTextInputChange = (field, value) => {
+ const formValues = Object.assign(this.state);
+ formValues[field] = value;
+ this.setState(formValues, () => {
+ const state2Save = JSON.parse(JSON.stringify(formValues));
+ // don't save the password
+ state2Save.password = "";
+ localStorage.setItem(CONNECT_KEY, JSON.stringify(state2Save));
+ });
+ };
+
handleConnect = () => {
- this.toggleDrawerHide();
- this.props.handleConnect(this.props.fromPath);
+ this.props.handleConnect(this.props.fromPath, this.state);
};
toggleDrawerHide = () => {
@@ -60,7 +73,7 @@ class ConnectForm extends React.Component {
};
render() {
- const { value1, value2, value3, value4 } = this.state;
+ const { address, port, username, password } = this.state;
return (
<div>
@@ -80,13 +93,15 @@ class ConnectForm extends React.Component {
fieldId={`form-address-${this.props.prefix}`}
>
<TextInput
- value={value1}
+ value={address}
isRequired
type="text"
id={`form-address-${this.props.prefix}`}
aria-describedby="horizontal-form-address-helper"
name="form-address"
- onChange={this.handleTextInputChange1}
+ onChange={value =>
+ this.handleTextInputChange("address", value)
+ }
/>
</FormGroup>
<FormGroup
@@ -95,8 +110,8 @@ class ConnectForm extends React.Component {
fieldId={`form-port-${this.props.prefix}`}
>
<TextInput
- value={value2}
- onChange={this.handleTextInputChange2}
+ value={port}
+ onChange={value => this.handleTextInputChange("port", value)}
isRequired
type="number"
id={`form-port-${this.props.prefix}`}
@@ -108,8 +123,10 @@ class ConnectForm extends React.Component {
fieldId={`form-user-${this.props.prefix}`}
>
<TextInput
- value={value3}
- onChange={this.handleTextInputChange3}
+ value={username}
+ onChange={value =>
+ this.handleTextInputChange("username", value)
+ }
isRequired
id={`form-user-${this.props.prefix}`}
name="form-user"
@@ -120,8 +137,10 @@ class ConnectForm extends React.Component {
fieldId={`form-password-${this.props.prefix}`}
>
<TextInput
- value={value4}
- onChange={this.handleTextInputChange4}
+ value={password}
+ onChange={value =>
+ this.handleTextInputChange("password", value)
+ }
type="password"
id={`form-password-${this.props.prefix}`}
name="form-password"
diff --git a/console/react/src/connectPage.js b/console/react/src/connectPage.js
index cd537d1..6d070b7 100644
--- a/console/react/src/connectPage.js
+++ b/console/react/src/connectPage.js
@@ -39,10 +39,8 @@ class ConnectPage extends React.Component {
</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.
+ This console provides information about routers and their
+ connected clients.
</Text>
</TextContent>
</div>
diff --git a/console/react/src/contextMenuComponent.jsx b/console/react/src/contextMenuComponent.js
similarity index 95%
rename from console/react/src/contextMenuComponent.jsx
rename to console/react/src/contextMenuComponent.js
index 18dd46c..5ae59a3 100644
--- a/console/react/src/contextMenuComponent.jsx
+++ b/console/react/src/contextMenuComponent.js
@@ -8,6 +8,8 @@ class ContextMenuComponent extends React.Component {
componentDidMount = () => {
this.registerHandlers();
+ console.log(`contextMenuComponent mounted with`);
+ console.log(this.props.contextEventPosition);
};
componentWillUnmount = () => {
diff --git a/console/react/src/layout.js b/console/react/src/layout.js
index 13d198f..cf59f4f 100644
--- a/console/react/src/layout.js
+++ b/console/react/src/layout.js
@@ -54,7 +54,7 @@ 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 TopologyPage from "./topology/topologyPage";
import MessageFlowPage from "./chord/qdrChord";
import LogDetails from "./overview/logDetails";
import { QDRService } from "./qdrService";
@@ -70,11 +70,28 @@ class PageLayout extends React.Component {
isDropdownOpen: false,
activeGroup: "overview",
activeItem: "dashboard",
- isConnectFormOpen: false
+ isConnectFormOpen: false,
+ isNavOpenDesktop: true,
+ isNavOpenMobile: false,
+ isMobileView: false
};
- this.tables = ["routers", "addresses", "links", "connections", "logs"];
this.hooks = { setLocation: this.setLocation };
this.service = new QDRService(this.hooks);
+ this.nav = {
+ overview: [
+ { name: "dashboard" },
+ { name: "routers", pre: true },
+ { name: "addresses", pre: true },
+ { name: "links", pre: true },
+ { name: "connections", pre: true },
+ { name: "logs", pre: true }
+ ],
+ visualizations: [
+ { name: "topology" },
+ { name: "flow", title: "Message flow" }
+ ],
+ details: [{ name: "entities" }, { name: "schema" }]
+ };
}
setLocation = where => {
@@ -93,24 +110,45 @@ class PageLayout extends React.Component {
});
};
- handleConnect = connectPath => {
+ handleConnect = (connectPath, connectInfo) => {
if (this.state.connected) {
- this.service.disconnect();
- this.setState({ connected: false });
+ this.setState(
+ { connectPath: "", connected: false, isConnectFormOpen: false },
+ () => {
+ this.service.disconnect();
+ }
+ );
} else {
- this.service
- .connect({ address: "localhost", port: 5673, reconnect: true })
- .then(
- r => {
- this.setState({
- connected: true,
- connectPath
- });
- },
- e => {
- console.log(e);
+ const connectOptions = JSON.parse(JSON.stringify(connectInfo));
+ if (connectOptions.username === "") connectOptions.username = undefined;
+ if (connectOptions.password === "") connectOptions.password = undefined;
+ connectOptions.reconnect = true;
+
+ this.service.connect(connectOptions).then(
+ r => {
+ if (connectPath === "/") connectPath = "/dashboard";
+ const activeItem = connectPath.split("/").pop();
+ // find the active group for this item
+ let activeGroup = "overview";
+ for (const group in this.nav) {
+ if (this.nav[group].some(item => item.name === activeItem)) {
+ activeGroup = group;
+ break;
+ }
}
- );
+
+ this.setState({
+ isConnectFormOpen: false,
+ activeItem,
+ activeGroup,
+ connected: true,
+ connectPath
+ });
+ },
+ e => {
+ console.log(e);
+ }
+ );
}
};
@@ -131,78 +169,59 @@ class PageLayout extends React.Component {
this.setState({ isConnectFormOpen: false });
};
+ onNavToggleDesktop = () => {
+ this.setState({
+ isNavOpenDesktop: !this.state.isNavOpenDesktop
+ });
+ };
+
+ onNavToggleMobile = () => {
+ this.setState({
+ isNavOpenMobile: !this.state.isNavOpenMobile
+ });
+ };
+
+ onPageResize = ({ mobileView, windowSize }) => {
+ this.setState({
+ isMobileView: mobileView
+ });
+ };
+
render() {
const { isDropdownOpen, activeItem, activeGroup } = this.state;
+ const { isNavOpenDesktop, isNavOpenMobile, isMobileView } = this.state;
const PageNav = (
<Nav onSelect={this.onNavSelect} aria-label="Nav" className="pf-m-dark">
<NavList>
- <NavExpandable
- title="Overview"
- groupId="overview"
- isActive={activeGroup === "overview"}
- isExpanded
- >
- <NavItem
- groupId="overview"
- itemId="dashboard"
- isActive={activeItem === "dashboard"}
- >
- <Link to="/dashboard">Dashboard</Link>
- </NavItem>
- {this.tables.map(t => {
- return (
- <NavItem
- groupId="overview"
- itemId={t}
- isActive={activeItem === { t }}
- key={t}
- >
- <Link to={`/overview/${t}`}>{this.icap(t)}</Link>
- </NavItem>
- );
- })}
- </NavExpandable>
- <NavExpandable
- title="Visualizations"
- groupId="visualizations"
- isActive={activeGroup === "visualizations"}
- >
- <NavItem
- groupId="visualizations"
- itemId="topology"
- isActive={activeItem === "topology"}
- >
- <Link to="/topology">Topology</Link>
- </NavItem>
- <NavItem
- groupId="visualizations"
- itemId="flow"
- isActive={activeItem === "flow"}
- >
- <Link to="/flow">Message flow</Link>
- </NavItem>
- </NavExpandable>
- <NavExpandable
- title="Details"
- groupId="detailsGroup"
- isActive={activeGroup === "detailsGroup"}
- >
- <NavItem
- groupId="detailsGroup"
- itemId="entities"
- isActive={activeItem === "entities"}
- >
- <Link to="/entities">Entities</Link>
- </NavItem>
- <NavItem
- groupId="detailsGroup"
- itemId="schema"
- isActive={activeItem === "schema"}
- >
- <Link to="/schema">Schema</Link>
- </NavItem>
- </NavExpandable>
+ {Object.keys(this.nav).map(section => {
+ const Section = this.icap(section);
+ return (
+ <NavExpandable
+ title={Section}
+ groupId={section}
+ isActive={activeGroup === section}
+ isExpanded
+ key={section}
+ >
+ {this.nav[section].map(item => {
+ const key = item.name;
+ return (
+ <NavItem
+ groupId={section}
+ itemId={key}
+ isActive={activeItem === key}
+ key={key}
+ >
+ <Link to={`/${item.pre ? section + "/" : ""}${key}`}>
+ {item.title ? item.title : this.icap(key)}
+ </Link>
+ </NavItem>
+ );
+ })}
+ </NavExpandable>
+ );
+ })}
</NavList>
</Nav>
);
@@ -270,16 +289,26 @@ class PageLayout extends React.Component {
toolbar={PageToolbar}
avatar={<Avatar src={avatarImg} alt="Avatar image" />}
showNavToggle
+ onNavToggle={
+ isMobileView ? this.onNavToggleMobile : this.onNavToggleDesktop
+ }
+ isNavOpen={isMobileView ? isNavOpenMobile : isNavOpenDesktop}
/>
);
- const pageId = "main-content-page-layout-expandable-nav";
+ const pageId = "main-content-page-layout-manual-nav";
const PageSkipToContent = (
<SkipToContent href={`#${pageId}`}>Skip to Content</SkipToContent>
);
const sidebar = PageNav => {
if (this.state.connected) {
- return <PageSidebar nav={PageNav} className="pf-m-dark" />;
+ return (
+ <PageSidebar
+ nav={PageNav}
+ isNavOpen={isMobileView ? isNavOpenMobile : isNavOpenDesktop}
+ theme="dark"
+ />
+ );
}
return <React.Fragment />;
};
@@ -316,6 +345,7 @@ class PageLayout extends React.Component {
if (this.state.isConnectFormOpen) {
return (
<ConnectForm
+ fromPath={"/"}
handleConnect={this.handleConnect}
handleConnectCancel={this.handleConnectCancel}
isConnected={this.state.connected}
@@ -331,8 +361,9 @@ class PageLayout extends React.Component {
<Page
header={Header}
sidebar={sidebar(PageNav)}
- isManagedSidebar
+ onPageResize={this.onPageResize}
skipToContent={PageSkipToContent}
+ mainContainerId={pageId}
>
{connectForm()}
<Switch>
diff --git a/console/react/src/overview/dataSources/logsData.js b/console/react/src/overview/dataSources/logsData.js
index d54769b..045ecd9 100644
--- a/console/react/src/overview/dataSources/logsData.js
+++ b/console/react/src/overview/dataSources/logsData.js
@@ -17,6 +17,28 @@ specific language governing permissions and limitations
under the License.
*/
+import React from "react";
+import { Button } from "@patternfly/react-core";
+class LogRecords extends React.Component {
+ detailClick = () => {
+ this.props.detailClick(this.props.value, this.props.extraInfo);
+ };
+ render() {
+ if (
+ this.props.extraInfo.rowData.enable.title !== "" &&
+ this.props.value !== "0"
+ ) {
+ return (
+ <Button className="link-button" onClick={this.detailClick}>
+ {this.props.value}
+ </Button>
+ );
+ } else {
+ return this.props.value;
+ }
+ }
+}
+
class LogsData {
constructor(service) {
this.service = service;
@@ -24,17 +46,53 @@ class LogsData {
{ title: "Router", field: "node" },
{ title: "Enable", field: "enable" },
{ title: "Module", field: "name" },
- { title: "Trace", field: "traceCount", numeric: true },
- { title: "Info", field: "infoCount", numeric: true },
- { title: "Debug", field: "debugCount", numeric: true },
- { title: "Notice", field: "noticeCount", numeric: true },
- { title: "Warning", field: "warningCount", numeric: true },
- { title: "Error", field: "errorCount", numeric: true },
- { title: "Critical", field: "criticalCount", numeric: true }
+ {
+ title: "Info",
+ field: "infoCount",
+ numeric: true,
+ formatter: LogRecords
+ },
+ {
+ title: "Trace",
+ field: "traceCount",
+ numeric: true,
+ formatter: LogRecords
+ },
+ {
+ title: "Debug",
+ field: "debugCount",
+ numeric: true,
+ formatter: LogRecords
+ },
+ {
+ title: "Notice",
+ field: "noticeCount",
+ numeric: true,
+ formatter: LogRecords
+ },
+ {
+ title: "Warning",
+ field: "warningCount",
+ numeric: true,
+ formatter: LogRecords
+ },
+ {
+ title: "Error",
+ field: "errorCount",
+ numeric: true,
+ formatter: LogRecords
+ },
+ {
+ title: "Critical",
+ field: "criticalCount",
+ numeric: true,
+ formatter: LogRecords
+ }
];
this.detailEntity = "log";
this.detailName = "Log";
this.detailPath = "/logs";
+ this.detailFormatter = true;
}
fetchRecord = (currentRecord, schema) => {
diff --git a/console/react/src/overview/logDetails.js b/console/react/src/overview/logDetails.js
index 22dff64..617c74f 100644
--- a/console/react/src/overview/logDetails.js
+++ b/console/react/src/overview/logDetails.js
@@ -25,8 +25,7 @@ import {
DataListItem,
DataListItemRow,
DataListItemCells,
- DataListCell,
- DataListContent
+ DataListCell
} from "@patternfly/react-core";
import {
Stack,
@@ -39,7 +38,6 @@ import {
} from "@patternfly/react-core";
import { Redirect } from "react-router-dom";
-import { dataMap } from "./entityData";
class LogDetails extends React.Component {
constructor(props) {
@@ -47,7 +45,8 @@ class LogDetails extends React.Component {
this.state = {
redirect: false,
redirectPath: "/dashboard",
- lastUpdated: new Date()
+ lastUpdated: new Date(),
+ logRecords: []
};
// if we get to this page and we don't have a props.location.state.entity
// then redirect back to the dashboard.
@@ -59,6 +58,8 @@ class LogDetails extends React.Component {
this.props.location.state.entity;
if (!this.entity) {
this.state.redirect = true;
+ } else {
+ this.info = props.location.state;
}
}
@@ -73,13 +74,57 @@ class LogDetails extends React.Component {
}
};
- update = () => {};
+ update = () => {
+ if (!this.info) return;
+ const nodeId = this.info.currentRecord.nodeId;
+ var gotLogInfo = (nodeId, response, context) => {
+ let statusCode = context.message.application_properties.statusCode;
+ if (statusCode < 200 || statusCode >= 300) {
+ console.log("Error " + context.message.statusDescription);
+ } else {
+ let levelLogs = response.filter(result => {
+ if (result[1] == null) result[1] = "error";
+ return (
+ result[1].toUpperCase() === this.info.property.toUpperCase() &&
+ result[0] === this.info.currentRecord.name
+ );
+ });
+ levelLogs.length = Math.min(levelLogs.length, 100);
+ let logRecords = levelLogs.map((result, i) => {
+ return [
+ <DataListCell key={`log-message-${i}`}>
+ <div className="log-record">
+ <div className="log-date">{Date(result[5]).toString()}</div>
+ <div className="log-source">{result[3]}</div>
+ <div className="log-message">{result[2]}</div>
+ </div>
+ </DataListCell>
+ ];
+ });
+ this.setState({ logRecords, lastUpdated: new Date() });
+ }
+ };
+ this.props.service.management.connection
+ .sendMethod(nodeId, undefined, {}, "GET-LOG", {
+ module: this.info.currentRecord.name
+ })
+ .then(response => {
+ gotLogInfo(nodeId, response.response, response.context);
+ });
+ };
icap = s => s.charAt(0).toUpperCase() + s.slice(1);
parentItem = () => {
- // otherwise return the 1st field
- return this.props.location.state.value;
+ return `${this.info.currentRecord.node} ${this.info.currentRecord.name} ${this.info.property}`;
};
+ breadcrumbSelected = () => {
+ this.setState({
+ redirect: true,
+ redirectPath: `/overview/${this.entity}`,
+ redirectState: this.props.location.state
+ });
+ };
+
render() {
if (this.state.redirect) {
return (
@@ -104,9 +149,7 @@ class LogDetails extends React.Component {
<Breadcrumb>
<BreadcrumbItem
className="link-button"
- onClick={() =>
- this.breadcrumbSelected(`/overview/${this.entity}`)
- }
+ onClick={this.breadcrumbSelected}
>
{this.icap(this.entity)}
</BreadcrumbItem>
@@ -115,7 +158,7 @@ class LogDetails extends React.Component {
<TextContent>
<Text className="overview-title" component={TextVariants.h1}>
- {`Logs ${this.parentItem()} attributes`}
+ {`${this.parentItem()} log records`}
</Text>
<Text className="overview-loading" component={TextVariants.pre}>
{`Updated ${this.props.service.utilities.strDate(
@@ -127,6 +170,30 @@ class LogDetails extends React.Component {
<StackItem className="overview-table">
<DataList aria-label="Simple data list example">
+ {this.state.logRecords.map((rec, i) => {
+ return (
+ <DataListItem
+ key={`log-item-${i}`}
+ aria-labelledby={`simple-item1-${i}`}
+ >
+ <DataListItemRow>
+ <DataListItemCells dataListCells={rec} />
+ </DataListItemRow>
+ </DataListItem>
+ );
+ })}
+ </DataList>
+ </StackItem>
+ </Stack>
+ </PageSection>
+ </React.Fragment>
+ );
+ }
+}
+
+export default LogDetails;
+
+/*
<DataListItem aria-labelledby="simple-item1">
<DataListItemRow>
<DataListItemCells
@@ -164,13 +231,4 @@ class LogDetails extends React.Component {
/>
</DataListItemRow>
</DataListItem>
- </DataList>
- </StackItem>
- </Stack>
- </PageSection>
- </React.Fragment>
- );
- }
-}
-
-export default LogDetails;
+*/
diff --git a/console/react/src/overview/overviewTable.js b/console/react/src/overview/overviewTable.js
index 6555199..e08f1f6 100644
--- a/console/react/src/overview/overviewTable.js
+++ b/console/react/src/overview/overviewTable.js
@@ -87,7 +87,10 @@ class OverviewTable extends React.Component {
);
}
});
- this.dataSource.fields[0].cellFormatters.push(this.detailLink);
+ // if the dataSource did not provide its own cell formatter for details
+ if (!this.dataSource.detailFormatter) {
+ this.dataSource.fields[0].cellFormatters.push(this.detailLink);
+ }
this.setState({ columns: this.dataSource.fields }, () => {
this.update();
@@ -139,13 +142,14 @@ class OverviewTable extends React.Component {
this.setState({
redirect: true,
redirectState: {
- value: extraInfo.rowData.cells[0],
+ value: extraInfo.rowData.cells[extraInfo.columnIndex],
currentRecord: extraInfo.rowData.data,
entity: this.entity,
page: this.state.page,
sortBy: this.state.sortBy,
filterBy: this.state.filterBy,
- perPage: this.state.perPage
+ perPage: this.state.perPage,
+ property: extraInfo.property
}
});
};
@@ -169,6 +173,7 @@ class OverviewTable extends React.Component {
value={value}
extraInfo={extraInfo}
service={this.props.service}
+ detailClick={this.detailClick}
/>
);
};
diff --git a/console/react/src/qdrGlobals.js b/console/react/src/qdrGlobals.js
index b43aabc..a121bfb 100644
--- a/console/react/src/qdrGlobals.js
+++ b/console/react/src/qdrGlobals.js
@@ -53,7 +53,6 @@ export class QDRLogger {
}
export const QDRTemplatePath = "html/";
-export const QDR_SETTINGS_KEY = "QDRSettings";
export const QDR_LAST_LOCATION = "QDRLastLocation";
export const QDR_INTERVAL = "QDRInterval";
diff --git a/console/react/src/qdrService.js b/console/react/src/qdrService.js
index 6ca41c7..9fcac79 100644
--- a/console/react/src/qdrService.js
+++ b/console/react/src/qdrService.js
@@ -73,7 +73,8 @@ export class QDRService {
disconnect() {
this.management.connection.disconnect();
delete this.management;
- this.management = new dm(this.$location.protocol(), DEFAULT_INTERVAL);
+ const url = utils.getUrlParts(window.location);
+ this.management = new dm(url.protocol, DEFAULT_INTERVAL);
}
}
diff --git a/console/react/src/topology/contextMenu.js b/console/react/src/topology/contextMenu.js
new file mode 100644
index 0000000..f68751d
--- /dev/null
+++ b/console/react/src/topology/contextMenu.js
@@ -0,0 +1,80 @@
+/*
+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, { Component } from "react";
+import ContextMenuComponent from "../contextMenuComponent";
+
+class ContextMenu extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+ this.contextMenuItems = [
+ {
+ title: "Freeze in place",
+ action: this.setFixed,
+ enabled: data => !this.isFixed(data)
+ },
+ {
+ title: "Unfreeze",
+ action: this.setFixed,
+ enabled: this.isFixed,
+ endGroup: true
+ },
+ {
+ title: "Unselect",
+ action: this.setSelected,
+ enabled: this.isSelected
+ },
+ {
+ title: "Select",
+ action: this.setSelected,
+ enabled: data => !this.isSelected(data)
+ }
+ ];
+ }
+
+ setFixed = (item, data) => {
+ data.setFixed(item.title !== "Unfreeze");
+ };
+
+ isFixed = data => {
+ return data.isFixed();
+ };
+
+ setSelected = (item, data) => {
+ this.props.setSelected(item, data);
+ };
+
+ isSelected = data => {
+ return data.selected ? true : false;
+ };
+
+ render() {
+ return (
+ <ContextMenuComponent
+ contextEventPosition={this.props.contextEventPosition}
+ contextEventData={this.props.contextEventData}
+ handleContextHide={this.props.handleContextHide}
+ menuItems={this.contextMenuItems}
+ />
+ );
+ }
+}
+
+export default ContextMenu;
diff --git a/console/react/src/topology/legend.js b/console/react/src/topology/legend.js
index cf96682..82a49d7 100644
--- a/console/react/src/topology/legend.js
+++ b/console/react/src/topology/legend.js
@@ -89,7 +89,6 @@ const lookFor = [
export class Legend {
constructor(nodes, QDRLog) {
this.nodes = nodes;
- this.log = QDRLog;
}
// create a new legend container svg
@@ -124,7 +123,7 @@ export class Legend {
lsvg = d3.select("#topo_svg_legend svg g").selectAll("g");
}
// add a node to legendNodes for each node type that is currently in the svg
- let legendNodes = new Nodes(this.log);
+ let legendNodes = new Nodes();
this.nodes.nodes.forEach((n, i) => {
let node = lookFor.find(lf => lf.cmp(n));
if (node) {
diff --git a/console/react/src/topology/legendComponent.js b/console/react/src/topology/legendComponent.js
index 5432fa9..7ee02ae 100644
--- a/console/react/src/topology/legendComponent.js
+++ b/console/react/src/topology/legendComponent.js
@@ -18,120 +18,46 @@ under the License.
*/
import React, { Component } from "react";
-import {
- Accordion,
- AccordionItem,
- AccordionContent,
- AccordionToggle
-} from "@patternfly/react-core";
-
-import ArrowsComponent from "./arrowsComponent";
-import TrafficComponent from "./trafficComponent";
-import MapLegendComponent from "./mapLegendComponent";
+import { Legend } from "./legend.js";
class LegendComponent extends Component {
constructor(props) {
super(props);
this.state = {};
+ this.legend = new Legend(this.props.nodes);
}
- render() {
- const toggle = id => {
- const idOpen = this.props[`${id}Open`];
- this.props.handleOpenChange(id, !idOpen);
- };
+ componentDidMount = () => {
+ this.legend.update();
+ };
+ render() {
return (
- <Accordion>
- <AccordionItem>
- <AccordionToggle
- onClick={() => toggle("traffic")}
- isExpanded={this.props.trafficOpen}
- id="traffic"
- >
- Traffic
- </AccordionToggle>
- <AccordionContent
- id="traffic-expand"
- isHidden={!this.props.trafficOpen}
- isFixed
- >
- <TrafficComponent
- addresses={this.props.addresses}
- addressColors={this.props.addressColors}
- open={this.props.trafficOpen}
- handleChangeTrafficAnimation={
- this.props.handleChangeTrafficAnimation
- }
- handleChangeTrafficFlowAddress={
- this.props.handleChangeTrafficFlowAddress
- }
- handleHoverAddress={this.props.handleHoverAddress}
- dots={this.props.dots}
- congestion={this.props.congestion}
- />
- </AccordionContent>
- </AccordionItem>
- <AccordionItem>
- <AccordionToggle
- onClick={() => toggle("legend")}
- isExpanded={this.props.legendOpen}
- id="legend"
- >
- Legend
- </AccordionToggle>
- <AccordionContent
- id="legend-expand"
- isHidden={!this.props.legendOpen}
- isFixed
- >
- <div id="topo_svg_legend"></div>
- </AccordionContent>
- </AccordionItem>
- <AccordionItem>
- <AccordionToggle
- onClick={() => toggle("map")}
- isExpanded={this.props.mapOpen}
- id="map"
- >
- Background map
- </AccordionToggle>
- <AccordionContent
- id="map-expand"
- isHidden={!this.props.mapOpen}
- isFixed
- >
- <MapLegendComponent
- open={this.props.mapOpen}
- mapShown={this.props.mapShown}
- areaColor={this.props.areaColor}
- oceanColor={this.props.oceanColor}
- handleUpdateMapColor={this.props.handleUpdateMapColor}
- handleUpdateMapShown={this.props.handleUpdateMapShown}
- />
- </AccordionContent>
- </AccordionItem>
- <AccordionItem>
- <AccordionToggle
- onClick={() => toggle("arrows")}
- isExpanded={this.props.arrowsOpen}
- id="arrows"
- >
- Arrows
- </AccordionToggle>
- <AccordionContent
- id="arrows-expand"
- isHidden={!this.props.arrowsOpen}
- isFixed
+ <div
+ id="topologyLegend"
+ className="pf-c-modal-box pf-u-box-shadow-md pf-m-sm"
+ role="dialog"
+ aria-modal="true"
+ aria-labelledby="modal-lg-title"
+ aria-describedby="modal-lg-description"
+ >
+ <header>
+ <h1 className="pf-c-title pf-m-xl" id="modal-lg-title">
+ Topology Legend
+ </h1>
+ <button
+ className="pf-c-button pf-m-plain"
+ type="button"
+ aria-label="Close"
+ onClick={this.props.handleCloseLegend}
>
- <ArrowsComponent
- handleChangeArrows={this.props.handleChangeArrows}
- routerArrows={this.props.routerArrows}
- clientArrows={this.props.clientArrows}
- />
- </AccordionContent>
- </AccordionItem>
- </Accordion>
+ <i className="fas fa-times" aria-hidden="true"></i>
+ </button>
+ </header>
+ <div className="pf-c-modal-box__body" id="modal-lg-description">
+ <div id="topo_svg_legend"></div>{" "}
+ </div>
+ </div>
);
}
}
diff --git a/console/react/src/topology/map.js b/console/react/src/topology/map.js
index 2ac3578..592b8a3 100644
--- a/console/react/src/topology/map.js
+++ b/console/react/src/topology/map.js
@@ -21,7 +21,7 @@ import * as d3 from "d3";
import * as topojson from "topojson-client";
const maxnorth = 84;
-const maxsouth = 60;
+const maxsouth = 74;
const MAPOPTIONSKEY = "QDRMapOptions";
const MAPPOSITIONKEY = "QDRMapPosition";
const defaultLandColor = "#A3D3E0";
@@ -75,7 +75,56 @@ export class BackgroundMap {
d3.select(".pf-c-page__main").style("background-color", color);
}
- init($scope, svg, width, height) {
+ setWidthHeight(width, height) {
+ if (!this.initialized) return;
+ this.width = width;
+ this.height = height;
+ // track last translation and scale event we processed
+ this.rotate = 20;
+ this.scaleExtent = [1, 10];
+
+ // setup the projection with some defaults
+ this.projection = d3.geo
+ .mercator()
+ .rotate([this.rotate, 0])
+ .scale(1)
+ .translate([width / 2, height / 2]);
+
+ // this path will hold the land coordinates once they are loaded
+ this.geoPath = d3.geo.path().projection(this.projection);
+
+ // set up the scale extent and initial scale for the projection
+ var b = getMapBounds(this.projection, Math.max(maxnorth, maxsouth)),
+ s = width / (b[1][0] - b[0][0]);
+ this.scaleExtent = [s, 15 * s];
+
+ this.projection.scale(this.scaleExtent[0]);
+
+ this.zoom = d3.behavior
+ .zoom()
+ .scaleExtent(this.scaleExtent)
+ .scale(this.projection.scale())
+ .translate([0, 0]) // not linked directly to projection
+ .on("zoom", this.zoomed);
+
+ this.geo
+ .select("rect.ocean")
+ .attr("width", width)
+ .attr("height", height)
+ .attr("fill", "#FFF");
+
+ if (this.options.show) {
+ this.svg.call(this.zoom).on("dblclick.zoom", null);
+ }
+
+ // restore map rotate, scale, translate
+ this.restoreState();
+
+ // draw with current positions
+ this.geo.selectAll(".land").attr("d", this.geoPath);
+ }
+
+ init(_unused, svg, width, height) {
return new Promise((resolve, reject) => {
if (this.initialized) {
resolve();
@@ -109,7 +158,7 @@ export class BackgroundMap {
this.lastProjection = savedOptions
? JSON.parse(savedOptions)
: {
- rotate: 20,
+ rotate: -10.884378033730373,
scale: this.scaleExtent[0],
translate: [width / 2, height / 2]
};
diff --git a/console/react/src/topology/mapLegendComponent.jsx b/console/react/src/topology/mapComponent.js
similarity index 100%
rename from console/react/src/topology/mapLegendComponent.jsx
rename to console/react/src/topology/mapComponent.js
diff --git a/console/react/src/topology/nodes.js b/console/react/src/topology/nodes.js
index 8b1c3c7..ceea875 100644
--- a/console/react/src/topology/nodes.js
+++ b/console/react/src/topology/nodes.js
@@ -224,7 +224,6 @@ nodeProperties["route-container"] = nodeProperties["normal"];
export class Nodes {
constructor(logger) {
this.nodes = [];
- this.logger = logger;
}
static radius(type) {
if (nodeProperties[type].radius) return nodeProperties[type].radius;
@@ -232,7 +231,7 @@ export class Nodes {
}
static textOffset(type) {
let r = Nodes.radius(type);
- let ret = type === "inter-router" || type === "_topo" ? r + 30 : 0;
+ let ret = type === "inter-router" || type === "_topo" ? r + 10 : 0;
return ret;
}
static maxRadius() {
@@ -291,7 +290,7 @@ export class Nodes {
if (index < this.getLength()) {
return this.nodes[index];
}
- this.logger.error(
+ console.log(
`Attempted to get node[${index}] but there were only ${this.getLength()} nodes`
);
return undefined;
diff --git a/console/react/src/topology/svgUtils.js b/console/react/src/topology/svgUtils.js
index a053603..f0dae10 100644
--- a/console/react/src/topology/svgUtils.js
+++ b/console/react/src/topology/svgUtils.js
@@ -17,6 +17,7 @@
import { Nodes } from "./nodes.js";
import { utils } from "../amqp/utilities.js";
+// update the node's classes based on the node's data
export function updateState(circle) {
circle
.selectAll("circle")
@@ -36,61 +37,61 @@ export function updateState(circle) {
export function appendCircle(g) {
// add new circles and set their attr/class/behavior
- return g
- .append("svg:circle")
- .attr("class", "node")
- .attr("r", function(d) {
- return Nodes.radius(d.nodeType);
- })
- .attr("fill", function(d) {
- if (d.cdir === "both" && !utils.isConsole(d)) {
- return "url(#half-circle)";
- }
- return null;
- })
- .classed("fixed", function(d) {
- return d.fixed ? d.fixed & 1 : false;
- })
- .classed("normal", function(d) {
- return d.nodeType === "normal" || utils.isConsole(d);
- })
- .classed("in", function(d) {
- return d.cdir === "in";
- })
- .classed("out", function(d) {
- return d.cdir === "out";
- })
- .classed("inout", function(d) {
- return d.cdir === "both";
- })
- .classed("inter-router", function(d) {
- return d.nodeType === "inter-router" || d.nodeType === "_topo";
- })
- .classed("on-demand", function(d) {
- return d.nodeType === "on-demand";
- })
- .classed("edge", function(d) {
- return d.nodeType === "edge" || d.nodeType === "_edge";
- })
- .classed("console", function(d) {
- return utils.isConsole(d);
- })
- .classed("artemis", function(d) {
- return utils.isArtemis(d);
- })
- .classed("qpid-cpp", function(d) {
- return utils.isQpid(d);
- })
- .classed("route-container", function(d) {
- return (
- !utils.isArtemis(d) &&
- !utils.isQpid(d) &&
- d.nodeType === "route-container"
- );
- })
- .classed("client", function(d) {
- return d.nodeType === "normal" && !d.properties.console_identifier;
- });
+ return (
+ g
+ .append("svg:circle")
+ .attr("class", "node")
+ // the following attrs and classes won't change after the node is created
+ .attr("r", function(d) {
+ return Nodes.radius(d.nodeType);
+ })
+ .attr("fill", function(d) {
+ if (d.cdir === "both" && !utils.isConsole(d)) {
+ return "url(#half-circle)";
+ }
+ return null;
+ })
+ .classed("normal", function(d) {
+ return d.nodeType === "normal" || utils.isConsole(d);
+ })
+ .classed("in", function(d) {
+ return d.cdir === "in";
+ })
+ .classed("out", function(d) {
+ return d.cdir === "out";
+ })
+ .classed("inout", function(d) {
+ return d.cdir === "both";
+ })
+ .classed("inter-router", function(d) {
+ return d.nodeType === "inter-router" || d.nodeType === "_topo";
+ })
+ .classed("on-demand", function(d) {
+ return d.nodeType === "on-demand";
+ })
+ .classed("edge", function(d) {
+ return d.nodeType === "edge" || d.nodeType === "_edge";
+ })
+ .classed("console", function(d) {
+ return utils.isConsole(d);
+ })
+ .classed("artemis", function(d) {
+ return utils.isArtemis(d);
+ })
+ .classed("qpid-cpp", function(d) {
+ return utils.isQpid(d);
+ })
+ .classed("route-container", function(d) {
+ return (
+ !utils.isArtemis(d) &&
+ !utils.isQpid(d) &&
+ d.nodeType === "route-container"
+ );
+ })
+ .classed("client", function(d) {
+ return d.nodeType === "normal" && !d.properties.console_identifier;
+ })
+ );
}
export function appendContent(g) {
diff --git a/console/react/src/topology/topoUtils.js b/console/react/src/topology/topoUtils.js
index 00eb196..0511d7b 100644
--- a/console/react/src/topology/topoUtils.js
+++ b/console/react/src/topology/topoUtils.js
@@ -268,17 +268,15 @@ export function connectionPopupHTML(d, nodeInfo) {
return HTML;
}
-export function getSizes(topologyRef, log) {
+export function getSizes(topologyRef) {
const gap = 5;
- let legendWidth = 194;
let topoWidth = topologyRef.offsetWidth;
- if (topoWidth < 768) legendWidth = 0;
- let width = topoWidth - gap - legendWidth;
+ let width = topoWidth - gap;
let top = topologyRef.offsetTop;
let height = window.innerHeight - top - gap;
if (width < 10 || height < 10) {
- log.log(`page width and height are abnormal w: ${width} h: ${height}`);
+ console.log(`page width and height are abnormal w: ${width} h: ${height}`);
return [0, 0];
}
- return [width, height];
+ return { width, height };
}
diff --git a/console/react/src/topology/topologyPage.js b/console/react/src/topology/topologyPage.js
new file mode 100644
index 0000000..f2af09e
--- /dev/null
+++ b/console/react/src/topology/topologyPage.js
@@ -0,0 +1,41 @@
+/*
+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, { Component } from "react";
+import { PageSection, PageSectionVariants } from "@patternfly/react-core";
+import TopologyViewer from "./topologyViewer";
+
+class TopologyPage extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ lastUpdated: new Date()
+ };
+ }
+
+ render() {
+ return (
+ <PageSection variant={PageSectionVariants.light} id="topologyPage">
+ <TopologyViewer service={this.props.service} />
+ </PageSection>
+ );
+ }
+}
+
+export default TopologyPage;
diff --git a/console/react/src/topology/topologyToolbar.js b/console/react/src/topology/topologyToolbar.js
new file mode 100644
index 0000000..218159b
--- /dev/null
+++ b/console/react/src/topology/topologyToolbar.js
@@ -0,0 +1,129 @@
+import React from "react";
+import { Toolbar, ToolbarGroup, ToolbarItem } from "@patternfly/react-core";
+import { Popover, PopoverPosition, Button } from "@patternfly/react-core";
+import TrafficComponent from "./trafficComponent";
+import MapComponent from "./mapComponent";
+import ArrowsComponent from "./arrowsComponent";
+
+class TopologyToolbar extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ isOpen: { traffic: false, arrows: false, map: false }
+ };
+ }
+ handleShowPopover = event => {
+ console.log("handleShowPopover called");
+ const { isOpen } = this.state;
+ const value = event.target.value;
+ isOpen[value] = !isOpen[value];
+ this.setState({ isOpen });
+ };
+
+ shouldClose = tip => {
+ console.log("should close called");
+ const { isOpen } = this.state;
+ const value = tip.reference.value;
+ isOpen[value] = false;
+ this.setState({ isOpen });
+ };
+
+ render() {
+ return (
+ <Toolbar className="pf-l-toolbar pf-u-justify-content-space-between pf-u-mx-xl pf-u-my-md">
+ <ToolbarGroup>
+ <ToolbarItem className="pf-u-mr-md">
+ <Popover
+ className="topology-toolbar-item"
+ position={PopoverPosition.bottom}
+ enableFlip={false}
+ appendTo={() => document.getElementById("root")}
+ aria-label="Popover traffic"
+ closeBtnAriaLabel="Close Popover traffic"
+ bodyContent={
+ <TrafficComponent
+ addresses={this.props.legendOptions.traffic.addresses}
+ addressColors={this.props.legendOptions.traffic.addressColors}
+ handleChangeTrafficAnimation={
+ this.props.handleChangeTrafficAnimation
+ }
+ handleChangeTrafficFlowAddress={
+ this.props.handleChangeTrafficFlowAddress
+ }
+ handleHoverAddress={this.props.handleHoverAddress}
+ dots={this.props.legendOptions.traffic.dots}
+ congestion={this.props.legendOptions.traffic.congestion}
+ />
+ }
+ >
+ <Button className="topology-toolbar-button" value="traffic">
+ <span className="pf-c-dropdown__toggle-text">Traffic</span>
+ <i
+ className="fas fa-caret-down pf-c-dropdown__toggle-icon"
+ aria-hidden="true"
+ ></i>
+ </Button>
+ </Popover>
+ </ToolbarItem>
+ <ToolbarItem className="pf-u-mr-md">
+ <Popover
+ className="topology-toolbar-item"
+ position={PopoverPosition.bottom}
+ enableFlip={false}
+ appendTo={() => document.getElementById("root")}
+ aria-label="Popover map"
+ closeBtnAriaLabel="Close Popover map"
+ bodyContent={
+ <MapComponent
+ mapShown={this.props.legendOptions.map.show}
+ areaColor={this.props.mapOptions.areaColor}
+ oceanColor={this.props.mapOptions.oceanColor}
+ handleUpdateMapColor={this.props.handleUpdateMapColor}
+ handleUpdateMapShown={this.props.handleUpdateMapShown}
+ />
+ }
+ >
+ <Button className="topology-toolbar-button" value="map">
+ <span className="pf-c-dropdown__toggle-text">
+ Background map
+ </span>
+ <i
+ className="fas fa-caret-down pf-c-dropdown__toggle-icon"
+ aria-hidden="true"
+ ></i>
+ </Button>
+ </Popover>
+ </ToolbarItem>
+ <ToolbarItem className="pf-u-mr-md">
+ <Popover
+ className="topology-toolbar-item"
+ position={PopoverPosition.bottom}
+ enableFlip={false}
+ appendTo={() => document.getElementById("root")}
+ aria-label="Popover arrows"
+ closeBtnAriaLabel="Close Popover arrows"
+ bodyContent={
+ <ArrowsComponent
+ routerArrows={this.props.legendOptions.arrows.routerArrows}
+ clientArrows={this.props.legendOptions.arrows.clientArrows}
+ handleChangeArrows={this.props.handleChangeArrows}
+ />
+ }
+ >
+ <Button className="topology-toolbar-button" value="arrows">
+ <span className="pf-c-dropdown__toggle-text">Arrows</span>
+ <i
+ className="fas fa-caret-down pf-c-dropdown__toggle-icon"
+ aria-hidden="true"
+ ></i>
+ </Button>
+ </Popover>
+ </ToolbarItem>
+ </ToolbarGroup>
+ </Toolbar>
+ );
+ }
+}
+
+export default TopologyToolbar;
diff --git a/console/react/src/topology/qdrTopology.js b/console/react/src/topology/topologyViewer.js
similarity index 86%
rename from console/react/src/topology/qdrTopology.js
rename to console/react/src/topology/topologyViewer.js
index a73ec5a..a720dcf 100644
--- a/console/react/src/topology/qdrTopology.js
+++ b/console/react/src/topology/topologyViewer.js
@@ -19,6 +19,13 @@ under the License.
import React, { Component } from "react";
import * as d3 from "d3";
+import {
+ TopologyView,
+ TopologyControlBar,
+ createTopologyControlButtons,
+ TopologySideBar
+} from "@patternfly/react-topology";
+
import QDRPopup from "../qdrPopup";
import { Traffic } from "./traffic.js";
import { separateAddresses } from "../chord/filters.js";
@@ -28,10 +35,11 @@ import { nextHop, connectionPopupHTML, getSizes } from "./topoUtils.js";
import { BackgroundMap } from "./map.js";
import { utils } from "../amqp/utilities.js";
import { Legend } from "./legend.js";
-import LegendComponent from "./legendComponent";
import RouterInfoComponent from "./routerInfoComponent";
import ClientInfoComponent from "./clientInfoComponent";
-import ContextMenuComponent from "../contextMenuComponent";
+import ContextMenu from "./contextMenu";
+import TopologyToolbar from "./topologyToolbar";
+import LegendComponent from "./legendComponent";
import {
appendCircle,
appendContent,
@@ -80,7 +88,8 @@ class TopologyPage extends Component {
legendOptions: savedOptions,
showRouterInfo: false,
showClientInfo: false,
- showContextMenu: false
+ showContextMenu: false,
+ showLegend: false
};
this.QDRLog = new QDRLogger(console, "Topology");
this.popupCancelled = true;
@@ -117,30 +126,6 @@ class TopologyPage extends Component {
}
);
this.state.mapOptions = this.backgroundMap.mapOptions;
-
- this.contextMenuItems = [
- {
- title: "Freeze in place",
- action: this.setFixed,
- enabled: data => !this.isFixed(data)
- },
- {
- title: "Unfreeze",
- action: this.setFixed,
- enabled: this.isFixed,
- endGroup: true
- },
- {
- title: "Unselect",
- action: this.setSelected,
- enabled: this.isSelected
- },
- {
- title: "Select",
- action: this.setSelected,
- enabled: data => !this.isSelected(data)
- }
- ];
}
// called only once when the component is initialized
@@ -156,9 +141,10 @@ class TopologyPage extends Component {
// get notified when a router is added/dropped and when
// the number of connections for a router changes
- this.props.service.management.topology.addChangedAction("topology", () => {
- this.init();
- });
+ this.props.service.management.topology.addChangedAction(
+ "topology",
+ this.init
+ );
};
componentWillUnmount = () => {
@@ -173,24 +159,16 @@ class TopologyPage extends Component {
resize = () => {
if (!this.svg) return;
- let sizes = getSizes(this.topologyRef, this.QDRLog);
- this.width = sizes[0];
- this.height = sizes[1];
+ const { width, height } = getSizes(this.topologyRef);
+ this.width = width;
+ this.height = height;
if (this.width > 0) {
// set attrs and 'resume' force
this.svg.attr("width", this.width);
this.svg.attr("height", this.height);
- this.force.size(sizes).resume();
+ this.backgroundMap.setWidthHeight(width, height);
+ this.force.size([width, height]).resume();
}
- this.updateLegend();
- };
-
- setFixed = (item, data) => {
- data.setFixed(item.title !== "Unfreeze");
- };
-
- isFixed = data => {
- return data.isFixed();
};
setSelected = (item, data) => {
@@ -203,9 +181,7 @@ class TopologyPage extends Component {
this.selected_node = data.selected ? data : null;
this.restart();
};
- isSelected = data => {
- return data.selected ? true : false;
- };
+
updateLegend = () => {
this.legend.update();
};
@@ -213,9 +189,9 @@ class TopologyPage extends Component {
// initialize the nodes and links array from the QDRService.topology._nodeInfo object
init = () => {
- let sizes = getSizes(this.topologyRef, this.QDRLog);
- this.width = sizes[0];
- this.height = sizes[1];
+ const { width, height } = getSizes(this.topologyRef);
+ this.width = width;
+ this.height = height;
if (this.width < 768) {
const legendOptions = this.state.legendOptions;
legendOptions.map.open = false;
@@ -455,41 +431,10 @@ class TopologyPage extends Component {
// path is a selection of all g elements under the g.links svg:group
// here we associate the links.links array with the {g.links g} selection
// based on the link.uid
- this.path = this.path.data(this.forceData.links.links, function(d) {
+ this.path = this.path.data(this.forceData.links.links, d => {
return d.uid;
});
- // update each existing {g.links g.link} element
- this.path
- .select(".link")
- .classed("selected", function(d) {
- return d.selected;
- })
- .classed("highlighted", function(d) {
- return d.highlighted;
- })
- .classed("unknown", function(d) {
- return !d.right && !d.left;
- });
-
- // reset the markers based on current highlighted/selected
- if (
- !this.state.legendOptions.traffic.open ||
- !this.state.legendOptions.traffic.congestion
- ) {
- this.path
- .select(".link")
- .attr("marker-end", d => {
- if (!this.showMarker(d)) return null;
- return d.right ? `url(#end${d.markerId("end")})` : null;
- })
- .attr("marker-start", d => {
- if (!this.showMarker(d)) return null;
- return d.left || (!d.left && !d.right)
- ? `url(#start${d.markerId("start")})`
- : null;
- });
- }
// add new links. if a link with a new uid is found in the data, add a new path
let enterpath = this.path
.enter()
@@ -548,23 +493,10 @@ class TopologyPage extends Component {
enterpath
.append("path")
.attr("class", "link")
- .attr("marker-end", d => {
- if (!this.showMarker(d)) return null;
- return d.right ? `url(#end${d.markerId("end")})` : null;
- })
- .attr("marker-start", d => {
- if (!this.showMarker(d)) return null;
- return d.left || (!d.left && !d.right)
- ? `url(#start${d.markerId("start")})`
- : null;
- })
.attr("id", function(d) {
const si = d.source.uid();
const ti = d.target.uid();
return ["path", si, ti].join("-");
- })
- .classed("unknown", function(d) {
- return !d.right && !d.left;
});
enterpath
@@ -575,6 +507,24 @@ class TopologyPage extends Component {
// remove old links
this.path.exit().remove();
+ // update each {g.links g.link} element
+ this.path
+ .select(".link")
+ .classed("selected", d => d.selected)
+ .classed("highlighted", d => d.highlighted)
+ .classed("unknown", d => !d.right && !d.left)
+ // reset the markers based on current highlighted/selected
+ .attr("marker-end", d => {
+ if (!this.showMarker(d)) return null;
+ return d.right ? `url(#end${d.markerId("end")})` : null;
+ })
+ .attr("marker-start", d => {
+ if (!this.showMarker(d)) return null;
+ return d.left || (!d.left && !d.right)
+ ? `url(#start${d.markerId("start")})`
+ : null;
+ });
+
// circle (node) group
this.circle = d3
.select("g.nodes")
@@ -583,16 +533,10 @@ class TopologyPage extends Component {
return d.uid();
});
- // update existing nodes visual states
- updateState(this.circle);
-
// add new circle nodes
let enterCircle = this.circle
.enter()
.append("g")
- .classed("multiple", function(d) {
- return d.normals && d.normals.length > 1;
- })
.attr("id", function(d) {
return (d.nodeType !== "normal" ? "router" : "client") + "-" + d.index;
});
@@ -656,9 +600,7 @@ class TopologyPage extends Component {
}
this.mousedown_node = d;
// mouse position relative to svg
- this.initial_mouse_down_position = d3
- .mouse(this.topologyRef.parentNode.parentNode.parentNode)
- .slice();
+ this.initial_mouse_down_position = d3.mouse(this.svg.node());
})
.on("mouseup", function(d) {
// mouse up for circle
@@ -678,10 +620,10 @@ class TopologyPage extends Component {
cur_mouse[1] !== self.initial_mouse_down_position[1]
) {
self.forceData.nodes.setFixed(d, true);
+ self.forceData.nodes.saveLonLat(self.backgroundMap);
self.forceData.nodes.savePositions();
- self.forceData.nodes.saveLonLat(this.backgroundMap);
- self.resetMouseVars();
self.restart();
+ self.resetMouseVars();
return;
}
@@ -737,6 +679,9 @@ class TopologyPage extends Component {
// remove old nodes
this.circle.exit().remove();
+ // update all nodes visual states
+ updateState(this.circle);
+
// add text to client circles if there are any that represent multiple clients
this.svg.selectAll(".subtext").remove();
let multiples = this.svg.selectAll(".multiple");
@@ -866,19 +811,15 @@ class TopologyPage extends Component {
this.setState({ showPopup: false });
return;
}
- let width = this.topologyRef.offsetWidth;
- let top = this.topologyRef.offsetTop - 5;
-
// position the popup
d3.select("#popover-div")
.style("left", event.pageX + 5 + "px")
- .style("top", event.pageY - top + "px");
+ .style("top", `${event.pageY}px`);
// show popup
- let pwidth = this.popupRef.offsetWidth;
this.setState({ showPopup: true }, () =>
d3
.select("#popover-div")
- .style("left", Math.min(width - pwidth, event.pageX + 5) + "px")
+ //.style("left", Math.min(width - pwidth, event.pageX + 5) + "px")
.on("mouseout", () => {
this.setState({ showPopup: false });
})
@@ -1015,51 +956,65 @@ class TopologyPage extends Component {
this.setState({ showContextMenu: false });
};
+ // clicked on the Legend button in the control bar
+ handleLegendClick = id => {
+ this.setState({ showLegend: !this.state.showLegend });
+ };
+
+ // clicked on the x button on the legend
+ handleCloseLegend = () => {
+ this.setState({ showLegend: false });
+ };
+
render() {
+ const controlButtons = createTopologyControlButtons({
+ legendCallback: this.handleLegendClick
+ });
return (
- <div className="qdrTopology">
- <LegendComponent
- addresses={this.state.legendOptions.traffic.addresses}
- addressColors={this.state.legendOptions.traffic.addressColors}
- trafficOpen={this.state.legendOptions.traffic.open}
- legendOpen={this.state.legendOptions.legend.open}
- mapOpen={this.state.legendOptions.map.open}
- mapShown={this.state.legendOptions.map.show}
- arrowsOpen={this.state.legendOptions.arrows.open}
- dots={this.state.legendOptions.traffic.dots}
- congestion={this.state.legendOptions.traffic.congestion}
- routerArrows={this.state.legendOptions.arrows.routerArrows}
- clientArrows={this.state.legendOptions.arrows.clientArrows}
- areaColor={this.state.mapOptions.areaColor}
- oceanColor={this.state.mapOptions.oceanColor}
- handleOpenChange={this.handleOpenChange}
- handleChangeArrows={this.handleChangeArrows}
- handleChangeTrafficAnimation={this.handleChangeTrafficAnimation}
- handleChangeTrafficFlowAddress={this.handleChangeTrafficFlowAddress}
- handleUpdateMapColor={this.handleUpdateMapColor}
- handleUpdateMapShown={this.handleUpdateMapShown}
- />
+ <TopologyView
+ viewToolbar={
+ <TopologyToolbar
+ legendOptions={this.state.legendOptions}
+ mapOptions={this.state.mapOptions}
+ handleOpenChange={this.handleOpenChange}
+ handleChangeArrows={this.handleChangeArrows}
+ handleChangeTrafficAnimation={this.handleChangeTrafficAnimation}
+ handleChangeTrafficFlowAddress={this.handleChangeTrafficFlowAddress}
+ handleUpdateMapColor={this.handleUpdateMapColor}
+ handleUpdateMapShown={this.handleUpdateMapShown}
+ />
+ }
+ controlBar={<TopologyControlBar controlButtons={controlButtons} />}
+ sideBar={<TopologySideBar show={false}></TopologySideBar>}
+ sideBarOpen={false}
+ className="qdrTopology"
+ >
<div className="diagram">
- {this.state.showContextMenu ? (
- <ContextMenuComponent
- ref={el => (this.contextRef = el)}
- contextEventPosition={this.contextEventPosition}
- contextEventData={this.contextEventData}
- handleContextHide={this.handleContextHide}
- menuItems={this.contextMenuItems}
- />
- ) : (
- <React.Fragment />
- )}
<div ref={el => (this.topologyRef = el)} id="topology"></div>
- <div
- id="popover-div"
- className={this.state.showPopup ? "qdrPopup" : "qdrPopup hidden"}
- ref={el => (this.popupRef = el)}
- >
- <QDRPopup content={this.state.popupContent}></QDRPopup>
- </div>
</div>
+ {this.state.showContextMenu && (
+ <ContextMenu
+ contextEventPosition={this.contextEventPosition}
+ contextEventData={this.contextEventData}
+ handleContextHide={this.handleContextHide}
+ setSelected={this.setSelected}
+ />
+ )}
+ {this.state.showLegend && (
+ <LegendComponent
+ nodes={this.forceData.nodes}
+ handleCloseLegend={this.handleCloseLegend}
+ />
+ )}
+
+ <div
+ id="popover-div"
+ className={this.state.showPopup ? "qdrPopup" : "qdrPopup hidden"}
+ ref={el => (this.popupRef = el)}
+ >
+ <QDRPopup content={this.state.popupContent}></QDRPopup>
+ </div>
+
{this.state.showRouterInfo ? (
<RouterInfoComponent
d={this.d}
@@ -1078,7 +1033,7 @@ class TopologyPage extends Component {
) : (
<div />
)}
- </div>
+ </TopologyView>
);
}
}
diff --git a/console/react/src/topology/traffic.js b/console/react/src/topology/traffic.js
index 4cc51b0..fc1b278 100644
--- a/console/react/src/topology/traffic.js
+++ b/console/react/src/topology/traffic.js
@@ -220,6 +220,7 @@ class Congestion extends TrafficAnimation {
color: congestion,
small: small
};
+ /*
path
.classed("traffic", true)
.attr("marker-start", function() {
@@ -228,6 +229,7 @@ class Congestion extends TrafficAnimation {
.attr("marker-end", function() {
return null;
});
+ */
path
.transition()
.duration(1000)
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org