#50315 Issue 50041 - Add the rest UI Plugin tabs - Part 1
Closed 3 years ago by spichugi. Opened 5 years ago by spichugi.
spichugi/389-ds-base plugin_ui_the_rest  into  master

@@ -1,24 +1,688 @@ 

+ import cockpit from "cockpit";

  import React from "react";

- import { noop } from "patternfly-react";

+ import {

+     Icon,

+     Modal,

+     Button,

+     Row,

+     Col,

+     Form,

+     noop,

+     FormGroup,

+     FormControl,

+     Checkbox,

+     ControlLabel

+ } from "patternfly-react";

+ import { Typeahead } from "react-bootstrap-typeahead";

  import PropTypes from "prop-types";

  import PluginBasicConfig from "./pluginBasicConfig.jsx";

+ import { log_cmd } from "../tools.jsx";

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

  

+ // Use default aacount policy name

+ 

  class AccountPolicy extends React.Component {

+     componentWillMount(prevProps) {

+         this.updateFields();

+     }

+ 

+     componentDidUpdate(prevProps) {

+         if (this.props.rows !== prevProps.rows) {

+             this.updateFields();

+         }

+     }

+ 

+     constructor(props) {

+         super(props);

+ 

+         this.getAttributes = this.getAttributes.bind(this);

+         this.updateFields = this.updateFields.bind(this);

+         this.handleFieldChange = this.handleFieldChange.bind(this);

+         this.handleCheckboxChange = this.handleCheckboxChange.bind(this);

+         this.openModal = this.openModal.bind(this);

+         this.closeModal = this.closeModal.bind(this);

+         this.addConfig = this.addConfig.bind(this);

+         this.editConfig = this.editConfig.bind(this);

+         this.deleteConfig = this.deleteConfig.bind(this);

+         this.cmdOperation = this.cmdOperation.bind(this);

+ 

+         this.state = {

+             attributes: [],

+             configArea: "",

+             configDN: "",

+             altStateAttrName: [],

+             alwaysRecordLogin: false,

+             alwaysRecordLoginAttr: [],

+             limitAttrName: [],

+             specAttrName: [],

+             stateAttrName: [],

+             configEntryModalShow: false,

+             fixupModalShow: false,

+             newEntry: false

+         };

+     }

+ 

+     openModal() {

+         this.getAttributes();

+         if (!this.state.configArea) {

+             this.setState({

+                 configEntryModalShow: true,

+                 newEntry: true,

+                 configDN: "",

+                 altStateAttrName: [],

+                 alwaysRecordLogin: false,

+                 alwaysRecordLoginAttr: [],

+                 limitAttrName: [],

+                 specAttrName: [],

+                 stateAttrName: []

+             });

+         } else {

+             let cmd = [

+                 "dsconf",

+                 "-j",

+                 "ldapi://%2fvar%2frun%2fslapd-" +

+                     this.props.serverId +

+                     ".socket",

+                 "plugin",

+                 "account-policy",

+                 "config-entry",

+                 "show",

+                 this.state.configArea

+             ];

+ 

+             this.props.toggleLoadingHandler();

+             log_cmd(

+                 "openModal",

+                 "Fetch the Account Policy Plugin config entry",

+                 cmd

+             );

+             cockpit

+                     .spawn(cmd, {

+                         superuser: true,

+                         err: "message"

+                     })

+                     .done(content => {

+                         let configEntry = JSON.parse(content).attrs;

+                         this.setState({

+                             configEntryModalShow: true,

+                             newEntry: false,

+                             configDN: this.state.configArea,

+                             altStateAttrName:

+                             configEntry["altstateattrname"] === undefined

+                                 ? []

+                                 : [{id: configEntry["altstateattrname"][0],

+                                     label: configEntry["altstateattrname"][0]}],

+                             alwaysRecordLogin: !(

+                                 configEntry["alwaysrecordlogin"] === undefined ||

+                             configEntry["alwaysrecordlogin"][0] == "no"

+                             ),

+                             alwaysRecordLoginAttr:

+                             configEntry["alwaysrecordloginattr"] === undefined

+                                 ? []

+                                 : [{id: configEntry["alwaysrecordloginattr"][0],

+                                     label: configEntry["alwaysrecordloginattr"][0]}],

+                             limitAttrName:

+                             configEntry["limitattrname"] === undefined

+                                 ? []

+                                 : [{id: configEntry["limitattrname"][0],

+                                     label: configEntry["limitattrname"][0]}],

+                             specAttrName:

+                             configEntry["specattrname"] === undefined

+                                 ? []

+                                 : [{id: configEntry["specattrname"][0],

+                                     label: configEntry["specattrname"][0]}],

+                             stateAttrName:

+                             configEntry["stateattrname"] === undefined

+                                 ? []

+                                 : [{id: configEntry["stateattrname"][0],

+                                     label: configEntry["stateattrname"][0]}],

+                         });

+                         this.props.toggleLoadingHandler();

+                     })

+                     .fail(_ => {

+                         this.setState({

+                             configEntryModalShow: true,

+                             newEntry: true,

+                             configDN: this.state.configArea,

+                             altStateAttrName: [],

+                             alwaysRecordLogin: false,

+                             alwaysRecordLoginAttr: [],

+                             limitAttrName: [],

+                             specAttrName: [],

+                             stateAttrName: []

+                         });

+                         this.props.toggleLoadingHandler();

+                     });

+         }

+     }

+ 

+     closeModal() {

+         this.setState({ configEntryModalShow: false });

+     }

+ 

+     cmdOperation(action) {

+         const {

+             configDN,

+             altStateAttrName,

+             alwaysRecordLogin,

+             alwaysRecordLoginAttr,

+             limitAttrName,

+             specAttrName,

+             stateAttrName

+         } = this.state;

+ 

+         let cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "plugin",

+             "account-policy",

+             "config-entry",

+             action,

+             configDN,

+             "--always-record-login",

+             alwaysRecordLogin ? "yes" : "no",

+         ];

+ 

+         cmd = [...cmd, "--alt-state-attr"];

+         if (altStateAttrName.length != 0) {

+             cmd = [...cmd, altStateAttrName[0].id];

+         } else if (action == "add") {

+             cmd = [...cmd, ""];

+         } else {

+             cmd = [...cmd, "delete"];

+         }

+ 

+         cmd = [...cmd, "--always-record-login-attr"];

+         if (alwaysRecordLoginAttr.length != 0) {

+             cmd = [...cmd, alwaysRecordLoginAttr[0].id];

+         } else if (action == "add") {

+             cmd = [...cmd, ""];

+         } else {

+             cmd = [...cmd, "delete"];

+         }

+ 

+         cmd = [...cmd, "--limit-attr"];

+         if (limitAttrName.length != 0) {

+             cmd = [...cmd, limitAttrName[0].id];

+         } else if (action == "add") {

+             cmd = [...cmd, ""];

+         } else {

+             cmd = [...cmd, "delete"];

+         }

+ 

+         cmd = [...cmd, "--spec-attr"];

+         if (specAttrName.length != 0) {

+             cmd = [...cmd, specAttrName[0].id];

+         } else if (action == "add") {

+             cmd = [...cmd, ""];

+         } else {

+             cmd = [...cmd, "delete"];

+         }

+ 

+         cmd = [...cmd, "--state-attr"];

+         if (stateAttrName.length != 0) {

+             cmd = [...cmd, stateAttrName[0].id];

+         } else if (action == "add") {

+             cmd = [...cmd, ""];

+         } else {

+             cmd = [...cmd, "delete"];

+         }

+ 

+         this.props.toggleLoadingHandler();

+         log_cmd(

+             "accountPolicyOperation",

+             `Do the ${action} operation on the Account Policy Plugin`,

+             cmd

+         );

+         cockpit

+                 .spawn(cmd, {

+                     superuser: true,

+                     err: "message"

+                 })

+                 .done(content => {

+                     console.info("accountPolicyOperation", "Result", content);

+                     this.props.addNotification(

+                         "success",

+                         `Config entry ${configDN} was successfully ${action}ed`

+                     );

+                     this.props.pluginListHandler();

+                     this.closeModal();

+                     this.props.toggleLoadingHandler();

+                 })

+                 .fail(err => {

+                     this.props.addNotification(

+                         "error",

+                         `Error during the config entry ${action} operation - ${err}`

+                     );

+                     this.props.pluginListHandler();

+                     this.closeModal();

+                     this.props.toggleLoadingHandler();

+                 });

+     }

+ 

+     deleteConfig() {

+         let cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "plugin",

+             "account-policy",

+             "config-entry",

+             "delete",

+             this.state.configDN

+         ];

+ 

+         this.props.toggleLoadingHandler();

+         log_cmd(

+             "deleteConfig",

+             "Delete the Account Policy Plugin config entry",

+             cmd

+         );

+         cockpit

+                 .spawn(cmd, {

+                     superuser: true,

+                     err: "message"

+                 })

+                 .done(content => {

+                     console.info("deleteConfig", "Result", content);

+                     this.props.addNotification(

+                         "success",

+                         `Config entry ${

+                             this.state.configDN

+                         } was successfully deleted`

+                     );

+                     this.props.pluginListHandler();

+                     this.closeModal();

+                     this.props.toggleLoadingHandler();

+                 })

+                 .fail(err => {

+                     this.props.addNotification(

+                         "error",

+                         `Error during the config entry removal operation - ${err}`

+                     );

+                     this.props.pluginListHandler();

+                     this.closeModal();

+                     this.props.toggleLoadingHandler();

+                 });

+     }

+ 

+     addConfig() {

+         this.cmdOperation("add");

+     }

+ 

+     editConfig() {

+         this.cmdOperation("set");

+     }

+ 

+     handleCheckboxChange(e) {

+         this.setState({

+             [e.target.id]: e.target.checked

+         });

+     }

+ 

+     handleFieldChange(e) {

+         this.setState({

+             [e.target.id]: e.target.value

+         });

+     }

+ 

+     updateFields() {

+         if (this.props.rows.length > 0) {

+             const pluginRow = this.props.rows.find(

+                 row => row.cn[0] === "Account Policy Plugin"

+             );

+ 

+             this.setState({

+                 configArea:

+                     pluginRow["nsslapd_pluginconfigarea"] === undefined

+                         ? ""

+                         : pluginRow["nsslapd_pluginconfigarea"][0]

+             });

+         }

+     }

+ 

+     getAttributes() {

+         const attr_cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "schema",

+             "attributetypes",

+             "list"

+         ];

+         log_cmd("getAttributes", "Get attrs", attr_cmd);

+         cockpit

+                 .spawn(attr_cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     const attrContent = JSON.parse(content);

+                     let attrs = [];

+                     for (let content of attrContent["items"]) {

+                         attrs.push({

+                             id: content.name,

+                             label: content.name

+                         });

+                     }

+                     this.setState({

+                         attributes: attrs

+                     });

+                 })

+                 .fail(err => {

+                     this.props.addNotification(

+                         "error",

+                         `Failed to get attributes - ${err}`

+                     );

+                 });

+     }

+ 

      render() {

+         const {

+             attributes,

+             configArea,

+             configDN,

+             altStateAttrName,

+             alwaysRecordLogin,

+             alwaysRecordLoginAttr,

+             limitAttrName,

+             specAttrName,

+             stateAttrName,

+             newEntry,

+             configEntryModalShow

+         } = this.state;

+ 

+         let specificPluginCMD = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "plugin",

+             "account-policy",

+             "set",

+             "--config-entry",

+             configArea || "delete"

+         ];

+ 

          return (

              <div>

+                 <Modal show={configEntryModalShow} onHide={this.closeModal}>

+                     <div className="ds-no-horizontal-scrollbar">

+                         <Modal.Header>

+                             <button

+                                 className="close"

+                                 onClick={this.closeModal}

+                                 aria-hidden="true"

+                                 aria-label="Close"

+                             >

+                                 <Icon type="pf" name="close" />

+                             </button>

+                             <Modal.Title>

+                                 Manage Account Policy Plugin Shared Config Entry

+                             </Modal.Title>

+                         </Modal.Header>

+                         <Modal.Body>

+                             <Row>

+                                 <Col sm={12}>

+                                     <Form horizontal>

+                                         <FormGroup controlId="configDN">

+                                             <Col sm={4}>

+                                                 <ControlLabel title="DN of the config entry">

+                                                     Config DN

+                                                 </ControlLabel>

+                                             </Col>

+                                             <Col sm={8}>

+                                                 <FormControl

+                                                     type="text"

+                                                     value={configDN}

+                                                     onChange={

+                                                         this.handleFieldChange

+                                                     }

+                                                     disabled={!newEntry}

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                     </Form>

+                                 </Col>

+                             </Row>

+                             <Row>

+                                 <Col sm={12}>

+                                     <Form horizontal>

+                                         <FormGroup

+                                             key="alwaysRecordLoginAttr"

+                                             controlId="alwaysRecordLoginAttr"

+                                             disabled={false}

+                                         >

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={4}

+                                                 title="Specifies the attribute to store the time of the last successful login in this attribute in the users directory entry (alwaysRecordLoginAttr)"

+                                             >

+                                                 Always Record Login Attribute

+                                             </Col>

+                                             <Col sm={5}>

+                                                 <Typeahead

+                                                     allowNew

+                                                     onChange={value => {

+                                                         this.setState({

+                                                             alwaysRecordLoginAttr: value

+                                                         });

+                                                     }}

+                                                     selected={alwaysRecordLoginAttr}

+                                                     options={attributes}

+                                                     newSelectionPrefix="Add a managed attribute: "

+                                                     placeholder="Type an attribute..."

+                                                 />

+                                             </Col>

+                                             <Col sm={3}>

+                                                 <Checkbox

+                                                     id="alwaysRecordLogin"

+                                                     checked={alwaysRecordLogin}

+                                                     title="Sets that every entry records its last login time (alwaysRecordLogin)"

+                                                     onChange={

+                                                         this

+                                                                 .handleCheckboxChange

+                                                     }

+                                                 >

+                                                     Always Record Login

+                                                 </Checkbox>

+                                             </Col>

+                                         </FormGroup>

+                                     </Form>

+                                 </Col>

+                             </Row>

+                             <Row>

+                                 <Col sm={12}>

+                                     <Form horizontal>

+                                         <FormGroup

+                                             key="specAttrName"

+                                             controlId="specAttrName"

+                                             disabled={false}

+                                         >

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={4}

+                                                 title="Specifies the attribute to identify which entries are account policy configuration entries (specAttrName)"

+                                             >

+                                                 Specific Attribute

+                                             </Col>

+                                             <Col sm={8}>

+                                                 <Typeahead

+                                                     allowNew

+                                                     onChange={value => {

+                                                         this.setState({

+                                                             specAttrName: value

+                                                         });

+                                                     }}

+                                                     selected={specAttrName}

+                                                     options={attributes}

+                                                     newSelectionPrefix="Add a managed attribute: "

+                                                     placeholder="Type an attribute..."

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                         <FormGroup

+                                             key="stateAttrName"

+                                             controlId="stateAttrName"

+                                             disabled={false}

+                                         >

+                                             <Col sm={4}>

+                                                 <ControlLabel title="Specifies the primary time attribute used to evaluate an account policy (stateAttrName)">

+                                                     State Attribute

+                                                 </ControlLabel>

+                                             </Col>

+                                             <Col sm={8}>

+                                                 <Typeahead

+                                                     allowNew

+                                                     onChange={value => {

+                                                         this.setState({

+                                                             stateAttrName: value

+                                                         });

+                                                     }}

+                                                     selected={stateAttrName}

+                                                     options={attributes}

+                                                     newSelectionPrefix="Add a managed attribute: "

+                                                     placeholder="Type an attribute..."

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                     </Form>

+                                 </Col>

+                             </Row>

+                             <Row>

+                                 <Col sm={12}>

+                                     <Form horizontal>

+                                         <FormGroup

+                                             key="altStateAttrName"

+                                             controlId="altStateAttrName"

+                                             disabled={false}

+                                         >

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={4}

+                                                 title="Provides a backup attribute for the server to reference to evaluate the expiration time (altStateAttrName)"

+                                             >

+                                                 Alternative State Attribute

+                                             </Col>

+                                             <Col sm={8}>

+                                                 <Typeahead

+                                                     allowNew

+                                                     onChange={value => {

+                                                         this.setState({

+                                                             altStateAttrName: value

+                                                         });

+                                                     }}

+                                                     selected={altStateAttrName}

+                                                     options={attributes}

+                                                     newSelectionPrefix="Add a managed attribute: "

+                                                     placeholder="Type an attribute..."

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                         <FormGroup

+                                             controlId="limitAttrName"

+                                             disabled={false}

+                                         >

+                                             <Col sm={4}>

+                                                 <ControlLabel title="Specifies the attribute within the policy to use for the account inactivation limit (limitAttrName)">

+                                                     Limit Attribute

+                                                 </ControlLabel>

+                                             </Col>

+                                             <Col sm={8}>

+                                                 <Typeahead

+                                                     allowNew

+                                                     onChange={value => {

+                                                         this.setState({

+                                                             limitAttrName: value

+                                                         });

+                                                     }}

+                                                     selected={limitAttrName}

+                                                     options={attributes}

+                                                     newSelectionPrefix="Add a managed attribute: "

+                                                     placeholder="Type an attribute..."

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                     </Form>

+                                 </Col>

+                             </Row>

+                         </Modal.Body>

+                         <Modal.Footer>

+                             <Button

+                                 bsStyle="default"

+                                 className="btn-cancel"

+                                 onClick={this.closeModal}

+                             >

+                                 Cancel

+                             </Button>

+                             <Button

+                                 bsStyle="primary"

+                                 onClick={this.deleteConfig}

+                                 disabled={newEntry}

+                             >

+                                 Delete

+                             </Button>

+                             <Button

+                                 bsStyle="primary"

+                                 onClick={this.editConfig}

+                                 disabled={newEntry}

+                             >

+                                 Save

+                             </Button>

+                             <Button

+                                 bsStyle="primary"

+                                 onClick={this.addConfig}

+                                 disabled={!newEntry}

+                             >

+                                 Add

+                             </Button>

+                         </Modal.Footer>

+                     </div>

+                 </Modal>

                  <PluginBasicConfig

                      rows={this.props.rows}

                      serverId={this.props.serverId}

                      cn="Account Policy Plugin"

                      pluginName="Account Policy"

                      cmdName="account-policy"

+                     specificPluginCMD={specificPluginCMD}

                      savePluginHandler={this.props.savePluginHandler}

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

                      toggleLoadingHandler={this.props.toggleLoadingHandler}

-                 />

+                 >

+                     <Row>

+                         <Col sm={6}>

+                             <Form horizontal>

+                                 <FormGroup

+                                     key="configArea"

+                                     controlId="configArea"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={3}

+                                         title="DN of the shared config entry (nsslapd-pluginConfigArea)"

+                                     >

+                                         Shared Config Entry

+                                     </Col>

+                                     <Col sm={6}>

+                                         <FormControl

+                                             type="text"

+                                             value={configArea}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                     <Col sm={3}>

+                                         <Button

+                                             bsSize="large"

+                                             bsStyle="primary"

+                                             onClick={this.openModal}

+                                         >

+                                             Manage

+                                         </Button>

+                                     </Col>

+                                 </FormGroup>

+                             </Form>

+                         </Col>

+                     </Row>

+                 </PluginBasicConfig>

              </div>

          );

      }

