This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new 32c1505 Issue 50545 - Add the new replication monitor functionality to UI
32c1505 is described below
commit 32c1505beaf77a29abd7e9a95f45c98aea2df298
Author: Simon Pichugin <spichugi(a)redhat.com>
AuthorDate: Tue Oct 8 22:59:03 2019 +0200
Issue 50545 - Add the new replication monitor functionality to UI
Description: As we ported repl-monitor.pl to dscon CLI
we should add the functionality to WebUI.
It is important to keep in mind that we shouldn't expose
user's password so the interactive option should be carried out.
Improve replication monitor CLI JSON output consistency.
Add Full Replication report functionality with ability of
continuous refresh.
https://pagure.io/389-ds-base/issue/50545
Reviewed by: mreynolds (Thanks!)
---
src/cockpit/389-console/.eslintrc.json | 2 +-
src/cockpit/389-console/src/css/ds.css | 4 +
.../389-console/src/lib/monitor/monitorModals.jsx | 762 +++++++++++++----
.../389-console/src/lib/monitor/monitorTables.jsx | 944 ++++++++++++++++++++-
.../389-console/src/lib/monitor/replMonitor.jsx | 873 ++++++++++++++++---
src/cockpit/389-console/src/lib/tools.jsx | 8 +-
src/cockpit/389-console/src/monitor.jsx | 3 +
src/lib389/lib389/agreement.py | 30 +-
src/lib389/lib389/cli_conf/backend.py | 2 +-
src/lib389/lib389/cli_conf/replication.py | 95 ++-
src/lib389/lib389/replica.py | 46 +-
11 files changed, 2403 insertions(+), 366 deletions(-)
diff --git a/src/cockpit/389-console/.eslintrc.json
b/src/cockpit/389-console/.eslintrc.json
index 03ff3c4..2f3d88f 100644
--- a/src/cockpit/389-console/.eslintrc.json
+++ b/src/cockpit/389-console/.eslintrc.json
@@ -21,7 +21,7 @@
"ObjectExpression": "first",
"CallExpression": { "arguments": "first"
},
"MemberExpression": 2,
- "ignoredNodes": ["JSXAttribute"]
+ "ignoredNodes": ["JSXAttribute",
"JSXElement", "JSXAttribute *", "JSXElement *"]
}
],
"newline-per-chained-call": ["error", {
"ignoreChainWithDepth": 2 }],
diff --git a/src/cockpit/389-console/src/css/ds.css
b/src/cockpit/389-console/src/css/ds.css
index e6bb1da..662d8de 100644
--- a/src/cockpit/389-console/src/css/ds.css
+++ b/src/cockpit/389-console/src/css/ds.css
@@ -885,6 +885,10 @@ option {
padding-left: 5px;
}
+.ds-raise-field {
+ margin-top: -3px;
+}
+
.content-view-pf-pagination > div > span:last-child {
position: relative;
}
diff --git a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
index 96007ea..e48809a 100644
--- a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
@@ -8,157 +8,16 @@ import {
Button,
Form,
noop,
+ FormGroup,
+ FormControl,
Spinner,
+ Checkbox
} from "patternfly-react";
import PropTypes from "prop-types";
import { get_date_string } from "../tools.jsx";
-import { LagReportTable } from "./monitorTables.jsx";
+import { ReportSingleTable, ReportConsumersTable } from "./monitorTables.jsx";
import "../../css/ds.css";
-class ReplLoginModal extends React.Component {
- render() {
- const {
- showModal,
- closeHandler,
- handleChange,
- doReport,
- spinning,
- error
- } = this.props;
-
- let spinner = "";
- if (spinning) {
- spinner =
- <Row className="ds-margin-top">
- <hr />
- <div className="ds-modal-spinner">
- <Spinner loading inline size="lg"
/>Authenticating to all the replicas ...
- </div>
- </Row>;
- }
-
- return (
- <Modal show={showModal} onHide={closeHandler}>
- <div className="ds-no-horizontal-scrollbar">
- <Modal.Header>
- <button
- className="close"
- onClick={closeHandler}
- aria-hidden="true"
- aria-label="Close"
- >
- <Icon type="pf" name="close" />
- </button>
- <Modal.Title>
- Replication Login Credentials
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <Form horizontal autoComplete="off">
- <p>
- In order to get the replication agreement lag times and
state the
- authentication credentials to the remote replicas must be
provided.
- This only works if the bind credentials used are valid on
all the
- replicas.
- </p>
- <hr />
- <Row>
- <Col sm={3}>
- <ControlLabel>
- Bind DN
- </ControlLabel>
- </Col>
- <Col sm={9}>
- <input
- className={error.binddn ?
"ds-input-auto-bad" : "ds-input-auto"}
- onChange={handleChange}
defaultValue="cn=Directory Manager"
- type="text" id="binddn"
- />
- </Col>
- </Row>
- <Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Password
- </ControlLabel>
- </Col>
- <Col sm={9}>
- <input
- className={error.bindpw ?
"ds-input-auto-bad" : "ds-input-auto"}
- onChange={handleChange} type="password"
id="bindpw"
- />
- </Col>
- </Row>
- {spinner}
- </Form>
- </Modal.Body>
- <Modal.Footer>
- <Button
- bsStyle="default"
- className="btn-cancel"
- onClick={closeHandler}
- >
- Close
- </Button>
- <Button
- bsStyle="primary"
- onClick={doReport}
- >
- Get Report
- </Button>
- </Modal.Footer>
- </div>
- </Modal>
- );
- }
-}
-
-class ReplLagReportModal extends React.Component {
- render() {
- const {
- showModal,
- closeHandler,
- agmts,
- pokeAgmt,
- viewAgmt
- } = this.props;
-
- return (
- <Modal backdrop="static"
contentClassName="ds-lag-report" show={showModal} onHide={closeHandler}>
- <Modal.Header>
- <button
- className="close"
- onClick={closeHandler}
- aria-hidden="true"
- aria-label="Close"
- >
- <Icon type="pf" name="close" />
- </button>
- <Modal.Title>
- Replication Lag Report
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <LagReportTable
- agmts={agmts}
- pokeAgmt={pokeAgmt}
- viewAgmt={viewAgmt}
- />
- </Modal.Body>
- <Modal.Footer>
- <Button
- bsStyle="default"
- className="btn-cancel"
- onClick={closeHandler}
- >
- Close
- </Button>
- </Modal.Footer>
- </Modal>
- );
- }
-}
-
class TaskLogModal extends React.Component {
render() {
const {
@@ -224,6 +83,16 @@ class AgmtDetailsModal extends React.Component {
convertedDate[attr] = get_date_string(agmt[attr]);
}
}
+ let initButton = null;
+ if (!this.props.isRemoteAgmt) {
+ initButton = <Button
+ bsStyle="default"
+ className="btn-primary ds-float-left"
+ onClick={this.props.initAgmt}
+ >
+ Initialize Agreement
+ </Button>;
+ }
return (
<Modal show={showModal} onHide={closeHandler}>
@@ -327,13 +196,7 @@ class AgmtDetailsModal extends React.Component {
</Form>
</Modal.Body>
<Modal.Footer>
- <Button
- bsStyle="default"
- className="btn-primary ds-float-left"
- onClick={this.props.initAgmt}
- >
- Initialize Agreement
- </Button>
+ {initButton}
<Button
bsStyle="default"
className="btn-cancel"
@@ -645,12 +508,494 @@ class ConflictCompareModal extends React.Component {
}
}
+class ReportCredentialsModal extends React.Component {
+ render() {
+ const {
+ handleFieldChange,
+ showModal,
+ closeHandler,
+ newEntry,
+ hostname,
+ port,
+ binddn,
+ pwInputInterractive,
+ bindpw,
+ addConfig,
+ editConfig
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>{newEntry ? "Add" :
"Edit"} Report Credentials</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal autoComplete="off">
+ <FormGroup
controlId="credsHostname">
+ <Col sm={3}>
+ <ControlLabel title="A regex for
hostname">
+ Hostname
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={hostname}
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="credsPort">
+ <Col sm={3}>
+ <ControlLabel title="A regex for
port">
+ Port
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={port}
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="credsBinddn">
+ <Col sm={3}>
+ <ControlLabel title="Bind DN for the
specified instances">
+ Bind DN
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={binddn}
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="credsBindpw">
+ <Col sm={3}>
+ <ControlLabel title="Bind password
for the specified instances">
+ Password
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="password"
+ value={bindpw}
+ onChange={handleFieldChange}
+ disabled={pwInputInterractive}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
controlId="interractiveInput">
+ <Col sm={3}>
+ <ControlLabel title="Input the
password interactively">
+ Interractive Input
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <Checkbox
+ checked={pwInputInterractive}
+ id="pwInputInterractive"
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button bsStyle="default"
className="btn-cancel" onClick={closeHandler}>
+ Cancel
+ </Button>
+ <Button bsStyle="primary" onClick={newEntry ?
addConfig : editConfig}>
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class ReportAliasesModal extends React.Component {
+ render() {
+ const {
+ handleFieldChange,
+ showModal,
+ closeHandler,
+ newEntry,
+ hostname,
+ port,
+ alias,
+ addConfig,
+ editConfig
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>{newEntry ? "Add" :
"Edit"} Report Credentials</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup controlId="aliasName">
+ <Col sm={3}>
+ <ControlLabel title="Alias name for
the instance">
+ Alias
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={alias}
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup
controlId="aliasHostname">
+ <Col sm={3}>
+ <ControlLabel title="An instance
hostname">
+ Hostname
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={hostname}
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="aliasPort">
+ <Col sm={3}>
+ <ControlLabel title="An instance
port">
+ Port
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="number"
+ value={port}
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button bsStyle="default"
className="btn-cancel" onClick={closeHandler}>
+ Cancel
+ </Button>
+ <Button bsStyle="primary" onClick={newEntry ?
addConfig : editConfig}>
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class ReportLoginModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ processCredsInput,
+ instanceName,
+ disableBinddn,
+ loginBinddn,
+ loginBindpw
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>Replication Login Credentials for
{instanceName}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <p>
+ In order to get the replication agreement lag times and
state the
+ authentication credentials to the remote replicas must be
provided.
+ </p>
+ <hr />
+ <p>
+ Bind DN was acquired from <b>Replica
Credentials</b> table. If you want
+ to bind as another user, change or remove the Bind DN
there.
+ </p>
+ <br />
+ <FormGroup controlId="loginBinddn">
+ <Col sm={3}>
+ <ControlLabel title="Bind DN for the
instance">
+ Bind DN
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={loginBinddn}
+ onChange={handleChange}
+ disabled={disableBinddn}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="loginBindpw">
+ <Col sm={3}>
+ <ControlLabel title="Password for the Bind
DN">
+ Password
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="password"
+ value={loginBindpw}
+ onChange={handleChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button bsStyle="default"
className="btn-cancel" onClick={closeHandler}>
+ Close
+ </Button>
+ <Button bsStyle="primary"
onClick={processCredsInput}>
+ Confirm Credentials Input
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class FullReportContent extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ oneTableReport: false,
+ showDisabledAgreements: false
+ };
+
+ this.handleSwitchChange = this.handleSwitchChange.bind(this);
+ }
+
+ handleSwitchChange(e) {
+ if (typeof e === "boolean") {
+ // Handle Switch object
+ this.setState({
+ oneTableReport: e
+ });
+ } else {
+ this.setState({
+ [e.target.id]: e.target.checked
+ });
+ }
+ }
+
+ render() {
+ const {
+ reportData,
+ handleRefresh,
+ reportRefreshing,
+ reportLoading
+ } = this.props;
+
+ let suppliers = [];
+ let supplierName;
+ let supplierData;
+ let resultRows = [];
+ let spinner = <ControlLabel />;
+ if (reportLoading) {
+ spinner = (
+ <div>
+ <ControlLabel title="Do the refresh every few
seconds">
+ {reportRefreshing ? "Refreshing" : "Loading"}
the report...
+ </ControlLabel>
+ <Spinner inline loading size="sm" />
+ </div>
+ );
+ }
+ let reportHeader = "";
+ if (reportData.length > 0) {
+ reportHeader = (
+ <Form horizontal autoComplete="off">
+ <FormGroup controlId="showDisabledAgreements">
+ <Col sm={8}>
+ <Checkbox
+ checked={this.state.showDisabledAgreements}
+ id="showDisabledAgreements"
+ onChange={this.handleSwitchChange}
+ title="Display all agreements including the disabled
ones and the ones we failed to connect to"
+ >
+ Show All (Including Disabled Agreements)
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="oneTableReport">
+ <Col sm={6} title="Show all data in one table (it makes
it easier to check lag times)">
+ <Checkbox
+ checked={this.state.oneTableReport}
+ onChange={this.handleSwitchChange}
+ id="oneTableReport"
+ title="Display all agreements including the disabled
ones and the ones we failed to connect to"
+ >
+ Table View
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ <Button
+ className="ds-margin-top"
+ bsStyle="default"
+ onClick={handleRefresh}
+ >
+ Refresh Report
+ </Button>
+ <hr />
+ </Form>
+ );
+ } else {
+ reportHeader = spinner;
+ }
+ if (this.state.oneTableReport) {
+ for (let supplier of reportData) {
+ for (let replica of supplier.data) {
+ resultRows = resultRows.concat(replica.agmts_status);
+ }
+ suppliers.push(supplierData);
+ }
+ suppliers = [(<div>
+ <ReportSingleTable
+ rows={resultRows}
+ viewAgmt={this.props.viewAgmt}
+ />
+ </div>
+ )];
+ } else {
+ for (let supplier of reportData) {
+ let s_data = supplier.data;
+ if (s_data.length === 1 &&
s_data[0].replica_status.startsWith("Unavailable")) {
+ supplierData = (
+ <div>
+ <h4>
+ <b>Can not get replication information from
Replica</b>
+ </h4>
+ <h4 title="Supplier availability status">
+ <b>Replica Status:</b>
{s_data[0].replica_status}
+ </h4>
+ </div>
+ );
+ } else {
+ supplierData = supplier.data.map(replica => (
+ <div key={replica.replica_root + replica.replica_id}>
+ <h4 title="Replica Root suffix">
+ <b>Replica Root:</b> {replica.replica_root}
+ </h4>
+ <h4 title="Replica ID">
+ <b>Replica ID:</b> {replica.replica_id}
+ </h4>
+ <h4 title="Replica Status">
+ <b>Replica Status:</b>
{replica.replica_status}
+ </h4>
+ <h4 title="Max CSN">
+ <b>Max CSN:</b> {replica.maxcsn}
+ </h4>
+ {"agmts_status" in replica &&
+ replica.agmts_status.length > 0 &&
+ "agmt-name" in replica.agmts_status[0] ? (
+ <ReportConsumersTable
+ rows={replica.agmts_status}
+ viewAgmt={this.props.viewAgmt}
+ />
+ ) : (
+ <h4>
+ <b>No Agreements Were Found</b>
+ </h4>
+ )}
+ </div>
+ ));
+ }
+ supplierName = (
+ <div key={supplier.name}>
+ <center>
+ <h2 title="Supplier host:port (and alias if
applicable)">
+ <b>Supplier:</b> {supplier.name}
+ </h2>
+ </center>
+ <hr />
+ {supplierData}
+ </div>
+ );
+ suppliers.push(supplierName);
+ }
+ }
+
+ let report = suppliers.map(supplier => (
+ <div key={supplier.key}>
+ {supplier}
+ <hr />
+ </div>
+ ));
+ if (reportLoading) {
+ report =
+ <Col sm={12} className="ds-center ds-margin-top">
+ {spinner}
+ </Col>;
+ }
+
+ return (
+ <div>
+ {reportHeader}
+ {report}
+ </div>
+ );
+ }
+}
// Prototypes and defaultProps
AgmtDetailsModal.propTypes = {
showModal: PropTypes.bool,
closeHandler: PropTypes.func,
agmt: PropTypes.object,
initAgmt: PropTypes.func,
+ isRemoteAgmt: PropTypes.bool
};
AgmtDetailsModal.defaultProps = {
@@ -658,6 +1003,7 @@ AgmtDetailsModal.defaultProps = {
closeHandler: noop,
agmt: {},
initAgmt: noop,
+ isRemoteAgmt: false
};
WinsyncAgmtDetailsModal.propTypes = {
@@ -686,24 +1032,6 @@ TaskLogModal.defaultProps = {
agreement: "",
};
-ReplLoginModal.propTypes = {
- showModal: PropTypes.bool,
- closeHandler: PropTypes.func,
- handleChange: PropTypes.func,
- doReport: PropTypes.func,
- spinning: PropTypes.bool,
- error: PropTypes.object,
-};
-
-ReplLoginModal.defaultProps = {
- showModal: false,
- closeHandler: noop,
- handleChange: noop,
- doReport: noop,
- spinning: false,
- error: {},
-};
-
ConflictCompareModal.propTypes = {
showModal: PropTypes.bool,
conflictEntry: PropTypes.object,
@@ -722,11 +1050,101 @@ ConflictCompareModal.defaultProps = {
closeHandler: noop,
};
+ReportCredentialsModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleFieldChange: PropTypes.func,
+ hostname: PropTypes.string,
+ port: PropTypes.string,
+ binddn: PropTypes.string,
+ bindpw: PropTypes.string,
+ pwInputInterractive: PropTypes.bool,
+ newEntry: PropTypes.bool,
+ addConfig: PropTypes.func,
+ editConfig: PropTypes.func
+};
+
+ReportCredentialsModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleFieldChange: noop,
+ hostname: "",
+ port: "",
+ binddn: "",
+ bindpw: "",
+ pwInputInterractive: false,
+ newEntry: false,
+ addConfig: noop,
+ editConfig: noop,
+};
+
+ReportAliasesModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleFieldChange: PropTypes.func,
+ hostname: PropTypes.string,
+ port: PropTypes.number,
+ alias: PropTypes.string,
+ newEntry: PropTypes.bool,
+ addConfig: PropTypes.func,
+ editConfig: PropTypes.func
+};
+
+ReportAliasesModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleFieldChange: noop,
+ hostname: "",
+ port: 389,
+ alias: "",
+ newEntry: false,
+ addConfig: noop,
+ editConfig: noop,
+};
+
+ReportLoginModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ processCredsInput: PropTypes.func,
+ instanceName: PropTypes.string,
+ disableBinddn: PropTypes.bool,
+ loginBinddn: PropTypes.string,
+ loginBindpw: PropTypes.string
+};
+
+ReportLoginModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ processCredsInput: noop,
+ instanceName: "",
+ disableBinddn: false,
+ loginBinddn: "",
+ loginBindpw: ""
+};
+
+FullReportContent.propTypes = {
+ reportData: PropTypes.array,
+ handleRefresh: PropTypes.func,
+ reportRefreshing: PropTypes.bool
+};
+
+FullReportContent.defaultProps = {
+ handleFieldChange: noop,
+ reportData: [],
+ handleRefresh: noop,
+ reportRefreshTimeout: 5,
+ reportRefreshing: false
+};
+
export {
TaskLogModal,
AgmtDetailsModal,
- ReplLagReportModal,
- ReplLoginModal,
WinsyncAgmtDetailsModal,
ConflictCompareModal,
+ ReportCredentialsModal,
+ ReportAliasesModal,
+ ReportLoginModal,
+ FullReportContent
};
diff --git a/src/cockpit/389-console/src/lib/monitor/monitorTables.jsx
b/src/cockpit/389-console/src/lib/monitor/monitorTables.jsx
index 21c0128..d38e2fc 100644
--- a/src/cockpit/389-console/src/lib/monitor/monitorTables.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/monitorTables.jsx
@@ -11,7 +11,7 @@ import {
import { DSTable, DSShortTable } from "../dsTable.jsx";
import PropTypes from "prop-types";
import "../../css/ds.css";
-import { get_date_string } from "../tools.jsx";
+import { get_date_string, searchFilter } from "../tools.jsx";
class AbortCleanALLRUVTable extends React.Component {
constructor(props) {
@@ -542,16 +542,16 @@ class WinsyncAgmtTable extends React.Component {
formatters: [
(value, { rowData }) => {
return [
- <td key={rowData['agmt-name']}>
- <DropdownButton
id={rowData['agmt-name']}
+ <td key={rowData['agmt-name'][0]}>
+ <DropdownButton
id={rowData['agmt-name'][0]}
bsStyle="default"
title="Actions">
<MenuItem eventKey="1"
onClick={() => {
-
this.props.viewAgmt(rowData['agmt-name']);
+
this.props.viewAgmt(rowData['agmt-name'][0]);
}}>
View Agreement Details
</MenuItem>
<MenuItem eventKey="2"
onClick={() => {
-
this.props.pokeAgmt(rowData['agmt-name']);
+
this.props.pokeAgmt(rowData['agmt-name'][0]);
}}>
Poke Agreement
</MenuItem>
@@ -760,16 +760,16 @@ class AgmtTable extends React.Component {
formatters: [
(value, { rowData }) => {
return [
- <td key={rowData['agmt-name']}>
- <DropdownButton
id={rowData['agmt-name']}
+ <td key={rowData['agmt-name'][0]}>
+ <DropdownButton
id={rowData['agmt-name'][0]}
bsStyle="default"
title="Actions">
<MenuItem eventKey="1"
onClick={() => {
-
this.props.viewAgmt(rowData['agmt-name']);
+
this.props.viewAgmt(rowData['agmt-name'][0]);
}}>
View Agreement Details
</MenuItem>
<MenuItem eventKey="2"
onClick={() => {
-
this.props.pokeAgmt(rowData['agmt-name']);
+
this.props.pokeAgmt(rowData['agmt-name'][0]);
}}>
Poke Agreement
</MenuItem>
@@ -1175,16 +1175,16 @@ class LagReportTable extends React.Component {
formatters: [
(value, { rowData }) => {
return [
- <td key={rowData['agmt-name']}>
- <DropdownButton
id={rowData['agmt-name']}
+ <td key={rowData['agmt-name'][0]}>
+ <DropdownButton
id={rowData['agmt-name'][0]}
bsStyle="default"
title="Actions">
<MenuItem eventKey="1"
onClick={() => {
-
this.props.viewAgmt(rowData['agmt-name']);
+
this.props.viewAgmt(rowData['agmt-name'][0]);
}}>
View Agreement Details
</MenuItem>
<MenuItem eventKey="2"
onClick={() => {
-
this.props.pokeAgmt(rowData['agmt-name']);
+
this.props.pokeAgmt(rowData['agmt-name'][0]);
}}>
Poke Agreement
</MenuItem>
@@ -1662,6 +1662,770 @@ class DiskTable extends React.Component {
header: {
label: "Available Space",
props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+
+ ]
+ };
+ this.getColumns = this.getColumns.bind(this);
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSShortTable
+ getColumns={this.getColumns}
+ rowKey={this.state.rowKey}
+ rows={this.props.disks}
+ disableLoadingSpinner
+ />
+ </div>
+ );
+ }
+}
+
+class ReportAliasesTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+
+ this.state = {
+ searchField: "Aliases",
+ fieldsToSearch: ["alias", "connData"],
+
+ columns: [
+ {
+ property: "alias",
+ header: {
+ label: "Alias",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "connData",
+ header: {
+ label: "Connection Data",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.alias}>
+ <DropdownButton
+ id={rowData.alias}
+ bsStyle="default"
+ title="Actions"
+ >
+ <MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+ >
+ Edit Alias
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+ >
+ Delete Alias
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Instance Aliases",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ render() {
+ let reportAliasTable;
+ if (this.props.rows.length < 1) {
+ reportAliasTable = (
+ <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{ msg: "No alias entries" }]}
+ disableLoadingSpinner
+ />
+ );
+ } else {
+ reportAliasTable = (
+ <DSShortTable
+ getColumns={this.getColumns}
+ rowKey="alias"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ />
+ );
+ }
+
+ return <div
className="ds-margin-top-xlg">{reportAliasTable}</div>;
+ }
+}
+
+class ReportCredentialsTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+
+ this.state = {
+ columns: [
+ {
+ property: "connData",
+ header: {
+ label: "Connection Data",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "credsBinddn",
+ header: {
+ label: "Bind DN",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.connData}>
+ {value == "" ? <i>Edit To Add a
Bind DN Data</i> : value }
+ </td>
+ ];
+ }
+ ]
+ }
+ },
+ {
+ property: "credsBindpw",
+ header: {
+ label: "Password",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [
+ (value, { rowData }) => {
+ let pwField = <i>Interractive Input is
set</i>;
+ if (!rowData.pwInputInterractive) {
+ if (value == "") {
+ pwField = <i>Both Password or Interractive
Input flag are not set</i>;
+ } else {
+ pwField = "********";
+ }
+ }
+ return [
+ <td key={rowData.connData}>
+ {pwField}
+ </td>
+ ];
+ }
+ ]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.connData}>
+ <DropdownButton
+ id={rowData.connData}
+ bsStyle="default"
+ title="Actions"
+ >
+ <MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+ >
+ Edit Connection
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+ >
+ Delete Connection
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Replica Credentials Table",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ render() {
+ let reportConnTable;
+ if (this.props.rows.length < 1) {
+ reportConnTable = (
+ <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{ msg: "No connection entries" }]}
+ disableLoadingSpinner
+ />
+ );
+ } else {
+ reportConnTable = (
+ <DSShortTable
+ getColumns={this.getColumns}
+ rowKey="connData"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ />
+ );
+ }
+
+ return <div
className="ds-margin-top-xlg">{reportConnTable}</div>;
+ }
+}
+
+class ReportSingleTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+
+ this.state = {
+ searchField: "Replica",
+ fieldsToSearch: [
+ "supplierName",
+ "replicaName",
+ "replicaStatus",
+ "agmt-name",
+ "replica",
+ "replicaStatus",
+ "replica-enabled",
+ "replication-lag-time"
+ ],
+
+ columns: [
+ {
+ property: "supplierName",
+ header: {
+ label: "Supplier",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replicaName",
+ header: {
+ label: "Suffix:ReplicaID",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replicaStatus",
+ header: {
+ label: "Replica Status",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "agmt-name",
+ header: {
+ label: "Agreement",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.rowKey}>
+ {value || <i>No Agreements Were
Found</i>}
+ </td>
+ ];
+ }
+ ]
+ }
+ },
+ {
+ property: "replica",
+ header: {
+ label: "Consumer",
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replica-enabled",
+ header: {
+ label: "Is Enabled",
+ props: {
+ index: 5,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 5
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replication-lag-time",
+ header: {
+ label: "Lag Time",
+ props: {
+ index: 6,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 6
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 7,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 7
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.rowKey}>
+ <Button
+ onClick={() => {
+
this.props.viewAgmt(rowData['supplierName'][0],
+
rowData['replicaName'][0],
+
rowData['agmt-name'][0]);
+ }}
+ >
+ View Data
+ </Button>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "All In One Report",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ render() {
+ let reportSingleTable;
+ let filteredRows = this.props.rows;
+ if (!this.props.showDisabledAgreements) {
+ filteredRows = searchFilter("on", ["replica-enabled"],
filteredRows);
+ }
+ if (filteredRows.length < 1) {
+ reportSingleTable = (
+ <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{ msg: "No replica entries" }]}
+ disableLoadingSpinner
+ noSearchBar
+ />
+ );
+ } else {
+ reportSingleTable = (
+ <DSShortTable
+ getColumns={this.getColumns}
+ rowKey="rowKey"
+ rows={filteredRows}
+ disableLoadingSpinner
+ noSearchBar
+ />
+ );
+ }
+
+ return <div>{reportSingleTable}</div>;
+ }
+}
+
+class ReportConsumersTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+
+ this.state = {
+ searchField: "Agreements",
+ fieldsToSearch: [
+ "agmt-name",
+ "replica-enabled",
+ "replication-status",
+ "replication-lag-time"
+ ],
+
+ columns: [
+ {
+ property: "agmt-name",
+ header: {
+ label: "Agreement Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replica-enabled",
+ header: {
+ label: "Is Enabled",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replication-status",
+ header: {
+ label: "Replication Status",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replication-lag-time",
+ header: {
+ label: "Replication Lag Time",
+ props: {
index: 3,
rowSpan: 1,
colSpan: 1,
@@ -1678,30 +2442,104 @@ class DiskTable extends React.Component {
formatters: [tableCellFormatter]
}
},
-
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.rowKey}>
+ <Button
+ onClick={() => {
+
this.props.viewAgmt(rowData['supplierName'][0],
+
rowData['replicaName'][0],
+
rowData['agmt-name'][0]);
+ }}
+ >
+ View Data
+ </Button>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
]
};
- this.getColumns = this.getColumns.bind(this);
}
getColumns() {
return this.state.columns;
}
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Report Consumers",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
render() {
- return (
- <div className="ds-margin-top-xlg">
+ let reportConsumersTable;
+ let filteredRows = this.props.rows;
+ if (!this.props.showDisabledAgreements) {
+ filteredRows = searchFilter("on", ["replica-enabled"],
filteredRows);
+ }
+ if (filteredRows.length < 1) {
+ reportConsumersTable = (
+ <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{ msg: "No agreement entries" }]}
+ disableLoadingSpinner
+ noSearchBar
+ />
+ );
+ } else {
+ reportConsumersTable = (
<DSShortTable
getColumns={this.getColumns}
- rowKey={this.state.rowKey}
- rows={this.props.disks}
+ rowKey="rowKey"
+ rows={filteredRows}
disableLoadingSpinner
+ noSearchBar
/>
- </div>
- );
+ );
+ }
+
+ return <div>{reportConsumersTable}</div>;
}
}
-
// Proptypes and defaults
LagReportTable.propTypes = {
@@ -1790,6 +2628,62 @@ GlueTable.defaultProps = {
deleteGlue: noop,
};
+ReportCredentialsTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+ReportCredentialsTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+ReportAliasesTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+ReportAliasesTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+ReportConsumersTable.propTypes = {
+ showDisabledAgreements: PropTypes.bool,
+ rows: PropTypes.array,
+ viewAgmt: PropTypes.func
+};
+
+ReportConsumersTable.defaultProps = {
+ showDisabledAgreements: false,
+ rows: [],
+ viewAgmt: noop
+};
+
+ReportSingleTable.propTypes = {
+ showDisabledAgreements: PropTypes.bool,
+ rows: PropTypes.array,
+ viewAgmt: PropTypes.func
+};
+
+ReportSingleTable.defaultProps = {
+ showDisabledAgreements: false,
+ rows: [],
+ viewAgmt: noop
+};
+
+DiskTable.defaultProps = {
+ rows: PropTypes.array
+};
+
+DiskTable.defaultProps = {
+ rows: []
+};
+
export {
ConnectionTable,
AgmtTable,
@@ -1799,5 +2693,9 @@ export {
AbortCleanALLRUVTable,
ConflictTable,
GlueTable,
- DiskTable,
+ ReportCredentialsTable,
+ ReportAliasesTable,
+ ReportConsumersTable,
+ ReportSingleTable,
+ DiskTable
};
diff --git a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
index ffbb5cc..482834b 100644
--- a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
@@ -5,6 +5,8 @@ import PropTypes from "prop-types";
import "../../css/ds.css";
import { ConfirmPopup } from "../notifications.jsx";
import {
+ ReportCredentialsTable,
+ ReportAliasesTable,
AgmtTable,
WinsyncAgmtTable,
CleanALLRUVTable,
@@ -13,11 +15,13 @@ import {
GlueTable,
} from "./monitorTables.jsx";
import {
+ FullReportContent,
+ ReportLoginModal,
+ ReportCredentialsModal,
+ ReportAliasesModal,
TaskLogModal,
AgmtDetailsModal,
WinsyncAgmtDetailsModal,
- ReplLagReportModal,
- ReplLoginModal,
ConflictCompareModal,
} from "./monitorModals.jsx";
import {
@@ -29,39 +33,151 @@ import {
Button,
noop
} from "patternfly-react";
+import CustomCollapse from "../customCollapse.jsx";
+
+const _ = cockpit.gettext;
export class ReplMonitor extends React.Component {
+ componentDidUpdate(prevProps, prevState) {
+ if (!(prevState.showReportLoginModal) &&
(this.state.showReportLoginModal)) {
+ // When the login modal turned on
+ // We set timeout to close it and stop the report
+ if (this.timer) window.clearTimeout(this.timer);
+
+ this.timer = window.setTimeout(() => {
+ this.setState({
+ showFullReportModal: false
+ });
+ this.timer = null;
+ }, 300);
+ }
+ if ((prevState.showReportLoginModal) &&
!(this.state.showReportLoginModal)) {
+ // When the login modal turned off
+ // We clear the timeout
+ if (this.timer) window.clearTimeout(this.timer);
+ }
+ }
+
+ componentWillUnmount() {
+ // It's important to do so we don't get the error
+ // on the unmounted component
+ if (this.timer) window.clearTimeout(this.timer);
+ }
+
+ componentDidMount() {
+ if (this.state.initCreds) {
+ let cmd = ["dsconf", "-j",
"ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "config", "get", "nsslapd-port",
"nsslapd-localhost", "nsslapd-rootdn"];
+ log_cmd("ReplMonitor", "add credentials during
componentDidMount", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ this.setState(prevState => ({
+ credentialsList: [
+ ...prevState.credentialsList,
+ {
+ connData:
`${config.attrs["nsslapd-localhost"]}:${config.attrs["nsslapd-port"]}`,
+ credsBinddn:
config.attrs["nsslapd-rootdn"],
+ credsBindpw: "",
+ pwInputInterractive: true
+ }
+ ]
+ }));
+ for (let agmt of this.props.data.replAgmts) {
+ this.setState(prevState => ({
+ credentialsList: [
+ ...prevState.credentialsList,
+ {
+ connData: `${agmt.replica}`,
+ credsBinddn:
config.attrs["nsslapd-rootdn"],
+ credsBindpw: "",
+ pwInputInterractive: true
+ }
+ ],
+ initCreds: false
+ }));
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to get config nsslapd-port, nsslapd-localhost and
nasslapd-rootdn: ${errMsg.desc}`
+ );
+ });
+ }
+ this.props.enableTree();
+ }
+
constructor (props) {
super(props);
this.state = {
activeKey: 1,
+ activeReportKey: 1,
logData: "",
showBindModal: false,
showLogModal: false,
showAgmtModal: false,
+ isRemoteAgmt: false,
+ showFullReportModal: false,
+ showReportLoginModal: false,
+ showCredentialsModal: false,
+ showAliasesModal: false,
showWinsyncAgmtModal: false,
showInitWinsyncConfirm: false,
showInitConfirm: false,
- showLoginModal: false,
- showLagReport: false,
showCompareModal: false,
showConfirmDeleteGlue: false,
showConfirmConvertGlue: false,
showConfirmSwapConflict: false,
showConfirmConvertConflict: false,
showConfirmDeleteConflict: false,
- reportLoading: false,
lagAgmts: [],
+ credsData: [],
+ aliasData: [],
+ reportData: [],
agmt: "",
convertRDN: "",
glueEntry: "",
conflictEntry: "",
binddn: "cn=Directory Manager",
bindpw: "",
- errObj: {}
+ errObj: {},
+ aliasList: [],
+ newEntry: false,
+ initCreds: true,
+
+ fullReportProcess: {},
+ interruptLoginCredsInput: false,
+ doFullReportCleanup: false,
+ reportRefreshing: false,
+ reportLoading: false,
+
+ credsInstanceName: "",
+ disableBinddn: false,
+ loginBinddn: "",
+ loginBindpw: "",
+ inputLoginData: false,
+
+ credsHostname: "",
+ credsPort: "",
+ credsBinddn: "cn=Directory Manager",
+ credsBindpw: "",
+ pwInputInterractive: false,
+
+ aliasHostname: "",
+ aliasPort: 389,
+ aliasName: "",
+
+ credentialsList: [],
+ dynamicCredentialsList: [],
+ aliasesList: []
};
+ this.handleFieldChange = this.handleFieldChange.bind(this);
this.handleNavSelect = this.handleNavSelect.bind(this);
+ this.handleReportNavSelect = this.handleReportNavSelect.bind(this);
this.pokeAgmt = this.pokeAgmt.bind(this);
this.initAgmt = this.initAgmt.bind(this);
this.initWinsyncAgmt = this.initWinsyncAgmt.bind(this);
@@ -71,17 +187,37 @@ export class ReplMonitor extends React.Component {
this.closeInitWinsyncConfirm = this.closeInitWinsyncConfirm.bind(this);
this.pokeWinsyncAgmt = this.pokeWinsyncAgmt.bind(this);
this.showAgmtModal = this.showAgmtModal.bind(this);
+ this.showAgmtModalRemote = this.showAgmtModalRemote.bind(this);
this.closeAgmtModal = this.closeAgmtModal.bind(this);
this.showWinsyncAgmtModal = this.showWinsyncAgmtModal.bind(this);
this.closeWinsyncAgmtModal = this.closeWinsyncAgmtModal.bind(this);
- this.getLagReportCreds = this.getLagReportCreds.bind(this);
- this.doLagReport = this.doLagReport.bind(this);
- this.closeLagReport = this.closeLagReport.bind(this);
this.viewCleanLog = this.viewCleanLog.bind(this);
this.viewAbortLog = this.viewAbortLog.bind(this);
this.closeLogModal = this.closeLogModal.bind(this);
- this.handleLoginModal = this.handleLoginModal.bind(this);
- this.closeLoginModal = this.closeLoginModal.bind(this);
+ this.closeReportLoginModal = this.closeReportLoginModal.bind(this);
+
+ // Replication report functions
+ this.addCreds = this.addCreds.bind(this);
+ this.editCreds = this.editCreds.bind(this);
+ this.removeCreds = this.removeCreds.bind(this);
+ this.openCredsModal = this.openCredsModal.bind(this);
+ this.showAddCredsModal = this.showAddCredsModal.bind(this);
+ this.showEditCredsModal = this.showEditCredsModal.bind(this);
+ this.closeCredsModal = this.closeCredsModal.bind(this);
+
+ this.addAliases = this.addAliases.bind(this);
+ this.editAliases = this.editAliases.bind(this);
+ this.removeAliases = this.removeAliases.bind(this);
+ this.openAliasesModal = this.openAliasesModal.bind(this);
+ this.showAddAliasesModal = this.showAddAliasesModal.bind(this);
+ this.showEditAliasesModal = this.showEditAliasesModal.bind(this);
+ this.closeAliasesModal = this.closeAliasesModal.bind(this);
+
+ this.doFullReport = this.doFullReport.bind(this);
+ this.processCredsInput = this.processCredsInput.bind(this);
+ this.closeReportModal = this.closeReportModal.bind(this);
+ this.refreshFullReport = this.refreshFullReport.bind(this);
+
// Conflict entry functions
this.convertConflict = this.convertConflict.bind(this);
this.swapConflict = this.swapConflict.bind(this);
@@ -105,8 +241,18 @@ export class ReplMonitor extends React.Component {
this.closeConfirmSwapConflict = this.closeConfirmSwapConflict.bind(this);
}
- componentDidMount() {
- this.props.enableTree();
+ handleFieldChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked :
e.target.value;
+ if (e.target.type === 'number') {
+ if (e.target.value) {
+ value = parseInt(e.target.value);
+ } else {
+ value = 1;
+ }
+ }
+ this.setState({
+ [e.target.id]: value
+ });
}
convertConflict (dn) {
@@ -341,6 +487,12 @@ export class ReplMonitor extends React.Component {
});
}
+ handleReportNavSelect(key) {
+ this.setState({
+ activeReportKey: key
+ });
+ }
+
closeLogModal() {
this.setState({
showLogModal: false
@@ -422,6 +574,7 @@ export class ReplMonitor extends React.Component {
if (agmt['agmt-name'] == name) {
this.setState({
showAgmtModal: true,
+ isRemoteAgmt: false,
agmt: agmt
});
break;
@@ -429,6 +582,34 @@ export class ReplMonitor extends React.Component {
}
}
+ showAgmtModalRemote (supplierName, replicaName, agmtName) {
+ if (!agmtName) {
+ this.props.addNotification(
+ "error",
+ `The agreement doesn't exist!`
+ );
+ } else {
+ for (let supplier of this.state.reportData) {
+ if (supplier.name == supplierName) {
+ for (let replica of supplier.data) {
+ if (`${replica.replica_root}:${replica.replica_id}` ==
replicaName) {
+ for (let agmt of replica.agmts_status) {
+ if (agmt['agmt-name'][0] == agmtName) {
+ this.setState({
+ showAgmtModal: true,
+ isRemoteAgmt: true,
+ agmt: agmt
+ });
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
closeAgmtModal() {
this.setState({
showAgmtModal: false,
@@ -533,108 +714,507 @@ export class ReplMonitor extends React.Component {
});
}
- getLagReportCreds () {
- if (this.props.data.replAgmts.length == 0) {
- // No agreements, don't proceed...
- this.props.addNotification(
- "error", "There are no replication agreements to report
on"
- );
+ handleConvertChange(e) {
+ const value = e.target.value;
+ this.setState({
+ convertRDN: value,
+ });
+ }
+
+ changeCreds(action) {
+ const { credentialsList, oldCredsHostname, oldCredsPort, credsHostname,
+ credsPort, credsBinddn, credsBindpw, pwInputInterractive } = this.state;
+
+ if (credsHostname === "" || credsPort === "" || credsBinddn
=== "") {
+ this.props.addNotification("warning", "Host, Port, and Bind DN
are required.");
+ } else if (credsBindpw === "" && !pwInputInterractive) {
+ this.props.addNotification("warning", "Password field
can't be empty, if Interractive Input is not selected");
} else {
- this.setState({
- showLoginModal: true,
- errObj: {
- bindpw: true
- }
- });
+ let credsExist = false;
+ if ((action == "add") && (credentialsList.some(row =>
row.connData === `${credsHostname}:${credsPort}`))) {
+ credsExist = true;
+ }
+ if ((action == "edit") && (credentialsList.some(row =>
row.connData === `${oldCredsHostname}:${oldCredsPort}`))) {
+ this.setState({
+ credentialsList: credentialsList.filter(
+ row => row.connData !== `${oldCredsHostname}:${oldCredsPort}`
+ )
+ });
+ }
+
+ if (!credsExist) {
+ this.setState(prevState => ({
+ credentialsList: [
+ ...prevState.credentialsList,
+ {
+ connData: `${credsHostname}:${credsPort}`,
+ credsBinddn: credsBinddn,
+ credsBindpw: credsBindpw,
+ pwInputInterractive: pwInputInterractive
+ }
+ ]
+ }));
+ } else {
+ this.props.addNotification(
+ "error",
+ `Credentials "${credsHostname}:${credsPort}" already
exists`
+ );
+ }
+ this.closeCredsModal();
}
}
- closeLoginModal () {
+ addCreds() {
+ this.changeCreds("add");
+ }
+
+ editCreds() {
+ this.changeCreds("edit");
+ }
+
+ removeCreds(rowData) {
this.setState({
- showLoginModal: false,
+ credentialsList: this.state.credentialsList.filter(
+ row => row.connData !== rowData.connData
+ )
});
}
- handleLoginModal(e) {
- const value = e.target.value.trim();
- let valueErr = false;
- let errObj = this.state.errObj;
- if (value == "") {
- valueErr = true;
- }
- errObj[e.target.id] = valueErr;
+ openCredsModal() {
this.setState({
- [e.target.id]: value,
- errObj: errObj
+ showCredentialsModal: true
});
}
- closeLagReport() {
+ showAddCredsModal() {
+ this.openCredsModal();
this.setState({
- showLagReport: false
+ newEntry: true,
+ oldCredsHostname: "",
+ oldCredsPort: "",
+ credsHostname: "",
+ credsPort: "",
+ credsBinddn: "cn=Directory Manager",
+ credsBindpw: "",
+ pwInputInterractive: false
});
}
- doLagReport() {
- // Get agmts but this time with with bind credentials, then clear
- // out bind credentials after we use them
+ showEditCredsModal(rowData) {
+ this.openCredsModal();
+ this.setState({
+ newEntry: false,
+ oldCredsHostname: rowData.connData.split(':')[0],
+ oldCredsPort: rowData.connData.split(':')[1],
+ credsHostname: rowData.connData.split(':')[0],
+ credsPort: rowData.connData.split(':')[1],
+ credsBinddn: rowData.credsBinddn,
+ credsBindpw: rowData.credsBindpw,
+ pwInputInterractive: rowData.pwInputInterractive
+ });
+ }
- if (this.state.binddn == "" || this.state.bindpw == "") {
- return;
+ closeCredsModal() {
+ this.setState({
+ showCredentialsModal: false
+ });
+ }
+
+ changeAlias(action) {
+ const { aliasesList, aliasHostname, aliasPort, oldAliasName, aliasName } =
this.state;
+
+ if (aliasPort === "" || aliasHostname === "" || aliasName ===
"") {
+ this.props.addNotification("warning", "Host, Port, and Alias
are required.");
+ } else {
+ let aliasExists = false;
+ if ((action == "add") && (aliasesList.some(row =>
row.alias === aliasName))) {
+ aliasExists = true;
+ }
+ if ((action == "edit") && (aliasesList.some(row =>
row.alias === oldAliasName))) {
+ this.setState({
+ aliasesList: aliasesList.filter(row => row.alias !==
oldAliasName)
+ });
+ }
+
+ if (!aliasExists) {
+ this.setState(prevState => ({
+ aliasesList: [
+ ...prevState.aliasesList,
+ {
+ connData: `${aliasHostname}:${aliasPort}`,
+ alias: aliasName
+ }
+ ]
+ }));
+ } else {
+ this.props.addNotification("error", `Alias
"${aliasName}" already exists`);
+ }
+ this.closeAliasesModal();
}
+ }
+ addAliases() {
+ this.changeAlias("add");
+ }
+
+ editAliases() {
+ this.changeAlias("edit");
+ }
+
+ removeAliases(rowData) {
this.setState({
- loginSpinning: true,
+ aliasesList: this.state.aliasesList.filter(row => row.alias !==
rowData.alias)
});
+ }
- let cmd = ["dsconf", "-j",
"ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
- "replication", "status", "--suffix=" +
this.props.suffix,
- "--bind-dn=" + this.state.binddn, "--bind-passwd=" +
this.state.bindpw];
- log_cmd("doLagReport", "Get agmts for lag report", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(content => {
- let config = JSON.parse(content);
+ openAliasesModal() {
+ this.setState({
+ showAliasesModal: true,
+ });
+ }
+
+ showAddAliasesModal() {
+ this.openAliasesModal();
+ this.setState({
+ newEntry: true,
+ aliasHostname: "",
+ aliasPort: 389,
+ oldAliasName: "",
+ aliasName: ""
+ });
+ }
+
+ showEditAliasesModal(rowData) {
+ this.openAliasesModal();
+ this.setState({
+ newEntry: false,
+ aliasHostname: rowData.connData.split(':')[0],
+ aliasPort: parseInt(rowData.connData.split(':')[1]),
+ oldAliasName: rowData.alias,
+ aliasName: rowData.alias
+ });
+ }
+
+ closeAliasesModal() {
+ this.setState({
+ showAliasesModal: false
+ });
+ }
+
+ refreshFullReport() {
+ this.doFullReport();
+ this.setState({
+ reportRefreshing: true
+ });
+ }
+
+ doFullReport() {
+ // Initiate the report and continue the processing in the input window
+ this.setState({
+ reportLoading: true,
+ activeReportKey: 2
+ });
+
+ let password = "";
+ let credentials = [];
+ let printCredentials = [];
+ for (let row of this.state.credentialsList) {
+ if (row.pwInputInterractive) {
+ password = "*";
+ } else {
+ password = `${row.credsBindpw}`;
+ }
+ credentials.push(`${row.connData}:${row.credsBinddn}:${password}`);
+ printCredentials.push(`${row.connData}:${row.credsBinddn}:********`);
+ }
+
+ let aliases = [];
+ for (let row of this.state.aliasesList) {
+ aliases.push(`${row.alias}=${row.connData}`);
+ }
+
+ let buffer = "";
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "replication",
+ "monitor"
+ ];
+
+ if (aliases.length != 0) {
+ cmd = [...cmd, "-a"];
+ for (let value of aliases) {
+ cmd = [...cmd, value];
+ }
+ }
+
+ // We should not print the passwords to console.log
+ let printCmd = cmd;
+ if (credentials.length != 0) {
+ cmd = [...cmd, "-c"];
+ for (let value of credentials) {
+ cmd = [...cmd, value];
+ }
+ printCmd = [...printCmd, "-c"];
+ for (let value of printCredentials) {
+ printCmd = [...printCmd, value];
+ }
+ }
+
+ log_cmd("doFullReport", "Get the report for the current instance
topology", printCmd);
+ // We need to set it here because 'input' will be run from inside
+ let proc = cockpit.spawn(cmd, { pty: true, environ: ["LC_ALL=C"],
superuser: true, err: "message", directory: self.path });
+ // We use it in processCredsInput
+ this.setState({
+ fullReportProcess: proc
+ });
+ proc
+ .done(data => {
+ // Use the buffer from stream. 'data' is empty
+ let report = JSON.parse(buffer);
+ // We need to reparse the report data because agmts json wasn't
parsed correctly because it was too nested
+ let agmts_reparsed = [];
+ let replica_reparsed = [];
+ let supplier_reparsed = [];
+ for (let supplier of report.items) {
+ replica_reparsed = [];
+ for (let replica of supplier.data) {
+ agmts_reparsed = [];
+ let agmts_done = false;
+ if (replica.hasOwnProperty("agmts_status")) {
+ for (let agmt of replica.agmts_status) {
+ // We need this for Agreement View Modal
+ agmt["supplierName"] = [supplier.name];
+ agmt["replicaName"] =
[`${replica.replica_root}:${replica.replica_id}`];
+ agmt["replicaStatus"] =
[`${replica.replica_status}`];
+ agmt["rowKey"] =
[`${supplier.name}:${replica.replica_root}:${replica.replica_id}:${agmt["agmt-name"]}`];
+ agmts_reparsed.push(agmt);
+ agmts_done = true;
+ }
+ }
+ if (!agmts_done) {
+ let agmt_empty = {};
+ agmt_empty["supplierName"] = [supplier.name];
+ if (replica.replica_root || replica.replica_id) {
+ agmt_empty["replicaName"] =
[`${replica.replica_root || ""}:${replica.replica_id || ""}`];
+ } else {
+ agmt_empty["replicaName"] =
[""];
+ }
+ agmt_empty["replicaStatus"] =
[`${replica.replica_status}`];
+ agmt_empty["rowKey"] =
[`${supplier.name}:${replica.replica_root}:${replica.replica_id}:None`];
+ agmts_reparsed.push(agmt_empty);
+ }
+ replica_reparsed.push({...replica, agmts_status:
agmts_reparsed});
+ }
+ supplier_reparsed.push({...supplier, data: replica_reparsed});
+ }
+ const report_reparsed = {...report, items: supplier_reparsed};
this.setState({
- lagAgmts: config.items,
- showLagReport: true,
- showLoginModal: false,
- loginSpinning: false,
- bindpw: ""
+ reportData: report_reparsed.items,
+ showFullReportModal: true,
+ reportLoading: false,
+ doFullReportCleanup: true
});
})
- .fail(err => {
- let errMsg = JSON.parse(err);
+ .fail(_ => {
+ let errMsg = JSON.parse(buffer);
this.props.addNotification(
"error",
- `Failed to get replication status - ${errMsg.desc}`
+ `Sync report has failed - ${errMsg.desc}`
);
this.setState({
- showLoginModal: false,
- loginSpinning: false,
- bindpw: ""
+ dynamicCredentialsList: [],
+ reportLoading: false,
+ doFullReportCleanup: true,
+ activeReportKey: 1
});
+ })
+ // Stream is run each time as a new character arriving
+ .stream(data => {
+ buffer += data;
+ let lines = buffer.split("\n");
+ let last_line = lines[lines.length - 1];
+ let found_creds = false;
+
+ // Interractive Input is required
+ // Check for Bind DN first
+ if (last_line.startsWith("Enter a bind DN") &&
last_line.endsWith(": ")) {
+ buffer = "";
+ // Get the instance name. We need it for fetching the creds data
from stored state list
+ this.setState({
+ credsInstanceName: data.split("a bind DN for
")[1].split(": ")[0]
+ });
+ // First check if DN is in the list already (either from previous
run or during this execution)
+ for (let creds of this.state.dynamicCredentialsList) {
+ if (creds.credsInstanceName == this.state.credsInstanceName)
{
+ found_creds = true;
+ proc.input(`${creds.binddn}\n`, true);
+ }
+ }
+
+ // If we don't have the creds - open the modal window and ask
the user for input
+ if (!found_creds) {
+ this.setState({
+ showReportLoginModal: true,
+ binddnRequired: true,
+ disableBinddn: false,
+ credsInstanceName: this.state.credsInstanceName,
+ loginBinddn: "",
+ loginBindpw: ""
+ });
+ }
+
+ // Check for password
+ } else if (last_line.startsWith("Enter a password")
&& last_line.endsWith(": ")) {
+ buffer = "";
+ // Do the same logic for password but the string parsing is
different
+ this.setState({
+ credsInstanceName: data.split(" on
")[1].split(": ")[0]
+ });
+ for (let creds of this.state.dynamicCredentialsList) {
+ if (creds.credsInstanceName == this.state.credsInstanceName)
{
+ found_creds = true;
+ proc.input(`${creds.bindpw}\n`, true);
+ this.setState({
+ credsInstanceName: ""
+ });
+ }
+ }
+
+ if (!found_creds) {
+ this.setState({
+ showReportLoginModal: true,
+ bindpwRequired: true,
+ credsInstanceName: this.state.credsInstanceName,
+ disableBinddn: true,
+ loginBinddn: data.split("Enter a password for
")[1].split(" on")[0],
+ loginBindpw: ""
+ });
+ }
+ }
});
}
- handleConvertChange(e) {
- const value = e.target.value;
+ closeReportLoginModal() {
this.setState({
- convertRDN: value,
+ showReportLoginModal: false,
+ reportLoading: false,
+ activeReportKey: 1
+ });
+ }
+
+ processCredsInput() {
+ const {
+ loginBinddn,
+ loginBindpw,
+ credsInstanceName,
+ fullReportProcess
+ } = this.state;
+
+ if (loginBinddn == "" || loginBindpw == "") {
+ this.props.addNotification("warning", "Bind DN and password
are required.");
+ } else {
+ this.setState({
+ showReportLoginModal: false,
+ reportLoading: false
+ });
+
+ // Store the temporary data in state
+ this.setState(prevState => ({
+ dynamicCredentialsList: [
+ ...prevState.dynamicCredentialsList,
+ {
+ binddn: loginBinddn,
+ bindpw: loginBindpw,
+ credsInstanceName: credsInstanceName
+ }
+ ]
+ }));
+
+ // We wait for some input - put the right one here
+ if (this.state.binddnRequired) {
+ fullReportProcess.input(`${loginBinddn}\n`, true);
+ this.setState({
+ binddnRequired: false
+ });
+ } else if (this.state.bindpwRequired) {
+ fullReportProcess.input(`${loginBindpw}\n`, true);
+ this.setState({
+ bindpwRequired: false
+ });
+ }
+ }
+ }
+
+ closeReportModal() {
+ this.setState({
+ showFullReportModal: false,
+ reportLoading: false
});
}
render() {
+ let reportData = this.state.reportData;
+ let credentialsList = this.state.credentialsList;
+ let aliasesList = this.state.aliasesList;
let replAgmts = this.props.data.replAgmts;
let replWinsyncAgmts = this.props.data.replWinsyncAgmts;
let cleanTasks = this.props.data.cleanTasks;
let abortTasks = this.props.data.abortTasks;
let conflictEntries = this.props.data.conflicts;
let glueEntries = this.props.data.glues;
+ let fullReportModal = "";
+ let reportLoginModal = "";
+ let reportCredentialsModal = "";
+ let reportAliasesModal = "";
let agmtDetailModal = "";
let winsyncAgmtDetailModal = "";
let compareConflictModal = "";
+ if (this.state.showReportLoginModal) {
+ reportLoginModal =
+ <ReportLoginModal
+ showModal={this.state.showReportLoginModal}
+ closeHandler={this.closeReportLoginModal}
+ handleChange={this.handleFieldChange}
+ processCredsInput={this.processCredsInput}
+ instanceName={this.state.credsInstanceName}
+ disableBinddn={this.state.disableBinddn}
+ loginBinddn={this.state.loginBinddn}
+ loginBindpw={this.state.loginBindpw}
+ />;
+ }
+ if (this.state.showCredentialsModal) {
+ reportCredentialsModal =
+ <ReportCredentialsModal
+ showModal={this.state.showCredentialsModal}
+ closeHandler={this.closeCredsModal}
+ handleFieldChange={this.handleFieldChange}
+ newEntry={this.state.newEntry}
+ hostname={this.state.credsHostname}
+ port={this.state.credsPort}
+ binddn={this.state.credsBinddn}
+ bindpw={this.state.credsBindpw}
+ pwInputInterractive={this.state.pwInputInterractive}
+ addConfig={this.addCreds}
+ editConfig={this.editCreds}
+ />;
+ }
+ if (this.state.showAliasesModal) {
+ reportAliasesModal =
+ <ReportAliasesModal
+ showModal={this.state.showAliasesModal}
+ closeHandler={this.closeAliasesModal}
+ handleFieldChange={this.handleFieldChange}
+ newEntry={this.state.newEntry}
+ hostname={this.state.aliasHostname}
+ port={this.state.aliasPort}
+ alias={this.state.aliasName}
+ addConfig={this.addAliases}
+ editConfig={this.editAliases}
+ />;
+ }
if (this.state.showAgmtModal) {
agmtDetailModal =
<AgmtDetailsModal
@@ -642,6 +1222,7 @@ export class ReplMonitor extends React.Component {
closeHandler={this.closeAgmtModal}
agmt={this.state.agmt}
initAgmt={this.confirmInit}
+ isRemoteAgmt={this.state.isRemoteAgmt}
/>;
}
@@ -668,6 +1249,105 @@ export class ReplMonitor extends React.Component {
/>;
}
+ let reportContent =
+ <div>
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem className="ds-nav-med" eventKey={1}>
+ {_("Prepare")}
+ </NavItem>
+ <NavItem className="ds-nav-med" eventKey={2}>
+ {_("Result")}
+ </NavItem>
+ </Nav>
+ <TabContent>
+ <TabPane eventKey={1}>
+ <div className="ds-indent ds-margin-top-lg">
+ <CustomCollapse textClosed="Show Help"
textOpened="Hide Help" className="h3">
+ <h3>How To Use Replication Sync Report</h3>
+ <ol className="ds-left-indent">
+ <li>
+ Fill in <b>Replica Credentials</b>;
+ <ul>
+ <li>• Initially, the list is populated
with existing instance agreements and the active instance itself;</li>
+ <li>• You can use regular expressions
for the <b>Connection Data</b> field;</li>
+ <li>• It is advised to use an
<b>Interactive Input</b> option for a password because it's more
secure.</li>
+ </ul>
+ </li>
+ <li>
+ Add <b>Instance Aliases</b> if
needed;
+ <ul>
+ <li>• Adding the aliases will make the
report more readable;</li>
+ <li>• Each instance can have one alias.
For example, you can give names like this:
+ <b> Alias</b>=Main Master,
<b>Hostname</b>=192.168.122.01, <b>Port</b>=38901;</li>
+ <li>• In a result, the report will have
an entry like this:
+ <b> Supplier: Main Master
(192.168.122.01:38901)</b>.</li>
+ </ul>
+ </li>
+ <li>
+ Press <b>Generate Report</b> button;
+ <ul>
+ <li>• It will initiate the report
creation;</li>
+ <li>• You may be asked for the
credentials while the process is running through the agreements.</li>
+ </ul>
+ </li>
+ <li>
+ Once report is generated you can review it and
enable continuous refreshing.
+ <ul>
+ <li>• More consumer replication data is
available under the 'View Data' button;</li>
+ <li>• You can set the timeout and the
new report will be created by that;</li>
+ <li>• It will use the specified
credentials (both preset and from interactive input).</li>
+ </ul>
+ </li>
+ </ol>
+ </CustomCollapse>
+ <ReportCredentialsTable
+ rows={credentialsList}
+ deleteConfig={this.removeCreds}
+ editConfig={this.showEditCredsModal}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="default"
+ onClick={this.showAddCredsModal}
+ >
+ Add Credentials
+ </Button>
+ <ReportAliasesTable
+ rows={aliasesList}
+ deleteConfig={this.removeAliases}
+ editConfig={this.showEditAliasesModal}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="default"
+ onClick={this.showAddAliasesModal}
+ >
+ Add Alias
+ </Button>
+ <p />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.doFullReport}
+ title="Use the specified credentials and display
full topology report"
+ >
+ Generate Report
+ </Button>
+ </div>
+ </TabPane>
+ <TabPane eventKey={2}>
+ <div className="ds-indent ds-margin-top-lg">
+ <FullReportContent
+ reportData={reportData}
+ viewAgmt={this.showAgmtModalRemote}
+ handleRefresh={this.refreshFullReport}
+ reportRefreshing={this.state.reportRefreshing}
+ reportLoading={this.state.reportLoading}
+ />
+ </div>
+ </TabPane>
+ </TabContent>
+ </div>;
let cleanNavTitle = 'CleanAllRUV Tasks <font size="1">('
+ cleanTasks.length + ')</font>';
let abortNavTitle = 'Abort CleanAllRUV Tasks <font
size="1">(' + abortTasks.length + ')</font>';
let taskContent =
@@ -750,8 +1430,9 @@ export class ReplMonitor extends React.Component {
</TabContent>
</div>;
- let replAgmtNavTitle = 'Replication Agreements <font
size="1">(' + replAgmts.length + ')</font>';
- let winsyncNavTitle = 'Winsync Agreements <font
size="1">(' + replWinsyncAgmts.length + ')</font>';
+ let fullReportTitle = 'Sync Report';
+ let replAgmtNavTitle = 'Agreements <font size="1">(' +
replAgmts.length + ')</font>';
+ let winsyncNavTitle = 'Winsync <font size="1">(' +
replWinsyncAgmts.length + ')</font>';
let tasksNavTitle = 'Tasks <font size="1">(' +
(cleanTasks.length + abortTasks.length) + ')</font>';
let conflictsNavTitle = 'Conflicts <font size="1">(' +
(conflictEntries.length + glueEntries.length) + ')</font>';
@@ -761,37 +1442,44 @@ export class ReplMonitor extends React.Component {
<div>
<Nav bsClass="nav nav-tabs nav-tabs-pf">
<NavItem eventKey={1}>
- <div dangerouslySetInnerHTML={{__html:
replAgmtNavTitle}} />
+ <div dangerouslySetInnerHTML={{__html:
fullReportTitle}} />
</NavItem>
<NavItem eventKey={2}>
- <div dangerouslySetInnerHTML={{__html:
winsyncNavTitle}} />
+ <div dangerouslySetInnerHTML={{__html:
replAgmtNavTitle}} />
</NavItem>
<NavItem eventKey={3}>
- <div dangerouslySetInnerHTML={{__html: tasksNavTitle}}
/>
+ <div dangerouslySetInnerHTML={{__html:
winsyncNavTitle}} />
</NavItem>
<NavItem eventKey={4}>
+ <div dangerouslySetInnerHTML={{__html: tasksNavTitle}}
/>
+ </NavItem>
+ <NavItem eventKey={5}>
<div dangerouslySetInnerHTML={{__html:
conflictsNavTitle}} />
</NavItem>
</Nav>
<TabContent>
<TabPane eventKey={1}>
<div className="ds-indent ds-tab-table">
+ <TabContainer
+ id="task-tabs"
+ defaultActiveKey={1}
+ onSelect={this.handleReportNavSelect}
+ activeKey={this.state.activeReportKey}
+ >
+ {reportContent}
+ </TabContainer>
+ </div>
+ </TabPane>
+ <TabPane eventKey={2}>
+ <div className="ds-indent ds-tab-table">
<AgmtTable
agmts={replAgmts}
pokeAgmt={this.pokeAgmt}
viewAgmt={this.showAgmtModal}
/>
- <Button
- className="ds-margin-top"
- bsStyle="primary"
- onClick={this.getLagReportCreds}
- title="Display report that shows the lag
time and replication status of each agreement in relationship to its replica"
- >
- Get Lag Report
- </Button>
</div>
</TabPane>
- <TabPane eventKey={2}>
+ <TabPane eventKey={3}>
<div className="dds-indent
ds-tab-table">
<WinsyncAgmtTable
agmts={replWinsyncAgmts}
@@ -800,14 +1488,14 @@ export class ReplMonitor extends React.Component {
/>
</div>
</TabPane>
- <TabPane eventKey={3}>
+ <TabPane eventKey={4}>
<div className="ds-indent ds-tab-table">
<TabContainer id="task-tabs"
defaultActiveKey={1}>
{taskContent}
</TabContainer>
</div>
</TabPane>
- <TabPane eventKey={4}>
+ <TabPane eventKey={5}>
<div className="ds-indent ds-tab-table">
<TabContainer id="task-tabs"
defaultActiveKey={1}>
{conflictContent}
@@ -838,21 +1526,6 @@ export class ReplMonitor extends React.Component {
msg="Are you really sure you want to reinitialize this
replication winsync agreement?"
msgContent={this.state.agmt['agmt-name']}
/>
- <ReplLoginModal
- showModal={this.state.showLoginModal}
- closeHandler={this.closeLoginModal}
- handleChange={this.handleLoginModal}
- doReport={this.doLagReport}
- spinning={this.state.loginSpinning}
- error={this.state.errObj}
- />
- <ReplLagReportModal
- showModal={this.state.showLagReport}
- closeHandler={this.closeLagReport}
- agmts={this.state.lagAgmts}
- pokeAgmt={this.pokeAgmt}
- viewAgmt={this.showAgmtModal}
- />
<ConfirmPopup
showModal={this.state.showConfirmDeleteGlue}
closeHandler={this.closeConfirmDeleteGlue}
@@ -893,6 +1566,10 @@ export class ReplMonitor extends React.Component {
msg="Are you really sure you want to delete this conflict
entry?"
msgContent={this.state.conflictEntry}
/>
+ {fullReportModal}
+ {reportLoginModal}
+ {reportCredentialsModal}
+ {reportAliasesModal}
{agmtDetailModal}
{winsyncAgmtDetailModal}
{compareConflictModal}
diff --git a/src/cockpit/389-console/src/lib/tools.jsx
b/src/cockpit/389-console/src/lib/tools.jsx
index b37dd86..631135e 100644
--- a/src/cockpit/389-console/src/lib/tools.jsx
+++ b/src/cockpit/389-console/src/lib/tools.jsx
@@ -4,9 +4,11 @@ export function searchFilter(searchFilterValue, columnsToSearch, rows) {
rows.forEach(row => {
let rowToSearch = [];
if (columnsToSearch && columnsToSearch.length) {
- columnsToSearch.forEach(column =>
- rowToSearch.push(row[column])
- );
+ columnsToSearch.forEach(column => {
+ if (column in row) {
+ rowToSearch.push(row[column]);
+ }
+ });
} else {
rowToSearch = row;
}
diff --git a/src/cockpit/389-console/src/monitor.jsx
b/src/cockpit/389-console/src/monitor.jsx
index fd2bc0d..1c97497 100644
--- a/src/cockpit/389-console/src/monitor.jsx
+++ b/src/cockpit/389-console/src/monitor.jsx
@@ -828,6 +828,9 @@ export class Monitor extends React.Component {
replLoading: false,
});
});
+ } else {
+ // We should enable it here because ReplMonitor never will be mounted
+ this.enableTree();
}
}
diff --git a/src/lib389/lib389/agreement.py b/src/lib389/lib389/agreement.py
index 93fd728..dfffbed 100644
--- a/src/lib389/lib389/agreement.py
+++ b/src/lib389/lib389/agreement.py
@@ -372,21 +372,21 @@ class Agreement(DSLdapObject):
# Case sensitive?
if use_json:
result = {
- 'agmt-name':
ensure_str(status_attrs_dict['cn'][0]),
- 'replica': consumer,
- 'replica-enabled':
ensure_str(status_attrs_dict['nsds5replicaenabled'][0]),
- 'update-in-progress':
ensure_str(status_attrs_dict['nsds5replicaupdateinprogress'][0]),
- 'last-update-start':
ensure_str(status_attrs_dict['nsds5replicalastupdatestart'][0]),
- 'last-update-end':
ensure_str(status_attrs_dict['nsds5replicalastupdateend'][0]),
- 'number-changes-sent':
ensure_str(status_attrs_dict['nsds5replicachangessentsincestartup'][0]),
- 'number-changes-skipped:':
ensure_str(status_attrs_dict['nsds5replicachangesskippedsince'][0]),
- 'last-update-status':
ensure_str(status_attrs_dict['nsds5replicalastupdatestatus'][0]),
- 'last-init-start':
ensure_str(status_attrs_dict['nsds5replicalastinitstart'][0]),
- 'last-init-end':
ensure_str(status_attrs_dict['nsds5replicalastinitend'][0]),
- 'last-init-status':
ensure_str(status_attrs_dict['nsds5replicalastinitstatus'][0]),
- 'reap-active':
ensure_str(status_attrs_dict['nsds5replicareapactive'][0]),
- 'replication-status': status,
- 'replication-lag-time': lag_time
+ 'agmt-name':
ensure_list_str(status_attrs_dict['cn']),
+ 'replica': [consumer],
+ 'replica-enabled':
ensure_list_str(status_attrs_dict['nsds5replicaenabled']),
+ 'update-in-progress':
ensure_list_str(status_attrs_dict['nsds5replicaupdateinprogress']),
+ 'last-update-start':
ensure_list_str(status_attrs_dict['nsds5replicalastupdatestart']),
+ 'last-update-end':
ensure_list_str(status_attrs_dict['nsds5replicalastupdateend']),
+ 'number-changes-sent':
ensure_list_str(status_attrs_dict['nsds5replicachangessentsincestartup']),
+ 'number-changes-skipped':
ensure_list_str(status_attrs_dict['nsds5replicachangesskippedsince']),
+ 'last-update-status':
ensure_list_str(status_attrs_dict['nsds5replicalastupdatestatus']),
+ 'last-init-start':
ensure_list_str(status_attrs_dict['nsds5replicalastinitstart']),
+ 'last-init-end':
ensure_list_str(status_attrs_dict['nsds5replicalastinitend']),
+ 'last-init-status':
ensure_list_str(status_attrs_dict['nsds5replicalastinitstatus']),
+ 'reap-active':
ensure_list_str(status_attrs_dict['nsds5replicareapactive']),
+ 'replication-status': [status],
+ 'replication-lag-time': [lag_time]
}
return (json.dumps(result))
else:
diff --git a/src/lib389/lib389/cli_conf/backend.py
b/src/lib389/lib389/cli_conf/backend.py
index dc482f4..b62446e 100644
--- a/src/lib389/lib389/cli_conf/backend.py
+++ b/src/lib389/lib389/cli_conf/backend.py
@@ -1111,7 +1111,7 @@ def create_parser(subparsers):
#######################################################
delete_parser = subcommands.add_parser('delete', help='Delete a backend
database')
delete_parser.set_defaults(func=backend_delete)
- delete_parser.add_argument('be_name', help='The backend name or suffix to
delete')
+ delete_parser.add_argument('be-name', help='The backend name or suffix to
delete')
#######################################################
# Get Suffix Tree (for use in web console)
diff --git a/src/lib389/lib389/cli_conf/replication.py
b/src/lib389/lib389/cli_conf/replication.py
index ede2529..c10e3ab 100644
--- a/src/lib389/lib389/cli_conf/replication.py
+++ b/src/lib389/lib389/cli_conf/replication.py
@@ -376,15 +376,15 @@ def get_repl_monitor_info(inst, basedn, log, args):
if connections:
for connection_str in connections:
- if len(connection_str.split(":")) != 4:
- raise ValueError(f"Connection string {connection_str} is in
wrong format."
- "It should be host:port:binddn:bindpw")
- host_regex = connection_str.split(":")[0]
- port_regex = connection_str.split(":")[1]
+ connection = connection_str.split(":")
+ if (len(connection) != 4 or not all([len(str) > 0 for str in
connection])):
+ raise ValueError(f"Please, fill in all Credential details. It
should be host:port:binddn:bindpw")
+ host_regex = connection[0]
+ port_regex = connection[1]
if re.match(host_regex, host) and re.match(port_regex, port):
found = True
- binddn = connection_str.split(":")[2]
- bindpw = connection_str.split(":")[3]
+ binddn = connection[2]
+ bindpw = connection[3]
# Search for the password file or ask the user to write it
if bindpw.startswith("[") and
bindpw.endswith("]"):
pwd_file_path = os.path.expanduser(bindpw[1:][:-1])
@@ -404,46 +404,63 @@ def get_repl_monitor_info(inst, basedn, log, args):
"bindpw": bindpw}
repl_monitor = ReplicationMonitor(inst)
- report_dict = repl_monitor.generate_report(get_credentials)
+ report_dict = repl_monitor.generate_report(get_credentials, args.json)
+ report_items = []
+
+ for instance, report_data in report_dict.items():
+ report_item = {}
+ found_alias = False
+ if args.aliases:
+ aliases = {al.split("=")[0]: al.split("=")[1] for al in
args.aliases}
+ elif connection_data["aliases"]:
+ aliases = connection_data["aliases"]
+ else:
+ aliases = {}
+ if aliases:
+ for alias_name, alias_host_port in aliases.items():
+ if alias_host_port.lower() == instance.lower():
+ supplier_header = f"{alias_name} ({instance})"
+ found_alias = True
+ break
+ if not found_alias:
+ supplier_header = f"{instance}"
- if args.json:
- log.info(json.dumps({"type": "list", "items":
report_dict}))
- else:
- for instance, report_data in report_dict.items():
- found_alias = False
- if args.aliases:
- aliases = {al.split("=")[0]: al.split("=")[1] for al
in args.aliases}
- elif connection_data["aliases"]:
- aliases = connection_data["aliases"]
- else:
- aliases = {}
- if aliases:
- for alias_name, alias_host_port in aliases.items():
- if alias_host_port.lower() == instance.lower():
- supplier_header = f"Supplier: {alias_name}
({instance})"
- found_alias = True
- break
- if not found_alias:
- supplier_header = f"Supplier: {instance}"
+ if args.json:
+ report_item["name"] = supplier_header
+ else:
+ supplier_header = f"Supplier: {supplier_header}"
log.info(supplier_header)
- # Draw a line with the same length as the header
+
+ # Draw a line with the same length as the header
+ status = ""
+ if not args.json:
log.info("-".join(["" for _ in range(0,
len(supplier_header)+1)]))
- if "status" in report_data and report_data["status"] ==
"Unavailable":
- status = report_data["status"]
- reason = report_data["reason"]
- log.info(f"Status: {status}")
- log.info(f"Reason: {reason}\n")
- else:
- for replica in report_data:
- replica_root = replica["replica_root"]
- replica_id = replica["replica_id"]
- maxcsn = replica["maxcsn"]
+ if
report_data[0]["replica_status"].startswith("Unavailable"):
+ status = report_data[0]["replica_status"]
+ if not args.json:
+ log.info(f"Replica Status: {status}\n")
+ else:
+ for replica in report_data:
+ replica_root = replica["replica_root"]
+ replica_id = replica["replica_id"]
+ replica_status = replica["replica_status"]
+ maxcsn = replica["maxcsn"]
+ if not args.json:
log.info(f"Replica Root: {replica_root}")
log.info(f"Replica ID: {replica_id}")
+ log.info(f"Replica Status: {replica_status}")
log.info(f"Max CSN: {maxcsn}\n")
- for agreement_status in replica["agmts_status"]:
+ for agreement_status in replica["agmts_status"]:
+ if not args.json:
log.info(agreement_status)
+ if args.json:
+ report_item["data"] = report_data
+ report_items.append(report_item)
+
+ if args.json:
+ log.info(json.dumps({"type": "list", "items":
report_items}))
+
def create_cl(inst, basedn, log, args):
cl = Changelog5(inst)
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
index c026ff7..0fc1275 100644
--- a/src/lib389/lib389/replica.py
+++ b/src/lib389/lib389/replica.py
@@ -2492,12 +2492,16 @@ class ReplicationMonitor(object):
protocol = agmt.get_attr_val_utf8_l('nsds5replicatransportinfo')
# Supply protocol here because we need it only for connection
# and agreement status is already preformatted for the user output
- consumer = f"{host}:{port}:{protocol}"
+ consumer = f"{host}:{port}"
if consumer not in report_data:
- report_data[consumer] = None
- agmts_status.append(agmt.status(use_json))
+ report_data[f"{consumer}:{protocol}"] = None
+ if use_json:
+ agmts_status.append(json.loads(agmt.status(use_json=True)))
+ else:
+ agmts_status.append(agmt.status())
replicas_status.append({"replica_id": replica_id,
"replica_root": replica_root,
+ "replica_status": "Available",
"maxcsn": replica_maxcsn,
"agmts_status": agmts_status})
return replicas_status
@@ -2514,9 +2518,13 @@ class ReplicationMonitor(object):
"""
report_data = {}
- initial_inst_key =
f"{self._instance.host.lower()}:{str(self._instance.port).lower()}"
+ initial_inst_key =
f"{self._instance.config.get_attr_val_utf8_l('nsslapd-localhost')}:{self._instance.config.get_attr_val_utf8_l('nsslapd-port')}"
# Do this on an initial instance to get the agreements to other instances
- report_data[initial_inst_key] = self._get_replica_status(self._instance,
report_data, use_json)
+ try:
+ report_data[initial_inst_key] = self._get_replica_status(self._instance,
report_data, use_json)
+ except ldap.LDAPError as e:
+ self._log.debug(f"Connection to consumer
({supplier_hostname}:{supplier_port}) failed, error: {e}")
+ report_data[initial_inst_key] = [{"replica_status":
f"Unavailable - {e.args[0]['desc']}"}]
# Check if at least some replica report on other instances was generated
repl_exists = False
@@ -2528,18 +2536,19 @@ class ReplicationMonitor(object):
except IndexError:
break
+ del report_data[supplier]
s_splitted = supplier.split(":")
supplier_hostname = s_splitted[0]
supplier_port = s_splitted[1]
supplier_protocol = s_splitted[2]
+ supplier_hostport_only = ":".join(s_splitted[:2])
# The function should be defined outside and
# it should have all the logic for figuring out the credentials.
# It is done for flexibility purpuses between CLI, WebUI and lib389 API
applications
credentials = get_credentials(supplier_hostname, supplier_port)
if not credentials["binddn"]:
- report_data[supplier] = {"status": "Unavailable",
- "reason": "Bind DN was not
specified"}
+ report_data[supplier_hostport_only] = [{"replica_status":
"Unavailable - Bind DN was not specified"}]
continue
# Open a connection to the consumer
@@ -2557,21 +2566,30 @@ class ReplicationMonitor(object):
supplier_inst.open()
except ldap.LDAPError as e:
self._log.debug(f"Connection to consumer
({supplier_hostname}:{supplier_port}) failed, error: {e}")
- report_data[supplier] = {"status": "Unavailable",
- "reason": e.args[0]['desc']}
+ report_data[supplier_hostport_only] = [{"replica_status":
f"Unavailable - {e.args[0]['desc']}"}]
continue
- report_data[supplier] = self._get_replica_status(supplier_inst, report_data,
use_json)
+ report_data[supplier_hostport_only] = self._get_replica_status(supplier_inst,
report_data, use_json)
repl_exists = True
+ # Get rid of the repeated items
+ report_data_parsed = {}
+ for key, value in report_data.items():
+ current_inst_rids = [val["replica_id"] for val in report_data[key]
if "replica_id" in val.keys()]
+ report_data_parsed[key] = sorted(current_inst_rids)
+ report_data_filtered = {}
+ for key, value in report_data_parsed.items():
+ if value not in report_data_filtered.values():
+ report_data_filtered[key] = value
+
# Now remove the protocol from the name
report_data_final = {}
for key, value in report_data.items():
# We take the initial instance only if it is the only existing part of the
report
- if key != initial_inst_key or not repl_exists:
+ if key in report_data_filtered.keys() or not repl_exists:
if not value:
- value = {"status": "Unavailable",
- "reason": "No replicas were found"}
- report_data_final[":".join(key.split(":")[:2])] =
value
+ value = [{"replica_status": "Unavailable - No replicas
were found"}]
+
+ report_data_final[key] = value
return report_data_final
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.