#50733 Issue 50545 - Add the new replication monitor functionality to UI
Closed 3 years ago by spichugi. Opened 4 years ago by spichugi.
spichugi/389-ds-base repl-monitor-ui  into  master

@@ -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 }],

@@ -885,6 +885,10 @@ 

      padding-left: 5px;

  }

  

+ .ds-raise-field {

+     margin-top: -3px;

+ }

+ 

  .content-view-pf-pagination > div > span:last-child {

      position: relative;

  }

@@ -8,157 +8,16 @@ 

      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 @@ 

                  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 @@ 

                          </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 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 @@ 

      closeHandler: noop,

      agmt: {},

      initAgmt: noop,

+     isRemoteAgmt: false

  };

  

  WinsyncAgmtDetailsModal.propTypes = {
@@ -686,24 +1032,6 @@ 

      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 @@ 

      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

  };

@@ -11,7 +11,7 @@ 

  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 @@ 

                          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 @@ 

                          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 @@ 

                          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 @@ 

                      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 @@ 

                          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 @@ 

      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 @@ 

      AbortCleanALLRUVTable,

      ConflictTable,

      GlueTable,

-     DiskTable,

+     ReportCredentialsTable,

+     ReportAliasesTable,

+     ReportConsumersTable,

+     ReportSingleTable,

+     DiskTable

  };

@@ -5,6 +5,8 @@ 

  import "../../css/ds.css";

  import { ConfirmPopup } from "../notifications.jsx";

  import {

+     ReportCredentialsTable,

+     ReportAliasesTable,

      AgmtTable,

      WinsyncAgmtTable,

      CleanALLRUVTable,
@@ -13,11 +15,13 @@ 

      GlueTable,

  } from "./monitorTables.jsx";

  import {

+     FullReportContent,

+     ReportLoginModal,

+     ReportCredentialsModal,

+     ReportAliasesModal,

      TaskLogModal,

      AgmtDetailsModal,

      WinsyncAgmtDetailsModal,

-     ReplLagReportModal,

-     ReplLoginModal,

      ConflictCompareModal,

  } from "./monitorModals.jsx";

  import {
@@ -29,39 +33,151 @@ 

      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 @@ 

          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 @@ 

          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 @@ 

          });

      }

  

+     handleReportNavSelect(key) {

+         this.setState({

+             activeReportKey: key

+         });

+     }

+ 

      closeLogModal() {

          this.setState({

              showLogModal: false
@@ -422,6 +574,7 @@ 

              if (agmt['agmt-name'] == name) {

                  this.setState({

                      showAgmtModal: true,

+                     isRemoteAgmt: false,

                      agmt: agmt

                  });

                  break;
@@ -429,6 +582,34 @@ 

          }

      }

  

+     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 @@ 

                  });

      }

  

-     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 @@ 

                      closeHandler={this.closeAgmtModal}

                      agmt={this.state.agmt}

                      initAgmt={this.confirmInit}

+                     isRemoteAgmt={this.state.isRemoteAgmt}

                  />;

          }

  
@@ -668,6 +1249,105 @@ 

                  />;

          }

  

+         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 @@ 

                  </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 @@ 

                      <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 @@ 

                                      />

                                  </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 @@ 

                      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 @@ 

                      msg="Are you really sure you want to delete this conflict entry?"

                      msgContent={this.state.conflictEntry}

                  />

+                 {fullReportModal}

+                 {reportLoginModal}

+                 {reportCredentialsModal}

+                 {reportAliasesModal}

                  {agmtDetailModal}

                  {winsyncAgmtDetailModal}

                  {compareConflictModal}

@@ -4,9 +4,11 @@ 

          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;

              }

@@ -828,6 +828,9 @@ 

                              replLoading: false,

                          });

                      });

+         } else {

+             // We should enable it here because ReplMonitor never will be mounted

+             this.enableTree();

          }

      }

  

file modified
+15 -15
@@ -372,21 +372,21 @@ 

          # 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:

@@ -1111,7 +1111,7 @@ 

      #######################################################

      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)

@@ -376,15 +376,15 @@ 

  

          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 @@ 

                  "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)