@@ -1,14 +1,698 @@ 

+ import cockpit from "cockpit";

  import React from "react";

- import { noop } from "patternfly-react";

- import PropTypes from "prop-types";

+ import {

+     Icon,

+     Modal,

+     Button,

+     Row,

+     Col,

+     Form,

+     Switch,

+     noop,

+     FormGroup,

+     FormControl,

+     Checkbox,

+     ControlLabel

+ } from "patternfly-react";

+ import { Typeahead } from "react-bootstrap-typeahead";

+ import { AttrUniqConfigTable } from "./pluginTables.jsx";

  import PluginBasicConfig from "./pluginBasicConfig.jsx";

+ import PropTypes from "prop-types";

+ import { log_cmd } from "../tools.jsx";

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

  

  class AttributeUniqueness extends React.Component {

+     componentWillMount() {

+         this.loadConfigs();

+     }

+ 

+     constructor(props) {

+         super(props);

+         this.state = {

+             configRows: [],

+             attributes: [],

+             objectClasses: [],

+ 

+             configName: "",

+             configEnabled: false,

+             attrNames: [],

+             subtrees: [],

+             acrossAllSubtrees: false,

+             topEntryOc: [],

+             subtreeEnriesOc: [],

+ 

+             newEntry: false,

+             showConfigModal: false,

+             showConfirmDeleteConfig: false

+         };

+ 

+         this.handleSwitchChange = this.handleSwitchChange.bind(this);

+         this.handleCheckboxChange = this.handleCheckboxChange.bind(this);

+         this.handleFieldChange = this.handleFieldChange.bind(this);

+         this.loadConfigs = this.loadConfigs.bind(this);

+         this.showEditConfigModal = this.showEditConfigModal.bind(this);

+         this.showAddConfigModal = this.showAddConfigModal.bind(this);

+         this.getAttributes = this.getAttributes.bind(this);

+         this.getObjectClasses = this.getObjectClasses.bind(this);

+         this.closeModal = this.closeModal.bind(this);

+         this.openModal = this.openModal.bind(this);

+         this.cmdOperation = this.cmdOperation.bind(this);

+         this.deleteConfig = this.deleteConfig.bind(this);

+         this.addConfig = this.addConfig.bind(this);

+         this.editConfig = this.editConfig.bind(this);

+     }

+ 

+     handleSwitchChange(value) {

+         this.setState({

+             configEnabled: !value

+         });

+     }

+ 

+     handleCheckboxChange(e) {

+         this.setState({

+             [e.target.id]: e.target.checked

+         });

+     }

+ 

+     handleFieldChange(e) {

+         this.setState({

+             [e.target.id]: e.target.value

+         });

+     }

+ 

+     loadConfigs() {

+         // Get all the attributes and matching rules now

+         const cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "plugin",

+             "attr-uniq",

+             "list"

+         ];

+         this.props.toggleLoadingHandler();

+         log_cmd("loadConfigs", "Get Attribute Uniqueness Plugin configs", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     let myObject = JSON.parse(content);

+                     this.setState({

+                         configRows: myObject.items.map(

+                             item => JSON.parse(item).attrs

+                         )

+                     });

+                     this.props.toggleLoadingHandler();

+                 })

+                 .fail(err => {

+                     if (err != 0) {

+                         console.log("loadConfigs failed", err);

+                     }

+                     this.props.toggleLoadingHandler();

+                 });

+     }

+ 

+     showEditConfigModal(rowData) {

+         this.openModal(rowData.cn[0]);

+     }

+ 

+     showAddConfigModal(rowData) {

+         this.openModal();

+     }

+ 

+     openModal(name) {

+         this.getAttributes();

+         this.getObjectClasses();

+         if (!name) {

+             this.setState({

+                 configEntryModalShow: true,

+                 newEntry: true,

+                 configName: "",

+                 attrNames: [],

+                 subtrees: [],

+                 acrossAllSubtrees: false,

+                 topEntryOc: [],

+                 subtreeEnriesOc: []

+             });

+         } else {

+             let configAttrNamesList = [];

+             let configSubtreesList = [];

+             let cmd = [

+                 "dsconf",

+                 "-j",

+                 "ldapi://%2fvar%2frun%2fslapd-" +

+                     this.props.serverId +

+                     ".socket",

+                 "plugin",

+                 "attr-uniq",

+                 "show",

+                 name

+             ];

+ 

+             this.props.toggleLoadingHandler();

+             log_cmd(

+                 "openModal",

+                 "Fetch the Attribute Uniqueness Plugin config entry",

+                 cmd

+             );

+             cockpit

+                     .spawn(cmd, {

+                         superuser: true,

+                         err: "message"

+                     })

+                     .done(content => {

+                         let configEntry = JSON.parse(content).attrs;

+                         this.setState({

+                             configEntryModalShow: true,

+                             newEntry: false,

+                             configName:

+                             configEntry["cn"] === undefined

+                                 ? ""

+                                 : configEntry["cn"][0],

+                             configEnabled: !(

+                                 configEntry["nsslapd-pluginenabled"] ===

+                                 undefined ||

+                             configEntry["nsslapd-pluginenabled"][0] == "off"

+                             ),

+                             acrossAllSubtrees: !(

+                                 configEntry["uniqueness-across-all-subtrees"] ===

+                                 undefined ||

+                             configEntry["uniqueness-across-all-subtrees"][0] ==

+                                 "off"

+                             ),

+                             topEntryOc:

+                             configEntry["uniqueness-top-entry-oc"] === undefined

+                                 ? []

+                                 : [{id: configEntry["uniqueness-top-entry-oc"][0],

+                                     label: configEntry["uniqueness-top-entry-oc"][0]}],

+                             subtreeEnriesOc:

+                             configEntry["uniqueness-subtree-entries-oc"] ===

+                             undefined

+                                 ? []

+                                 : [{id: configEntry["uniqueness-subtree-entries-oc"][0],

+                                     label: configEntry["uniqueness-subtree-entries-oc"][0]}]

+                         });

+ 

+                         if (

+                             configEntry["uniqueness-attribute-name"] === undefined

+                         ) {

+                             this.setState({ attrNames: [] });

+                         } else {

+                             for (let value of configEntry["uniqueness-attribute-name"]) {

+                                 configAttrNamesList = [

+                                     ...configAttrNamesList,

+                                     { id: value, label: value }

+                                 ];

+                             }

+                             this.setState({ attrNames: configAttrNamesList });

+                         }

+                         if (configEntry["uniqueness-subtrees"] === undefined) {

+                             this.setState({ subtrees: [] });

+                         } else {

+                             for (let value of configEntry["uniqueness-subtrees"]) {

+                                 configSubtreesList = [

+                                     ...configSubtreesList,

+                                     { id: value, label: value }

+                                 ];

+                             }

+                             this.setState({ subtrees: configSubtreesList });

+                         }

+                         this.props.toggleLoadingHandler();

+                     })

+                     .fail(_ => {

+                         this.setState({

+                             configEntryModalShow: true,

+                             newEntry: true,

+                             configName: "",

+                             attrNames: [],

+                             subtrees: [],

+                             acrossAllSubtrees: false,

+                             topEntryOc: [],

+                             subtreeEnriesOc: []

+                         });

+                         this.props.toggleLoadingHandler();

+                     });

+         }

+     }

+ 

+     closeModal() {

+         this.setState({ configEntryModalShow: false });

+     }

+ 

+     cmdOperation(action) {

+         const {

+             configName,

+             configEnabled,

+             attrNames,

+             subtrees,

+             acrossAllSubtrees,

+             topEntryOc,

+             subtreeEnriesOc

+         } = this.state;

+ 

+         let cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "plugin",

+             "attr-uniq",

+             action,

+             configName,

+             "--enabled",

+             configEnabled ? "on" : "off",

+             "--across-all-subtrees",

+             acrossAllSubtrees ? "on" : "off"

+         ];

+ 

+         // Delete attributes if the user set an empty value to the field

+         if (!(action == "add" && attrNames.length == 0)) {

+             cmd = [...cmd, "--attr-name"];

+             if (attrNames.length != 0) {

+                 for (let value of attrNames) {

+                     cmd = [...cmd, value.id];

+                 }

+             } else if (action == "add") {

+                 cmd = [...cmd, ""];

+             } else {

+                 cmd = [...cmd, "delete"];

+             }

+         }

+ 

+         if (!(action == "add" && subtrees.length == 0)) {

+             cmd = [...cmd, "--subtree"];

+             if (subtrees.length != 0) {

+                 for (let value of subtrees) {

+                     cmd = [...cmd, value.id];

+                 }

+             } else if (action == "add") {

+                 cmd = [...cmd, ""];

+             } else {

+                 cmd = [...cmd, "delete"];

+             }

+         }

+ 

+         cmd = [...cmd, "--top-entry-oc"];

+         if (topEntryOc.length != 0) {

+             cmd = [...cmd, topEntryOc[0].id];

+         } else if (action == "add") {

+             cmd = [...cmd, ""];

+         } else {

+             cmd = [...cmd, "delete"];

+         }

+ 

+         cmd = [...cmd, "--subtree-entries-oc"];

+         if (subtreeEnriesOc.length != 0) {

+             cmd = [...cmd, subtreeEnriesOc[0].id];

+         } else if (action == "add") {

+             cmd = [...cmd, ""];

+         } else {

+             cmd = [...cmd, "delete"];

+         }

+ 

+         this.props.toggleLoadingHandler();

+         log_cmd(

+             "attrUniqOperation",

+             `Do the ${action} operation on the Attribute Uniqueness Plugin`,

+             cmd

+         );

+         cockpit

+                 .spawn(cmd, {

+                     superuser: true,

+                     err: "message"

+                 })

+                 .done(content => {

+                     console.info("attrUniqOperation", "Result", content);

+                     this.props.addNotification(

+                         "success",

+                         `The ${action} operation was successfully done on "${configName}" entry`

+                     );

+                     this.loadConfigs();

+                     this.closeModal();

+                     this.props.toggleLoadingHandler();

+                 })

+                 .fail(err => {

+                     this.props.addNotification(

+                         "error",

+                         `Error during the config entry ${action} operation - ${err}`

+                     );

+                     this.loadConfigs();

+                     this.closeModal();

+                     this.props.toggleLoadingHandler();

+                 });

+     }

+ 

+     deleteConfig(rowData) {

+         let configName = rowData.cn[0];

+         let cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "plugin",

+             "attr-uniq",

+             "delete",

+             configName

+         ];

+ 

+         this.props.toggleLoadingHandler();

+         log_cmd(

+             "deleteConfig",

+             "Delete the Attribute Uniqueness Plugin config entry",

+             cmd

+         );

+         cockpit

+                 .spawn(cmd, {

+                     superuser: true,

+                     err: "message"

+                 })

+                 .done(content => {

+                     console.info("deleteConfig", "Result", content);

+                     this.props.addNotification(

+                         "success",

+                         `Config entry ${configName} was successfully deleted`

+                     );

+                     this.loadConfigs();

+                     this.closeModal();

+                     this.props.toggleLoadingHandler();

+                 })

+                 .fail(err => {

+                     this.props.addNotification(

+                         "error",

+                         `Error during the config entry removal operation - ${err}`

+                     );

+                     this.loadConfigs();

+                     this.closeModal();

+                     this.props.toggleLoadingHandler();

+                 });

+     }

+ 

+     addConfig() {

+         this.cmdOperation("add");

+     }

+ 

+     editConfig() {

+         this.cmdOperation("set");

+     }

+ 

+     getAttributes() {

+         const attr_cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "schema",

+             "attributetypes",

+             "list"

+         ];

+         log_cmd("getAttributes", "Get attrs", attr_cmd);

+         cockpit

+                 .spawn(attr_cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     const attrContent = JSON.parse(content);

+                     let attrs = [];

+                     for (let content of attrContent["items"]) {

+                         attrs.push({

+                             id: content.name,

+                             label: content.name

+                         });

+                     }

+                     this.setState({

+                         attributes: attrs

+                     });

+                 })

+                 .fail(err => {

+                     this.props.addNotification(

+                         "error",

+                         `Failed to get attributes - ${err}`

+                     );

+                 });

+     }

+ 

+     getObjectClasses() {

+         const oc_cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "schema",

+             "objectclasses",

+             "list"

+         ];

+         log_cmd("getObjectClasses", "Get objectClasses", oc_cmd);

+         cockpit

+                 .spawn(oc_cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     const ocContent = JSON.parse(content);

+                     let ocs = [];

+                     for (let content of ocContent["items"]) {

+                         ocs.push({

+                             id: content.name,

+                             label: content.name

+                         });

+                     }

+                     this.setState({

+                         objectClasses: ocs

+                     });

+                 })

+                 .fail(err => {

+                     this.props.addNotification(

+                         "error",

+                         `Failed to get objectClasses - ${err}`

+                     );

+                 });

+     }