file modified
+32 -14
@@ -2492,12 +2492,16 @@ 

                  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 @@ 

          """

          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 @@ 

              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 @@ 

                  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

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: ?

console crashes, if I just click on generate report and enter binds credentials.

index.js:159957 Uncaught TypeError: report.items[Symbol.iterator] is not a function
    at Function.<anonymous> (index.js:159882)
    at s (cockpit.js:962)
    at cockpit.js:974
    at n (cockpit.js:880)

If I add credential, it still crashes

replMonitor.jsx:998 Uncaught TypeError: report.items[Symbol.iterator] is not a function
    at Function.<anonymous> (replMonitor.jsx:998)
    at s (cockpit.js:962)
    at cockpit.js:974
    at n (cockpit.js:880)
(anonymous) @   replMonitor.jsx:998

Clicking on Add Alias also crashes browser:

checkPropTypes.js:19 Warning: Failed prop type: Invalid prop `port` of type `string` supplied to `ReportAliasesModal`, expected `number`.
    in ReportAliasesModal (created by ReplMonitor)
    in ReplMonitor (created by Monitor)
    in div (created by Monitor)
    in div (created by Monitor)
    in div (created by Monitor)
    in div (created by Monitor)
    in div (created by Monitor)
    in div (created by Monitor)
    in Monitor

Besides that I have some UX comments. I think the description paragraph is lacking details. I was not sure how to use it. Do I need to add "instance credentials" for each server I want to look at? It looks like if you don't have any defined it will look at the local agreements, but this is not described anywhere. You should say that you need to define all the replicas you want in the report, that you can pre-set the password or have it asked interactively. Explain what the alias table is, etc. Like I said I had no idea how to use it, so I just started pressing buttons, but if we are going have descriptive text then it should be comprehensive.

I think you should call it Replica Credentials Table and not Instance Credentials.

I think it would be useful to auto populate the table using the existing agreements, then they can be edited to add bind credentials. This will save an Admin a lot of time to setup and get a report. Add a column to the table stating if credentials are set for that replica.

Also, while you're at it. I think we should default all replica bind DN's to "cn=replication manager,cn=config". We do it some places but not all, and I think we should do it everywhere. In your report modals, but also in agreement creation modal, and anywhere else we ask for it.

Also what you did sort of obsoletes the "Lag Report" under the Replication Agreements tab. So maybe you can get rid of the Lag Report and its modals?

Also I think we should rename some of the tabs in replication monitor page. They are taking up a lot of horizontal space and it easily wraps on smaller monitors.

Full Topology Report --> Replication Report/Sync Report?
Replication Agreements -- > Agreements
Winsync Agreements --> Winsync

Also, while you're at it. I think we should default all replica bind DN's to "cn=replication manager,cn=config". We do it some places but not all, and I think we should do it everywhere. In your report modals, but also in agreement creation modal, and anywhere else we ask for it.

Actually in your authentication modals, it should default to "cn=directory manager" not "cn=replicaiton manager,cn=config", so you can ignore this for now...

Here is the code fix to get the report working and aliases:

diff --git a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
index 7fcdb3274..901ff797f 100644
--- a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
@@ -125,7 +125,7 @@ export class ReplMonitor extends React.Component {
             pwInputInterractive: false,

             aliasHostname: "",
-            aliasPort: "",
+            aliasPort: 389,
             aliasName: "",

             credentialsList: [],
@@ -903,7 +903,7 @@ export class ReplMonitor extends React.Component {
         this.setState({
             newEntry: true,
             aliasHostname: "",
-            aliasPort: "",
+            aliasPort: 389,
             aliasName: ""
         });
     }
@@ -913,7 +913,7 @@ export class ReplMonitor extends React.Component {
         this.setState({
             newEntry: false,
             aliasHostname: rowData.connData.split(':')[0],
-            aliasPort: rowData.connData.split(':')[1],
+            aliasPort: parseInt(rowData.connData.split(':')[1]),
             aliasName: rowData.alias
         });
     }
@@ -1001,11 +1001,10 @@ export class ReplMonitor extends React.Component {
                             agmts_reparsed = [];
                             if (replica.hasOwnProperty("agmts_status")) {
                                 for (let agmt of replica.agmts_status) {
-                                    let agmt_parsed = JSON.parse(agmt);
                                     // We need this for Agreement View Modal
-                                    agmt_parsed["supplierName"] = supplier.name;
-                                    agmt_parsed["replicaName"] = `${replica.replica_root}:${replica.replica_id}`;
-                                    agmts_reparsed.push(agmt_parsed);
+                                    agmt["supplierName"] = supplier.name;
+                                    agmt["replicaName"] = `${replica.replica_root}:${replica.replica_id}`;
+                                    agmts_reparsed.push(agmt);
                                 }
                             }
                             replica_reparsed.push({...replica, agmts_status: agmts_reparsed});
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
index dfb08edd8..90df1cb77 100644
--- a/src/lib389/lib389/replica.py
+++ b/src/lib389/lib389/replica.py
@@ -2428,6 +2428,10 @@ class ReplicationMonitor(object):
                 if consumer not in report_data:
                     report_data[f"{consumer}:{protocol}"] = None
                 agmts_status.append(agmt.status(use_json=use_json))
+                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,
                                     "maxcsn": replica_maxcsn,

Next, a few issues I noticed

[1] When generating the report there is a long lag between prompting for credentials, or just generating the report. A spinner should be used, or something, to show that the UI is doing something during then entire process

[2] Generating a report, I get prompted for the local credentials, why? These credentials are already available

[3] the report modal. The "Close Report' button should be a primary button not a default button

[4] The modal report is cramped and narrow. What happens when you have 4 masters and 10 agreements? Is it just one long report? I think I created a "wide modal" css, I think you should use it, and it will make the report slightly more readable. I wonder we should have an entire page (not a modal) dedicated to the report? Maybe do a tabbed pane (one settings, the other for the report)? It's such a useful report, it would be a shame to not present it in the most readable/useful way.

console crashes, if I just click on generate report and enter binds credentials.
If I add credential, it still crashes

For some reason, both issues are not present for me... I can add, edit and remove credentials without any crashes.

Clicking on Add Alias also crashes browser:

Missed that one! The report still displays an alias correctly even after the error. But it's fixed, thanks!

Besides that I have some UX comments. I think the description paragraph is lacking details. I was not sure how to use it. Do I need to add "instance credentials" for each server I want to look at?

It has a title(hint) on both hostname and port that it accepts regular expressions. But I think I will add a smallHow to` hidable note in the report preparation tab.

I think you should call it Replica Credentials Table and not Instance Credentials.

Sure, sounds good.

I think it would be useful to auto populate the table using the existing agreements, then they can be edited to add bind credentials. This will save an Admin a lot of time to setup and get a report. Add a column to the table stating if credentials are set for that replica.

Good catch!

Also what you did sort of obsoletes the "Lag Report" under the Replication Agreements tab. So maybe you can get rid of the Lag Report and its modals?

Yeah, I had the same thought. You can initialize the agreement from Lag Report modal but it's a bonus, I think. I will get rid of Lag Report button.

Here is the code fix to get the report working and aliases:

Thanks! Very helpful!

Next, a few issues I noticed
[1] When generating the report there is a long lag between prompting for credentials, or just generating the report. A spinner should be used, or something, to show that the UI is doing something during then entire process

Yeah, I even have the state for that but apparently it wasn't connected to any element. I'll add it.

[2] Generating a report, I get prompted for the local credentials, why? These credentials are already available

It is how we process the instances. We check the agreement credentials and the protocol type and connect using this way.
For example, you may be connected to the instance through 389 port but the agreements work through 636 (and maybe some other hostname). I think we shouldn't ignore that and we should open the exact connection that is defined in the agreement.

[3] the report modal. The "Close Report' button should be a primary button not a default button

It will look better, true!

[4] The modal report is cramped and narrow. What happens when you have 4 masters and 10 agreements? Is it just one long report? I think I created a "wide modal" css, I think you should use it, and it will make the report slightly more readable. I wonder we should have an entire page (not a modal) dedicated to the report? Maybe do a tabbed pane (one settings, the other for the report)? It's such a useful report, it would be a shame to not present it in the most readable/useful way.

I had the same feeling. I've tried to use 'wide' but it wasn't working properly (and in general I don't like to diverse far from PF4 CSS because it may create the issues in the future).
But I like the tab idea a lot. So I think I can add two tabs under Sync Report tab - one is Preparation and another is Result (I'll think a bit more about exact words...).
It will make everything look better for sure.

rebased onto 2b8d25abad60c1427835dd4900adc6449f7357ed

4 years ago

Fixed. Please, review.

This is a bug on my part from a previous patch, but you should add it:

monitor.jsx line 1175

              if (this.state.replicatedSuffixes.length < 1) {
                    monitor_element =
                        <div>
                            <p>There are no suffixes that have been configured for replication</p>
                        </div>;
                    // Must enable tree since we are not calling any components
                    this.enableTree();

---> Must enableTree here in this case

In the replica/credential table can you change this text to use italics: Edit To Add a Bind DN Data

There should be a table header to say what the "credential table" is.

The Bind DN in the report modals do not appear to be auto filled with "cn=directory manager"

There should be a success notification once the report is written, and it should state that you need to goto the report tab. Or better yet, move the UI to the report tab once the report is written.

Finally on the report tab, the Refresh and Refresh timeout header is somewhat distracting. Maybe remove the bold or even shrink the font size. It just needs better separation from the report. It's kind of blending in, and taking away from the "report" IMHO

This is a bug on my part from a previous patch, but you should add it:
monitor.jsx line 1175
if (this.state.replicatedSuffixes.length < 1) {
monitor_element =


There are no suffixes that have been configured for replication


;
// Must enable tree since we are not calling any components
this.enableTree();

---> Must enableTree here in this case

This code won't work. I think we need to call the replMonitor component with a null suffix, and in the component we check for the null suffix and just enable the tree, and return the "There are no suffixes that have been configured for replication" paragraph. Otherwise the entire monitor treeview is disabled after clicking the replication node (if there are no replicated suffixes).

rebased onto cbd42bf5fa5f05633f1e29751c63e390a349da82

4 years ago

In the replica/credential table can you change this text to use italics: Edit To Add a Bind DN Data

Fixed.

There should be a table header to say what the "credential table" is.

I did try to add it and I think it looks redundant.
We already have the header when there is not data in the table (as we have for every other table in our WebUI). Also, we have a button Add Credentials which makes it clear what's in the table (and we see that there are credentials in the table itself)

The Bind DN in the report modals do not appear to be auto filled with "cn=directory manager"

Done.

There should be a success notification once the report is written, and it should state that you need to goto the report tab. Or better yet, move the UI to the report tab once the report is written.

I already have the feature.
There was a bug that the tab was not switched to Report when we input the credentials interactively.

Finally on the report tab, the Refresh and Refresh timeout header is somewhat distracting. Maybe remove the bold or even shrink the font size. It just needs better separation from the report. It's kind of blending in, and taking away from the "report" IMHO

Done.

Also, I've fixed the issue you have reported. I've just added

} else {
        // We should enable it here because ReplMonitor never will be mounted
        this.enableTree();
}

to loadMonitorReplication(). So when we don't run the loading chain - we just enable the tree.

And there was one crash that is fixed now. When we load Replication Suffix data and we try to select Database Suffix tab - it crashes because dc=example,dc=com is found and the database suffix data is not loaded. I've extended if:

if (
            selectedNode.id in this.state &&
            ("chainingData" in this.state[selectedNode.id] ||
                "suffixData" in this.state[selectedNode.id])
) {
            // This suffix is already cached

3 new commits added

  • Fix issues 2
  • Fix various issues
  • Issue 50545 - Add the new replication monitor functionality to UI
4 years ago

It looks a lot better but the report seems off. I'm attaching a screenshot to the Issue (since I can't do it in the PR)

It looks a lot better but the report seems off. I'm attaching a screenshot to the Issue (since I can't do it in the PR)

Could you please send me an email with your agreements and replica entries?

For me, the report prints clearly because my hostnames are the same everywhere.. So I wonder how exactly reproduce what you have

It looks a lot better but the report seems off. I'm attaching a screenshot to the Issue (since I can't do it in the PR)

Could you please send me an email with your agreements and replica entries?
For me, the report prints clearly because my hostnames are the same everywhere.. So I wonder how exactly reproduce what you have

Here is the config and agreement from the instance I am generating the report . The other replica does not have any agreements:

dn: cn=replica,cn=dc\3Dexample\2Cdc\3Dcom,cn=mapping tree,cn=config
objectClass: top
objectClass: nsds5Replica
cn: replica
nsDS5ReplicaRoot: dc=example,dc=com
nsDS5Flags: 1
nsDS5ReplicaType: 3
nsDS5ReplicaId: 2
nsDS5ReplicaBindDN: cn=replication manager,cn=config
nsState:: AgAAAAAAAAAUSuldAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAA==
nsDS5ReplicaName: 91aabc03-178b11ea-a2c4bc99-2f841849


dn: cn=master,cn=replica,cn=dc\3Dexample\2Cdc\3Dcom,cn=mapping tree,cn=config
objectClass: top
objectClass: nsds5replicationagreement
cn: master
nsDS5ReplicaRoot: dc=example,dc=com
description: master
nsDS5ReplicaHost: localhost
nsDS5ReplicaPort: 389
nsDS5ReplicaBindMethod: simple
nsDS5ReplicaTransportInfo: LDAP
nsDS5ReplicaBindDN: cn=replication manager,cn=config
nsDS5ReplicaCredentials: ...
nsds50ruv: {replicageneration} 5de71816000000010000
nsds50ruv: {replica 1 ldap://localhost.localdomain:389}
nsruvReplicaLastModified: {replica 1 ldap://localhost.localdomain:389} 00000000

For completeness this is replica entry from the other master (that has no agreements)

dn: cn=replica,cn=dc\3Dexample\2Cdc\3Dcom,cn=mapping tree,cn=config
objectClass: top
objectClass: nsds5Replica
cn: replica
nsDS5ReplicaRoot: dc=example,dc=com
nsDS5Flags: 1
nsDS5ReplicaType: 3
nsDS5ReplicaId: 1
nsDS5ReplicaBindDN: cn=replication manager,cn=config
nsState:: AQAAAAAAAADPtOpdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
nsDS5ReplicaName: d2760403-186311ea-a803c30d-f0de2b88

1 new commit added

  • Filter repeated items and get the port correctly
4 years ago

Okay, I found the cause of the issues. Please, check.

Much better, but I have have some concerns. I updated the ticket with screen shots and comments.

1 new commit added

  • Fix eslintrc indent conflict
4 years ago

More improvements! Please, check.

After clicking "generate report" the Auth Modal "Bind DN" field is disabled and I can not change it.

If I edit the connection in the Cred Table the new bind DN sticks, but it should be editable in the modal. I like how you have a switch to show the table, but I don't like the title "All-In-One". Maybe call it "Table View"?

Minor issues: the "Refresh" label and checkbox are really far apart. They should be in the same Column, not in separate columns, same for the Timeout field and label - they should be connected/static.

Besides that I think we are good to go! Thanks for listening to all my annoying suggestions.

One more thing :-)

In the Report Table, the table itself is really wide. I think we can remove the "Is Enabled" column. For this report we should not be looking at disabled agreements anyway.

That is all...

rebased onto 3b10d22a52db28aa121b53468c7bac93c8b489ad

4 years ago

Now I am not prompted at all when generating the report, and it still uses "cn=directory manager" not my custom Root DN value (cn=dm). Then the browser crashes and dsconf did not report a useful error message:

replMonitor.jsx:1028 MARK err:  B {problem: null, exit_status: 1, exit_signal: null, message: "dsconf exited with code 1", toString: ƒ}exit_signal: nullexit_status: 1message: "dsconf exited with code 1"problem: nulltoString: ƒ ()__proto__: Object

12:24:09.453 VM1301:1 Uncaught SyntaxError: Unexpected token d in JSON at position 0
    at JSON.parse (<anonymous>)
    at Function.<anonymous> (replMonitor.jsx:1029)
    at s (cockpit.js:962)
    at cockpit.js:974
    at n (cockpit.js:880)
(anonymous) @ replMonitor.jsx:1029

Even if I edit the connection to use the correct bind DN the browser still crashes the same way.

1 new commit added

  • Correct BindDN auto-fill
4 years ago

7 new commits added

  • Correct BindDN auto-fill
  • Make more improvements
  • Fix eslintrc indent conflict
  • Filter repeated items and get the port correctly
  • Fix issues 2
  • Fix various issues
  • Issue 50545 - Add the new replication monitor functionality to UI
4 years ago

rebased onto 4ec9e6744ee697a35191304569e627da4c6fecf8

4 years ago

Fixed a small issue with clearing dynamic credentials at a failure.

When I goto the replication monitor and click generate report (without doing anything else ) I do NOT get prompted for a password (even though it says interactive prompt is set), and I get an error:

Sync report has failed - Please, fill in all Credential details. It should be host:port:binddn:bindpw

So for the connection to the remote replica it does not have a bind DN set. If I add a bind DN for the other connection then when I click on generate report it does propt for the password, but it should prompt when the bind DN is also not set. Ir better yet, if you don't know the remote bind DN then use the local bind DN. Typically people use the same Root DN for all their servers, so it's a good assumption to make

After entering all the correct data, the report works, and I do not see any console crashes.

However the header of the report, that has all the options, is still bulky. The font size should be reduced, and I think the table columns should be wider. Also the text "loading data" is word wrapped, no need for that. If you don't mind please consider applying this patch to your PR

diff --git a/src/cockpit/389-console/src/css/ds.css b/src/cockpit/389-console/src/css/ds.css
index e6bb1da5d..662d8de5f 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 aef07f809..f7a46b1a2 100644
--- a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
@@ -1,5 +1,4 @@
 import React from "react";
-import Switch from "react-switch";
 import {
     Modal,
     Row,
@@ -849,7 +848,7 @@ class FullReportContent extends React.Component {
         let spinner = "";
         if (reportLoading) {
             spinner = (
-                <Col sm={3}>
+                <Col sm={12} className="ds-center ds-margin-top">
                     <ControlLabel title="Do the refresh every few seconds">
                         {reportRefreshing ? "Refreshing" : "Loading"} the report...
                     </ControlLabel>
@@ -862,54 +861,60 @@ class FullReportContent extends React.Component {
             reportHeader = (
                 <Form horizontal autoComplete="off">
                     <FormGroup controlId="reportRefreshTimeout">
-                        <Col sm={3}>
-                            <Checkbox
-                                checked={reportRefreshing}
-                                id="reportRefreshing"
-                                onChange={handleRefresh}
-                                title="Do the refresh every few seconds"
-                            >
-                                Refresh
-                            </Checkbox>
-                        </Col>
-                        <Col componentClass={ControlLabel} sm={2} title="Refresh Timeout">
-                            Timeout (seconds)
-                        </Col>
-                        <Col sm={2}>
-                            <FormControl
-                                id="reportRefreshTimeout"
-                                type="number"
-                                min="1"
-                                max="65534"
-                                value={reportRefreshTimeout}
-                                onChange={handleFieldChange}
-                            />
+                        <Col sm={5}>
+                            <div className="ds-inline">
+                                <Checkbox
+                                    checked={reportRefreshing}
+                                    id="reportRefreshing"
+                                    onChange={handleRefresh}
+                                    title="Do the refresh every few seconds"
+                                >
+                                    Refresh
+                                </Checkbox>
+                            </div>
+                            <div className="ds-inline ds-left-margin ds-raise-field">
+                                <FormControl
+                                    type="number"
+                                    min="1"
+                                    max="65534"
+                                    className="ds-input-right"
+                                    value={reportRefreshTimeout}
+                                    onChange={handleFieldChange}
+                                    title="Refresh timeout in seconds"
+                                />
+                            </div>
+                            <div className="ds-inline ds-left-margin">
+                                <font size="2">seconds</font>
+                            </div>
                         </Col>
-                        {spinner}
                     </FormGroup>
-                    <FormGroup controlId="oneTableReport">
-                        <Col sm={3}>
+                    <FormGroup controlId="showDisabledAgreements">
+                        <Col sm={8}>
                             <Checkbox
                                 checked={this.state.showDisabledAgreements}
                                 id="showDisabledAgreements"
                                 onChange={this.handleSwitchChange}
-                                title="Display all agreements including the disabled and the ones that we are failed to connect to"
+                                title="Display all agreements including the disabled ones and the ones we failed to connect to"
                             >
                                 Show All (Including Disabled Agreements)
                             </Checkbox>
                         </Col>
-                        <Col componentClass={ControlLabel} sm={2} title="Show all data in one table (it makes it easier to check lag times)">
-                            Table View
-                        </Col>
-                        <Col sm={3}>
-                            <Switch
-                                className="ds-switch"
-                                onChange={this.handleSwitchChange}
+                    </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}
-                                height={20}
-                            />
+                                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>
+                    <FormGroup>
+                        {spinner}
+                    </FormGroup>
                     <hr />
                 </Form>
             );

rebased onto aec00692c5d744de373253288661a1d4f3d96187

4 years ago

Thanks! I like how it looks.

I've just fixed one thing - when we set refresh timeout = 1, it will refresh the UI very often and the whole report will be bouncing each second.

So I've initialise the spinner variable with an empty ControlLablel so at least some element is always present there.

Please, check.

Thanks Simon! I have one more patch to suggest. It sets the columns width for the refresh setting to the max, this prevents it from wrapping on smaller screens.

I set the minimum refresh to 5 seconds, With lots of replicas and agreements, one second refresh would never complete anyway. So I set it to 5 seconds as the minimum.\

Then, especially in PF4 being "bigger", vertical space on the page is limited. So I changed the refresh spinner to replace the report body when loading.

Let me know what you think. Feel free to tweak it anyway you want:

diff --git a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
index 952fbf047..ef4e83025 100644
--- a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
@@ -861,7 +861,7 @@ class FullReportContent extends React.Component {
             reportHeader = (
                 <Form horizontal autoComplete="off">
                     <FormGroup controlId="reportRefreshTimeout">
-                        <Col sm={5}>
+                        <Col sm={12}>
                             <div className="ds-inline">
                                 <Checkbox
                                     checked={reportRefreshing}
@@ -875,7 +875,7 @@ class FullReportContent extends React.Component {
                             <div className="ds-inline ds-left-margin ds-raise-field">
                                 <FormControl
                                     type="number"
-                                    min="1"
+                                    min="5"
                                     max="65534"
                                     className="ds-input-right"
                                     value={reportRefreshTimeout}
@@ -912,11 +912,6 @@ class FullReportContent extends React.Component {
                             </Checkbox>
                         </Col>
                     </FormGroup>
-                    <FormGroup>
-                        <Col sm={12} className="ds-center ds-margin-top">
-                            {spinner}
-                        </Col>
-                    </FormGroup>
                     <hr />
                 </Form>
             );
@@ -996,15 +991,22 @@ class FullReportContent extends React.Component {
             }
         }

+        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}
-                {suppliers.map(supplier => (
-                    <div key={supplier.key}>
-                        {supplier}
-                        <hr />
-                    </div>
-                ))}
+                {report}
             </div>
         );
     }

I like all of the changes except this one:

Then, especially in PF4 being "bigger", vertical space on the page is limited. So I changed the refresh spinner to replace the report body when loading.

I think it breaks the report usability a lot.
For example, you what to continuously refresh a big report on a slow machine. It will make the feature (continuous refresh) unusable because the report will be present on the screen only for 1 second and the rest of the time it will be Refreshing the report... And it will be like this all the time - 1-2 second of ther report, 3-4 seconds of the Refreshing...

Or what point of view on the continuous refresh feature do you have? Maybe I miss something

To be honest, i don't think continuous refresh is that useful for replication reports. It's just not that dynamic. If things are "off", it could take hours for them to get "fixed". I can't imagine someone wanting a refresh to be done every 5 or 10 seconds. Maybe it could just be a button, and refresh it when "you" want to?

1 new commit added

  • Change continuous refresh to a button
4 years ago

Actually, yeah, sounds good! I think it makes sense.
Please, check.

rebased onto 3054205

4 years ago

Pull-Request has been merged by spichugi

4 years ago

389-ds-base is moving from Pagure to Github. This means that new issues and pull requests
will be accepted only in 389-ds-base's github repository.

This pull request has been cloned to Github as issue and is available here:
- https://github.com/389ds/389-ds-base/issues/3788

If you want to continue to work on the PR, please navigate to the github issue,
download the patch from the attachments and file a new pull request.

Thank you for understanding. We apologize for all inconvenience.

Pull-Request has been closed by spichugi

3 years ago