+ 

      render() {

+         const {

+             configEntryModalShow,

+             configName,

+             attrNames,

+             subtrees,

+             acrossAllSubtrees,

+             configEnabled,

+             topEntryOc,

+             subtreeEnriesOc,

+             newEntry,

+             attributes,

+             objectClasses

+         } = this.state;

+ 

          return (

              <div>

+                 <Modal show={configEntryModalShow} onHide={this.closeModal}>

+                     <div className="ds-no-horizontal-scrollbar">

+                         <Modal.Header>

+                             <button

+                                 className="close"

+                                 onClick={this.closeModal}

+                                 aria-hidden="true"

+                                 aria-label="Close"

+                             >

+                                 <Icon type="pf" name="close" />

+                             </button>

+                             <Modal.Title>

+                                 {newEntry ? "Add" : "Edit"} Attribute Uniqueness

+                                 Plugin Config Entry

+                             </Modal.Title>

+                         </Modal.Header>

+                         <Modal.Body>

+                             <Row>

+                                 <Col sm={12}>

+                                     <Form horizontal>

+                                         <FormGroup controlId="configName">

+                                             <Col sm={3}>

+                                                 <ControlLabel title="Sets the name of the plug-in configuration record. (cn) You can use any string, but &quot;attribute_name Attribute Uniqueness&quot; is recommended.">

+                                                     Config Name

+                                                 </ControlLabel>

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <FormControl

+                                                     type="text"

+                                                     value={configName}

+                                                     onChange={

+                                                         this.handleFieldChange

+                                                     }

+                                                     disabled={!newEntry}

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                         <FormGroup

+                                             key="attrNames"

+                                             controlId="attrNames"

+                                             disabled={false}

+                                         >

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={3}

+                                                 title="Sets the name of the attribute whose values must be unique. This attribute is multi-valued. (uniqueness-attribute-name)"

+                                             >

+                                                 Attribute Names

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <Typeahead

+                                                     allowNew

+                                                     multiple

+                                                     onChange={values => {

+                                                         this.setState({

+                                                             attrNames: values

+                                                         });

+                                                     }}

+                                                     selected={attrNames}

+                                                     newSelectionPrefix="Add an attribute: "

+                                                     options={attributes}

+                                                     placeholder="Type an attribute name..."

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                         <FormGroup

+                                             key="subtrees"

+                                             controlId="subtrees"

+                                             disabled={false}

+                                         >

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={3}

+                                                 title="Sets the DN under which the plug-in checks for uniqueness of the attributes value. This attribute is multi-valued (uniqueness-subtrees)"

+                                             >

+                                                 Subtrees

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <Typeahead

+                                                     allowNew

+                                                     multiple

+                                                     onChange={values => {

+                                                         this.setState({

+                                                             subtrees: values

+                                                         });

+                                                     }}

+                                                     selected={subtrees}

+                                                     options={[""]}

+                                                     newSelectionPrefix="Add a subtree: "

+                                                     placeholder="Type a subtree DN..."

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                     </Form>

+                                 </Col>

+                             </Row>

+                             <Row>

+                                 <Col sm={12}>

+                                     <Form horizontal>

+                                         <FormGroup

+                                             key="topEntryOc"

+                                             controlId="topEntryOc"

+                                             disabled={false}

+                                         >

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={3}

+                                                 title="Verifies that the value of the attribute set in uniqueness-attribute-name is unique in this subtree (uniqueness-top-entry-oc)"

+                                             >

+                                                 Top Entry OC

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <Typeahead

+                                                     allowNew

+                                                     onChange={value => {

+                                                         this.setState({

+                                                             topEntryOc: value

+                                                         });

+                                                     }}

+                                                     selected={topEntryOc}

+                                                     options={objectClasses}

+                                                     newSelectionPrefix="Add a top entry objectClass: "

+                                                     placeholder="Type an objectClass..."

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                         <FormGroup

+                                             key="subtreeEnriesOc"

+                                             controlId="subtreeEnriesOc"

+                                             disabled={false}

+                                         >

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={3}

+                                                 title="Verifies if an attribute is unique, if the entry contains the object class set in this parameter (uniqueness-subtree-entries-oc)"

+                                             >

+                                                 Subtree Entries OC

+                                             </Col>

+                                             <Col sm={6}>

+                                                 <Typeahead

+                                                     allowNew

+                                                     onChange={value => {

+                                                         this.setState({

+                                                             subtreeEnriesOc: value

+                                                         });

+                                                     }}

+                                                     selected={subtreeEnriesOc}

+                                                     options={objectClasses}

+                                                     newSelectionPrefix="Add a subtree entries objectClass: "

+                                                     placeholder="Type an objectClass..."

+                                                 />

+                                             </Col>

+                                             <Col sm={3}>

+                                                 <Checkbox

+                                                     id="acrossAllSubtrees"

+                                                     checked={acrossAllSubtrees}

+                                                     title="If enabled (on), the plug-in checks that the attribute is unique across all subtrees set. If you set the attribute to off, uniqueness is only enforced within the subtree of the updated entry (uniqueness-across-all-subtrees)"

+                                                     onChange={

+                                                         this

+                                                                 .handleCheckboxChange

+                                                     }

+                                                 >

+                                                     Across All Subtrees

+                                                 </Checkbox>

+                                             </Col>

+                                         </FormGroup>

+                                         <FormGroup

+                                             key="configEnabled"

+                                             controlId="configEnabled"

+                                             disabled={false}

+                                         >

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={3}

+                                                 title="Identifies whether or not the config is enabled."

+                                             >

+                                                 Enable config

+                                             </Col>

+                                             <Col sm={3}>

+                                                 <Switch

+                                                     bsSize="normal"

+                                                     title="normal"

+                                                     id="configEnabled"

+                                                     value={configEnabled}

+                                                     onChange={() =>

+                                                         this.handleSwitchChange(

+                                                             configEnabled

+                                                         )

+                                                     }

+                                                     animate={false}

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                     </Form>

+                                 </Col>

+                             </Row>

+                         </Modal.Body>

+                         <Modal.Footer>

+                             <Button

+                                 bsStyle="default"

+                                 className="btn-cancel"

+                                 onClick={this.closeModal}

+                             >

+                                 Cancel

+                             </Button>

+                             <Button

+                                 bsStyle="primary"

+                                 onClick={

+                                     newEntry ? this.addConfig : this.editConfig

+                                 }

+                             >

+                                 Save

+                             </Button>

+                         </Modal.Footer>

+                     </div>

+                 </Modal>

                  <PluginBasicConfig

+                     removeSwitch

                      rows={this.props.rows}

                      serverId={this.props.serverId}

                      cn="attribute uniqueness"
@@ -18,7 +702,23 @@ 

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

                      toggleLoadingHandler={this.props.toggleLoadingHandler}

-                 />

+                 >

+                     <Row>

+                         <Col sm={9}>

+                             <AttrUniqConfigTable

+                                 rows={this.state.configRows}

+                                 editConfig={this.showEditConfigModal}

+                                 deleteConfig={this.deleteConfig}

+                             />

+                             <Button

+                                 bsStyle="primary"

+                                 onClick={this.showAddConfigModal}

+                             >

+                                 Add Config

+                             </Button>

+                         </Col>

+                     </Row>

+                 </PluginBasicConfig>

              </div>

          );

      }

@@ -1,13 +1,467 @@ 

+ import cockpit from "cockpit";

  import React from "react";

- import { noop } from "patternfly-react";

- import PropTypes from "prop-types";

+ import {

+     Icon,

+     Modal,

+     Button,

+     Row,

+     Col,

+     Form,

+     noop,

+     FormGroup,

+     FormControl,

+     ControlLabel

+ } from "patternfly-react";

+ import { Typeahead } from "react-bootstrap-typeahead";

+ import { LinkedAttributesTable } from "./pluginTables.jsx";

  import PluginBasicConfig from "./pluginBasicConfig.jsx";

+ import PropTypes from "prop-types";

+ import { log_cmd } from "../tools.jsx";

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

  

  class LinkedAttributes extends React.Component {

+     componentWillMount() {

+         this.loadConfigs();

+     }

+ 

+     constructor(props) {

+         super(props);

+         this.state = {

+             configRows: [],

+             attributes: [],

+ 

+             configName: "",

+             linkType: [],

+             managedType: [],

+             linkScope: "",

+ 

+             newEntry: false,

+             showConfigModal: false,

+             showConfirmDeleteConfig: false

+         };

+ 

+         this.getAttributes = this.getAttributes.bind(this);

+         this.handleFieldChange = this.handleFieldChange.bind(this);

+         this.loadConfigs = this.loadConfigs.bind(this);

+         this.showEditConfigModal = this.showEditConfigModal.bind(this);

+         this.showAddConfigModal = this.showAddConfigModal.bind(this);

+         this.closeModal = this.closeModal.bind(this);

+         this.openModal = this.openModal.bind(this);

+         this.cmdOperation = this.cmdOperation.bind(this);

+         this.deleteConfig = this.deleteConfig.bind(this);

+         this.addConfig = this.addConfig.bind(this);

+         this.editConfig = this.editConfig.bind(this);

+     }

+ 

+     handleFieldChange(e) {

+         this.setState({

+             [e.target.id]: e.target.value

+         });

+     }

+ 

+     loadConfigs() {

+         // Get all the attributes and matching rules now

+         const cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "plugin",

+             "linked-attr",

+             "list"

+         ];

+         this.props.toggleLoadingHandler();

+         log_cmd("loadConfigs", "Get Linked Attributes Plugin configs", cmd);

+         cockpit

+                 .spawn(cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     let myObject = JSON.parse(content);

+                     this.setState({

+                         configRows: myObject.items.map(

+                             item => JSON.parse(item).attrs

+                         )

+                     });

+                     this.props.toggleLoadingHandler();

+                 })

+                 .fail(err => {

+                     if (err != 0) {

+                         console.log("loadConfigs failed", err);

+                     }

+                     this.props.toggleLoadingHandler();

+                 });

+     }

+ 

+     showEditConfigModal(rowData) {

+         this.openModal(rowData.cn[0]);

+     }

+ 

+     showAddConfigModal(rowData) {

+         this.openModal();

+     }

+ 

+     openModal(name) {

+         this.getAttributes();

+         if (!name) {

+             this.setState({

+                 configEntryModalShow: true,

+                 newEntry: true,

+                 configName: "",

+                 linkType: [],

+                 managedType: [],

+                 linkScope: ""

+             });

+         } else {

+             let cmd = [

+                 "dsconf",

+                 "-j",

+                 "ldapi://%2fvar%2frun%2fslapd-" +

+                     this.props.serverId +

+                     ".socket",

+                 "plugin",

+                 "linked-attr",

+                 "config",

+                 name,

+                 "show"

+             ];

+ 

+             this.props.toggleLoadingHandler();

+             log_cmd(

+                 "openModal",

+                 "Fetch the Linked Attributes Plugin config entry",

+                 cmd

+             );

+             cockpit

+                     .spawn(cmd, {

+                         superuser: true,

+                         err: "message"

+                     })

+                     .done(content => {

+                         let configEntry = JSON.parse(content).attrs;

+                         this.setState({

+                             configEntryModalShow: true,

+                             newEntry: false,

+                             configName:

+                             configEntry["cn"] === undefined

+                                 ? ""

+                                 : configEntry["cn"][0],

+                             linkType:

+                             configEntry["linktype"] === undefined

+                                 ? []

+                                 : [{id: configEntry["linktype"][0],

+                                     label: configEntry["linktype"][0]}],

+                             managedType:

+                             configEntry["managedtype"] === undefined

+                                 ? []

+                                 : [{id: configEntry["managedtype"][0],

+                                     label: configEntry["managedtype"][0]}],

+                             linkScope:

+                             configEntry["linkscope"] === undefined

+                                 ? ""

+                                 : configEntry["linkscope"][0]

+                         });

+ 

+                         this.props.toggleLoadingHandler();

+                     })

+                     .fail(_ => {

+                         this.setState({

+                             configEntryModalShow: true,

+                             newEntry: true,

+                             configName: "",

+                             linkType: [],

+                             managedType: [],

+                             linkScope: ""

+                         });

+                         this.props.toggleLoadingHandler();

+                     });

+         }

+     }

+ 

+     closeModal() {

+         this.setState({ configEntryModalShow: false });

+     }

+ 

+     cmdOperation(action) {

+         const { configName, linkType, managedType, linkScope } = this.state;

+ 

+         let cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "plugin",

+             "linked-attr",

+             "config",

+             configName,

+             action,

+             "--link-scope",

+             linkScope || action == "add" ? linkScope : "delete"

+         ];

+ 

+         cmd = [...cmd, "--link-type"];

+         if (linkType.length != 0) {

+             cmd = [...cmd, linkType[0].id];

+         } else if (action == "add") {

+             cmd = [...cmd, ""];

+         } else {

+             cmd = [...cmd, "delete"];

+         }

+ 

+         cmd = [...cmd, "--managed-type"];

+         if (managedType.length != 0) {

+             cmd = [...cmd, managedType[0].id];

+         } else if (action == "add") {

+             cmd = [...cmd, ""];

+         } else {

+             cmd = [...cmd, "delete"];

+         }

+ 

+         this.props.toggleLoadingHandler();

+         log_cmd(

+             "linkedAttributesOperation",

+             `Do the ${action} operation on the Linked Attributes Plugin`,

+             cmd

+         );

+         cockpit

+                 .spawn(cmd, {

+                     superuser: true,

+                     err: "message"

+                 })

+                 .done(content => {

+                     console.info("linkedAttributesOperation", "Result", content);

+                     this.props.addNotification(

+                         "success",

+                         `The ${action} operation was successfully done on "${configName}" entry`

+                     );

+                     this.loadConfigs();

+                     this.closeModal();

+                     this.props.toggleLoadingHandler();

+                 })

+                 .fail(err => {

+                     this.props.addNotification(

+                         "error",

+                         `Error during the config entry ${action} operation - ${err}`

+                     );

+                     this.loadConfigs();

+                     this.closeModal();

+                     this.props.toggleLoadingHandler();

+                 });

+     }

+ 

+     deleteConfig(rowData) {

+         let configName = rowData.cn[0];

+         let cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "plugin",

+             "linked-attr",

+             "config",

+             configName,

+             "delete"

+         ];

+ 

+         this.props.toggleLoadingHandler();

+         log_cmd(

+             "deleteConfig",

+             "Delete the Linked Attributes Plugin config entry",

+             cmd

+         );

+         cockpit

+                 .spawn(cmd, {

+                     superuser: true,

+                     err: "message"

+                 })

+                 .done(content => {

+                     console.info("deleteConfig", "Result", content);

+                     this.props.addNotification(

+                         "success",

+                         `Config entry ${configName} was successfully deleted`

+                     );

+                     this.loadConfigs();

+                     this.closeModal();

+                     this.props.toggleLoadingHandler();

+                 })

+                 .fail(err => {

+                     this.props.addNotification(

+                         "error",

+                         `Error during the config entry removal operation - ${err}`

+                     );

+                     this.loadConfigs();

+                     this.closeModal();

+                     this.props.toggleLoadingHandler();

+                 });

+     }

+ 

+     addConfig() {

+         this.cmdOperation("add");

+     }

+ 

+     editConfig() {

+         this.cmdOperation("set");

+     }

+ 

+     getAttributes() {

+         const attr_cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "schema",

+             "attributetypes",

+             "list"

+         ];

+         log_cmd("getAttributes", "Get attrs", attr_cmd);

+         cockpit

+                 .spawn(attr_cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     const attrContent = JSON.parse(content);

+                     let attrs = [];

+                     for (let content of attrContent["items"]) {

+                         attrs.push({

+                             id: content.name,

+                             label: content.name

+                         });

+                     }

+                     this.setState({

+                         attributes: attrs

+                     });

+                 })

+                 .fail(err => {

+                     this.props.addNotification(

+                         "error",

+                         `Failed to get attributes - ${err}`

+                     );

+                 });

+     }

+ 

      render() {

+         const {

+             configEntryModalShow,

+             configName,

+             linkType,

+             managedType,

+             linkScope,

+             newEntry,

+             attributes

+         } = this.state;

+ 

          return (

              <div>

+                 <Modal show={configEntryModalShow} onHide={this.closeModal}>

+                     <div className="ds-no-horizontal-scrollbar">

+                         <Modal.Header>

+                             <button

+                                 className="close"

+                                 onClick={this.closeModal}

+                                 aria-hidden="true"

+                                 aria-label="Close"

+                             >

+                                 <Icon type="pf" name="close" />

+                             </button>

+                             <Modal.Title>

+                                 {newEntry ? "Add" : "Edit"} Linked Attributes

+                                 Plugin Config Entry

+                             </Modal.Title>

+                         </Modal.Header>

+                         <Modal.Body>

+                             <Row>

+                                 <Col sm={12}>

+                                     <Form horizontal>

+                                         <FormGroup controlId="configName">

+                                             <Col sm={3}>

+                                                 <ControlLabel title="The Linked Attributes configuration name">

+                                                     Config Name

+                                                 </ControlLabel>

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <FormControl

+                                                     type="text"

+                                                     value={configName}

+                                                     onChange={

+                                                         this.handleFieldChange

+                                                     }

+                                                     disabled={!newEntry}

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                         <FormGroup controlId="linkType">

+                                             <Col sm={3}>

+                                                 <ControlLabel title="Sets the attribute that is managed manually by administrators (linkType)">

+                                                     Link Type

+                                                 </ControlLabel>

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <Typeahead

+                                                     allowNew

+                                                     onChange={value => {

+                                                         this.setState({

+                                                             linkType: value

+                                                         });

+                                                     }}

+                                                     selected={linkType}

+                                                     options={attributes}

+                                                     newSelectionPrefix="Add a managed attribute: "

+                                                     placeholder="Type an attribute..."

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                         <FormGroup controlId="managedType">

+                                             <Col sm={3}>

+                                                 <ControlLabel title="Sets the attribute that is created dynamically by the plugin (managedType)">

+                                                     Managed Type

+                                                 </ControlLabel>

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <Typeahead

+                                                     allowNew

+                                                     onChange={value => {

+                                                         this.setState({

+                                                             managedType: value

+                                                         });

+                                                     }}

+                                                     selected={managedType}

+                                                     options={attributes}

+                                                     newSelectionPrefix="Add a dynamic attribute: "

+                                                     placeholder="Type an attribute..."

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                         <FormGroup controlId="linkScope">

+                                             <Col sm={3}>

+                                                 <ControlLabel title="Sets the scope that restricts the plugin to a specific part of the directory tree (linkScope)">

+                                                     Link Scope

+                                                 </ControlLabel>

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <FormControl

+                                                     type="text"

+                                                     value={linkScope}

+                                                     onChange={

+                                                         this.handleFieldChange

+                                                     }

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                     </Form>

+                                 </Col>

+                             </Row>

+                         </Modal.Body>

+                         <Modal.Footer>

+                             <Button

+                                 bsStyle="default"

+                                 className="btn-cancel"

+                                 onClick={this.closeModal}

+                             >

+                                 Cancel

+                             </Button>

+                             <Button

+                                 bsStyle="primary"

+                                 onClick={

+                                     newEntry ? this.addConfig : this.editConfig

+                                 }

+                             >

+                                 Save

+                             </Button>

+                         </Modal.Footer>

+                     </div>

+                 </Modal>

                  <PluginBasicConfig

                      rows={this.props.rows}

                      serverId={this.props.serverId}
@@ -18,7 +472,23 @@ 

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

                      toggleLoadingHandler={this.props.toggleLoadingHandler}

-                 />

+                 >

+                     <Row>

+                         <Col sm={9}>

+                             <LinkedAttributesTable

+                                 rows={this.state.configRows}

+                                 editConfig={this.showEditConfigModal}

+                                 deleteConfig={this.deleteConfig}

+                             />

+                             <Button

+                                 bsStyle="primary"

+                                 onClick={this.showAddConfigModal}

+                             >

+                                 Add Config

+                             </Button>

+                         </Col>

+                     </Row>

+                 </PluginBasicConfig>

              </div>

          );

      }

@@ -21,6 +21,7 @@ 

  

  class MemberOf extends React.Component {

      componentWillMount(prevProps) {

+         this.getObjectClasses();

          this.updateFields();

      }

  
@@ -33,6 +34,7 @@ 

      constructor(props) {

          super(props);

  

+         this.getObjectClasses = this.getObjectClasses.bind(this);

          this.updateFields = this.updateFields.bind(this);

          this.handleFieldChange = this.handleFieldChange.bind(this);

          this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
@@ -46,11 +48,13 @@ 

          this.toggleFixupModal = this.toggleFixupModal.bind(this);

  

          this.state = {

+             objectClasses: [],

+ 

              memberOfAttr: [],

              memberOfGroupAttr: [],

              memberOfEntryScope: "",

              memberOfEntryScopeExcludeSubtree: "",

-             memberOfAutoAddOC: "",

+             memberOfAutoAddOC: [],

              memberOfAllBackends: false,

              memberOfSkipNested: false,

              memberOfConfigEntry: "",
@@ -62,7 +66,7 @@ 

              configGroupAttr: [],

              configEntryScope: "",

              configEntryScopeExcludeSubtree: "",

-             configAutoAddOC: "",

+             configAutoAddOC: [],

              configAllBackends: false,

              configSkipNested: false,

              newEntry: true,
@@ -87,7 +91,9 @@ 

              let cmd = [

                  "dsconf",

                  "-j",

-                 "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+                 "ldapi://%2fvar%2frun%2fslapd-" +

+                     this.props.serverId +

+                     ".socket",

                  "plugin",

                  "memberof",

                  "fixup",
@@ -129,6 +135,7 @@ 

      }

  

      openModal() {

+         this.getObjectClasses();

          if (!this.state.memberOfConfigEntry) {

              this.setState({

                  configEntryModalShow: true,
@@ -138,7 +145,7 @@ 

                  configGroupAttr: [],

                  configEntryScope: "",

                  configEntryScopeExcludeSubtree: "",

-                 configAutoAddOC: "",

+                 configAutoAddOC: [],

                  configAllBackends: false,

                  configSkipNested: false

              });
@@ -148,7 +155,9 @@ 

              let cmd = [

                  "dsconf",

                  "-j",

-                 "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+                 "ldapi://%2fvar%2frun%2fslapd-" +

+                     this.props.serverId +

+                     ".socket",

                  "plugin",

                  "memberof",

                  "config-entry",
@@ -157,7 +166,11 @@ 

              ];

  

              this.props.toggleLoadingHandler();

-             log_cmd("openMemberOfModal", "Fetch the MemberOf Plugin config entry", cmd);

+             log_cmd(

+                 "openMemberOfModal",

+                 "Fetch the MemberOf Plugin config entry",

+                 cmd

+             );

              cockpit

                      .spawn(cmd, {

                          superuser: true,
@@ -171,8 +184,9 @@ 

                              configDN: this.state.memberOfConfigEntry,

                              configAutoAddOC:

                              configEntry["memberofautoaddoc"] === undefined

-                                 ? ""

-                                 : configEntry["memberofautoaddoc"][0],

+                                 ? []

+                                 : [{id:configEntry["memberofautoaddoc"][0],

+                                     label: configEntry["memberofautoaddoc"][0]}],

                              configAllBackends: !(

                                  configEntry["memberofallbackends"] === undefined ||

                              configEntry["memberofallbackends"][0] == "off"
@@ -182,7 +196,8 @@ 

                              configEntry["memberofskipnested"][0] == "off"

                              ),

                              configConfigEntry:

-                             configEntry["nsslapd-pluginConfigArea"] === undefined

+                             configEntry["nsslapd-pluginConfigArea"] ===

+                             undefined

                                  ? ""

                                  : configEntry["nsslapd-pluginConfigArea"][0],

                              configEntryScope:
@@ -190,7 +205,8 @@ 

                                  ? ""

                                  : configEntry["memberofentryscope"][0],

                              configEntryScopeExcludeSubtree:

-                             configEntry["memberofentryscopeexcludesubtree"] === undefined

+                             configEntry["memberofentryscopeexcludesubtree"] ===

+                             undefined

                                  ? ""

                                  : configEntry["memberofentryscopeexcludesubtree"][0]

                          });
@@ -214,9 +230,11 @@ 

                                      { id: value, label: value }

                                  ];

                              }

-                             this.setState({ configGroupAttr: configGroupAttrObjectList });

-                             this.props.toggleLoadingHandler();

+                             this.setState({

+                                 configGroupAttr: configGroupAttrObjectList

+                             });

                          }

+                         this.props.toggleLoadingHandler();

                      })

                      .fail(_ => {

                          this.setState({
@@ -227,7 +245,7 @@ 

                              configGroupAttr: [],

                              configEntryScope: "",

                              configEntryScopeExcludeSubtree: "",

-                             configAutoAddOC: "",

+                             configAutoAddOC: [],

                              configAllBackends: false,

                              configSkipNested: false

                          });
@@ -261,26 +279,37 @@ 

              let cmd = [

                  "dsconf",

                  "-j",

-                 "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+                 "ldapi://%2fvar%2frun%2fslapd-" +

+                     this.props.serverId +

+                     ".socket",

                  "plugin",

                  "memberof",

                  "config-entry",

                  action,

                  configDN,

                  "--scope",

-                 configEntryScope || action == "add" ? configEntryScope : "delete",

+                 configEntryScope || action == "add"

+                     ? configEntryScope

+                     : "delete",

                  "--exclude",

                  configEntryScopeExcludeSubtree || action == "add"

                      ? configEntryScopeExcludeSubtree

                      : "delete",

-                 "--autoaddoc",

-                 configAutoAddOC || action == "add" ? configAutoAddOC : "delete",

                  "--allbackends",

                  configAllBackends ? "on" : "off",

                  "--skipnested",

                  configSkipNested ? "on" : "off"

              ];

  

+             cmd = [...cmd, "--autoaddoc"];

+             if (configAutoAddOC.length != 0) {

+                 cmd = [...cmd, configAutoAddOC[0].id];

+             } else if (action == "add") {

+                 cmd = [...cmd, ""];

+             } else {

+                 cmd = [...cmd, "delete"];

+             }

+ 

              // Delete attributes if the user set an empty value to the field

              cmd = [...cmd, "--attr"];

              if (configAttr.length != 0) {
@@ -296,7 +325,11 @@ 

              }

  

              this.props.toggleLoadingHandler();

-             log_cmd("memberOfOperation", `Do the ${action} operation on the MemberOf Plugin`, cmd);

+             log_cmd(

+                 "memberOfOperation",

+                 `Do the ${action} operation on the MemberOf Plugin`,

+                 cmd

+             );

              cockpit

                      .spawn(cmd, {

                          superuser: true,
@@ -347,7 +380,9 @@ 

                      console.info("deleteConfig", "Result", content);

                      this.props.addNotification(

                          "success",

-                         `Config entry ${this.state.configDN} was successfully deleted`

+                         `Config entry ${

+                             this.state.configDN

+                         } was successfully deleted`

                      );

                      this.props.pluginListHandler();

                      this.closeModal();
@@ -389,13 +424,20 @@ 

          let memberOfGroupAttrObjectList = [];

  

          if (this.props.rows.length > 0) {

-             const pluginRow = this.props.rows.find(row => row.cn[0] === "MemberOf Plugin");

+             const pluginRow = this.props.rows.find(

+                 row => row.cn[0] === "MemberOf Plugin"

+             );

  

              this.setState({

                  memberOfAutoAddOC:

                      pluginRow["memberofautoaddoc"] === undefined

-                         ? ""

-                         : pluginRow["memberofautoaddoc"][0],

+                         ? []

+                         : [

+                             {

+                                 id: pluginRow["memberofautoaddoc"][0],

+                                 label: pluginRow["memberofautoaddoc"][0]

+                             }

+                         ],

                  memberOfAllBackends: !(

                      pluginRow["memberofallbackends"] === undefined ||

                      pluginRow["memberofallbackends"][0] == "off"
@@ -437,13 +479,49 @@ 

                          { id: value, label: value }

                      ];

                  }

-                 this.setState({ memberOfGroupAttr: memberOfGroupAttrObjectList });

+                 this.setState({

+                     memberOfGroupAttr: memberOfGroupAttrObjectList

+                 });

              }

          }

      }

  

+     getObjectClasses() {

+         const oc_cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "schema",

+             "objectclasses",

+             "list"

+         ];

+         log_cmd("getObjectClasses", "Get objectClasses", oc_cmd);

+         cockpit

+                 .spawn(oc_cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     const ocContent = JSON.parse(content);

+                     let ocs = [];

+                     for (let content of ocContent["items"]) {

+                         ocs.push({

+                             id: content.name,

+                             label: content.name

+                         });

+                     }

+                     this.setState({

+                         objectClasses: ocs

+                     });

+                 })

+                 .fail(err => {

+                     this.props.addNotification(

+                         "error",

+                         `Failed to get objectClasses - ${err}`

+                     );

+                 });

+     }

+ 

      render() {

          const {

+             objectClasses,

              memberOfAttr,

              memberOfGroupAttr,

              memberOfEntryScope,
@@ -478,8 +556,6 @@ 

              memberOfEntryScope || "delete",

              "--exclude",

              memberOfEntryScopeExcludeSubtree || "delete",

-             "--autoaddoc",

-             memberOfAutoAddOC || "delete",

              "--config-entry",

              memberOfConfigEntry || "delete",

              "--allbackends",
@@ -488,6 +564,13 @@ 

              memberOfSkipNested ? "on" : "off"

          ];

  

+         specificPluginCMD = [...specificPluginCMD, "--autoaddoc"];

+         if (memberOfAutoAddOC.length != 0) {

+             specificPluginCMD = [...specificPluginCMD, memberOfAutoAddOC[0].id];

+         } else {

+             specificPluginCMD = [...specificPluginCMD, "delete"];

+         }

+ 

          // Delete attributes if the user set an empty value to the field

          specificPluginCMD = [...specificPluginCMD, "--attr"];

          if (memberOfAttr.length != 0) {
@@ -526,27 +609,41 @@ 

                              <Row>

                                  <Col sm={12}>

                                      <Form horizontal>

-                                         <FormGroup controlId="fixupDN" key="fixupDN">

+                                         <FormGroup

+                                             controlId="fixupDN"

+                                             key="fixupDN"

+                                         >

                                              <Col sm={3}>

-                                                 <ControlLabel>Base DN</ControlLabel>

+                                                 <ControlLabel title="Base DN that contains entries to fix up">

+                                                     Base DN

+                                                 </ControlLabel>

                                              </Col>

                                              <Col sm={9}>

                                                  <FormControl

                                                      type="text"

                                                      value={fixupDN}

-                                                     onChange={this.handleFieldChange}

+                                                     onChange={

+                                                         this.handleFieldChange

+                                                     }

                                                  />

                                              </Col>

                                          </FormGroup>

-                                         <FormGroup controlId="fixupFilter" key="fixupFilter">

+                                         <FormGroup

+                                             controlId="fixupFilter"

+                                             key="fixupFilter"

+                                         >

                                              <Col sm={3}>

-                                                 <ControlLabel>Filter DN</ControlLabel>

+                                                 <ControlLabel title="Filter for entries to fix up. If omitted, all entries with objectclass inetuser/inetadmin/nsmemberof under the specified base will have their memberOf attribute regenerated.">

+                                                     Filter DN

+                                                 </ControlLabel>

                                              </Col>

                                              <Col sm={9}>

                                                  <FormControl

                                                      type="text"

                                                      value={fixupFilter}

-                                                     onChange={this.handleFieldChange}

+                                                     onChange={

+                                                         this.handleFieldChange

+                                                     }

                                                  />

                                              </Col>

                                          </FormGroup>
@@ -579,7 +676,9 @@ 

                              >

                                  <Icon type="pf" name="close" />

                              </button>

-                             <Modal.Title>Manage MemberOf Plugin Shared Config Entry</Modal.Title>

+                             <Modal.Title>

+                                 Manage MemberOf Plugin Shared Config Entry

+                             </Modal.Title>

                          </Modal.Header>

                          <Modal.Body>

                              <Row>
@@ -587,13 +686,17 @@ 

                                      <Form horizontal>

                                          <FormGroup controlId="configDN">

                                              <Col sm={3}>

-                                                 <ControlLabel>Config DN</ControlLabel>

+                                                 <ControlLabel title="The config entry full DN">

+                                                     Config DN

+                                                 </ControlLabel>

                                              </Col>

                                              <Col sm={9}>

                                                  <FormControl

                                                      type="text"

                                                      value={configDN}

-                                                     onChange={this.handleFieldChange}

+                                                     onChange={

+                                                         this.handleFieldChange

+                                                     }

                                                      disabled={!newEntry}

                                                  />

                                              </Col>
@@ -603,7 +706,11 @@ 

                                              controlId="configAttr"

                                              disabled={false}

                                          >

-                                             <Col componentClass={ControlLabel} sm={3}>

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={3}

+                                                 title="Specifies the attribute in the user entry for the Directory Server to manage to reflect group membership (memberOfAttr)"

+                                             >

                                                  Attribute

                                              </Col>

                                              <Col sm={9}>
@@ -632,7 +739,11 @@ 

                                              controlId="configGroupAttr"

                                              disabled={false}

                                          >

-                                             <Col componentClass={ControlLabel} sm={3}>

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={3}

+                                                 title="Specifies the attribute in the group entry to use to identify the DNs of group members (memberOfGroupAttr)"

+                                             >

                                                  Group Attribute

                                              </Col>

                                              <Col sm={9}>
@@ -667,21 +778,31 @@ 

                                              controlId="configEntryScope"

                                              disabled={false}

                                          >

-                                             <Col componentClass={ControlLabel} sm={3}>

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={3}

+                                                 title="Specifies backends or multiple-nested suffixes for the MemberOf plug-in to work on (memberOfEntryScope)"

+                                             >

                                                  Entry Scope

                                              </Col>

                                              <Col sm={6}>

                                                  <FormControl

                                                      type="text"

                                                      value={configEntryScope}

-                                                     onChange={this.handleFieldChange}

+                                                     onChange={

+                                                         this.handleFieldChange

+                                                     }

                                                  />

                                              </Col>

                                              <Col sm={3}>

                                                  <Checkbox

                                                      id="configAllBackends"

                                                      checked={configAllBackends}

-                                                     onChange={this.handleCheckboxChange}

+                                                     onChange={

+                                                         this

+                                                                 .handleCheckboxChange

+                                                     }

+                                                     title="Specifies whether to search the local suffix for user entries on all available suffixes (memberOfAllBackends)"

                                                  >

                                                      All Backends

                                                  </Checkbox>
@@ -692,21 +813,33 @@ 

                                              controlId="configEntryScopeExcludeSubtree"

                                              disabled={false}

                                          >

-                                             <Col componentClass={ControlLabel} sm={3}>

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={3}

+                                                 title="Specifies backends or multiple-nested suffixes for the MemberOf plug-in to exclude (memberOfEntryScopeExcludeSubtree)"

+                                             >

                                                  Entry Scope Exclude Subtree

                                              </Col>

                                              <Col sm={6}>

                                                  <FormControl

                                                      type="text"

-                                                     value={configEntryScopeExcludeSubtree}

-                                                     onChange={this.handleFieldChange}

+                                                     value={

+                                                         configEntryScopeExcludeSubtree

+                                                     }

+                                                     onChange={

+                                                         this.handleFieldChange

+                                                     }

                                                  />

                                              </Col>

                                              <Col sm={3}>

                                                  <Checkbox

                                                      id="configSkipNested"

                                                      checked={configSkipNested}

-                                                     onChange={this.handleCheckboxChange}

+                                                     onChange={

+                                                         this

+                                                                 .handleCheckboxChange

+                                                     }

+                                                     title="Specifies wherher to skip nested groups or not (memberOfSkipNested)"

                                                  >

                                                      Skip Nested

                                                  </Checkbox>
@@ -718,15 +851,27 @@ 

                              <Row>

                                  <Col sm={12}>

                                      <Form horizontal>

-                                         <FormGroup controlId="configAutoAddOC" disabled={false}>

+                                         <FormGroup

+                                             controlId="configAutoAddOC"

+                                             disabled={false}

+                                         >

                                              <Col sm={3}>

-                                                 <ControlLabel>Auto Add OC</ControlLabel>

+                                                 <ControlLabel title="If an entry does not have an object class that allows the memberOf attribute then the memberOf plugin will automatically add the object class listed in the memberOfAutoAddOC parameter">

+                                                     Auto Add OC

+                                                 </ControlLabel>

                                              </Col>

                                              <Col sm={9}>

-                                                 <FormControl

-                                                     type="text"

-                                                     value={configAutoAddOC}

-                                                     onChange={this.handleFieldChange}

+                                                 <Typeahead

+                                                     allowNew

+                                                     onChange={value => {

+                                                         this.setState({

+                                                             configAutoAddOC: value

+                                                         });

+                                                     }}

+                                                     selected={configAutoAddOC}

+                                                     options={objectClasses}

+                                                     newSelectionPrefix="Add a memberOf objectClass: "

+                                                     placeholder="Type an objectClass..."

                                                  />

                                              </Col>

                                          </FormGroup>
@@ -749,10 +894,18 @@ 

                              >

                                  Delete

                              </Button>

-                             <Button bsStyle="primary" onClick={this.editConfig} disabled={newEntry}>

+                             <Button

+                                 bsStyle="primary"

+                                 onClick={this.editConfig}

+                                 disabled={newEntry}

+                             >

                                  Save

                              </Button>

-                             <Button bsStyle="primary" onClick={this.addConfig} disabled={!newEntry}>

+                             <Button

+                                 bsStyle="primary"

+                                 onClick={this.addConfig}

+                                 disabled={!newEntry}

+                             >

                                  Add

                              </Button>

                          </Modal.Footer>
@@ -778,7 +931,11 @@ 

                                      controlId="memberOfAttr"

                                      disabled={false}

                                  >

-                                     <Col componentClass={ControlLabel} sm={3}>

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={3}

+                                         title="Specifies the attribute in the user entry for the Directory Server to manage to reflect group membership (memberOfAttr)"

+                                     >

                                          Attribute

                                      </Col>

                                      <Col sm={9}>
@@ -815,7 +972,11 @@ 

                                      controlId="memberOfGroupAttr"

                                      disabled={false}

                                  >

-                                     <Col componentClass={ControlLabel} sm={3}>

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={3}

+                                         title="Specifies the attribute in the group entry to use to identify the DNs of group members (memberOfGroupAttr)"

+                                     >

                                          Group Attribute

                                      </Col>

                                      <Col sm={9}>
@@ -862,7 +1023,11 @@ 

                                      controlId="memberOfEntryScope"

                                      disabled={false}

                                  >

-                                     <Col componentClass={ControlLabel} sm={3}>

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={3}

+                                         title="Specifies backends or multiple-nested suffixes for the MemberOf plug-in to work on (memberOfEntryScope)"

+                                     >

                                          Entry Scope

                                      </Col>

                                      <Col sm={6}>
@@ -877,6 +1042,7 @@ 

                                              id="memberOfAllBackends"

                                              checked={memberOfAllBackends}

                                              onChange={this.handleCheckboxChange}

+                                             title="Specifies whether to search the local suffix for user entries on all available suffixes (memberOfAllBackends)"

                                          >

                                              All Backends

                                          </Checkbox>
@@ -887,13 +1053,19 @@ 

                                      controlId="memberOfEntryScopeExcludeSubtree"

                                      disabled={false}

                                  >

-                                     <Col componentClass={ControlLabel} sm={3}>

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={3}

+                                         title="Specifies backends or multiple-nested suffixes for the MemberOf plug-in to exclude (memberOfEntryScopeExcludeSubtree)"

+                                     >

                                          Entry Scope Exclude Subtree

                                      </Col>

                                      <Col sm={6}>

                                          <FormControl

                                              type="text"

-                                             value={memberOfEntryScopeExcludeSubtree}

+                                             value={

+                                                 memberOfEntryScopeExcludeSubtree

+                                             }

                                              onChange={this.handleFieldChange}

                                          />

                                      </Col>
@@ -902,6 +1074,7 @@ 

                                              id="memberOfSkipNested"

                                              checked={memberOfSkipNested}

                                              onChange={this.handleCheckboxChange}

+                                             title="Specifies wherher to skip nested groups or not (memberOfSkipNested)"

                                          >

                                              Skip Nested

                                          </Checkbox>
@@ -917,7 +1090,11 @@ 

                                      key="memberOfConfigEntry"

                                      controlId="memberOfConfigEntry"

                                  >

-                                     <Col componentClass={ControlLabel} sm={3}>

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={3}

+                                         title="The value to set as nsslapd-pluginConfigArea"

+                                     >

                                          Shared Config Entry

                                      </Col>

                                      <Col sm={6}>
@@ -943,15 +1120,28 @@ 

                      <Row>

                          <Col sm={9}>

                              <Form horizontal>

-                                 <FormGroup controlId="memberOfAutoAddOC" disabled={false}>

-                                     <Col sm={3}>

+                                 <FormGroup

+                                     controlId="memberOfAutoAddOC"

+                                     disabled={false}

+                                 >

+                                     <Col

+                                         sm={3}

+                                         title="If an entry does not have an object class that allows the memberOf attribute then the memberOf plugin will automatically add the object class listed in the memberOfAutoAddOC parameter"

+                                     >

                                          <ControlLabel>Auto Add OC</ControlLabel>

                                      </Col>

                                      <Col sm={9}>

-                                         <FormControl

-                                             type="text"

-                                             value={memberOfAutoAddOC}

-                                             onChange={this.handleFieldChange}

+                                         <Typeahead

+                                             allowNew

+                                             onChange={value => {

+                                                 this.setState({

+                                                     memberOfAutoAddOC: value

+                                                 });

+                                             }}

+                                             selected={memberOfAutoAddOC}

+                                             options={objectClasses}

+                                             newSelectionPrefix="Add a memberOf objectClass: "

+                                             placeholder="Type an objectClass..."

                                          />

                                      </Col>

                                  </FormGroup>

@@ -64,7 +64,9 @@ 

              addNotification,

              toggleLoadingHandler

          } = this.props;

-         const new_status = this.state.currentPluginEnabled ? "disable" : "enable";

+         const new_status = this.state.currentPluginEnabled

+             ? "disable"

+             : "enable";

          const cmd = [

              "dsconf",

              "-j",
@@ -76,7 +78,11 @@ 

  

          toggleLoadingHandler();

          this.setState({ disableSwitch: true });

-         log_cmd("handleSwitchChange", "Switch plugin states from the plugin tab", cmd);

+         log_cmd(

+             "handleSwitchChange",

+             "Switch plugin states from the plugin tab",

+             cmd

+         );

          cockpit

                  .spawn(cmd, { superuser: true, err: "message" })

                  .done(content => {
@@ -101,7 +107,9 @@ 

  

      updateFields() {

          if (this.props.rows.length > 0) {

-             const pluginRow = this.props.rows.find(row => row.cn[0] === this.props.cn);

+             const pluginRow = this.props.rows.find(

+                 row => row.cn[0] === this.props.cn

+             );

  

              this.setState({

                  currentPluginType: pluginRow["nsslapd-pluginType"][0],
@@ -110,7 +118,8 @@ 

                  currentPluginId: pluginRow["nsslapd-pluginId"][0],

                  currentPluginVendor: pluginRow["nsslapd-pluginVendor"][0],

                  currentPluginVersion: pluginRow["nsslapd-pluginVersion"][0],

-                 currentPluginDescription: pluginRow["nsslapd-pluginDescription"][0],

+                 currentPluginDescription:

+                     pluginRow["nsslapd-pluginDescription"][0],

                  currentPluginDependsOnType:

                      pluginRow["nsslapd-plugin-depends-on-type"] === undefined

                          ? ""
@@ -126,7 +135,9 @@ 

  

      updateSwitch() {

          if (this.props.rows.length > 0) {

-             const pluginRow = this.props.rows.find(row => row.cn[0] === this.props.cn);

+             const pluginRow = this.props.rows.find(

+                 row => row.cn[0] === this.props.cn

+             );

  

              var pluginEnabled;

              if (pluginRow["nsslapd-pluginEnabled"][0] === "on") {
@@ -185,24 +196,32 @@ 

                                  </ControlLabel>

                              </h3>

                          </Col>

-                         <Col smOffset={1} sm={3}>

-                             <FormGroup key="switchPluginStatus" controlId="switchPluginStatus">

-                                 <ControlLabel

-                                     className="toolbar-pf-find ds-float-left ds-right-indent"

+ 

+                         {this.props.removeSwitch || (

+                             <Col smOffset={1} sm={3}>

+                                 <FormGroup

+                                     key="switchPluginStatus"

+                                     controlId="switchPluginStatus"

                                  >

-                                     Status

-                                 </ControlLabel>

-                                 <Switch

-                                     bsSize="normal"

-                                     title="normal"

-                                     id="bsSize-example"

-                                     value={currentPluginEnabled}

-                                     onChange={() => this.handleSwitchChange(currentPluginEnabled)}

-                                     animate={false}

-                                     disabled={disableSwitch}

-                                 />

-                             </FormGroup>

-                         </Col>

+                                     <ControlLabel className="toolbar-pf-find ds-float-left ds-right-indent">

+                                         Status

+                                     </ControlLabel>

+                                     <Switch

+                                         bsSize="normal"

+                                         title="normal"

+                                         id="bsSize-example"

+                                         value={currentPluginEnabled}

+                                         onChange={() =>

+                                             this.handleSwitchChange(

+                                                 currentPluginEnabled

+                                             )

+                                         }

+                                         animate={false}

+                                         disabled={disableSwitch}

+                                     />

+                                 </FormGroup>

+                             </Col>

+                         )}

                      </Row>

                  </Form>

                  {this.props.children}
@@ -210,21 +229,35 @@ 

                      <Row>

                          <Col sm={4}>

                              <Form horizontal>

-                                 {Object.entries(modalFieldsCol1).map(([id, value]) => (

-                                     <FormGroup key={id} controlId={id} disabled={false}>

-                                         <Col componentClass={ControlLabel} sm={6}>

-                                             {this.props.memberOfAttr} Plugin{" "}

-                                             {id.replace("currentPlugin", "")}

-                                         </Col>

-                                         <Col sm={6}>

-                                             <FormControl

-                                                 type="text"

-                                                 value={value}

-                                                 onChange={this.handleFieldChange}

-                                             />

-                                         </Col>

-                                     </FormGroup>

-                                 ))}

+                                 {Object.entries(modalFieldsCol1).map(

+                                     ([id, value]) => (

+                                         <FormGroup

+                                             key={id}

+                                             controlId={id}

+                                             disabled={false}

+                                         >

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={6}

+                                             >

+                                                 {this.props.memberOfAttr} Plugin{" "}

+                                                 {id.replace(

+                                                     "currentPlugin",

+                                                     ""

+                                                 )}

+                                             </Col>

+                                             <Col sm={6}>

+                                                 <FormControl

+                                                     type="text"

+                                                     value={value}

+                                                     onChange={

+                                                         this.handleFieldChange

+                                                     }

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                     )

+                                 )}

                                  <FormGroup

                                      key="currentPluginDependsOnType"

                                      controlId="currentPluginDependsOnType"
@@ -236,7 +269,10 @@ 

                                      <Col sm={6}>

                                          <FormControl

                                              type="text"

-                                             value={this.state.currentPluginDependsOnType}

+                                             value={

+                                                 this.state

+                                                         .currentPluginDependsOnType

+                                             }

                                              onChange={this.handleFieldChange}

                                          />

                                      </Col>
@@ -252,7 +288,10 @@ 

                                      <Col sm={6}>

                                          <FormControl

                                              type="text"

-                                             value={this.state.currentPluginDependsOnNamed}

+                                             value={

+                                                 this.state

+                                                         .currentPluginDependsOnNamed

+                                             }

                                              onChange={this.handleFieldChange}

                                          />

                                      </Col>
@@ -261,20 +300,35 @@ 

                          </Col>

                          <Col sm={4}>

                              <Form horizontal>

-                                 {Object.entries(modalFieldsCol2).map(([id, value]) => (

-                                     <FormGroup key={id} controlId={id} disabled={false}>

-                                         <Col componentClass={ControlLabel} sm={5}>

-                                             Plugin {id.replace("currentPlugin", "")}

-                                         </Col>

-                                         <Col sm={7}>

-                                             <FormControl

-                                                 type="text"

-                                                 value={value}

-                                                 onChange={this.handleFieldChange}

-                                             />

-                                         </Col>

-                                     </FormGroup>

-                                 ))}

+                                 {Object.entries(modalFieldsCol2).map(

+                                     ([id, value]) => (

+                                         <FormGroup

+                                             key={id}

+                                             controlId={id}

+                                             disabled={false}

+                                         >

+                                             <Col

+                                                 componentClass={ControlLabel}

+                                                 sm={5}

+                                             >

+                                                 Plugin{" "}

+                                                 {id.replace(

+                                                     "currentPlugin",

+                                                     ""

+                                                 )}

+                                             </Col>

+                                             <Col sm={7}>

+                                                 <FormControl

+                                                     type="text"

+                                                     value={value}

+                                                     onChange={

+                                                         this.handleFieldChange

+                                                     }

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                     )

+                                 )}

                              </Form>

                          </Col>

                      </Row>
@@ -296,7 +350,8 @@ 

                                      description: currentPluginDescription,

                                      dependsOnType: currentPluginDependsOnType,

                                      dependsOnNamed: currentPluginDependsOnNamed,

-                                     specificPluginCMD: this.props.specificPluginCMD

+                                     specificPluginCMD: this.props

+                                             .specificPluginCMD

                                  })

                              }

                          >
@@ -316,6 +371,7 @@ 

      cn: PropTypes.string,

      pluginName: PropTypes.string,

      cmdName: PropTypes.string,

+     removeSwitch: PropTypes.bool,

      specificPluginCMD: PropTypes.array,

      savePluginHandler: PropTypes.func,

      pluginListHandler: PropTypes.func,
@@ -329,6 +385,7 @@ 

      cn: "",

      pluginName: "",

      cmdName: "",

+     removeSwitch: false,

      specificPluginCMD: [],

      savePluginHandler: noop,

      pluginListHandler: noop,

@@ -1,153 +0,0 @@ 

- import React from "react";

- import {

-     Button,

-     noop,

-     actionHeaderCellFormatter,

-     sortableHeaderCellFormatter,

-     tableCellFormatter,

- } from "patternfly-react";

- import PropTypes from "prop-types";

- import { DSTable } from "../dsTable.jsx";

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

- 

- class PluginTable extends React.Component {

-     constructor(props) {

-         super(props);

- 

-         this.state = {

-             searchFilterValue: "",

-             fieldsToSearch: ["cn", "nsslapd-pluginType"],

-             columns: [

-                 {

-                     property: "cn",

-                     header: {

-                         label: "Plugin Name",

-                         props: {

-                             index: 0,

-                             rowSpan: 1,

-                             colSpan: 1,

-                             sort: true

-                         },

-                         transforms: [],

-                         formatters: [],

-                         customFormatters: [sortableHeaderCellFormatter]

-                     },

-                     cell: {

-                         props: {

-                             index: 0

-                         },

-                         formatters: [tableCellFormatter]

-                     }

-                 },

-                 {

-                     property: "nsslapd-pluginType",

-                     header: {

-                         label: "Plugin Type",

-                         props: {

-                             index: 1,

-                             rowSpan: 1,

-                             colSpan: 1,

-                             sort: true

-                         },

-                         transforms: [],

-                         formatters: [],

-                         customFormatters: [sortableHeaderCellFormatter]

-                     },

-                     cell: {

-                         props: {

-                             index: 1

-                         },

-                         formatters: [tableCellFormatter]

-                     }

-                 },

-                 {

-                     property: "nsslapd-pluginEnabled",

-                     header: {

-                         label: "Enabled",

-                         props: {

-                             index: 2,

-                             rowSpan: 1,

-                             colSpan: 1,

-                             sort: true

-                         },

-                         transforms: [],

-                         formatters: [],

-                         customFormatters: [sortableHeaderCellFormatter]

-                     },

-                     cell: {

-                         props: {

-                             index: 2

-                         },

-                         formatters: [tableCellFormatter]

-                     }

-                 },

-                 {

-                     property: "actions",

-                     header: {

-                         label: "Actions",

-                         props: {

-                             index: 3,

-                             rowSpan: 1,

-                             colSpan: 1

-                         },

-                         formatters: [actionHeaderCellFormatter]

-                     },

-                     cell: {

-                         props: {

-                             index: 3

-                         },

-                         formatters: [

-                             (value, { rowData }) => {

-                                 return [

-                                     <td key={rowData.cn[0]}>

-                                         <Button

-                                             onClick={() => {

-                                                 this.props.loadModalHandler(

-                                                     rowData

-                                                 );

-                                             }}

-                                         >

-                                             Edit Plugin

-                                         </Button>

-                                     </td>

-                                 ];

-                             }

-                         ]

-                     }

-                 }

-             ],

-         };

-         this.getColumns = this.getColumns.bind(this);

-     }

- 

-     getColumns() {

-         return this.state.columns;

-     }

- 

-     render() {

-         return (

-             <div>

-                 <DSTable

-                     getColumns={this.getColumns}

-                     fieldsToSearch={this.state.fieldsToSearch}

-                     rowKey="cn"

-                     rows={this.props.rows}

-                     toolBarSearchField="Plugins"

-                     toolBarDisableLoadingSpinner

-                 />

-             </div>

-         );

-     }

- }

- 

- PluginTable.propTypes = {

-     rows: PropTypes.array,

-     loadModalHandler: PropTypes.func,

- };

- 

- PluginTable.defaultProps = {

-     rows: [],

-     loadModalHandler: noop,

- };

- 

- export default PluginTable;

@@ -0,0 +1,500 @@ 

+ import React from "react";

+ import {

+     Button,

+     DropdownButton,

+     MenuItem,

+     actionHeaderCellFormatter,

+     sortableHeaderCellFormatter,

+     tableCellFormatter,

+     noop

+ } from "patternfly-react";

+ import { DSTable } from "../dsTable.jsx";

+ import PropTypes from "prop-types";

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

+ 

+ class PluginTable extends React.Component {

+     constructor(props) {

+         super(props);

+ 

+         this.state = {

+             searchFilterValue: "",

+             fieldsToSearch: ["cn", "nsslapd-pluginType"],

+             columns: [

+                 {

+                     property: "cn",

+                     header: {

+                         label: "Plugin Name",

+                         props: {

+                             index: 0,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 0

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "nsslapd-pluginType",

+                     header: {

+                         label: "Plugin Type",

+                         props: {

+                             index: 1,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 1

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "nsslapd-pluginEnabled",

+                     header: {

+                         label: "Enabled",

+                         props: {

+                             index: 2,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 2

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "actions",

+                     header: {

+                         label: "Actions",

+                         props: {

+                             index: 3,

+                             rowSpan: 1,

+                             colSpan: 1

+                         },

+                         formatters: [actionHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 3

+                         },

+                         formatters: [

+                             (value, { rowData }) => {

+                                 return [

+                                     <td key={rowData.cn[0]}>

+                                         <Button

+                                             onClick={() => {

+                                                 this.props.loadModalHandler(

+                                                     rowData

+                                                 );

+                                             }}

+                                         >

+                                             Edit Plugin

+                                         </Button>

+                                     </td>

+                                 ];

+                             }

+                         ]

+                     }

+                 }

+             ]

+         };

+         this.getColumns = this.getColumns.bind(this);

+     }

+ 

+     getColumns() {

+         return this.state.columns;

+     }

+ 

+     render() {

+         return (

+             <div>

+                 <DSTable

+                     getColumns={this.getColumns}

+                     fieldsToSearch={this.state.fieldsToSearch}

+                     rowKey="cn"

+                     rows={this.props.rows}

+                     toolBarSearchField="Plugins"

+                     toolBarDisableLoadingSpinner

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ PluginTable.propTypes = {

+     rows: PropTypes.array,

+     loadModalHandler: PropTypes.func

+ };

+ 

+ PluginTable.defaultProps = {

+     rows: [],

+     loadModalHandler: noop

+ };

+ 

+ class AttrUniqConfigTable extends React.Component {

+     constructor(props) {

+         super(props);

+ 

+         this.getColumns = this.getColumns.bind(this);

+ 

+         this.state = {

+             searchField: "Configs",

+             fieldsToSearch: ["cn", "uniqueness-attribute-name"],

+             columns: [

+                 {

+                     property: "cn",

+                     header: {

+                         label: "Config Name",

+                         props: {

+                             index: 0,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 0

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "uniqueness-attribute-name",

+                     header: {

+                         label: "Attribute",

+                         props: {

+                             index: 1,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 1

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "nsslapd-pluginenabled",

+                     header: {

+                         label: "Enabled",

+                         props: {

+                             index: 2,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 2

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "actions",

+                     header: {

+                         props: {

+                             index: 3,

+                             rowSpan: 1,

+                             colSpan: 1

+                         },

+                         formatters: [actionHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 3

+                         },

+                         formatters: [

+                             (value, { rowData }) => {

+                                 return [

+                                     <td key={rowData.cn[0]}>

+                                         <DropdownButton

+                                             id={rowData.cn[0]}

+                                             bsStyle="default"

+                                             title="Actions"

+                                         >

+                                             <MenuItem

+                                                 eventKey="1"

+                                                 onClick={() => {

+                                                     this.props.editConfig(

+                                                         rowData

+                                                     );

+                                                 }}

+                                             >

+                                                 Edit Config

+                                             </MenuItem>

+                                             <MenuItem divider />

+                                             <MenuItem

+                                                 eventKey="2"

+                                                 onClick={() => {

+                                                     this.props.deleteConfig(

+                                                         rowData

+                                                     );

+                                                 }}

+                                             >

+                                                 Delete Config

+                                             </MenuItem>

+                                         </DropdownButton>

+                                     </td>

+                                 ];

+                             }

+                         ]

+                     }

+                 }

+             ]

+         };

+     }

+ 

+     getColumns() {

+         return this.state.columns;

+     }

+ 

+     render() {

+         return (

+             <div className="ds-margin-top-xlg">

+                 <DSTable

+                     getColumns={this.getColumns}

+                     fieldsToSearch={this.state.fieldsToSearch}

+                     toolBarSearchField={this.state.searchField}

+                     rowKey="cn"

+                     rows={this.props.rows}

+                     disableLoadingSpinner

+                     toolBarPagination={[6, 12, 24, 48, 96]}

+                     toolBarPaginationPerPage={6}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ AttrUniqConfigTable.propTypes = {

+     rows: PropTypes.array,

+     editConfig: PropTypes.func,

+     deleteConfig: PropTypes.func

+ };

+ 

+ AttrUniqConfigTable.defaultProps = {

+     rows: [],

+     editConfig: noop,

+     deleteConfig: noop

+ };

+ 

+ class LinkedAttributesTable extends React.Component {

+     constructor(props) {

+         super(props);

+ 

+         this.getColumns = this.getColumns.bind(this);

+ 

+         this.state = {

+             searchField: "Configs",

+             fieldsToSearch: ["cn", "linkType", "managedType", "linkScope"],

+             columns: [

+                 {

+                     property: "cn",

+                     header: {

+                         label: "Config Name",

+                         props: {

+                             index: 0,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 0

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "linktype",

+                     header: {

+                         label: "Link Type",

+                         props: {

+                             index: 1,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 1

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "managedtype",

+                     header: {

+                         label: "Managed Type",

+                         props: {

+                             index: 2,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 2

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "linkscope",

+                     header: {

+                         label: "Link Scope",

+                         props: {

+                             index: 2,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 2

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "actions",

+                     header: {

+                         props: {

+                             index: 3,

+                             rowSpan: 1,

+                             colSpan: 1

+                         },

+                         formatters: [actionHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 3

+                         },

+                         formatters: [

+                             (value, { rowData }) => {

+                                 return [

+                                     <td key={rowData.cn[0]}>

+                                         <DropdownButton

+                                             id={rowData.cn[0]}

+                                             bsStyle="default"

+                                             title="Actions"

+                                         >

+                                             <MenuItem

+                                                 eventKey="1"

+                                                 onClick={() => {

+                                                     this.props.editConfig(

+                                                         rowData

+                                                     );

+                                                 }}

+                                             >

+                                                 Edit Config

+                                             </MenuItem>

+                                             <MenuItem divider />

+                                             <MenuItem

+                                                 eventKey="2"

+                                                 onClick={() => {

+                                                     this.props.deleteConfig(

+                                                         rowData

+                                                     );

+                                                 }}

+                                             >

+                                                 Delete Config

+                                             </MenuItem>

+                                         </DropdownButton>

+                                     </td>

+                                 ];

+                             }

+                         ]

+                     }

+                 }

+             ]

+         };

+     }

+ 

+     getColumns() {

+         return this.state.columns;

+     }

+ 

+     render() {

+         return (

+             <div className="ds-margin-top-xlg">

+                 <DSTable

+                     getColumns={this.getColumns}

+                     fieldsToSearch={this.state.fieldsToSearch}

+                     toolBarSearchField={this.state.searchField}

+                     rowKey="cn"

+                     rows={this.props.rows}

+                     disableLoadingSpinner

+                     toolBarPagination={[6, 12, 24, 48, 96]}

+                     toolBarPaginationPerPage={6}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ LinkedAttributesTable.propTypes = {

+     rows: PropTypes.array,

+     editConfig: PropTypes.func,

+     deleteConfig: PropTypes.func

+ };

+ 

+ LinkedAttributesTable.defaultProps = {

+     rows: [],

+     editConfig: noop,

+     deleteConfig: noop

+ };

+ 

+ export { PluginTable, AttrUniqConfigTable, LinkedAttributesTable };

@@ -1,11 +1,166 @@ 

+ import cockpit from "cockpit";

  import React from "react";

- import { noop } from "patternfly-react";

+ import {

+     noop,

+     FormGroup,

+     FormControl,

+     Row,

+     Col,

+     Form,

+     ControlLabel

+ } from "patternfly-react";

+ import { Typeahead } from "react-bootstrap-typeahead";

  import PropTypes from "prop-types";

  import PluginBasicConfig from "./pluginBasicConfig.jsx";

+ import { log_cmd } from "../tools.jsx";

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

  

  class ReferentialIntegrity extends React.Component {

+     componentWillMount(prevProps) {

+         this.getAttributes();

+         this.updateFields();

+     }

+ 

+     componentDidUpdate(prevProps) {

+         if (this.props.rows !== prevProps.rows) {

+             this.updateFields();

+         }

+     }

+ 

+     constructor(props) {

+         super(props);

+ 

+         this.state = {

+             updateDelay: "",

+             membershipAttr: [],

+             entryScope: "",

+             excludeEntryScope: "",

+             containerScope: "",

+             attributes: []

+         };

+ 

+         this.updateFields = this.updateFields.bind(this);

+         this.handleFieldChange = this.handleFieldChange.bind(this);

+         this.getAttributes = this.getAttributes.bind(this);

+     }

+ 

+     handleFieldChange(e) {

+         this.setState({

+             [e.target.id]: e.target.value

+         });

+     }

+ 

+     updateFields() {

+         let membershipAttrList = [];

+ 

+         if (this.props.rows.length > 0) {

+             const pluginRow = this.props.rows.find(

+                 row => row.cn[0] === "referential integrity postoperation"

+             );

+ 

+             this.setState({

+                 updateDelay:

+                     pluginRow["referint-update-delay"] === undefined

+                         ? ""

+                         : pluginRow["referint-update-delay"][0],

+                 entryScope:

+                     pluginRow["nsslapd-pluginEntryScope"] === undefined

+                         ? ""

+                         : pluginRow["nsslapd-pluginEntryScope"][0],

+                 excludeEntryScope:

+                     pluginRow["nsslapd-pluginExcludeEntryScope"] === undefined

+                         ? ""

+                         : pluginRow["nsslapd-pluginExcludeEntryScope"][0],

+                 containerScope:

+                     pluginRow["nsslapd-pluginContainerScope"] === undefined

+                         ? ""

+                         : pluginRow["nsslapd-pluginContainerScope"][0]

+             });

+ 

+             if (pluginRow["referint-membership-attr"] === undefined) {

+                 this.setState({ membershipAttr: [] });

+             } else {

+                 for (let value of pluginRow["referint-membership-attr"]) {

+                     membershipAttrList = [

+                         ...membershipAttrList,

+                         { id: value, label: value }

+                     ];

+                 }

+                 this.setState({ membershipAttr: membershipAttrList });

+             }

+         }

+     }

+ 

+     getAttributes() {

+         const attr_cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "schema",

+             "attributetypes",

+             "list"

+         ];

+         log_cmd("getAttributes", "Get attrs", attr_cmd);

+         cockpit

+                 .spawn(attr_cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     const attrContent = JSON.parse(content);

+                     let attrs = [];

+                     for (let content of attrContent["items"]) {

+                         attrs.push({

+                             id: content.name,

+                             label: content.name

+                         });

+                     }

+                     this.setState({

+                         attributes: attrs

+                     });

+                 })

+                 .fail(err => {

+                     this.props.addNotification(

+                         "error",

+                         `Failed to get attributes - ${err}`

+                     );

+                 });

+     }

+ 

      render() {

+         const {

+             updateDelay,

+             membershipAttr,

+             entryScope,

+             excludeEntryScope,

+             containerScope,

+             attributes

+         } = this.state;

+ 

+         let specificPluginCMD = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "plugin",

+             "referential-integrity",

+             "set",

+             "--update-delay",

+             updateDelay || "delete",

+             "--entry-scope",

+             entryScope || "delete",

+             "--exclude-entry-scope",

+             excludeEntryScope || "delete",

+             "--container-scope",

+             containerScope || "delete"

+         ];

+ 

+         // Delete attributes if the user set an empty value to the field

+         specificPluginCMD = [...specificPluginCMD, "--membership-attr"];

+         if (membershipAttr.length != 0) {

+             for (let value of membershipAttr) {

+                 specificPluginCMD = [...specificPluginCMD, value.label];

+             }

+         } else {

+             specificPluginCMD = [...specificPluginCMD, "delete"];

+         }

+ 

          return (

              <div>

                  <PluginBasicConfig
@@ -14,11 +169,122 @@ 

                      cn="referential integrity postoperation"

                      pluginName="Referential Integrity"

                      cmdName="referential-integrity"

+                     specificPluginCMD={specificPluginCMD}

                      savePluginHandler={this.props.savePluginHandler}

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

                      toggleLoadingHandler={this.props.toggleLoadingHandler}

-                 />

+                 >

+                     <Row>

+                         <Col sm={12}>

+                             <Form horizontal>

+                                 <FormGroup

+                                     key="updateDelay"

+                                     controlId="updateDelay"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         title="Sets the update interval. Special values: 0 - The check is performed immediately, -1 - No check is performed (referint-update-delay)"

+                                     >

+                                         Update Delay

+                                     </Col>

+                                     <Col sm={6}>

+                                         <FormControl

+                                             type="text"

+                                             value={updateDelay}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="membershipAttr"

+                                     controlId="membershipAttr"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         title="Specifies attributes to check for and update (referint-membership-attr)"

+                                     >

+                                         Membership Attribute

+                                     </Col>

+                                     <Col sm={6}>

+                                         <Typeahead

+                                             allowNew

+                                             multiple

+                                             onChange={value => {

+                                                 this.setState({

+                                                     membershipAttr: value

+                                                 });

+                                             }}

+                                             selected={membershipAttr}

+                                             options={attributes}

+                                             newSelectionPrefix="Add a membership attribute: "

+                                             placeholder="Type an attribute..."

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="entryScope"

+                                     controlId="entryScope"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         title="Defines the subtree in which the plug-in looks for the delete or rename operations of a user entry (nsslapd-pluginEntryScope)"

+                                     >

+                                         Entry Scope

+                                     </Col>

+                                     <Col sm={6}>

+                                         <FormControl

+                                             type="text"

+                                             value={entryScope}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="excludeEntryScope"

+                                     controlId="excludeEntryScope"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         title="Defines the subtree in which the plug-in ignores any operations for deleting or renaming a user (nsslapd-pluginExcludeEntryScope)"

+                                     >

+                                         Exclude Entry Scope

+                                     </Col>

+                                     <Col sm={6}>

+                                         <FormControl

+                                             type="text"

+                                             value={excludeEntryScope}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="containerScope"

+                                     controlId="containerScope"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         title="Specifies which branch the plug-in searches for the groups to which the user belongs. It only updates groups that are under the specified container branch, and leaves all other groups not updated (nsslapd-pluginContainerScope)"

+                                     >

+                                         Container Scope

+                                     </Col>

+                                     <Col sm={6}>

+                                         <FormControl

+                                             type="text"

+                                             value={containerScope}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                             </Form>

+                         </Col>

+                     </Row>

+                 </PluginBasicConfig>

              </div>

          );

      }

@@ -1,11 +1,160 @@ 

+ import cockpit from "cockpit";

  import React from "react";

- import { noop } from "patternfly-react";

+ import {

+     noop,

+     FormGroup,

+     FormControl,

+     Row,

+     Col,

+     Form,

+     ControlLabel,

+     Checkbox

+ } from "patternfly-react";

  import PropTypes from "prop-types";

+ import { Typeahead } from "react-bootstrap-typeahead";

  import PluginBasicConfig from "./pluginBasicConfig.jsx";

+ import { log_cmd } from "../tools.jsx";

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

  

  class RetroChangelog extends React.Component {

+     componentWillMount(prevProps) {

+         this.getAttributes();

+         this.updateFields();

+     }

+ 

+     componentDidUpdate(prevProps) {

+         if (this.props.rows !== prevProps.rows) {

+             this.updateFields();

+         }

+     }

+ 

+     constructor(props) {

+         super(props);

+ 

+         this.state = {

+             isReplicated: false,

+             attribute: [],

+             directory: "",

+             maxAge: "",

+             excludeSuffix: "",

+             attributes: []

+         };

+ 

+         this.updateFields = this.updateFields.bind(this);

+         this.handleFieldChange = this.handleFieldChange.bind(this);

+         this.handleCheckboxChange = this.handleCheckboxChange.bind(this);

+     }

+ 

+     handleCheckboxChange(e) {

+         this.setState({

+             [e.target.id]: e.target.checked

+         });

+     }

+ 

+     handleFieldChange(e) {

+         this.setState({

+             [e.target.id]: e.target.value

+         });

+     }

+ 

+     updateFields() {

+         if (this.props.rows.length > 0) {

+             const pluginRow = this.props.rows.find(

+                 row => row.cn[0] === "Retro Changelog Plugin"

+             );

+ 

+             this.setState({

+                 isReplicated: !(

+                     pluginRow["isReplicated"] === undefined ||

+                     pluginRow["isReplicated"][0] == "FALSE"

+                 ),

+                 attribute:

+                     pluginRow["nsslapd-attribute"] === undefined

+                         ? []

+                         : [

+                             {

+                                 id: pluginRow["nsslapd-attribute"][0],

+                                 label: pluginRow["nsslapd-attribute"][0]

+                             }

+                         ],

+                 directory:

+                     pluginRow["nsslapd-changelogdir"] === undefined

+                         ? ""

+                         : pluginRow["nsslapd-changelogdir"][0],

+                 maxAge:

+                     pluginRow["nsslapd-changelogmaxage"] === undefined

+                         ? ""

+                         : pluginRow["nsslapd-changelogmaxage"][0],

+                 excludeSuffix:

+                     pluginRow["nsslapd-exclude-suffix"] === undefined

+                         ? ""

+                         : pluginRow["nsslapd-exclude-suffix"][0]

+             });

+         }

+     }

+ 

+     getAttributes() {

+         const attr_cmd = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "schema",

+             "attributetypes",

+             "list"

+         ];

+         log_cmd("getAttributes", "Get attrs", attr_cmd);

+         cockpit

+                 .spawn(attr_cmd, { superuser: true, err: "message" })

+                 .done(content => {

+                     const attrContent = JSON.parse(content);

+                     let attrs = [];

+                     for (let content of attrContent["items"]) {

+                         attrs.push({

+                             id: content.name,

+                             label: content.name

+                         });

+                     }

+                     this.setState({

+                         attributes: attrs

+                     });

+                 })

+                 .fail(err => {

+                     this.props.addNotification(

+                         "error",

+                         `Failed to get attributes - ${err}`

+                     );

+                 });

+     }

+ 

      render() {

+         const {

+             isReplicated,

+             attribute,

+             directory,

+             maxAge,

+             excludeSuffix,

+             attributes

+         } = this.state;

+ 

+         let specificPluginCMD = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "plugin",

+             "retro-changelog",

+             "set",

+             "--is-replicated",

+             isReplicated ? "TRUE" : "FALSE",

+             "--attribute",

+             attribute.length != 0 ? attribute[0].id : "delete",

+             "--directory",

+             directory || "delete",

+             "--max-age",

+             maxAge || "delete",

+             "--exclude-suffix",

+             excludeSuffix || "delete"

+         ];

+ 

          return (

              <div>

                  <PluginBasicConfig
@@ -14,11 +163,109 @@ 

                      cn="Retro Changelog Plugin"

                      pluginName="Retro Changelog"

                      cmdName="retro-changelog"

+                     specificPluginCMD={specificPluginCMD}

                      savePluginHandler={this.props.savePluginHandler}

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

                      toggleLoadingHandler={this.props.toggleLoadingHandler}

-                 />

+                 >

+                     <Row>

+                         <Col sm={12}>

+                             <Form horizontal>

+                                 <FormGroup

+                                     key="attribute"

+                                     controlId="attribute"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         title="Specifies another Directory Server attribute which must be included in the retro changelog entries (nsslapd-attribute)"

+                                     >

+                                         Attribute

+                                     </Col>

+                                     <Col sm={7}>

+                                         <Typeahead

+                                             allowNew

+                                             onChange={value => {

+                                                 this.setState({

+                                                     attribute: value

+                                                 });

+                                             }}

+                                             selected={attribute}

+                                             options={attributes}

+                                             newSelectionPrefix="Add an attribute: "

+                                             placeholder="Type an attribute..."

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="directory"

+                                     controlId="directory"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         title="Specifies the name of the directory in which the changelog database is created the first time the plug-in is run"

+                                     >

+                                         Directory

+                                     </Col>

+                                     <Col sm={7}>

+                                         <FormControl

+                                             type="text"

+                                             value={directory}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup key="maxAge" controlId="maxAge">

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         title="This attribute specifies the maximum age of any entry in the changelog (nsslapd-changelogmaxage)"

+                                     >

+                                         Max Age

+                                     </Col>

+                                     <Col sm={7}>

+                                         <FormControl

+                                             type="text"

+                                             value={maxAge}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="excludeSuffix"

+                                     controlId="excludeSuffix"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         title="This attribute specifies the suffix which will be excluded from the scope of the plugin (nsslapd-exclude-suffix)"

+                                     >

+                                         Exclude Suffix

+                                     </Col>

+                                     <Col sm={5}>

+                                         <FormControl

+                                             type="text"

+                                             value={excludeSuffix}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                     <Col sm={2}>

+                                         <Checkbox

+                                             id="isReplicated"

+                                             checked={isReplicated}

+                                             onChange={this.handleCheckboxChange}

+                                             title="Sets a flag to indicate on a change in the changelog whether the change is newly made on that server or whether it was replicated over from another server (isReplicated)"

+                                         >

+                                             Is Replicated

+                                         </Checkbox>

+                                     </Col>

+                                 </FormGroup>

+                             </Form>

+                         </Col>

+                     </Row>

+                 </PluginBasicConfig>

              </div>

          );

      }

@@ -1,11 +1,178 @@ 

  import React from "react";

- import { noop } from "patternfly-react";

+ import {

+     noop,

+     FormGroup,

+     FormControl,

+     Row,

+     Col,

+     Form,

+     ControlLabel

+ } from "patternfly-react";

+ import { Typeahead } from "react-bootstrap-typeahead";

  import PropTypes from "prop-types";

  import PluginBasicConfig from "./pluginBasicConfig.jsx";

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

  

  class RootDNAccessControl extends React.Component {

+     componentWillMount(prevProps) {

+         this.updateFields();

+     }

+ 

+     componentDidUpdate(prevProps) {

+         if (this.props.rows !== prevProps.rows) {

+             this.updateFields();

+         }

+     }

+ 

+     constructor(props) {

+         super(props);

+ 

+         this.state = {

+             allowHost: [],

+             denyHost: [],

+             allowIP: [],

+             denyIP: [],

+             openTime: "",

+             closeTime: "",

+             daysAllowed: ""

+         };

+ 

+         this.updateFields = this.updateFields.bind(this);

+         this.handleFieldChange = this.handleFieldChange.bind(this);

+     }

+ 

+     handleFieldChange(e) {

+         this.setState({

+             [e.target.id]: e.target.value

+         });

+     }

+ 

+     updateFields() {

+         let allowHostList = [];

+         let denyHostList = [];

+         let allowIPList = [];

+         let denyIPList = [];

+ 

+         if (this.props.rows.length > 0) {

+             const pluginRow = this.props.rows.find(

+                 row => row.cn[0] === "RootDN Access Control"

+             );

+             this.setState({

+                 openTime:

+                     pluginRow["rootdn-open-time"] === undefined

+                         ? ""

+                         : pluginRow["rootdn-open-time"][0],

+                 closeTime:

+                     pluginRow["rootdn-close-time"] === undefined

+                         ? ""

+                         : pluginRow["rootdn-close-time"][0],

+                 daysAllowed:

+                     pluginRow["rootdn-days-allowed"] === undefined

+                         ? ""

+                         : pluginRow["rootdn-days-allowed"][0]

+             });

+ 

+             if (pluginRow["rootdn-allow-host"] === undefined) {

+                 this.setState({ allowHost: [] });

+             } else {

+                 for (let value of pluginRow["rootdn-allow-host"]) {

+                     allowHostList = [

+                         ...allowHostList,

+                         { id: value, label: value }

+                     ];

+                 }

+                 this.setState({ allowHost: allowHostList });

+             }

+             if (pluginRow["rootdn-deny-host"] === undefined) {

+                 this.setState({ denyHost: [] });

+             } else {

+                 for (let value of pluginRow["rootdn-deny-host"]) {

+                     denyHostList = [

+                         ...denyHostList,

+                         { id: value, label: value }

+                     ];

+                 }

+                 this.setState({ denyHost: denyHostList });

+             }

+             if (pluginRow["rootdn-allow-ip"] === undefined) {

+                 this.setState({ allowIP: [] });

+             } else {

+                 for (let value of pluginRow["rootdn-allow-ip"]) {

+                     allowIPList = [...allowIPList, { id: value, label: value }];

+                 }

+                 this.setState({ allowIP: allowIPList });

+             }

+             if (pluginRow["rootdn-deny-ip"] === undefined) {

+                 this.setState({ denyIP: [] });

+             } else {

+                 for (let value of pluginRow["rootdn-deny-ip"]) {

+                     denyIPList = [...denyIPList, { id: value, label: value }];

+                 }

+                 this.setState({ denyIP: denyIPList });

+             }

+         }

+     }

+ 

      render() {

+         const {

+             allowHost,

+             denyHost,

+             allowIP,

+             denyIP,

+             openTime,

+             closeTime,

+             daysAllowed

+         } = this.state;

+ 

+         let specificPluginCMD = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "plugin",

+             "root-dn",

+             "set",

+             "--open-time",

+             openTime || "delete",

+             "--close-time",

+             closeTime || "delete",

+             "--days-allowed",

+             daysAllowed || "delete"

+         ];

+ 

+         // Delete attributes if the user set an empty value to the field

+         specificPluginCMD = [...specificPluginCMD, "--allow-host"];

+         if (allowHost.length != 0) {

+             for (let value of allowHost) {

+                 specificPluginCMD = [...specificPluginCMD, value.label];

+             }

+         } else {

+             specificPluginCMD = [...specificPluginCMD, "delete"];

+         }

+         specificPluginCMD = [...specificPluginCMD, "--deny-host"];

+         if (denyHost.length != 0) {

+             for (let value of denyHost) {

+                 specificPluginCMD = [...specificPluginCMD, value.label];

+             }

+         } else {

+             specificPluginCMD = [...specificPluginCMD, "delete"];

+         }

+         specificPluginCMD = [...specificPluginCMD, "--allow-ip"];

+         if (allowIP.length != 0) {

+             for (let value of allowIP) {

+                 specificPluginCMD = [...specificPluginCMD, value.label];

+             }

+         } else {

+             specificPluginCMD = [...specificPluginCMD, "delete"];

+         }

+         specificPluginCMD = [...specificPluginCMD, "--allow-host"];

+         if (allowHost.length != 0) {

+             for (let value of allowHost) {

+                 specificPluginCMD = [...specificPluginCMD, value.label];

+             }

+         } else {

+             specificPluginCMD = [...specificPluginCMD, "delete"];

+         }

+ 

          return (

              <div>

                  <PluginBasicConfig
@@ -14,11 +181,172 @@ 

                      cn="RootDN Access Control"

                      pluginName="RootDN Access Control"

                      cmdName="root-dn"

+                     specificPluginCMD={specificPluginCMD}

                      savePluginHandler={this.props.savePluginHandler}

                      pluginListHandler={this.props.pluginListHandler}

                      addNotification={this.props.addNotification}

                      toggleLoadingHandler={this.props.toggleLoadingHandler}

-                 />

+                 >

+                     <Row>

+                         <Col sm={12}>

+                             <Form horizontal>

+                                 <FormGroup

+                                     key="allowHost"

+                                     controlId="allowHost"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         help="Sets what hosts, by fully-qualified domain name, the root user is allowed to use to access the Directory Server. Any hosts not listed are implicitly denied (rootdn-allow-host)"

+                                     >

+                                         Allow Host

+                                     </Col>

+                                     <Col sm={6}>

+                                         <Typeahead

+                                             allowNew

+                                             multiple

+                                             onChange={value => {

+                                                 this.setState({

+                                                     allowHost: value

+                                                 });

+                                             }}

+                                             selected={allowHost}

+                                             options={[]}

+                                             newSelectionPrefix="Add a host to allow: "

+                                             placeholder="Type a hostname (wild cards are allowed)..."

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup key="denyHost" controlId="denyHost">

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         help="Sets what hosts, by fully-qualified domain name, the root user is not allowed to use to access the Directory Server Any hosts not listed are implicitly allowed (rootdn-deny-host). If an host address is listed in both the rootdn-allow-host and rootdn-deny-host attributes, it is denied access."

+                                     >

+                                         Deny Host

+                                     </Col>

+                                     <Col sm={6}>

+                                         <Typeahead

+                                             allowNew

+                                             multiple

+                                             onChange={value => {

+                                                 this.setState({

+                                                     denyHost: value

+                                                 });

+                                             }}

+                                             selected={denyHost}

+                                             options={[]}

+                                             newSelectionPrefix="Add a host to deny: "

+                                             placeholder="Type a hostname (wild cards are allowed)..."

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup key="allowIP" controlId="allowIP">

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         help="Sets what IP addresses, either IPv4 or IPv6, for machines the root user is allowed to use to access the Directory Server Any IP addresses not listed are implicitly denied (rootdn-allow-ip)"

+                                     >

+                                         Allow IP address

+                                     </Col>

+                                     <Col sm={6}>

+                                         <Typeahead

+                                             allowNew

+                                             multiple

+                                             onChange={value => {

+                                                 this.setState({

+                                                     allowIP: value

+                                                 });

+                                             }}

+                                             selected={allowIP}

+                                             options={[]}

+                                             newSelectionPrefix="Add an IP address to allow: "

+                                             placeholder="Type an IP address (wild cards are allowed)..."

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup key="denyIP" controlId="denyIP">

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         help="Sets what IP addresses, either IPv4 or IPv6, for machines the root user is not allowed to use to access the Directory Server. Any IP addresses not listed are implicitly allowed (rootdn-deny-ip) If an IP address is listed in both the rootdn-allow-ip and rootdn-deny-ip attributes, it is denied access."

+                                     >

+                                         Deny IP address

+                                     </Col>

+                                     <Col sm={6}>

+                                         <Typeahead

+                                             allowNew

+                                             multiple

+                                             onChange={value => {

+                                                 this.setState({

+                                                     denyIP: value

+                                                 });

+                                             }}

+                                             selected={denyIP}

+                                             options={[]}

+                                             newSelectionPrefix="Add an IP address to deny: "

+                                             placeholder="Type an IP address (wild cards are allowed)..."

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup key="openTime" controlId="openTime">

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         help="Sets part of a time period or range when the root user is allowed to access the Directory Server. This sets when the time-based access begins (rootdn-open-time)"

+                                     >

+                                         Open Time

+                                     </Col>

+                                     <Col sm={6}>

+                                         <FormControl

+                                             type="text"

+                                             value={openTime}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="closeTime"

+                                     controlId="closeTime"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         help="Sets part of a time period or range when the root user is allowed to access the Directory Server. This sets when the time-based access ends (rootdn-close-time)"

+                                     >

+                                         Close Time

+                                     </Col>

+                                     <Col sm={6}>

+                                         <FormControl

+                                             type="text"

+                                             value={closeTime}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="daysAllowed"

+                                     controlId="daysAllowed"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={2}

+                                         help="Gives a comma-separated list of what days the root user is allowed to use to access the Directory Server. Any days listed are implicitly denied (rootdn-days-allowed)"

+                                     >

+                                         Days Allowed

+                                     </Col>

+                                     <Col sm={6}>

+                                         <FormControl

+                                             type="text"

+                                             value={daysAllowed}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                             </Form>

+                         </Col>

+                     </Row>

+                 </PluginBasicConfig>

              </div>

          );

      }

@@ -0,0 +1,247 @@ 

+ import React from "react";

+ import {

+     Row,

+     Col,

+     Form,

+     noop,

+     FormGroup,

+     Checkbox,

+     ControlLabel

+ } from "patternfly-react";

+ import PropTypes from "prop-types";

+ import PluginBasicConfig from "./pluginBasicConfig.jsx";

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

+ 

+ class WinSync extends React.Component {

+     componentWillMount(prevProps) {

+         this.updateFields();

+     }

+ 

+     componentDidUpdate(prevProps) {

+         if (this.props.rows !== prevProps.rows) {

+             this.updateFields();

+         }

+     }

+ 

+     constructor(props) {

+         super(props);

+ 

+         this.handleCheckboxChange = this.handleCheckboxChange.bind(this);

+         this.updateFields = this.updateFields.bind(this);

+ 

+         this.state = {

+             posixWinsyncCreateMemberOfTask: false,

+             posixWinsyncLowerCaseUID: false,

+             posixWinsyncMapMemberUID: false,

+             posixWinsyncMapNestedGrouping: false,

+             posixWinsyncMsSFUSchema: false

+         };

+     }

+ 

+     handleCheckboxChange(e) {

+         this.setState({

+             [e.target.id]: e.target.checked

+         });

+     }

+ 

+     updateFields() {

+         if (this.props.rows.length > 0) {

+             const pluginRow = this.props.rows.find(

+                 row => row.cn[0] === "Posix Winsync API"

+             );

+ 

+             this.setState({

+                 posixWinsyncCreateMemberOfTask: !(

+                     pluginRow["posixwinsynccreatememberoftask"] === undefined ||

+                     pluginRow["posixwinsynccreatememberoftask"][0] == "false"

+                 ),

+                 posixWinsyncLowerCaseUID: !(

+                     pluginRow["posixwinsynclowercaseuid"] === undefined ||

+                     pluginRow["posixwinsynclowercaseuid"][0] == "false"

+                 ),

+                 posixWinsyncMapMemberUID: !(

+                     pluginRow["posixwinsyncmapmemberuid"] === undefined ||

+                     pluginRow["posixwinsyncmapmemberuid"][0] == "false"

+                 ),

+                 posixWinsyncMapNestedGrouping: !(

+                     pluginRow["posixwinsyncmapnestedgrouping"] === undefined ||

+                     pluginRow["posixwinsyncmapnestedgrouping"][0] == "false"

+                 ),

+                 posixWinsyncMsSFUSchema: !(

+                     pluginRow["posixwinsyncmssfuschema"] === undefined ||

+                     pluginRow["posixwinsyncmssfuschema"][0] == "false"

+                 )

+             });

+         }

+     }

+ 

+     render() {

+         const {

+             posixWinsyncCreateMemberOfTask,

+             posixWinsyncLowerCaseUID,

+             posixWinsyncMapMemberUID,

+             posixWinsyncMapNestedGrouping,

+             posixWinsyncMsSFUSchema

+         } = this.state;

+ 

+         let specificPluginCMD = [

+             "dsconf",

+             "-j",

+             "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",

+             "plugin",

+             "posix-winsync",

+             "set",

+             "--create-memberof-task",

+             posixWinsyncCreateMemberOfTask ? "true" : "false",

+             "--lower-case-uid",

+             posixWinsyncLowerCaseUID ? "true" : "false",

+             "--map-member-uid",

+             posixWinsyncMapMemberUID ? "true" : "false",

+             "--map-nested-grouping",

+             posixWinsyncMapNestedGrouping ? "true" : "false",

+             "--ms-sfu-schema",

+             posixWinsyncMsSFUSchema ? "true" : "false"

+         ];

+         return (

+             <div>

+                 <PluginBasicConfig

+                     rows={this.props.rows}

+                     serverId={this.props.serverId}

+                     cn="Posix Winsync API"

+                     pluginName="Posix Winsync API"

+                     cmdName="posix-winsync"

+                     specificPluginCMD={specificPluginCMD}

+                     savePluginHandler={this.props.savePluginHandler}

+                     pluginListHandler={this.props.pluginListHandler}

+                     addNotification={this.props.addNotification}

+                     toggleLoadingHandler={this.props.toggleLoadingHandler}

+                 >

+                     <Row>

+                         <Col sm={9}>

+                             <Form horizontal>

+                                 <FormGroup

+                                     key="posixWinsyncCreateMemberOfTask"

+                                     controlId="posixWinsyncCreateMemberOfTask"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={3}

+                                         title="Sets whether to run the memberOf fix-up task immediately after a sync run in order to update group memberships for synced users"

+                                     >

+                                         Create MemberOf Task

+                                     </Col>

+                                     <Col sm={6}>

+                                         <Checkbox

+                                             id="posixWinsyncCreateMemberOfTask"

+                                             checked={

+                                                 posixWinsyncCreateMemberOfTask

+                                             }

+                                             onChange={this.handleCheckboxChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="posixWinsyncLowerCaseUID"

+                                     controlId="posixWinsyncLowerCaseUID"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={3}

+                                         title="Sets whether to store (and, if necessary, convert) the UID value in the memberUID attribute in lower case"

+                                     >

+                                         Lower Case UID

+                                     </Col>

+                                     <Col sm={6}>

+                                         <Checkbox

+                                             id="posixWinsyncLowerCaseUID"

+                                             checked={posixWinsyncLowerCaseUID}

+                                             onChange={this.handleCheckboxChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="posixWinsyncMapMemberUID"

+                                     controlId="posixWinsyncMapMemberUID"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={3}

+                                         title="Sets whether to map the memberUID attribute in an Active Directory group to the uniqueMember attribute in a Directory Server group"

+                                     >

+                                         Map Member UID

+                                     </Col>

+                                     <Col sm={6}>

+                                         <Checkbox

+                                             id="posixWinsyncMapMemberUID"

+                                             checked={posixWinsyncMapMemberUID}

+                                             onChange={this.handleCheckboxChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="posixWinsyncMapNestedGrouping"

+                                     controlId="posixWinsyncMapNestedGrouping"

+                                 >

+                                     <Col componentClass={ControlLabel} sm={3}>

+                                         Map Nested Grouping

+                                     </Col>

+                                     <Col sm={6}>

+                                         <Checkbox

+                                             id="posixWinsyncMapNestedGrouping"

+                                             checked={

+                                                 posixWinsyncMapNestedGrouping

+                                             }

+                                             title="Manages if nested groups are updated when memberUID \

+                                         attributes in an Active Directory POSIX group change"

+                                             onChange={this.handleCheckboxChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="posixWinsyncMsSFUSchema"

+                                     controlId="posixWinsyncMsSFUSchema"

+                                 >

+                                     <Col

+                                         componentClass={ControlLabel}

+                                         sm={3}

+                                         title="Sets whether to the older Microsoft System Services  for Unix 3.0 (msSFU30) schema when syncing Posix attributes  from Active Directory"

+                                     >

+                                         Microsoft System Services for Unix 3.0

+                                         (msSFU30) schema

+                                     </Col>

+                                     <Col sm={6}>

+                                         <Checkbox

+                                             id="posixWinsyncMsSFUSchema"

+                                             checked={posixWinsyncMsSFUSchema}

+                                             onChange={this.handleCheckboxChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                             </Form>

+                         </Col>

+                     </Row>

+                 </PluginBasicConfig>

+             </div>

+         );

+     }

+ }

+ 

+ WinSync.propTypes = {

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ WinSync.defaultProps = {

+     rows: [],

+     serverId: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default WinSync;

@@ -4,7 +4,7 @@ 

  import { log_cmd } from "./lib/tools.jsx";

  import { Col, Row, Tab, Nav, NavItem, Spinner } from "patternfly-react";

  import PluginEditModal from "./lib/plugins/pluginModal.jsx";

- import PluginTable from "./lib/plugins/pluginTable.jsx";

+ import { PluginTable } from "./lib/plugins/pluginTables.jsx";

  import AccountPolicy from "./lib/plugins/accountPolicy.jsx";

  import AttributeUniqueness from "./lib/plugins/attributeUniqueness.jsx";

  import AutoMembership from "./lib/plugins/autoMembership.jsx";
@@ -17,6 +17,7 @@ 

  import RetroChangelog from "./lib/plugins/retroChangelog.jsx";

  import RootDNAccessControl from "./lib/plugins/rootDNAccessControl.jsx";

  import USN from "./lib/plugins/usn.jsx";

+ import WinSync from "./lib/plugins/winsync.jsx";

  import { NotificationController } from "./lib/notifications.jsx";

  import "./css/ds.css";

  
@@ -339,10 +340,10 @@ 

                      />

                  )

              },

-             autoMembership: {

-                 name: "Auto Membership",

+             linkedAttributes: {

+                 name: "Linked Attributes",

                  component: (

-                     <AutoMembership

+                     <LinkedAttributes

                          rows={this.state.rows}

                          serverId={this.props.serverId}

                          savePluginHandler={this.savePlugin}
@@ -365,10 +366,10 @@ 

                      />

                  )

              },

-             linkedAttributes: {

-                 name: "Linked Attributes",

+             autoMembership: {

+                 name: "Auto Membership",

                  component: (

-                     <LinkedAttributes

+                     <AutoMembership

                          rows={this.state.rows}

                          serverId={this.props.serverId}

                          savePluginHandler={this.savePlugin}
@@ -417,6 +418,19 @@ 

                      />

                  )

              },

+             winsync: {

+                 name: "Posix Winsync",

+                 component: (

+                     <WinSync

+                         rows={this.state.rows}

+                         serverId={this.props.serverId}

+                         savePluginHandler={this.savePlugin}

+                         pluginListHandler={this.pluginList}

+                         addNotification={this.addNotification}

+                         toggleLoadingHandler={this.toggleLoading}

+                     />

+                 )

+             },

              referentialIntegrity: {

                  name: "Referential Integrity",

                  component: (

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

  from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add

  

  arg_to_attr = {

-     'config_entry': 'nsslapd-pluginConfigArea'

+     'config_entry': 'nsslapd_pluginconfigarea'

  }

  

  arg_to_attr_config = {
@@ -23,6 +23,7 @@ 

      'state_attr': 'stateattrname'

  }

  

+ 

  def accountpolicy_edit(inst, basedn, log, args):

      log = log.getChild('accountpolicy_edit')

      plugin = AccountPolicyPlugin(inst)

@@ -14,6 +14,7 @@ 

  from lib389._constants import DN_PLUGIN

  

  arg_to_attr = {

+     'enabled': 'nsslapd-pluginenabled',

      'attr_name': 'uniqueness-attribute-name',

      'subtree': 'uniqueness-subtrees',

      'across_all_subtrees': 'uniqueness-across-all-subtrees',
@@ -80,6 +81,8 @@ 

  def _add_parser_args(parser):

      parser.add_argument('NAME', help='Sets the name of the plug-in configuration record. (cn) You can use any string, '

                                       'but "attribute_name Attribute Uniqueness" is recommended.')

+     parser.add_argument('--enabled', choices=['on', 'off'],

+                         help='Identifies whether or not the config is enabled.')

      parser.add_argument('--attr-name', nargs='+',

                          help='Sets the name of the attribute whose values must be unique. '

                               'This attribute is multi-valued. (uniqueness-attribute-name)')

@@ -26,7 +26,7 @@ 

  

  def _add_parser_args(parser):

      parser.add_argument('--create-memberof-task', choices=['true', 'false'], type=str.lower,

-                         help=' sets whether to run the memberOf fix-up task immediately after a sync run in order '

+                         help='Sets whether to run the memberOf fix-up task immediately after a sync run in order '

                               'to update group memberships for synced users (posixWinsyncCreateMemberOfTask)')

      parser.add_argument('--lower-case-uid', choices=['true', 'false'], type=str.lower,

                          help='Sets whether to store (and, if necessary, convert) the UID value in the memberUID '

@@ -36,7 +36,7 @@ 

      parser.add_argument('--exclude-entry-scope',

                          help='Defines the subtree in which the plug-in ignores any operations '

                               'for deleting or renaming a user (nsslapd-pluginExcludeEntryScope)')

-     parser.add_argument('--container_scope',

+     parser.add_argument('--container-scope',

                          help='Specifies which branch the plug-in searches for the groups to which the user belongs. '

                               'It only updates groups that are under the specified container branch, '

                               'and leaves all other groups not updated (nsslapd-pluginContainerScope)')

@@ -25,7 +25,7 @@ 

  

  

  def _add_parser_args(parser):

-     parser.add_argument('--is-replicated', choices=['true', 'false'], type=str.lower,

+     parser.add_argument('--is-replicated', choices=['TRUE', 'FALSE'], type=str.upper,

                          help='Sets a flag to indicate on a change in the changelog whether the change is newly made '

                               'on that server or whether it was replicated over from another server (isReplicated)')

      parser.add_argument('--attribute',

@@ -125,7 +125,7 @@ 

      backups_parser.add_argument('--delete', nargs=1, help="Delete backup directory")

      backups_parser.set_defaults(func=dbtasks_backups)

  

-     ldifs_parser = subcommands.add_parser('ldifs', help="List all the DLIF files located in the server's LDIF directory")

+     ldifs_parser = subcommands.add_parser('ldifs', help="List all the LDIF files located in the server's LDIF directory")

      ldifs_parser.add_argument('--delete', nargs=1, help="Delete LDIF file")

      ldifs_parser.set_defaults(func=dbtasks_ldifs)

  

file modified
+1 -1
@@ -184,7 +184,7 @@ 

      """

  

      def __init__(self, instance, basedn="cn=plugins,cn=config"):

-         super(DSLdapObjects, self).__init__(instance)

+         super(DSLdapObjects, self).__init__(instance.verbose)

          self._instance = instance

          self._objectclasses = ['top', 'nsslapdplugin', 'extensibleObject']

          self._filterattrs = ['cn', 'nsslapd-pluginPath']

Description: Add UI plugin tabs for accountPolicy, attributeUniqueness,
linkedAttributes, referentialIntegrity, retroChangelog, rootDNAccessControl
and winsync.
Reorder the tabs to make the usage more intuitive.
Fix Attribute Uniqueness logging level issue.
Move pluginTable.jsx content to pluginTables.jsx.
Fix a small 'help' typo in dbtasks.py.

https://pagure.io/389-ds-base/issue/50041

Reviewed by: ?

Initial code review looks good, I'll be testing the UI later today....

rebased onto 59b8a5bd8d6fffc9fcd897e642df1ead34912561

5 years ago

Console error on initial load:

checkPropTypes.js:19 Warning: Failed prop type: Invalid prop `selected` supplied to `TypeaheadContainer(WrappedTypeahead)`.
    in TypeaheadContainer(WrappedTypeahead) (created by OnClickOutside(TypeaheadContainer(WrappedTypeahead)))
    in OnClickOutside(TypeaheadContainer(WrappedTypeahead)) (created by RetroChangelog)
    in div (created by Col)
    in Col (created by RetroChangelog)
    in div (created by FormGroup)
    in FormGroup (created by RetroChangelog)
    in form (created by Form)
    in Form (created by RetroChangelog)
    in div (created by Col)
    in Col (created by RetroChangelog)
    in div (created by Row)
    in Row (created by RetroChangelog)
    in div (created by PluginBasicConfig)
    in PluginBasicConfig (created by RetroChangelog)
    in div (created by RetroChangelog)
    in RetroChangelog (created by Plugins)
    in div (created by TabPane)
    in TabPane (created by Plugins)
    in div (created by TabContent)
    in TabContent (created by Plugins)
    in div (created by Col)
    in Col (created by Plugins)
    in div (created by Row)
    in Row (created by Plugins)
    in TabContainer (created by Uncontrolled(TabContainer))
    in Uncontrolled(TabContainer) (created by Plugins)
    in div (created by Plugins)
    in Plugins

I would also like to see more "title"s added to config setting rows to describe what the setting does, and the attribute it updates. I need to work on this too, but for example the RI plugin, it's not clear what "Container Scope" is. I was doing this for most config settings, and we should keep doing it as its adds a nice user experience.

1 new commit added

  • Add titles and fix TypeAhead element
5 years ago

The issues are fixed. Please, check.

Trying to update the config of Root DN access control plugin fails saying no update occurred.

Adding new attribute uniquness config entgry generates console error:

checkPropTypes.js:19 Warning: Failed prop type: Invalid prop `selected` supplied to `TypeaheadContainer(WrappedTypeahead)`.
    in TypeaheadContainer(WrappedTypeahead) (created by OnClickOutside(TypeaheadContainer(WrappedTypeahead)))
    in OnClickOutside(TypeaheadContainer(WrappedTypeahead)) (created by AttributeUniqueness)
    in div (created by Col)
    in Col (created by AttributeUniqueness)
    in div (created by FormGroup)
    in FormGroup (created by AttributeUniqueness)
    in form (created by Form)
    in Form (created by AttributeUniqueness)
    in div (created by Col)
    in Col (created by AttributeUniqueness)
    in div (created by Row)
    in Row (created by AttributeUniqueness)
    in div (created by ModalBody)
    in ModalBody (created by AttributeUniqueness)
    in div (created by AttributeUniqueness)
    in div (created by CustomModalDialog)
    in div (created by CustomModalDialog)
    in div (created by CustomModalDialog)
    in CustomModalDialog (created by Modal)
    in Transition (created by Fade)
    in Fade (created by DialogTransition)
    in DialogTransition (created by Modal)
    in RefHolder (created by Modal)
    in div (created by Modal)
    in Portal (created by Modal)
    in Modal (created by Modal)
    in Modal (created by AttributeUniqueness)
    in div (created by AttributeUniqueness)
    in AttributeUniqueness (created by Plugins)
    in div (created by TabPane)
    in TabPane (created by Plugins)
    in div (created by TabContent)
    in TabContent (created by Plugins)
    in div (created by Col)
    in Col (created by Plugins)
    in div (created by Row)
    in Row (created by Plugins)
    in TabContainer (created by Uncontrolled(TabContainer))
    in Uncontrolled(TabContainer) (created by Plugins)
    in div (created by Plugins)
    in Plugins

Then trying to edit the new attribute uniqueness plugin doesn't pull in the current config values. The form is empty except for the name, but at least the attribute field is not loaded - maybe others too?

Managed entries, the "Entry scope" input filed is not aligned

LInked attrs. When creating config, could the form use typeAhead or a dropdown list for some of those fields?

Posix Winsync. There is an option to perform a memberOf Task - that probably should not be there

RI plugin, the title actually prints to the page for the update interval. Also changing the member attributes results in a CLI usage error:

usage: dsconf [-h] [-v] [-D BINDDN] [-w BINDPW] [-W] [-y PWDFILE] [-b BASEDN]
              [-Z] [-j]
              instance
              {backend,backup,chaining,config,directory_manager,healthcheck,monitor,plugin,pwpolicy,localpwp,replication,repl-agmt,repl-winsync-agmt,repl-tasks,sasl,schema}
              ...
dsconf: error: unrecognized arguments: --container_scope delete error during referential integrity postoperation modification

Probably shouldn't be an underscore?

MemberOf plugin: the "Auto Add OC" field should be a typeAhead of all the objectlcasses

That should keep you busy for a little while :-)

1 new commit added

  • Fix few more issues
5 years ago

Trying to update the config of Root DN access control plugin fails saying no update occurred.

Works for me... Probably, you haven't added the new element to the TypeAhead type (after typing, you should click on the item and it will add it. It is how the component was designed...)

Adding new attribute uniquness config entgry generates console error:

Fixed.

Then trying to edit the new attribute uniqueness plugin doesn't pull in the current config values. The form is empty except for the name, but at least the attribute field is not loaded - maybe others too?

Weird... It works for me. Maybe fixing the previous error has fixed this one. Please check.

Managed entries, the "Entry scope" input filed is not aligned

Fixed.

LInked attrs. When creating config, could the form use typeAhead or a dropdown list for some of those fields?

Good catch!

Posix Winsync. There is an option to perform a memberOf Task - that probably should not be there

Do you mean a checkbox?
It is posixWinsyncCreateMemberOfTask... It is listed in our guide.
https://access.redhat.com/documentation/en-us/red_hat_directory_server/10/html-single/configuration_command_and_file_reference/#posixWinsyncCreateMemberOfTask

RI plugin, the title actually prints to the page for the update interval. Also changing the member attributes results in a CLI usage error:
Probably shouldn't be an underscore?

Yeah, I fixed in my next PR but I will move the fix here. Makes more sense.

MemberOf plugin: the "Auto Add OC" field should be a typeAhead of all the objectlcasses

Fixed. :)

That should keep you busy for a little while :-)

Trying to update the config of Root DN access control plugin fails saying no update occurred.

Works for me... Probably, you haven't added the new element to the TypeAhead type (after typing, you should click on the item and it will add it. It is how the component was designed...)

I was updating an allowed (or denied) IP address, and it was ignoring the change on save

Trying to update the config of Root DN access control plugin fails saying no update occurred.
Works for me... Probably, you haven't added the new element to the TypeAhead type (after typing, you should click on the item and it will add it. It is how the component was designed...)

I was updating an allowed (or denied) IP address, and it was ignoring the change on save

Okay this is working fine.

Creating shared config entry for account policy plugin should use typeAhead for attribute selection for all fields, The rest of the plugins look good.

Note - RI plugin, and some other plugins can have shared config entries as well. Maybe those could be added later if you want.

rebased onto 2693bd07f3a3ed6ceb2fd05e73fb0327d2c9f46c

5 years ago

Okay this is working fine.
Creating shared config entry for account policy plugin should use typeAhead for attribute selection for all fields, The rest of the plugins look good.

Fixed. Please, have one final look and I'll merge.

Note - RI plugin, and some other plugins can have shared config entries as well. Maybe those could be added later if you want.

I was using the admin guide mostly for the UI design... And it has its issues.
But now, after a closer look at the source code, I see what you mean.

We have a really weird situation in the docs and in the existing plugin structure.

  • Some plugins have explicit chapters in Admin guide about nsslapd-pluginConfigArea attribute (memberOf for example)
  • Some plugins don't have the chapters like this at all (like RI plugin or maybe some others)
  • Some plugins have another config area definitions (dnaSharedCfgDN for example and maybe some others)

So it is a bit confusing.

I've grepped the sources for set_config_area and it gave me the idea where I can add nsslapd-pluginConfigArea.
I will do it in my second and final Plugin UI PR.

rebased onto 9e4e56cdec2d30eb5dbb5b1fd284dab8e931c225

5 years ago

rebased onto 68b6319

5 years ago

Pull-Request has been merged by spichugi

5 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/3374

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