#50191 Issue 50041 - CLI and WebUI - Add memberOf plugin functionality
Closed 3 years ago by spichugi. Opened 5 years ago by spichugi.
spichugi/389-ds-base memberof-1.4.0  into  389-ds-base-1.4.0

@@ -44,7 +44,8 @@ 

          "eqeqeq": "off",

          "import/no-webpack-loader-syntax": "off",

          "object-property-newline": "off",

-         "react/jsx-no-bind": "off"

+         "react/jsx-no-bind": "off",

+         "max-len": ["error", { "code": 100 }]

      },

      "globals": {

          "require": false,

@@ -14,13 +14,13 @@ 

      "@babel/core": "^7.0.0",

      "@babel/preset-env": "^7.0.0",

      "@babel/preset-react": "^7.0.0",

+     "ajv": "^6.0.0",

      "babel-eslint": "^9.0.0",

      "babel-loader": "^8.0.0",

      "chrome-remote-interface": "^0.25.5",

      "compression-webpack-plugin": "^1.1.11",

      "copy-webpack-plugin": "^4.5.2",

      "css-loader": "^0.28.11",

-     "style-loader": "^0.23.1",

      "eslint": "^5.4.0",

      "eslint-config-standard": "^11.0.0",

      "eslint-config-standard-react": "^6.0.0",
@@ -34,17 +34,20 @@ 

      "extract-text-webpack-plugin": "^4.0.0-beta.0",

      "htmlparser": "^1.7.7",

      "jed": "^1.1.1",

-     "ajv": "^6.0.0",

      "sass-loader": "^7.0.3",

      "sizzle": "^2.3.3",

      "stdio": "^0.2.7",

+     "style-loader": "^0.23.1",

      "webpack": "^4.17.1",

      "webpack-cli": "^3.1.0"

    },

    "dependencies": {

      "patternfly": "3.58.0",

      "patternfly-react": "2.24.5",

+     "bootstrap": "4.2.1",

+     "node-sass": "4.11.0",

      "react-bootstrap": "0.32.4",

+     "react-bootstrap-typeahead": "3.2.4",

      "react": "16.6.1",

      "react-dom": "16.6.1",

      "prop-types": "15.6.2",

@@ -1310,7 +1310,7 @@ 

  .ds-td {

      padding-left: 10px !important;

      padding-top: 8px !important;

-     padding-bottom: 0px ~important;

+     padding-bottom: 0px !important;

      valign: middle !important;

  }

  
@@ -1454,6 +1454,11 @@ 

      bottom: 10px;

  }

  

+ .ds-plugin-button {

+     margin-right: 5px;

+     margin-left: 5px;

+ }

+ 

  .ds-plugin-spinner {

      margin-top: 20px;

      margin-bottom: 10px;
@@ -1494,3 +1499,12 @@ 

  .control-label {

      text-align: left !important;

  }

+ 

+ .rbt-token {

+     background-color: #ededed;

+     color: #363636;

+ }

+ 

+ .rbt-input-multi {

+     height: auto !important;

+ }

@@ -30,6 +30,7 @@ 

    <link href="static/jquery.timepicker.min.css" type="text/css" rel="stylesheet">

    <link href="static/style.min.css" rel="stylesheet">

    <link href="static/patternfly-additions.css" rel="stylesheet" type="text/css">

+   <link href="static/Typeahead.css" rel="stylesheet" type="text/css">

    <link rel="stylesheet" href="../base1/patternfly.css">

    <link href="css/ds.css" type="text/css" rel="stylesheet">

  </head>

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

                  >

                      <Icon

                          type="fa"

-                         size="1,5x"

                          name={open ? "caret-down" : "caret-right"}

                      />{" "}

                      {open ? textOpened : textClosed}

@@ -1,24 +1,975 @@ 

+ 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";

  

  class MemberOf extends React.Component {

+     componentWillMount(prevProps) {

+         this.updateFields();

+     }

+ 

+     componentDidUpdate(prevProps) {

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

+             this.updateFields();

+         }

+     }

+ 

+     constructor(props) {

+         super(props);

+ 

+         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.runFixup = this.runFixup.bind(this);

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

+ 

+         this.state = {

+             memberOfAttr: [],

+             memberOfGroupAttr: [],

+             memberOfEntryScope: "",

+             memberOfEntryScopeExcludeSubtree: "",

+             memberOfAutoAddOC: "",

+             memberOfAllBackends: false,

+             memberOfSkipNested: false,

+             memberOfConfigEntry: "",

+             configEntryModalShow: false,

+             fixupModalShow: false,

+ 

+             configDN: "",

+             configAttr: [],

+             configGroupAttr: [],

+             configEntryScope: "",

+             configEntryScopeExcludeSubtree: "",

+             configAutoAddOC: "",

+             configAllBackends: false,

+             configSkipNested: false,

+             newEntry: true,

+ 

+             fixupDN: "",

+             fixupFilter: ""

+         };

+     }

+ 

+     toggleFixupModal() {

+         this.setState(prevState => ({

+             fixupModalShow: !prevState.fixupModalShow,

+             fixupDN: "",

+             fixupFilter: ""

+         }));

+     }

+ 

+     runFixup() {

+         if (!this.state.fixupDN) {

+             this.props.addNotification("warning", "Fixup DN is required.");

+         } else {

+             let cmd = [

+                 "dsconf",

+                 "-j",

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

+                 "plugin",

+                 "memberof",

+                 "fixup",

+                 this.state.fixupDN

+             ];

+ 

+             if (this.state.fixupFilter) {

+                 cmd = [...cmd, "--filter", this.state.fixupFilter];

+             }

+ 

+             this.props.toggleLoadingHandler();

+             log_cmd("runFixup", "Run fixup MemberOf Plugin ", cmd);

+             cockpit

+                     .spawn(cmd, {

+                         superuser: true,

+                         err: "message"

+                     })

+                     .done(content => {

+                         this.props.addNotification(

+                             "success",

+                             `Fixup task for ${this.state.fixupDN} was successfull`

+                         );

+                         this.props.toggleLoadingHandler();

+                         this.setState({

+                             fixupModalShow: false

+                         });

+                     })

+                     .fail(err => {

+                         this.props.addNotification(

+                             "error",

+                             `Fixup task for ${this.state.fixupDN} has failed ${err}`

+                         );

+                         this.props.toggleLoadingHandler();

+                         this.setState({

+                             fixupModalShow: false

+                         });

+                     });

+         }

+     }

+ 

+     openModal() {

+         if (!this.state.memberOfConfigEntry) {

+             this.setState({

+                 configEntryModalShow: true,

+                 newEntry: true,

+                 configDN: "",

+                 configAttr: [],

+                 configGroupAttr: [],

+                 configEntryScope: "",

+                 configEntryScopeExcludeSubtree: "",

+                 configAutoAddOC: "",

+                 configAllBackends: false,

+                 configSkipNested: false

+             });

+         } else {

+             let configAttrObjectList = [];

+             let configGroupAttrObjectList = [];

+             let cmd = [

+                 "dsconf",

+                 "-j",

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

+                 "plugin",

+                 "memberof",

+                 "config-entry",

+                 "show",

+                 this.state.memberOfConfigEntry

+             ];

+ 

+             this.props.toggleLoadingHandler();

+             log_cmd("openMemberOfModal", "Fetch the MemberOf 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.memberOfConfigEntry,

+                             configAutoAddOC:

+                             configEntry["memberofautoaddoc"] === undefined

+                                 ? ""

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

+                             configAllBackends: !(

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

+                             configEntry["memberofallbackends"][0] == "off"

+                             ),

+                             configSkipNested: !(

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

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

+                             ),

+                             configConfigEntry:

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

+                                 ? ""

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

+                             configEntryScope:

+                             configEntry["memberofentryscope"] === undefined

+                                 ? ""

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

+                             configEntryScopeExcludeSubtree:

+                             configEntry["memberofentryscopeexcludesubtree"] === undefined

+                                 ? ""

+                                 : configEntry["memberofentryscopeexcludesubtree"][0]

+                         });

+                         if (configEntry["memberofattr"] === undefined) {

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

+                         } else {

+                             for (let value of configEntry["memberofattr"]) {

+                                 configAttrObjectList = [

+                                     ...configAttrObjectList,

+                                     { id: value, label: value }

+                                 ];

+                             }

+                             this.setState({ configAttr: configAttrObjectList });

+                         }

+                         if (configEntry["memberofgroupattr"] === undefined) {

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

+                         } else {

+                             for (let value of configEntry["memberofgroupattr"]) {

+                                 configGroupAttrObjectList = [

+                                     ...configGroupAttrObjectList,

+                                     { id: value, label: value }

+                                 ];

+                             }

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

+                             this.props.toggleLoadingHandler();

+                         }

+                     })

+                     .fail(_ => {

+                         this.setState({

+                             configEntryModalShow: true,

+                             newEntry: true,

+                             configDN: this.state.memberOfConfigEntry,

+                             configAttr: [],

+                             configGroupAttr: [],

+                             configEntryScope: "",

+                             configEntryScopeExcludeSubtree: "",

+                             configAutoAddOC: "",

+                             configAllBackends: false,

+                             configSkipNested: false

+                         });

+                         this.props.toggleLoadingHandler();

+                     });

+         }

+     }

+ 

+     closeModal() {

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

+     }

+ 

+     cmdOperation(action) {

+         const {

+             configDN,

+             configAttr,

+             configGroupAttr,

+             configEntryScope,

+             configEntryScopeExcludeSubtree,

+             configAutoAddOC,

+             configAllBackends,

+             configSkipNested

+         } = this.state;

+ 

+         if (configAttr.length == 0 || configGroupAttr.length == 0) {

+             this.props.addNotification(

+                 "warning",

+                 "Config Attribute and Group Attribute are required."

+             );

+         } else {

+             let cmd = [

+                 "dsconf",

+                 "-j",

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

+                 "plugin",

+                 "memberof",

+                 "config-entry",

+                 action,

+                 configDN,

+                 "--scope",

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

+             ];

+ 

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

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

+             if (configAttr.length != 0) {

+                 for (let value of configAttr) {

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

+                 }

+             }

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

+             if (configGroupAttr.length != 0) {

+                 for (let value of configGroupAttr) {

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

+                 }

+             }

+ 

+             this.props.toggleLoadingHandler();

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

+             cockpit

+                     .spawn(cmd, {

+                         superuser: true,

+                         err: "message"

+                     })

+                     .done(content => {

+                         console.info("memberOfOperation", "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",

+             "memberof",

+             "config-entry",

+             "delete",

+             this.state.configDN

+         ];

+ 

+         this.props.toggleLoadingHandler();

+         log_cmd("deleteConfig", "Delete the MemberOf 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("edit");

+     }

+ 

+     handleCheckboxChange(e) {

+         this.setState({

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

+         });

+     }

+ 

+     handleFieldChange(e) {

+         this.setState({

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

+         });

+     }

+ 

+     updateFields() {

+         let memberOfAttrObjectList = [];

+         let memberOfGroupAttrObjectList = [];

+ 

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

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

+ 

+             this.setState({

+                 memberOfAutoAddOC:

+                     pluginRow["memberofautoaddoc"] === undefined

+                         ? ""

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

+                 memberOfAllBackends: !(

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

+                     pluginRow["memberofallbackends"][0] == "off"

+                 ),

+                 memberOfSkipNested: !(

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

+                     pluginRow["memberofskipnested"][0] == "off"

+                 ),

+                 memberOfConfigEntry:

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

+                         ? ""

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

+                 memberOfEntryScope:

+                     pluginRow["memberofentryscope"] === undefined

+                         ? ""

+                         : pluginRow["memberofentryscope"][0],

+                 memberOfEntryScopeExcludeSubtree:

+                     pluginRow["memberofentryscopeexcludesubtree"] === undefined

+                         ? ""

+                         : pluginRow["memberofentryscopeexcludesubtree"][0]

+             });

+             if (pluginRow["memberofattr"] === undefined) {

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

+             } else {

+                 for (let value of pluginRow["memberofattr"]) {

+                     memberOfAttrObjectList = [

+                         ...memberOfAttrObjectList,

+                         { id: value, label: value }

+                     ];

+                 }

+                 this.setState({ memberOfAttr: memberOfAttrObjectList });

+             }

+             if (pluginRow["memberofgroupattr"] === undefined) {

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

+             } else {

+                 for (let value of pluginRow["memberofgroupattr"]) {

+                     memberOfGroupAttrObjectList = [

+                         ...memberOfGroupAttrObjectList,

+                         { id: value, label: value }

+                     ];

+                 }

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

+             }

+         }

+     }

+ 

      render() {

+         const {

+             memberOfAttr,

+             memberOfGroupAttr,

+             memberOfEntryScope,

+             memberOfEntryScopeExcludeSubtree,

+             memberOfAutoAddOC,

+             memberOfAllBackends,

+             memberOfSkipNested,

+             memberOfConfigEntry,

+             configDN,

+             configEntryModalShow,

+             configAttr,

+             configGroupAttr,

+             configEntryScope,

+             configEntryScopeExcludeSubtree,

+             configAutoAddOC,

+             configAllBackends,

+             configSkipNested,

+             newEntry,

+             fixupModalShow,

+             fixupDN,

+             fixupFilter

+         } = this.state;

+ 

+         let specificPluginCMD = [

+             "dsconf",

+             "-j",

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

+             "plugin",

+             "memberof",

+             "edit",

+             "--scope",

+             memberOfEntryScope || "delete",

+             "--exclude",

+             memberOfEntryScopeExcludeSubtree || "delete",

+             "--autoaddoc",

+             memberOfAutoAddOC || "delete",

+             "--config-entry",

+             memberOfConfigEntry || "delete",

+             "--allbackends",

+             memberOfAllBackends ? "on" : "off",

+             "--skipnested",

+             memberOfSkipNested ? "on" : "off"

+         ];

+ 

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

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

+         if (memberOfAttr.length != 0) {

+             for (let value of memberOfAttr) {

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

+             }

+         } else {

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

+         }

+ 

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

+         if (memberOfGroupAttr.length != 0) {

+             for (let value of memberOfGroupAttr) {

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

+             }

+         } else {

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

+         }

+ 

          return (

              <div>

+                 <Modal show={fixupModalShow} onHide={this.toggleFixupModal}>

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

+                         <Modal.Header>

+                             <button

+                                 className="close"

+                                 onClick={this.toggleFixupModal}

+                                 aria-hidden="true"

+                                 aria-label="Close"

+                             >

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

+                             </button>

+                             <Modal.Title>Fixup MemberOf Task</Modal.Title>

+                         </Modal.Header>

+                         <Modal.Body>

+                             <Row>

+                                 <Col sm={12}>

+                                     <Form horizontal>

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

+                                             <Col sm={3}>

+                                                 <ControlLabel>Base DN</ControlLabel>

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <FormControl

+                                                     type="text"

+                                                     value={fixupDN}

+                                                     onChange={this.handleFieldChange}

+                                                 />

+                                             </Col>

+                                         </FormGroup>

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

+                                             <Col sm={3}>

+                                                 <ControlLabel>Filter DN</ControlLabel>

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <FormControl

+                                                     type="text"

+                                                     value={fixupFilter}

+                                                     onChange={this.handleFieldChange}

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                     </Form>

+                                 </Col>

+                             </Row>

+                         </Modal.Body>

+                         <Modal.Footer>

+                             <Button

+                                 bsStyle="default"

+                                 className="btn-cancel"

+                                 onClick={this.toggleFixupModal}

+                             >

+                                 Cancel

+                             </Button>

+                             <Button bsStyle="primary" onClick={this.runFixup}>

+                                 Run

+                             </Button>

+                         </Modal.Footer>

+                     </div>

+                 </Modal>

+                 <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 MemberOf Plugin Shared Config Entry</Modal.Title>

+                         </Modal.Header>

+                         <Modal.Body>

+                             <Row>

+                                 <Col sm={12}>

+                                     <Form horizontal>

+                                         <FormGroup controlId="configDN">

+                                             <Col sm={3}>

+                                                 <ControlLabel>Config DN</ControlLabel>

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <FormControl

+                                                     type="text"

+                                                     value={configDN}

+                                                     onChange={this.handleFieldChange}

+                                                     disabled={!newEntry}

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                         <FormGroup

+                                             key="configAttr"

+                                             controlId="configAttr"

+                                             disabled={false}

+                                         >

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

+                                                 Attribute

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <Typeahead

+                                                     allowNew

+                                                     multiple

+                                                     onChange={values => {

+                                                         this.setState({

+                                                             configAttr: values

+                                                         });

+                                                     }}

+                                                     selected={configAttr}

+                                                     newSelectionPrefix="Add a member: "

+                                                     options={[

+                                                         {

+                                                             id: "memberOf",

+                                                             label: "memberOf"

+                                                         }

+                                                     ]}

+                                                     placeholder="Type a member attribute..."

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                         <FormGroup

+                                             key="configGroupAttr"

+                                             controlId="configGroupAttr"

+                                             disabled={false}

+                                         >

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

+                                                 Group Attribute

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <Typeahead

+                                                     allowNew

+                                                     multiple

+                                                     onChange={values => {

+                                                         this.setState({

+                                                             configGroupAttr: values

+                                                         });

+                                                     }}

+                                                     selected={configGroupAttr}

+                                                     newSelectionPrefix="Add a group member: "

+                                                     options={[

+                                                         {

+                                                             id: "member",

+                                                             label: "member"

+                                                         }

+                                                     ]}

+                                                     placeholder="Type a member group attribute..."

+                                                 />

+                                             </Col>

+                                         </FormGroup>

+                                     </Form>

+                                 </Col>

+                             </Row>

+                             <Row>

+                                 <Col sm={12}>

+                                     <Form horizontal>

+                                         <FormGroup

+                                             key="configEntryScope"

+                                             controlId="configEntryScope"

+                                             disabled={false}

+                                         >

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

+                                                 Entry Scope

+                                             </Col>

+                                             <Col sm={6}>

+                                                 <FormControl

+                                                     type="text"

+                                                     value={configEntryScope}

+                                                     onChange={this.handleFieldChange}

+                                                 />

+                                             </Col>

+                                             <Col sm={3}>

+                                                 <Checkbox

+                                                     id="configAllBackends"

+                                                     checked={configAllBackends}

+                                                     onChange={this.handleCheckboxChange}

+                                                 >

+                                                     All Backends

+                                                 </Checkbox>

+                                             </Col>

+                                         </FormGroup>

+                                         <FormGroup

+                                             key="configEntryScopeExcludeSubtree"

+                                             controlId="configEntryScopeExcludeSubtree"

+                                             disabled={false}

+                                         >

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

+                                                 Entry Scope Exclude Subtree

+                                             </Col>

+                                             <Col sm={6}>

+                                                 <FormControl

+                                                     type="text"

+                                                     value={configEntryScopeExcludeSubtree}

+                                                     onChange={this.handleFieldChange}

+                                                 />

+                                             </Col>

+                                             <Col sm={3}>

+                                                 <Checkbox

+                                                     id="configSkipNested"

+                                                     checked={configSkipNested}

+                                                     onChange={this.handleCheckboxChange}

+                                                 >

+                                                     Skip Nested

+                                                 </Checkbox>

+                                             </Col>

+                                         </FormGroup>

+                                     </Form>

+                                 </Col>

+                             </Row>

+                             <Row>

+                                 <Col sm={12}>

+                                     <Form horizontal>

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

+                                             <Col sm={3}>

+                                                 <ControlLabel>Auto Add OC</ControlLabel>

+                                             </Col>

+                                             <Col sm={9}>

+                                                 <FormControl

+                                                     type="text"

+                                                     value={configAutoAddOC}

+                                                     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={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="MemberOf Plugin"

                      pluginName="MemberOf"

                      cmdName="memberof"

+                     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="memberOfAttr"

+                                     controlId="memberOfAttr"

+                                     disabled={false}

+                                 >

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

+                                         Attribute

+                                     </Col>

+                                     <Col sm={9}>

+                                         <Typeahead

+                                             allowNew

+                                             multiple

+                                             onChange={values => {

+                                                 this.setState({

+                                                     memberOfAttr: values

+                                                 });

+                                             }}

+                                             selected={memberOfAttr}

+                                             newSelectionPrefix="Add a member: "

+                                             options={[

+                                                 {

+                                                     id: "member",

+                                                     label: "member"

+                                                 },

+                                                 {

+                                                     id: "memberCertificate",

+                                                     label: "memberCertificate"

+                                                 },

+                                                 {

+                                                     id: "uniqueMember",

+                                                     label: "uniqueMember"

+                                                 }

+                                             ]}

+                                             placeholder="Type a member attribute..."

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="memberOfGroupAttr"

+                                     controlId="memberOfGroupAttr"

+                                     disabled={false}

+                                 >

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

+                                         Group Attribute

+                                     </Col>

+                                     <Col sm={9}>

+                                         <Typeahead

+                                             allowNew

+                                             multiple

+                                             onChange={values => {

+                                                 this.setState({

+                                                     memberOfGroupAttr: values

+                                                 });

+                                             }}

+                                             selected={memberOfGroupAttr}

+                                             newSelectionPrefix="Add a group member: "

+                                             options={[

+                                                 {

+                                                     id: "groupOfNames",

+                                                     label: "groupOfNames"

+                                                 },

+                                                 {

+                                                     id: "groupOfURLs",

+                                                     label: "groupOfURLs"

+                                                 },

+                                                 {

+                                                     id: "groupOfUniqueNames",

+                                                     label: "groupOfUniqueNames"

+                                                 },

+                                                 {

+                                                     id: "groupOfCertificates",

+                                                     label: "groupOfCertificates"

+                                                 }

+                                             ]}

+                                             placeholder="Type a member group attribute..."

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                             </Form>

+                         </Col>

+                     </Row>

+                     <Row>

+                         <Col sm={9}>

+                             <Form horizontal>

+                                 <FormGroup

+                                     key="memberOfEntryScope"

+                                     controlId="memberOfEntryScope"

+                                     disabled={false}

+                                 >

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

+                                         Entry Scope

+                                     </Col>

+                                     <Col sm={6}>

+                                         <FormControl

+                                             type="text"

+                                             value={memberOfEntryScope}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                     <Col sm={3}>

+                                         <Checkbox

+                                             id="memberOfAllBackends"

+                                             checked={memberOfAllBackends}

+                                             onChange={this.handleCheckboxChange}

+                                         >

+                                             All Backends

+                                         </Checkbox>

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="memberOfEntryScopeExcludeSubtree"

+                                     controlId="memberOfEntryScopeExcludeSubtree"

+                                     disabled={false}

+                                 >

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

+                                         Entry Scope Exclude Subtree

+                                     </Col>

+                                     <Col sm={6}>

+                                         <FormControl

+                                             type="text"

+                                             value={memberOfEntryScopeExcludeSubtree}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                     <Col sm={3}>

+                                         <Checkbox

+                                             id="memberOfSkipNested"

+                                             checked={memberOfSkipNested}

+                                             onChange={this.handleCheckboxChange}

+                                         >

+                                             Skip Nested

+                                         </Checkbox>

+                                     </Col>

+                                 </FormGroup>

+                             </Form>

+                         </Col>

+                     </Row>

+                     <Row>

+                         <Col sm={9}>

+                             <Form horizontal>

+                                 <FormGroup

+                                     key="memberOfConfigEntry"

+                                     controlId="memberOfConfigEntry"

+                                 >

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

+                                         Shared Config Entry

+                                     </Col>

+                                     <Col sm={6}>

+                                         <FormControl

+                                             type="text"

+                                             value={memberOfConfigEntry}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                     <Col sm={3}>

+                                         <Button

+                                             bsSize="large"

+                                             bsStyle="primary"

+                                             onClick={this.openModal}

+                                         >

+                                             Manage

+                                         </Button>

+                                     </Col>

+                                 </FormGroup>

+                             </Form>

+                         </Col>

+                     </Row>

+                     <Row>

+                         <Col sm={9}>

+                             <Form horizontal>

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

+                                     <Col sm={3}>

+                                         <ControlLabel>Auto Add OC</ControlLabel>

+                                     </Col>

+                                     <Col sm={9}>

+                                         <FormControl

+                                             type="text"

+                                             value={memberOfAutoAddOC}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                             </Form>

+                         </Col>

+                     </Row>

+                     <Row>

+                         <Col sm={9}>

+                             <Button

+                                 bsSize="large"

+                                 bsStyle="primary"

+                                 onClick={this.toggleFixupModal}

+                             >

+                                 Run Fixup Task

+                             </Button>

+                         </Col>

+                     </Row>

+                 </PluginBasicConfig>

              </div>

          );

      }

@@ -43,7 +43,9 @@ 

              currentPluginId: "",

              currentPluginVendor: "",

              currentPluginVersion: "",

-             currentPluginDescription: ""

+             currentPluginDescription: "",

+             currentPluginDependsOnType: "",

+             currentPluginDependsOnNamed: ""

          };

      }

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

              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,11 +76,7 @@ 

  

          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 => {
@@ -88,7 +84,8 @@ 

                      pluginListHandler();

                      addNotification(

                          "success",

-                         `${pluginName} plugin was successfully ${new_status}d`

+                         `${pluginName} plugin was successfully ${new_status}d.

+                         Please, restart the instance.`

                      );

                      toggleLoadingHandler();

                  })
@@ -104,9 +101,7 @@ 

  

      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],
@@ -115,8 +110,15 @@ 

                  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

+                         ? ""

+                         : pluginRow["nsslapd-plugin-depends-on-type"][0],

+                 currentPluginDependsOnNamed:

+                     pluginRow["nsslapd-plugin-depends-on-named"] === undefined

+                         ? ""

+                         : pluginRow["nsslapd-plugin-depends-on-named"][0]

              });

          }

          this.updateSwitch();
@@ -124,9 +126,7 @@ 

  

      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") {
@@ -158,19 +158,21 @@ 

              currentPluginVendor,

              currentPluginVersion,

              currentPluginDescription,

+             currentPluginDependsOnType,

+             currentPluginDependsOnNamed,

              disableSwitch

          } = this.state;

  

          const modalFieldsCol1 = {

              currentPluginType: this.state.currentPluginType,

              currentPluginPath: this.state.currentPluginPath,

-             currentPluginInitfunc: this.state.currentPluginInitfunc,

-             currentPluginId: this.state.currentPluginId

+             currentPluginInitfunc: this.state.currentPluginInitfunc

          };

          const modalFieldsCol2 = {

              currentPluginVendor: this.state.currentPluginVendor,

              currentPluginVersion: this.state.currentPluginVersion,

-             currentPluginDescription: this.state.currentPluginDescription

+             currentPluginDescription: this.state.currentPluginDescription,

+             currentPluginId: this.state.currentPluginId

          };

          return (

              <div>
@@ -184,11 +186,10 @@ 

                              </h3>

                          </Col>

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

-                             <FormGroup

-                                 key="switchPluginStatus"

-                                 controlId="switchPluginStatus"

-                             >

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

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

+                                 <ControlLabel

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

+                                 >

                                      Status

                                  </ControlLabel>

                                  <Switch
@@ -196,11 +197,7 @@ 

                                      title="normal"

                                      id="bsSize-example"

                                      value={currentPluginEnabled}

-                                     onChange={() =>

-                                         this.handleSwitchChange(

-                                             currentPluginEnabled

-                                         )

-                                     }

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

                                      animate={false}

                                      disabled={disableSwitch}

                                  />
@@ -213,94 +210,100 @@ 

                      <Row>

                          <Col sm={4}>

                              <Form horizontal>

-                                 {Object.entries(modalFieldsCol1).map(

-                                     ([id, value]) => (

-                                         <FormGroup

-                                             key={id}

-                                             controlId={id}

-                                             disabled={false}

-                                         >

-                                             <Col

-                                                 componentClass={ControlLabel}

-                                                 sm={4}

-                                             >

-                                                 Plugin{" "}

-                                                 {id.replace(

-                                                     "currentPlugin",

-                                                     ""

-                                                 )}

-                                             </Col>

-                                             <Col sm={8}>

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

+                                     disabled={false}

+                                 >

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

+                                         Plugin Depends On Type

+                                     </Col>

+                                     <Col sm={6}>

+                                         <FormControl

+                                             type="text"

+                                             value={this.state.currentPluginDependsOnType}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

+                                 <FormGroup

+                                     key="currentPluginDependsOnNamed"

+                                     controlId="currentPluginDependsOnNamed"

+                                     disabled={false}

+                                 >

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

+                                         Plugin Depends On Named

+                                     </Col>

+                                     <Col sm={6}>

+                                         <FormControl

+                                             type="text"

+                                             value={this.state.currentPluginDependsOnNamed}

+                                             onChange={this.handleFieldChange}

+                                         />

+                                     </Col>

+                                 </FormGroup>

                              </Form>

                          </Col>

                          <Col sm={4}>

                              <Form horizontal>

-                                 {Object.entries(modalFieldsCol2).map(

-                                     ([id, value]) => (

-                                         <FormGroup

-                                             key={id}

-                                             controlId={id}

-                                             disabled={false}

-                                         >

-                                             <Col

-                                                 componentClass={ControlLabel}

-                                                 sm={4}

-                                             >

-                                                 Plugin{" "}

-                                                 {id.replace(

-                                                     "currentPlugin",

-                                                     ""

-                                                 )}

-                                             </Col>

-                                             <Col sm={8}>

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

-                     <Row>

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

-                             <Button

-                                 bsSize="large"

-                                 bsStyle="primary"

-                                 onClick={() =>

-                                     this.props.savePluginHandler({

-                                         name: this.props.cn,

-                                         type: currentPluginType,

-                                         path: currentPluginPath,

-                                         initfunc: currentPluginInitfunc,

-                                         id: currentPluginId,

-                                         vendor: currentPluginVendor,

-                                         version: currentPluginVersion,

-                                         description: currentPluginDescription

-                                     })

-                                 }

-                             >

-                                 Save Config

-                             </Button>

-                         </Col>

-                     </Row>

                  </CustomCollapse>

+                 <Row>

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

+                         <Button

+                             bsSize="large"

+                             bsStyle="primary"

+                             onClick={() =>

+                                 this.props.savePluginHandler({

+                                     name: this.props.cn,

+                                     type: currentPluginType,

+                                     path: currentPluginPath,

+                                     initfunc: currentPluginInitfunc,

+                                     id: currentPluginId,

+                                     vendor: currentPluginVendor,

+                                     version: currentPluginVersion,

+                                     description: currentPluginDescription,

+                                     dependsOnType: currentPluginDependsOnType,

+                                     dependsOnNamed: currentPluginDependsOnNamed,

+                                     specificPluginCMD: this.props.specificPluginCMD

+                                 })

+                             }

+                         >

+                             Save Config

+                         </Button>

+                     </Col>

+                 </Row>

              </div>

          );

      }
@@ -313,6 +316,7 @@ 

      cn: PropTypes.string,

      pluginName: PropTypes.string,

      cmdName: PropTypes.string,

+     specificPluginCMD: PropTypes.array,

      savePluginHandler: PropTypes.func,

      pluginListHandler: PropTypes.func,

      addNotification: PropTypes.func,
@@ -325,6 +329,7 @@ 

      cn: "",

      pluginName: "",

      cmdName: "",

+     specificPluginCMD: [],

      savePluginHandler: noop,

      pluginListHandler: noop,

      addNotification: noop,

@@ -16,18 +16,14 @@ 

  

  class PluginEditModal extends React.Component {

      render() {

-         const modalFields = {

-             currentPluginType: this.props.currentPluginType,

-             currentPluginPath: this.props.currentPluginPath,

-             currentPluginInitfunc: this.props.currentPluginInitfunc,

-             currentPluginId: this.props.currentPluginId,

-             currentPluginVendor: this.props.currentPluginVendor,

-             currentPluginVersion: this.props.currentPluginVersion,

-             currentPluginDescription: this.props.currentPluginDescription

-         };

          const {

              showModal,

              closeHandler,

+             handleChange,

+             handleSwitchChange,

+             savePluginHandler

+         } = this.props;

+         const {

              currentPluginName,

              currentPluginEnabled,

              currentPluginType,
@@ -37,10 +33,19 @@ 

              currentPluginVendor,

              currentPluginVersion,

              currentPluginDescription,

-             handleChange,

-             handleSwitchChange,

-             savePluginHandler

-         } = this.props;

+             currentPluginDependsOnType,

+             currentPluginDependsOnNamed

+         } = this.props.pluginData;

+         const modalFields = {

+             currentPluginType: currentPluginType,

+             currentPluginPath: currentPluginPath,

+             currentPluginInitfunc: currentPluginInitfunc,

+             currentPluginId: currentPluginId,

+             currentPluginVendor: currentPluginVendor,

+             currentPluginVersion: currentPluginVersion,

+             currentPluginDescription: currentPluginDescription

+         };

+ 

          return (

              <Modal show={showModal} onHide={closeHandler}>

                  <div className="ds-no-horizontal-scrollbar">
@@ -53,9 +58,7 @@ 

                          >

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

                          </button>

-                         <Modal.Title>

-                             Edit Plugin - {currentPluginName}

-                         </Modal.Title>

+                         <Modal.Title>Edit Plugin - {currentPluginName}</Modal.Title>

                      </Modal.Header>

                      <Modal.Body>

                          <Form horizontal>
@@ -64,34 +67,26 @@ 

                                  controlId="currentPluginEnabled"

                                  disabled={false}

                              >

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

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

                                      Plugin Status

                                  </Col>

-                                 <Col sm={7}>

+                                 <Col sm={6}>

                                      <Switch

                                          bsSize="normal"

                                          title="normal"

                                          id="pluginEnableSwitch"

                                          value={currentPluginEnabled}

-                                         onChange={() =>

-                                             handleSwitchChange(

-                                                 currentPluginEnabled

-                                             )

-                                         }

+                                         onChange={() => handleSwitchChange(currentPluginEnabled)}

                                          animate={false}

                                      />

                                  </Col>

                              </FormGroup>

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

-                                 <FormGroup

-                                     key={id}

-                                     controlId={id}

-                                     disabled={false}

-                                 >

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

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

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

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

                                      </Col>

-                                     <Col sm={7}>

+                                     <Col sm={6}>

                                          <FormControl

                                              type="text"

                                              value={value}
@@ -100,29 +95,61 @@ 

                                      </Col>

                                  </FormGroup>

                              ))}

+                             <FormGroup

+                                 key="currentPluginDependsOnType"

+                                 controlId="currentPluginDependsOnType"

+                                 disabled={false}

+                             >

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

+                                     Plugin Depends On Type

+                                 </Col>

+                                 <Col sm={6}>

+                                     <FormControl

+                                         type="text"

+                                         value={currentPluginDependsOnType}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                             </FormGroup>

+                             <FormGroup

+                                 key="currentPluginDependsOnNamed"

+                                 controlId="currentPluginDependsOnNamed"

+                                 disabled={false}

+                             >

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

+                                     Plugin Depends On Type

+                                 </Col>

+                                 <Col sm={6}>

+                                     <FormControl

+                                         type="text"

+                                         value={currentPluginDependsOnNamed}

+                                         onChange={handleChange}

+                                     />

+                                 </Col>

+                             </FormGroup>

                          </Form>

                      </Modal.Body>

-                     <Modal.Footer className="ds-modal-footer">

-                         <Button

-                             bsStyle="default"

-                             className="btn-cancel"

-                             onClick={closeHandler}

-                         >

+                     <Modal.Footer>

+                         <Button bsStyle="default" className="btn-cancel" onClick={closeHandler}>

                              Cancel

                          </Button>

                          <Button

                              bsStyle="primary"

-                             onClick={() => savePluginHandler({

-                                 name: currentPluginName,

-                                 enabled: currentPluginEnabled,

-                                 type: currentPluginType,

-                                 path: currentPluginPath,

-                                 initfunc: currentPluginInitfunc,

-                                 id: currentPluginId,

-                                 vendor: currentPluginVendor,

-                                 version: currentPluginVersion,

-                                 description: currentPluginDescription

-                             })}

+                             onClick={() =>

+                                 savePluginHandler({

+                                     name: currentPluginName,

+                                     enabled: currentPluginEnabled,

+                                     type: currentPluginType,

+                                     path: currentPluginPath,

+                                     initfunc: currentPluginInitfunc,

+                                     id: currentPluginId,

+                                     vendor: currentPluginVendor,

+                                     version: currentPluginVersion,

+                                     description: currentPluginDescription,

+                                     dependsOnType: currentPluginDependsOnType,

+                                     dependsOnNamed: currentPluginDependsOnNamed

+                                 })

+                             }

                          >

                              Save

                          </Button>
@@ -136,15 +163,19 @@ 

  PluginEditModal.propTypes = {

      handleChange: PropTypes.func,

      handleSwitchChange: PropTypes.func,

-     currentPluginName: PropTypes.string,

-     currentPluginType: PropTypes.string,

-     currentPluginEnabled: PropTypes.bool,

-     currentPluginPath: PropTypes.string,

-     currentPluginInitfunc: PropTypes.string,

-     currentPluginId: PropTypes.string,

-     currentPluginVendor: PropTypes.string,

-     currentPluginVersion: PropTypes.string,

-     currentPluginDescription: PropTypes.string,

+     pluginData: PropTypes.exact({

+         currentPluginName: PropTypes.string,

+         currentPluginType: PropTypes.string,

+         currentPluginEnabled: PropTypes.bool,

+         currentPluginPath: PropTypes.string,

+         currentPluginInitfunc: PropTypes.string,

+         currentPluginId: PropTypes.string,

+         currentPluginVendor: PropTypes.string,

+         currentPluginVersion: PropTypes.string,

+         currentPluginDescription: PropTypes.string,

+         currentPluginDependsOnType: PropTypes.string,

+         currentPluginDependsOnNamed: PropTypes.string

+     }),

      closeHandler: PropTypes.func,

      savePluginHandler: PropTypes.func,

      showModal: PropTypes.bool
@@ -153,15 +184,19 @@ 

  PluginEditModal.defaultProps = {

      handleChange: noop,

      handleSwitchChange: noop,

-     currentPluginName: "",

-     currentPluginType: "",

-     currentPluginEnabled: false,

-     currentPluginPath: "",

-     currentPluginInitfunc: "",

-     currentPluginId: "",

-     currentPluginVendor: "",

-     currentPluginVersion: "",

-     currentPluginDescription: "",

+     pluginData: {

+         currentPluginName: "",

+         currentPluginType: "",

+         currentPluginEnabled: false,

+         currentPluginPath: "",

+         currentPluginInitfunc: "",

+         currentPluginId: "",

+         currentPluginVendor: "",

+         currentPluginVersion: "",

+         currentPluginDescription: "",

+         currentPluginDependsOnType: "",

+         currentPluginDependsOnNamed: ""

+     },

      closeHandler: noop,

      savePluginHandler: noop,

      showModal: false

@@ -69,7 +69,9 @@ 

              currentPluginId: "",

              currentPluginVendor: "",

              currentPluginVersion: "",

-             currentPluginDescription: ""

+             currentPluginDescription: "",

+             currentPluginDependsOnType: "",

+             currentPluginDependsOnNamed: ""

          };

      }

  
@@ -147,6 +149,14 @@ 

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

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

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

+             currentPluginDependsOnType:

+                 rowData["nsslapd-plugin-depends-on-type"] === undefined

+                     ? ""

+                     : rowData["nsslapd-plugin-depends-on-type"][0],

+             currentPluginDependsOnNamed:

+                 rowData["nsslapd-plugin-depends-on-named"] === undefined

+                     ? ""

+                     : rowData["nsslapd-plugin-depends-on-named"][0],

              showPluginModal: true

          });

      }
@@ -179,7 +189,9 @@ 

      }

  

      savePlugin(data) {

-         cmd = [

+         let nothingToSetErr = false;

+         let basicPluginSuccess = false;

+         let cmd = [

              "dsconf",

              "-j",

              "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
@@ -187,19 +199,23 @@ 

              "edit",

              data.name,

              "--type",

-             data.type,

+             data.type || "delete",

              "--path",

-             data.path,

+             data.path || "delete",

              "--initfunc",

-             data.initfunc,

+             data.initfunc || "delete",

              "--id",

-             data.id,

+             data.id || "delete",

              "--vendor",

-             data.vendor,

+             data.vendor || "delete",

              "--version",

-             data.version,

+             data.version || "delete",

              "--description",

-             data.description

+             data.description || "delete",

+             "--depends-on-type",

+             data.dependsOnType || "delete",

+             "--depends-on-named",

+             data.dependsOnNamed || "delete"

          ];

  

          if ("enabled" in data) {
@@ -207,26 +223,80 @@ 

          }

  

          this.toggleLoading();

-         log_cmd("savePlugin", "Edit the plugin from the modal form", cmd);

+ 

+         log_cmd("savePlugin", "Edit the plugin", cmd);

          cockpit

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

                  .done(content => {

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

+                     basicPluginSuccess = true;

                      this.addNotification(

                          "success",

                          `Plugin ${data.name} was successfully modified`

                      );

-                     this.pluginList();

                      this.closePluginModal();

                      this.toggleLoading();

                  })

                  .fail(err => {

-                     this.addNotification(

-                         "error",

-                         `Error during plugin ${data.name} modification - ${err}`

-                     );

+                     if (err.message.indexOf("nothing to set") >= 0) {

+                         nothingToSetErr = true;

+                     } else {

+                         this.addNotification(

+                             "error",

+                             `${err.message} error during ${data.name} modification`

+                         );

+                     }

                      this.closePluginModal();

                      this.toggleLoading();

+                 })

+                 .always(() => {

+                     if ("specificPluginCMD" in data) {

+                         this.toggleLoading();

+                         log_cmd(

+                             "savePlugin",

+                             "Edit the plugin from the plugin config tab",

+                             data.specificPluginCMD

+                         );

+                         cockpit

+                                 .spawn(data.specificPluginCMD, {

+                                     superuser: true,

+                                     err: "message"

+                                 })

+                                 .done(content => {

+                                     // Notify success only one time

+                                     if (!basicPluginSuccess) {

+                                         this.addNotification(

+                                             "success",

+                                             `Plugin ${data.name} was successfully modified`

+                                         );

+                                     }

+                                     this.pluginList();

+                                     this.toggleLoading();

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

+                                 })

+                                 .fail(err => {

+                                     if (

+                                         (err.message.indexOf(

+                                             "nothing to set"

+                                         ) >= 0 &&

+                                     nothingToSetErr) ||

+                                 err.message.indexOf("nothing to set") < 0

+                                     ) {

+                                         if (basicPluginSuccess) {

+                                             this.addNotification(

+                                                 "success",

+                                                 `Plugin ${data.name} was successfully modified`

+                                             );

+                                             this.pluginList();

+                                         }

+                                         this.addNotification(

+                                             "error",

+                                             `${err.message} error during ${data.name} modification`

+                                         );

+                                     }

+                                     this.toggleLoading();

+                                 });

+                     }

                  });

      }

  
@@ -235,10 +305,7 @@ 

              allPlugins: {

                  name: "All Plugins",

                  component: (

-                     <PluginTable

-                         rows={this.state.rows}

-                         loadModalHandler={this.openPluginModal}

-                     />

+                     <PluginTable rows={this.state.rows} loadModalHandler={this.openPluginModal} />

                  )

              },

              accountPolicy: {
@@ -424,24 +491,20 @@ 

                      <Row className="clearfix">

                          <Col sm={2}>

                              <Nav bsStyle="pills" stacked>

-                                 {Object.entries(selectPlugins).map(

-                                     ([id, item]) => (

-                                         <NavItem key={id} eventKey={id}>

-                                             {item.name}

-                                         </NavItem>

-                                     )

-                                 )}

+                                 {Object.entries(selectPlugins).map(([id, item]) => (

+                                     <NavItem key={id} eventKey={id}>

+                                         {item.name}

+                                     </NavItem>

+                                 ))}

                              </Nav>

                          </Col>

                          <Col sm={10}>

                              <Tab.Content animation={false}>

-                                 {Object.entries(selectPlugins).map(

-                                     ([id, item]) => (

-                                         <Tab.Pane key={id} eventKey={id}>

-                                             {item.component}

-                                         </Tab.Pane>

-                                     )

-                                 )}

+                                 {Object.entries(selectPlugins).map(([id, item]) => (

+                                     <Tab.Pane key={id} eventKey={id}>

+                                         {item.component}

+                                     </Tab.Pane>

+                                 ))}

                              </Tab.Content>

                          </Col>

                      </Row>
@@ -449,17 +512,19 @@ 

                  <PluginEditModal

                      handleChange={this.handleFieldChange}

                      handleSwitchChange={this.handleSwitchChange}

-                     currentPluginName={this.state.currentPluginName}

-                     currentPluginType={this.state.currentPluginType}

-                     currentPluginEnabled={this.state.currentPluginEnabled}

-                     currentPluginPath={this.state.currentPluginPath}

-                     currentPluginInitfunc={this.state.currentPluginInitfunc}

-                     currentPluginId={this.state.currentPluginId}

-                     currentPluginVendor={this.state.currentPluginVendor}

-                     currentPluginVersion={this.state.currentPluginVersion}

-                     currentPluginDescription={

-                         this.state.currentPluginDescription

-                     }

+                     pluginData={{

+                         currentPluginName: this.state.currentPluginName,

+                         currentPluginType: this.state.currentPluginType,

+                         currentPluginEnabled: this.state.currentPluginEnabled,

+                         currentPluginPath: this.state.currentPluginPath,

+                         currentPluginInitfunc: this.state.currentPluginInitfunc,

+                         currentPluginId: this.state.currentPluginId,

+                         currentPluginVendor: this.state.currentPluginVendor,

+                         currentPluginVersion: this.state.currentPluginVersion,

+                         currentPluginDescription: this.state.currentPluginDescription,

+                         currentPluginDependsOnType: this.state.currentPluginDependsOnType,

+                         currentPluginDependsOnNamed: this.state.currentPluginDependsOnNamed

+                     }}

                      closeHandler={this.closePluginModal}

                      showModal={this.state.showPluginModal}

                      savePluginHandler={this.savePlugin}

@@ -0,0 +1,256 @@ 

+ .rbt {

+   outline: none;

+ }

+ 

+ .rbt-menu {

+   margin-bottom: 2px;

+ }

+ 

+ .rbt-menu > li a {

+   overflow: hidden;

+   text-overflow: ellipsis;

+ }

+ 

+ .rbt-menu > li a:focus {

+   outline: none;

+ }

+ 

+ .rbt-menu-pagination-option {

+   text-align: center;

+ }

+ 

+ .rbt .rbt-input-main::-ms-clear {

+   display: none;

+ }

+ 

+ .rbt-input-multi {

+   cursor: text;

+   overflow: hidden;

+   position: relative;

+   height: auto;

+ }

+ 

+ .rbt-input-multi.focus {

+   -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);

+   -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);

+   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);

+   border-color: #66afe9;

+   outline: 0;

+ }

+ 

+ .rbt-input-multi.form-control[disabled] {

+   background-color: #e9ecef;

+   opacity: 1;

+ }

+ 

+ .rbt-input-multi input::-moz-placeholder {

+   color: #999;

+   opacity: 1;

+ }

+ 

+ .rbt-input-multi input:-ms-input-placeholder {

+   color: #999;

+ }

+ 

+ .rbt-input-multi input::-webkit-input-placeholder {

+   color: #999;

+ }

+ 

+ .rbt-input-multi .rbt-input-wrapper {

+   margin-bottom: -4px;

+   margin-top: -1px;

+   overflow: hidden;

+ }

+ 

+ .rbt-input-multi .rbt-input-main {

+   height: 20px;

+   margin: 1px 0 4px;

+ }

+ 

+ .rbt-input-multi .rbt-input-hint-container {

+   display: inline-block;

+ }

+ 

+ .rbt-input-multi.input-lg .rbt-input-main, .rbt-input-multi.form-control-lg .rbt-input-main {

+   height: 24px;

+ }

+ 

+ .rbt-input-multi.input-sm .rbt-input-main, .rbt-input-multi.form-control-sm .rbt-input-main {

+   height: 18px;

+ }

+ 

+ .rbt-close {

+   z-index: 1;

+ }

+ 

+ .rbt-close-lg {

+   font-size: 24px;

+ }

+ 

+ .rbt-token {

+   background-color: #e7f4ff;

+   border: 0;

+   border-radius: 2px;

+   color: #1f8dd6;

+   display: inline-block;

+   line-height: 1em;

+   margin: 0 3px 3px 0;

+   padding: 4px 7px;

+   position: relative;

+ }

+ 

+ .rbt-token-disabled {

+   background-color: #ddd;

+   color: #888;

+   pointer-events: none;

+ }

+ 

+ .rbt-token-removeable {

+   cursor: pointer;

+   padding-right: 21px;

+ }

+ 

+ .rbt-token-active {

+   background-color: #1f8dd6;

+   color: #fff;

+   outline: none;

+   text-decoration: none;

+ }

+ 

+ .rbt-token .rbt-token-remove-button {

+   bottom: 0;

+   color: inherit;

+   font-size: inherit;

+   font-weight: normal;

+   opacity: 1;

+   outline: none;

+   padding: 3px 7px;

+   position: absolute;

+   right: 0;

+   text-shadow: none;

+   top: -2px;

+ }

+ 

+ .rbt-loader {

+   -webkit-animation: loader-animation 600ms infinite linear;

+   -o-animation: loader-animation 600ms infinite linear;

+   animation: loader-animation 600ms infinite linear;

+   border: 1px solid #d5d5d5;

+   border-radius: 50%;

+   border-top-color: #1f8dd6;

+   display: block;

+   height: 16px;

+   width: 16px;

+ }

+ 

+ .rbt-loader-lg {

+   height: 20px;

+   width: 20px;

+ }

+ 

+ .rbt-aux {

+   display: -webkit-box;

+   display: -moz-box;

+   display: -ms-flexbox;

+   display: -webkit-flex;

+   display: flex;

+   align-items: center;

+   bottom: 0;

+   justify-content: center;

+   pointer-events: none;

+   /* Don't block clicks on the input */

+   position: absolute;

+   right: 0;

+   top: 0;

+   width: 34px;

+ }

+ 

+ .rbt-aux-lg {

+   width: 46px;

+ }

+ 

+ .rbt-aux .rbt-close {

+   margin-top: -4px;

+   pointer-events: auto;

+   /* Override pointer-events: none; above */

+ }

+ 

+ .has-aux .rbt-input {

+   padding-right: 34px;

+ }

+ 

+ .rbt-highlight-text {

+   background-color: inherit;

+   color: inherit;

+   font-weight: bold;

+   padding: 0;

+ }

+ 

+ /* Input Groups */

+ .input-group > .rbt {

+   -webkit-box-flex: 1;

+   -moz-box-flex: 1;

+   -webkit-flex: 1;

+   -ms-flex: 1;

+   flex: 1;

+ }

+ 

+ .input-group > .rbt .rbt-input-hint-container {

+   display: -webkit-box;

+   display: -moz-box;

+   display: -ms-flexbox;

+   display: -webkit-flex;

+   display: flex;

+ }

+ 

+ .input-group > .rbt .rbt-input-hint {

+   z-index: 5;

+ }

+ 

+ .input-group > .rbt .rbt-aux {

+   z-index: 5;

+ }

+ 

+ .input-group > .rbt:not(:first-child) .form-control {

+   border-top-left-radius: 0;

+   border-bottom-left-radius: 0;

+ }

+ 

+ .input-group > .rbt:not(:last-child) .form-control {

+   border-top-right-radius: 0;

+   border-bottom-right-radius: 0;

+ }

+ 

+ /* Validation States */

+ .has-error .rbt-input-multi.focus {

+   border-color: #843534;

+   -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;

+   -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;

+   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;

+ }

+ 

+ .has-warning .rbt-input-multi.focus {

+   border-color: #66512c;

+   -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;

+   -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;

+   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;

+ }

+ 

+ .has-success .rbt-input-multi.focus {

+   border-color: #2b542c;

+   -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;

+   -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;

+   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;

+ }

+ 

+ @keyframes loader-animation {

+   to {

+     transform: rotate(1turn);

+   }

+ }

+ 

+ @-webkit-keyframes loader-animation {

+   to {

+     -webkit-transform: rotate(1turn);

+   }

+ }

@@ -6,24 +6,22 @@ 

  const CompressionPlugin = require("compression-webpack-plugin");

  

  var externals = {

-     "cockpit": "cockpit",

+     cockpit: "cockpit"

  };

  

  /* These can be overridden, typically from the Makefile.am */

  const srcdir = (process.env.SRCDIR || __dirname) + path.sep + "src";

- const builddir = (process.env.SRCDIR || __dirname);

+ const builddir = process.env.SRCDIR || __dirname;

  const distdir = builddir + path.sep + "dist";

  const section = process.env.ONLYDIR || null;

- const nodedir = path.resolve((process.env.SRCDIR || __dirname), "node_modules");

+ const nodedir = path.resolve(process.env.SRCDIR || __dirname, "node_modules");

  

  /* A standard nodejs and webpack pattern */

- var production = process.env.NODE_ENV === 'production';

+ var production = process.env.NODE_ENV === "production";

  

  var info = {

      entries: {

-         "index": [

-             "./index.es6"

-         ]

+         index: ["./index.es6"]

      },

      files: [

          "backend.html",
@@ -46,14 +44,14 @@ 

          "servers.html",

          "servers.js",

          "static",

-         "manifest.json",

-     ],

+         "manifest.json"

+     ]

  };

  

  var output = {

      path: distdir,

      filename: "[name].js",

-     sourceMapFilename: "[file].map",

+     sourceMapFilename: "[file].map"

  };

  

  /*
@@ -67,8 +65,7 @@ 

  function vpath(/* ... */) {

      var filename = Array.prototype.join.call(arguments, path.sep);

      var expanded = builddir + path.sep + filename;

-     if (fs.existsSync(expanded))

-         return expanded;

+     if (fs.existsSync(expanded)) return expanded;

      expanded = srcdir + path.sep + filename;

      return expanded;

  }
@@ -81,10 +78,8 @@ 

      }

  

      info.entries[key] = info.entries[key].map(function(value) {

-         if (value.indexOf("/") === -1)

-             return value;

-         else

-             return vpath(value);

+         if (value.indexOf("/") === -1) return value;

+         else return vpath(value);

      });

  });

  
@@ -96,26 +91,25 @@ 

  });

  info.files = files;

  

- var plugins = [

-     new copy(info.files),

-     new extract("[name].css")

- ];

+ var plugins = [new copy(info.files), new extract("[name].css")];

  

  /* Only minimize when in production mode */

  if (production) {

      /* Rename output files when minimizing */

      output.filename = "[name].min.js";

  

-     plugins.unshift(new CompressionPlugin({

-         asset: "[path].gz[query]",

-         test: /\.(js|html)$/,

-         minRatio: 0.9,

-         deleteOriginalAssets: true

-     }));

+     plugins.unshift(

+         new CompressionPlugin({

+             asset: "[path].gz[query]",

+             test: /\.(js|html)$/,

+             minRatio: 0.9,

+             deleteOriginalAssets: true

+         })

+     );

  }

  

  module.exports = {

-     mode: production ? 'production' : 'development',

+     mode: production ? "production" : "development",

      entry: info.entries,

      externals: externals,

      output: output,
@@ -123,42 +117,44 @@ 

      module: {

          rules: [

              {

-                 enforce: 'pre',

+                 enforce: "pre",

                  exclude: /node_modules/,

-                 loader: 'eslint-loader',

+                 loader: "eslint-loader",

                  test: /\.jsx$/

              },

              {

-                 enforce: 'pre',

+                 enforce: "pre",

                  exclude: /node_modules/,

-                 loader: 'eslint-loader',

+                 loader: "eslint-loader",

                  test: /\.es6$/

              },

              {

                  exclude: /node_modules/,

-                 loader: 'babel-loader',

+                 loader: "babel-loader",

                  test: /\.js$/

              },

              {

                  exclude: /node_modules/,

-                 loader: 'babel-loader',

+                 loader: "babel-loader",

                  test: /\.jsx$/

              },

              {

                  exclude: /node_modules/,

-                 loader: 'babel-loader',

+                 loader: "babel-loader",

                  test: /\.es6$/

              },

              {

+                   // Transform our own .css files with PostCSS and CSS-modules

+                 test: /\.css$/,

                  exclude: /node_modules/,

-                 loader: extract.extract('css-loader!sass-loader'),

-                 test: /\.scss$/

+                 use: ['style-loader', 'css-loader'],

              },

              {

                  test: /\.css$/,

-                 use: [ 'style-loader', 'css-loader' ]

+                 include: /node_modules/,

+                 use: ['style-loader', 'css-loader'],

              }

          ]

      },

      plugins: plugins

- }

+ };

@@ -270,7 +270,14 @@ 

          :param *args: tuples of key,value to replace.

          :type *args: (str, str)

          """

-         mods = list(map(lambda t: (ldap.MOD_REPLACE, ensure_str(t[0]), ensure_bytes(t[1])), args))

+ 

+         mods = []

+         for arg in args:

+             if isinstance(arg[1], list):

+                 value = ensure_list_bytes(arg[1])

+             else:

+                 value = [ensure_bytes(arg[1])]

+             mods.append((ldap.MOD_REPLACE, ensure_str(arg[0]), value))

          return self._instance.modify_ext_s(self._dn, mods, serverctrls=self._server_controls, clientctrls=self._client_controls)

  

      # This needs to work on key + val, and key
@@ -371,7 +378,7 @@ 

      def apply_mods(self, mods):

          """Perform modification operation using several mods at once

  

-         :param mods: [(action, key, value),]

+         :param mods: [(action, key, value),] or [(ldap.MOD_DELETE, key),]

          :type mods: list of tuples

          :raises: ValueError - if a provided mod op is invalid

          """
@@ -381,25 +388,25 @@ 

              if len(mod) < 2:

                  # Error

                  raise ValueError('Not enough arguments in the mod op')

-             elif len(mod) == 2:  # no action

-                 action = ldap.MOD_REPLACE

-                 key, value = mod

+             elif len(mod) == 2:  # delete all attributes action

+                 action, key = mod

+                 if action != ldap.MOD_DELETE:

+                     raise ValueError('Not enough arguments in the mod op')

+                 mod_list.append((action, key, None))

              elif len(mod) == 3:

                  action, key, value = mod

                  if action != ldap.MOD_REPLACE and \

                     action != ldap.MOD_ADD and \

                     action != ldap.MOD_DELETE:

                      raise ValueError('Invalid mod action(%s)' % str(action))

+                 if isinstance(value, list):

+                     value = ensure_list_bytes(value)

+                 else:

+                     value = [ensure_bytes(value)]

+                 mod_list.append((action, key, value))

              else:

                  # Error too many items

                  raise ValueError('Too many arguments in the mod op')

- 

-             if isinstance(value, list):

-                 value = ensure_list_bytes(value)

-             else:

-                 value = [ensure_bytes(value)]

- 

-             mod_list.append((action, key, value))

          return self._instance.modify_ext_s(self._dn, mod_list, serverctrls=self._server_controls, clientctrls=self._client_controls)

  

      @classmethod

@@ -5,6 +5,67 @@ 

  # License: GPL (version 3 or any later version).

  # See LICENSE for details.

  # --- END COPYRIGHT BLOCK ---

+ import ldap

+ from lib389 import ensure_list_str

+ 

+ 

+ def _args_to_attrs(args, arg_to_attr):

+     attrs = {}

+     for arg in vars(args):

+         val = getattr(args, arg)

+         if arg in arg_to_attr and val is not None:

+             attrs[arg_to_attr[arg]] = val

+         elif arg == 'DN':

+             # Extract RDN from the DN

+             attribute = ldap.dn.str2dn(val)[0][0][0]

+             value = ldap.dn.str2dn(val)[0][0][1]

+             attrs[attribute] = value

+     return attrs

+ 

+ 

+ def generic_object_add(dsldap_object, log, args, arg_to_attr, props={}):

+     """Create an entry using DSLdapObject interface

+ 

+     dsldap_object should be a single instance of DSLdapObject with a set dn

+     """

+ 

+     log = log.getChild('generic_object_add')

+     # Gather the attributes

+     attrs = _args_to_attrs(args, arg_to_attr)

+     # Update the parameters (which should have at least 'cn') with arg attributes

+     props.update({attr: value for (attr, value) in attrs.items() if value != ""})

+     new_object = dsldap_object.create(properties=props)

+     log.info("Successfully created the %s", new_object.dn)

+ 

+ 

+ def generic_object_edit(dsldap_object, log, args, arg_to_attr):

+     """Create an entry using DSLdapObject interface

+ 

+     dsldap_object should be a single instance of DSLdapObject with a set dn

+     """

+ 

+     log = log.getChild('generic_object_edit')

+     # Gather the attributes

+     attrs = _args_to_attrs(args, arg_to_attr)

+     existing_attributes = dsldap_object.get_all_attrs()

+ 

+     modlist = []

+     for attr, value in attrs.items():

+         # Delete the attribute only if the user set it to 'delete' value

+         if value in ("delete", ["delete"]):

+             if attr in existing_attributes:

+                 modlist.append((ldap.MOD_DELETE, attr))

+         else:

+             if not isinstance(value, list):

+                 value = [value]

+             if not (attr in existing_attributes and value == ensure_list_str(existing_attributes[attr])):

+                 modlist.append((ldap.MOD_REPLACE, attr, value))

+     if len(modlist) > 0:

+         dsldap_object.apply_mods(modlist)

+         log.info("Successfully changed the %s", dsldap_object.dn)

+     else:

+         raise ValueError("There is nothing to set in the %s plugin entry" % dsldap_object.dn)

+ 

  

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

      """Display plugin configuration."""
@@ -15,27 +76,27 @@ 

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

      plugin = args.plugin_cls(inst)

      if plugin.status():

-         print("Plugin '%s' already enabled" % plugin.rdn)

+         print("Plugin '%s' already enabled", plugin.rdn)

      else:

          plugin.enable()

-         print("Enabled plugin '%s'" % plugin.rdn)

+         print("Enabled plugin '%s'", plugin.rdn)

  

  

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

      plugin = args.plugin_cls(inst)

      if not plugin.status():

-         print("Plugin '%s' already disabled " % plugin.rdn)

+         print("Plugin '%s' already disabled", plugin.rdn)

      else:

          plugin.disable()

-         print("Disabled plugin '%s'" % plugin.rdn)

+         print("Disabled plugin '%s'", plugin.rdn)

  

  

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

      plugin = args.plugin_cls(inst)

      if plugin.status() is True:

-         print("Plugin '%s' is enabled" % plugin.rdn)

+         print("Plugin '%s' is enabled", plugin.rdn)

      else:

-         print("Plugin '%s' is disabled" % plugin.rdn)

+         print("Plugin '%s' is disabled", plugin.rdn)

  

  

  def add_generic_plugin_parsers(subparser, plugin_cls):

@@ -170,7 +170,7 @@ 

  

      be = Backend(inst)

      be.create(properties=props)

-     print("The database was sucessfully created")

+     print("The database was successfully created")

  

  

  def _recursively_del_backends(be):
@@ -197,7 +197,7 @@ 

      _recursively_del_backends(be)

      be.delete()

  

-     print("The database, and any sub-suffixes, were sucessfully deleted")

+     print("The database, and any sub-suffixes, were successfully deleted")

  

  

  def backend_import(inst, basedn, log, args):
@@ -307,7 +307,7 @@ 

          be.enable()

      if args.disable:

          be.disable()

-     print("The backend configuration was sucessfully updated")

+     print("The backend configuration was successfully updated")

  

  

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

@@ -13,6 +13,7 @@ 

      _generic_get,

      _get_arg,

  )

+ from lib389.cli_conf import generic_object_edit

  from lib389.cli_conf.plugins import memberof as cli_memberof

  from lib389.cli_conf.plugins import usn as cli_usn

  from lib389.cli_conf.plugins import rootdn_ac as cli_rootdn_ac
@@ -31,6 +32,19 @@ 

  MANY = Plugins

  RDN = 'cn'

  

+ arg_to_attr = {

+     'initfunc': 'nsslapd-pluginInitfunc',

+     'enabled': 'nsslapd-pluginEnabled',

+     'path': 'nsslapd-pluginPath',

+     'type': 'nsslapd-pluginType',

+     'id': 'nsslapd-pluginId',

+     'version': 'nsslapd-pluginVersion',

+     'vendor': 'nsslapd-pluginVendor',

+     'description': 'nsslapd-pluginDescription',

+     'depends_on_type': 'nsslapd-plugin-depends-on-type',

+     'depends_on_named': 'nsslapd-plugin-depends-on-named'

+ }

+ 

  

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

      plugin_log = log.getChild('plugin_list')
@@ -77,25 +91,7 @@ 

      rdn = _get_arg(args.selector, msg="Enter %s to retrieve" % RDN)

      plugins = Plugins(inst)

      plugin = plugins.get(rdn)

- 

-     if args.enabled is not None and args.enabled.lower() not in ["on", "off"]:

-         raise ValueError("Plugin enabled argument should be 'on' or 'off'")

- 

-     plugin_args = {'nsslapd-pluginInitfunc': args.initfunc,

-                    'nsslapd-pluginEnabled': args.enabled,

-                    'nsslapd-pluginPath': args.path,

-                    'nsslapd-pluginType': args.type,

-                    'nsslapd-pluginId': args.id,

-                    'nsslapd-pluginVersion': args.version,

-                    'nsslapd-pluginVendor': args.vendor,

-                    'nsslapd-pluginDescription': args.description}

-     mods = vaidate_args(plugin, plugin_args)

- 

-     if len(mods) > 0:

-         plugin.replace_many(*mods)

-         log.info("Successfully changed the plugin %s", rdn)

-     else:

-         raise ValueError("Nothing to change")

+     generic_object_edit(plugin, log, args, arg_to_attr)

  

  

  def create_parser(subparsers):
@@ -128,11 +124,17 @@ 

      edit_parser.set_defaults(func=plugin_edit)

      edit_parser.add_argument('selector', nargs='?', help='The plugin to edit')

      edit_parser.add_argument('--type', help='The type of plugin.')

-     edit_parser.add_argument('--enabled',

-                              help='Identifies whether or not the plugin is enabled. It should have "on" or "off" values.')

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

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

      edit_parser.add_argument('--path', help='The plugin library name (without the library suffix).')

      edit_parser.add_argument('--initfunc', help='An initialization function of the plugin.')

      edit_parser.add_argument('--id', help='The plugin ID.')

      edit_parser.add_argument('--vendor', help='The vendor of plugin.')

      edit_parser.add_argument('--version', help='The version of plugin.')

      edit_parser.add_argument('--description', help='The description of the plugin.')

+     edit_parser.add_argument('--depends-on-type',

+                              help='All plug-ins with a type value which matches one of the values '

+                                   'in the following valid range will be started by the server prior to this plug-in.')

+     edit_parser.add_argument('--depends-on-named',

+                              help='The plug-in name matching one of the following values will be '

+                                   'started by the server prior to this plug-in')

@@ -7,199 +7,92 @@ 

  # --- END COPYRIGHT BLOCK ---

  

  import ldap

- 

- from lib389.plugins import MemberOfPlugin

- from lib389.cli_conf import add_generic_plugin_parsers

- 

- 

- def manage_attr(inst, basedn, log, args):

-     if args.value is not None:

-         set_attr(inst, basedn, log, args)

-     else:

-         display_attr(inst, basedn, log, args)

- 

- def display_attr(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     log.info(plugin.get_attr_formatted())

- 

- def set_attr(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     try:

-         plugin.set_attr(args.value)

-     except ldap.UNWILLING_TO_PERFORM:

-         log.error('Error: Illegal value "{}". Failed to set.'.format(args.value))

-     else:

-         log.info('memberOfAttr set to "{}"'.format(args.value))

- 

- def display_groupattr(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     log.info(plugin.get_groupattr_formatted())

- 

- def add_groupattr(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     try:

-         plugin.add_groupattr(args.value)

-     except ldap.UNWILLING_TO_PERFORM:

-         log.error('Error: Illegal value "{}". Failed to add.'.format(args.value))

-     except ldap.TYPE_OR_VALUE_EXISTS:

-         log.info('Value "{}" already exists.'.format(args.value))

-     else:

-         log.info('successfully added memberOfGroupAttr value "{}"'.format(args.value))

- 

- def remove_groupattr(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     try:

-         plugin.remove_groupattr(args.value)

-     except ldap.UNWILLING_TO_PERFORM:

-         log.error("Error: Failed to delete. memberOfGroupAttr is required.")

-     except ldap.NO_SUCH_ATTRIBUTE:

-         log.error('Error: Failed to delete. No value "{0}" found.'.format(args.value))

-     else:

-         log.info('successfully removed memberOfGroupAttr value "{}"'.format(args.value))

- 

- def display_allbackends(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     val = plugin.get_allbackends_formatted()

-     if not val:

-         log.info("memberOfAllBackends is not set")

+ from lib389.plugins import MemberOfPlugin, Plugins, MemberOfSharedConfig

+ from lib389.cli_conf import add_generic_plugin_parsers, generic_object_edit, generic_object_add

+ 

+ arg_to_attr = {

+     'initfunc': 'nsslapd-pluginInitfunc',

+     'attr': 'memberOfAttr',

+     'groupattr': 'memberOfGroupAttr',

+     'allbackends': 'memberOfAllBackends',

+     'skipnested': 'memberOfSkipNested',

+     'scope': 'memberOfEntryScope',

+     'exclude': 'memberOfEntryScopeExcludeSubtree',

+     'autoaddoc': 'memberOfAutoAddOC',

+     'config_entry': 'nsslapd-pluginConfigArea'

+ }

+ 

+ 

+ def memberof_edit(inst, basedn, log, args):

+     log = log.getChild('memberof_edit')

+     plugins = Plugins(inst)

+     plugin = plugins.get("MemberOf Plugin")

+     generic_object_edit(plugin, log, args, arg_to_attr)

+ 

+ 

+ def memberof_add_config(inst, basedn, log, args):

+     log = log.getChild('memberof_add_config')

+     targetdn = args.DN

+     config = MemberOfSharedConfig(inst, targetdn)

+     generic_object_add(config, log, args, arg_to_attr)

+     plugins = Plugins(inst)

+     plugin = plugins.get("MemberOf Plugin")

+     plugin.replace('nsslapd-pluginConfigArea', config.dn)

+     log.info('MemberOf attribute nsslapd-pluginConfigArea (config-entry) '

+              'was set in the main plugin config')

+ 

+ 

+ def memberof_edit_config(inst, basedn, log, args):

+     log = log.getChild('memberof_edit_config')

+     targetdn = args.DN

+     config = MemberOfSharedConfig(inst, targetdn)

+     generic_object_edit(config, log, args, arg_to_attr)

+ 

+ 

+ def memberof_show_config(inst, basedn, log, args):

+     log = log.getChild('memberof_show_config')

+     targetdn = args.DN

+     config = MemberOfSharedConfig(inst, targetdn)

+ 

+     if not config.exists():

+         raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % targetdn)

+     if args and args.json:

+         o_str = config.get_all_attrs_json()

+         print(o_str)

      else:

-         log.info(val)

+         print(config.display())

  

- def enable_allbackends(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     plugin.enable_allbackends()

-     log.info("memberOfAllBackends enabled successfully")

- 

- def disable_allbackends(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     plugin.disable_allbackends()

-     log.info("memberOfAllBackends disabled successfully")

  

- def display_skipnested(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     val = plugin.get_skipnested_formatted()

-     if not val:

-         log.info("memberOfSkipNested is not set")

-     else:

-         log.info(val)

- 

- def enable_skipnested(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     plugin.enable_skipnested()

-     log.info("memberOfSkipNested set successfully")

+ def memberof_del_config(inst, basedn, log, args):

+     log = log.getChild('memberof_del_config')

+     targetdn = args.DN

+     config = MemberOfSharedConfig(inst, targetdn)

+     config.delete()

+     log.info("Successfully deleted the %s", targetdn)

  

- def disable_skipnested(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     plugin.disable_skipnested()

-     log.info("memberOfSkipNested unset successfully")

- 

- def manage_autoaddoc(inst, basedn, log, args):

-     if args.value == "del":

-         remove_autoaddoc(inst, basedn, log, args)

-     elif args.value is not None:

-         set_autoaddoc(inst, basedn, log, args)

-     else:

-         display_autoaddoc(inst, basedn, log, args)

- 

- def display_autoaddoc(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     val = plugin.get_autoaddoc_formatted()

-     if not val:

-         log.info("memberOfAutoAddOc is not set")

-     else:

-         log.info(val)

- 

- def set_autoaddoc(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     d = {'nsmemberof': 'nsMemberOf', 'inetuser': 'inetUser', 'inetadmin': 'inetAdmin'}

-     plugin.set_autoaddoc(d[args.value])

-     log.info('memberOfAutoAddOc set to "{}"'.format(d[args.value]))

- 

- def remove_autoaddoc(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     if not plugin.get_autoaddoc():

-         log.info("memberOfAutoAddOc was not set")

-     else:

-         plugin.remove_autoaddoc()

-         log.info("memberOfAutoAddOc attribute deleted")

- 

- def display_scope(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     val = plugin.get_entryscope_formatted()

-     if not val:

-         log.info("memberOfEntryScope is not set")

-     else:

-         log.info(val)

- 

- def add_scope(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     try:

-         plugin.add_entryscope(args.value)

-     except ldap.UNWILLING_TO_PERFORM as ex:

-         if "is also listed as an exclude suffix" in ex.args[0]['info']:

-             log.error('Error: Include suffix ({0}) is also listed as an exclude suffix.'.format(args.value))

-         else:

-             log.error('Error: Invalid DN "{}". Failed to add.'.format(args.value))

-     except ldap.TYPE_OR_VALUE_EXISTS:

-         log.info('Value "{}" already exists.'.format(args.value))

-     else:

-         log.info('successfully added memberOfEntryScope value "{}"'.format(args.value))

- 

- def remove_scope(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     try:

-         plugin.remove_entryscope(args.value)

-     except ldap.NO_SUCH_ATTRIBUTE:

-         log.error('Error: Failed to delete. No value "{0}" found.'.format(args.value))

-     else:

-         log.info('successfully removed memberOfEntryScope value "{}"'.format(args.value))

- 

- def remove_all_scope(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     plugin.remove_all_entryscope()

-     log.info('successfully removed all memberOfEntryScope values')

- 

- def display_excludescope(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     val = plugin.get_excludescope_formatted()

-     if not val:

-         log.info("memberOfEntryScopeExcludeSubtree is not set")

-     else:

-         log.info(val)

- 

- def add_excludescope(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     try:

-         plugin.add_excludescope(args.value)

-     except ldap.UNWILLING_TO_PERFORM as ex:

-         if "is also listed as an exclude suffix" in ex.args[0]['info']:

-             log.error('Error: Suffix ({0}) is listed in entry scope.'.format(args.value))

-         else:

-             log.error('Error: Invalid DN "{}". Failed to add.'.format(args.value))

-     except ldap.TYPE_OR_VALUE_EXISTS:

-         log.info('Value "{}" already exists.'.format(args.value))

-     else:

-         log.info('successfully added memberOfEntryScopeExcludeSubtree value "{}"'.format(args.value))

- 

- def remove_excludescope(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     try:

-         plugin.remove_excludescope(args.value)

-     except ldap.NO_SUCH_ATTRIBUTE:

-         log.error('Error: Failed to delete. No value "{0}" found.'.format(args.value))

-     else:

-         log.info('successfully removed memberOfEntryScopeExcludeSubtree value "{}"'.format(args.value))

- 

- def remove_all_excludescope(inst, basedn, log, args):

-     plugin = MemberOfPlugin(inst)

-     plugin.remove_all_excludescope()

-     log.info('successfully removed all memberOfEntryScopeExcludeSubtree values')

  

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

      plugin = MemberOfPlugin(inst)

      log.info('Attempting to add task entry... This will fail if MemberOf plug-in is not enabled.')

-     fixup_task = plugin.fixup(args.basedn, args.filter)

-     log.info('Successfully added task entry ' + fixup_task.dn)

+     assert plugin.status(), "'%s' is disabled. Fix up task can't be executed" % plugin.rdn

+     fixup_task = plugin.fixup(args.DN, args.filter)

+     fixup_task.wait()

+     exitcode = fixup_task.get_exit_code()

+     assert exitcode == 0, 'MemberOf fixup task for %s has failed. Please, check logs'

+     log.info('Successfully added task entry for %s', args.DN)

+ 

+ 

+ def _add_parser_args(parser):

+     parser.add_argument('--attr', nargs='+', help='The value to set as memberOfAttr')

+     parser.add_argument('--groupattr', nargs='+', help='The value to set as memberOfGroupAttr')

+     parser.add_argument('--allbackends', choices=['on', 'off'], type=str.lower,

+                         help='The value to set as memberOfAllBackends')

+     parser.add_argument('--skipnested', choices=['on', 'off'], type=str.lower,

+                         help='The value to set as memberOfSkipNested')

+     parser.add_argument('--scope', help='The value to set as memberOfEntryScope')

+     parser.add_argument('--exclude', help='The value to set as memberOfEntryScopeExcludeSubtree')

+     parser.add_argument('--autoaddoc', type=str.lower, help='The value to set as memberOfAutoAddOC')

+ 

  

  def create_parser(subparsers):

      memberof_parser = subparsers.add_parser('memberof', help='Manage and configure MemberOf plugin')
@@ -208,69 +101,32 @@ 

  

      add_generic_plugin_parsers(subcommands, MemberOfPlugin)

  

-     attr_parser = subcommands.add_parser('attr', help='get or set memberofattr')

-     attr_parser.set_defaults(func=manage_attr)

-     attr_parser.add_argument('value', nargs='?', help='The value to set as memberofattr')

- 

-     groupattr_parser = subcommands.add_parser('groupattr', help='get or manage memberofgroupattr')

-     groupattr_parser.set_defaults(func=display_groupattr)

-     groupattr_subcommands = groupattr_parser.add_subparsers(help='action')

-     add_groupattr_parser = groupattr_subcommands.add_parser('add', help='add memberofgroupattr value')

-     add_groupattr_parser.set_defaults(func=add_groupattr)

-     add_groupattr_parser.add_argument('value', help='The value to add in memberofgroupattr')

-     del_groupattr_parser = groupattr_subcommands.add_parser('del', help='remove memberofgroupattr value')

-     del_groupattr_parser.set_defaults(func=remove_groupattr)

-     del_groupattr_parser.add_argument('value', help='The value to remove from memberofgroupattr')

- 

-     allbackends_parser = subcommands.add_parser('allbackends', help='get or manage memberofallbackends')

-     allbackends_parser.set_defaults(func=display_allbackends)

-     allbackends_subcommands = allbackends_parser.add_subparsers(help='action')

-     on_allbackends_parser = allbackends_subcommands.add_parser('on', help='enable all backends for memberof')

-     on_allbackends_parser.set_defaults(func=enable_allbackends)

-     off_allbackends_parser = allbackends_subcommands.add_parser('off', help='disable all backends for memberof')

-     off_allbackends_parser.set_defaults(func=disable_allbackends)

- 

-     skipnested_parser = subcommands.add_parser('skipnested', help='get or manage memberofskipnested')

-     skipnested_parser.set_defaults(func=display_skipnested)

-     skipnested_subcommands = skipnested_parser.add_subparsers(help='action')

-     on_skipnested_parser = skipnested_subcommands.add_parser('on', help='skip nested groups for memberof')

-     on_skipnested_parser.set_defaults(func=enable_skipnested)

-     off_skipnested_parser = skipnested_subcommands.add_parser('off', help="don't skip nested groups for memberof")

-     off_skipnested_parser.set_defaults(func=disable_skipnested)

- 

-     autoaddoc_parser = subcommands.add_parser('autoaddoc', help='get or set memberofautoaddoc')

-     autoaddoc_parser.set_defaults(func=manage_autoaddoc)

-     autoaddoc_parser.add_argument('value', nargs='?', choices=['nsmemberof', 'inetuser', 'inetadmin', 'del'],

-                                    type=str.lower, help='The value to set as memberofautoaddoc or del to remove the attribute')

- 

-     scope_parser = subcommands.add_parser('scope', help='get or manage memberofentryscope')

-     scope_parser.set_defaults(func=display_scope)

-     scope_subcommands = scope_parser.add_subparsers(help='action')

-     add_scope_parser = scope_subcommands.add_parser('add', help='add memberofentryscope value')

-     add_scope_parser.set_defaults(func=add_scope)

-     add_scope_parser.add_argument('value', help='The value to add in memberofentryscope')

-     del_scope_parser = scope_subcommands.add_parser('del', help='remove memberofentryscope value')

-     del_scope_parser.set_defaults(func=remove_scope)

-     del_scope_parser.add_argument('value', help='The value to remove from memberofentryscope')

-     delall_scope_parser = scope_subcommands.add_parser('delall', help='remove all memberofentryscope values')

-     delall_scope_parser.set_defaults(func=remove_all_scope)

- 

-     exclude_parser = subcommands.add_parser('exclude', help='get or manage memberofentryscopeexcludesubtree')

-     exclude_parser.set_defaults(func=display_excludescope)

-     exclude_subcommands = exclude_parser.add_subparsers(help='action')

-     add_exclude_parser = exclude_subcommands.add_parser('add', help='add memberofentryscopeexcludesubtree value')

-     add_exclude_parser.set_defaults(func=add_excludescope)

-     add_exclude_parser.add_argument('value', help='The value to add in memberofentryscopeexcludesubtree')

-     del_exclude_parser = exclude_subcommands.add_parser('del', help='remove memberofentryscopeexcludesubtree value')

-     del_exclude_parser.set_defaults(func=remove_excludescope)

-     del_exclude_parser.add_argument('value', help='The value to remove from memberofentryscopeexcludesubtree')

-     delall_exclude_parser = exclude_subcommands.add_parser('delall', help='remove all memberofentryscopeexcludesubtree values')

-     delall_exclude_parser.set_defaults(func=remove_all_excludescope)

- 

-     fixup_parser = subcommands.add_parser('fixup', help='run the fix-up task for memberof plugin')

+     edit_parser = subcommands.add_parser('edit', help='Edit the plugin')

+     edit_parser.set_defaults(func=memberof_edit)

+     _add_parser_args(edit_parser)

+     edit_parser.add_argument('--config-entry', help='The value to set as nsslapd-pluginConfigArea')

+ 

+     config_parser = subcommands.add_parser('config-entry', help='Manage the config entry')

+     config_subcommands = config_parser.add_subparsers(help='action')

+     add_config_parser = config_subcommands.add_parser('add', help='Add the config entry')

+     add_config_parser.set_defaults(func=memberof_add_config)

+     add_config_parser.add_argument('DN', help='The config entry full DN')

+     _add_parser_args(add_config_parser)

+     edit_config_parser = config_subcommands.add_parser('edit', help='Edit the config entry')

+     edit_config_parser.set_defaults(func=memberof_edit_config)

+     edit_config_parser.add_argument('DN', help='The config entry full DN')

+     _add_parser_args(edit_config_parser)

+     show_config_parser = config_subcommands.add_parser('show', help='Display the config entry')

+     show_config_parser.set_defaults(func=memberof_show_config)

+     show_config_parser.add_argument('DN', help='The config entry full DN')

+     del_config_parser = config_subcommands.add_parser('delete', help='Delete the config entry')

+     del_config_parser.set_defaults(func=memberof_del_config)

+     del_config_parser.add_argument('DN', help='The config entry full DN')

+ 

+     fixup_parser = subcommands.add_parser('fixup', help='Run the fix-up task for memberOf plugin')

      fixup_parser.set_defaults(func=fixup)

-     fixup_parser.add_argument('-b', '--basedn', required=True, help="base DN that contains entries to fix up")

-     fixup_parser.add_argument('-f', '--filter', help="Filter for entries to fix up.\n"

-         "If omitted, all entries with objectclass inetuser/inetadmin/nsmemberof under the\n"

-         "specified base will have their memberOf attribute regenerated."

-     )

+     fixup_parser.add_argument('DN', help="base DN that contains entries to fix up")

+     fixup_parser.add_argument('-f', '--filter',

+                               help='Filter for entries to fix up.\n If omitted, all entries with objectclass '

+                                    'inetuser/inetadmin/nsmemberof under the specified base will have '

+                                    'their memberOf attribute regenerated.')

@@ -68,7 +68,7 @@ 

  

      sd = SetupDs(args.verbose, args.dryrun, log, args.containerised)

      if sd.create_from_inf(args.file):

-         # print("Sucessfully created instance")

+         # print("Successfully created instance")

          return True

      else:

          # print("Failed to create instance")
@@ -200,6 +200,3 @@ 

      remove_parser.set_defaults(func=instance_remove)

      remove_parser.add_argument('--do-it', dest="ack", help="By default we do a dry run. This actually initiates the removal of the instance.",

                                 action='store_true', default=False)

- 

- 

- 

@@ -107,13 +107,13 @@ 

      mc = manager_class(inst, basedn)

      o = mc.create(properties=kwargs)

      o_str = o.__unicode__()

-     log.info('Sucessfully created %s' % o_str)

+     log.info('Successfully created %s' % o_str)

  

  

  def _generic_delete(inst, basedn, log, object_class, dn, args=None):

      # Load the oc direct

      o = object_class(inst, dn)

      o.delete()

-     log.info('Sucessfully deleted %s' % dn)

+     log.info('Successfully deleted %s' % dn)

  

  # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4

@@ -66,7 +66,7 @@ 

      print('Launching ...')

      sd = SetupDs(args.verbose, args.dryrun)

      if sd.create_from_inf(args.file):

-         print("Sucessfully created instance")

+         print("Successfully created instance")

      else:

          print("Failed to create instance")

          sys.exit(1)
@@ -75,4 +75,3 @@ 

  

  if __name__ == '__main__':

      ds_setup_main()

- 

file modified
+10 -10
@@ -1073,15 +1073,15 @@ 

                  raise ValueError('Failed to update replica: ' + str(e))

          elif replicarole == ReplicaRole.CONSUMER and newrole == ReplicaRole.MASTER:

              try:

-                 self.apply_mods([(REPL_TYPE, str(REPLICA_RDWR_TYPE)),

-                                  (REPL_FLAGS, str(REPLICA_FLAGS_WRITE)),

-                                  (REPL_ID, str(rid))])

+                 self.replace_many([(REPL_TYPE, str(REPLICA_RDWR_TYPE)),

+                                    (REPL_FLAGS, str(REPLICA_FLAGS_WRITE)),

+                                    (REPL_ID, str(rid))])

              except ldap.LDAPError as e:

                  raise ValueError('Failed to update replica: ' + str(e))

          elif replicarole == ReplicaRole.HUB and newrole == ReplicaRole.MASTER:

              try:

-                 self.apply_mods([(REPL_TYPE, str(REPLICA_RDWR_TYPE)),

-                                  (REPL_ID, str(rid))])

+                 self.replace_many([(REPL_TYPE, str(REPLICA_RDWR_TYPE)),

+                                    (REPL_ID, str(rid))])

              except ldap.LDAPError as e:

                  raise ValueError('Failed to update replica: ' + str(e))

  
@@ -1103,15 +1103,15 @@ 

          # Demote it - set the replica type, flags and rid

          if replicarole == ReplicaRole.MASTER and newrole == ReplicaRole.HUB:

              try:

-                 self.apply_mods([(REPL_TYPE, str(REPLICA_RDONLY_TYPE)),

-                                  (REPL_ID, str(CONSUMER_REPLICAID))])

+                 self.replace_many([(REPL_TYPE, str(REPLICA_RDONLY_TYPE)),

+                                    (REPL_ID, str(CONSUMER_REPLICAID))])

              except ldap.LDAPError as e:

                  raise ValueError('Failed to update replica: ' + str(e))

          elif replicarole == ReplicaRole.MASTER and newrole == ReplicaRole.CONSUMER:

              try:

-                 self.apply_mods([(REPL_TYPE, str(REPLICA_RDONLY_TYPE)),

-                                  (REPL_FLAGS, str(REPLICA_FLAGS_RDONLY)),

-                                  (REPL_ID, str(CONSUMER_REPLICAID))])

+                 self.replace_many([(REPL_TYPE, str(REPLICA_RDONLY_TYPE)),

+                                    (REPL_FLAGS, str(REPLICA_FLAGS_RDONLY)),

+                                    (REPL_ID, str(CONSUMER_REPLICAID))])

              except ldap.LDAPError as e:

                  raise ValueError('Failed to update replica: ' + str(e))

          elif replicarole == ReplicaRole.HUB and newrole == ReplicaRole.CONSUMER:

@@ -53,7 +53,7 @@ 

  

      args.suffix = SUFFIX

      backend_create(topology_st.standalone, None, None, args)

-     check_output("The database was sucessfully created")

+     check_output("The database was successfully created")

  

      def fin():

          sys.stdout = io.StringIO()
@@ -66,7 +66,7 @@ 

  

          # Delete backend

          backend_delete(topology_st.standalone, None, None, args, warn=False)

-         check_output("sucessfully deleted")

+         check_output("successfully deleted")

  

          # Verify it's removed

          args.suffix = False
@@ -137,7 +137,7 @@ 

      args.suffix = SUB_SUFFIX

      args.be_name = SUB_BE_NAME

      backend_create(topology_st.standalone, None, topology_st.logcap.log, args)

-     check_output("The database was sucessfully created")

+     check_output("The database was successfully created")

  

      # Verify subsuffix

      args.suffix = False
@@ -155,7 +155,7 @@ 

      args.enable_readonly = True  # Setting nsslapd-readonly to "on"

      args.disable_readonly = False

      backend_set(topology_st.standalone, None, topology_st.logcap.log, args)

-     check_output("sucessfully updated")

+     check_output("successfully updated")

  

      # Verify modified worked

      args.selector = SUB_BE_NAME
@@ -165,7 +165,7 @@ 

      # Delete subsuffix

      args.suffix = SUB_SUFFIX

      backend_delete(topology_st.standalone, None, topology_st.logcap.log, args, warn=False)

-     check_output("sucessfully deleted")

+     check_output("successfully deleted")

  

      # Verify it is deleted

      args.suffix = False
@@ -175,7 +175,7 @@ 

      # Modify backend (use same args from subsuffix modify)

      args.be_name = BE_NAME

      backend_set(topology_st.standalone, None, topology_st.logcap.log, args)

-     check_output("sucessfully updated")

+     check_output("successfully updated")

  

      # Verify modified worked

      args.selector = BE_NAME

@@ -46,7 +46,7 @@ 

      args.create_entries = True

      args.suffix = SUFFIX

      backend_create(topology_st.standalone, None, None, args)

-     check_output("The database was sucessfully created")

+     check_output("The database was successfully created")

  

      def fin():

          sys.stdout = io.StringIO()
@@ -60,7 +60,7 @@ 

  

          # Delete backend

          backend_delete(topology_st.standalone, None, None, args, warn=False)

-         check_output("sucessfully deleted")

+         check_output("successfully deleted")

  

          # Verify it's removed

          args.suffix = False

@@ -35,7 +35,7 @@ 

      topology.logcap.flush()

      g_args.cn = 'testgroup'

      create(topology.standalone, DEFAULT_SUFFIX, topology.logcap.log, g_args)

-     assert(topology.logcap.contains("Sucessfully created testgroup"))

+     assert(topology.logcap.contains("Successfully created testgroup"))

  

      # Assert it exists

      topology.logcap.flush()
@@ -54,7 +54,7 @@ 

      u_args.uidNumber = '5000'

      u_args.gidNumber = '5000'

      create_user(topology.standalone, DEFAULT_SUFFIX, topology.logcap.log, u_args)

-     assert(topology.logcap.contains("Sucessfully created testuser"))

+     assert(topology.logcap.contains("Successfully created testuser"))

  

      # Add them to the group as a member

      topology.logcap.flush()
@@ -86,5 +86,4 @@ 

      topology.logcap.flush()

      g_args.dn = "cn=testgroup,ou=groups,dc=example,dc=com"

      delete(topology.standalone, DEFAULT_SUFFIX, topology.logcap.log, g_args, warn=False)

-     assert(topology.logcap.contains("Sucessfully deleted cn=testgroup,ou=groups,dc=example,dc=com"))

- 

+     assert(topology.logcap.contains("Successfully deleted cn=testgroup,ou=groups,dc=example,dc=com"))

@@ -52,7 +52,7 @@ 

      u_args.gidNumber = '5000'

      create(topology.standalone, DEFAULT_SUFFIX, topology.logcap.log, u_args)

  

-     assert(topology.logcap.contains("Sucessfully created testuser"))

+     assert(topology.logcap.contains("Successfully created testuser"))

      # Assert they exist

      topology.logcap.flush()

      get(topology.standalone, DEFAULT_SUFFIX, topology.logcap.log, u_args)
@@ -82,9 +82,8 @@ 

  

      # Enroll a dummy sshkey

  

-     # Delete it 

+     # Delete it

      topology.logcap.flush()

      u_args.dn = 'uid=testuser,ou=people,dc=example,dc=com'

      delete(topology.standalone, DEFAULT_SUFFIX, topology.logcap.log, u_args, warn=False)

-     assert(topology.logcap.contains('Sucessfully deleted uid=testuser,ou=people,dc=example,dc=com'))

- 

+     assert(topology.logcap.contains('Successfully deleted uid=testuser,ou=people,dc=example,dc=com'))

Description: Add the main functionality to memberOf plugin tab.
Increase the eslint max line length from 80 to 100.
Rework plugin properties to be more compact.
Eslint webpack config. Add react-bootstrap-typeahead for
multivalued attributes. Fix the word 'successfully' typos.

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

Reviewed by: mreynolds, wibrown (Thanks!)

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

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
Metadata
Changes Summary 24
+2 -1
file changed
src/cockpit/389-console/.eslintrc.json
+5 -2
file changed
src/cockpit/389-console/package.json
+15 -1
file changed
src/cockpit/389-console/src/css/ds.css
+1 -0
file changed
src/cockpit/389-console/src/index.html
+0 -1
file changed
src/cockpit/389-console/src/lib/customCollapse.jsx
+953 -2
file changed
src/cockpit/389-console/src/lib/plugins/memberOf.jsx
+116 -111
file changed
src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
+100 -65
file changed
src/cockpit/389-console/src/lib/plugins/pluginModal.jsx
+109 -44
file changed
src/cockpit/389-console/src/plugins.jsx
+256
file added
src/cockpit/389-console/src/static/Typeahead.css
+34 -38
file changed
src/cockpit/389-console/webpack.config.js
+19 -12
file changed
src/lib389/lib389/_mapped_object.py
+67 -6
file changed
src/lib389/lib389/cli_conf/__init__.py
+3 -3
file changed
src/lib389/lib389/cli_conf/backend.py
+23 -21
file changed
src/lib389/lib389/cli_conf/plugin.py
+106 -250
file changed
src/lib389/lib389/cli_conf/plugins/memberof.py
+1 -4
file changed
src/lib389/lib389/cli_ctl/instance.py
+2 -2
file changed
src/lib389/lib389/cli_idm/__init__.py
+1 -2
file changed
src/lib389/lib389/clitools/ds_setup
+10 -10
file changed
src/lib389/lib389/replica.py
+6 -6
file changed
src/lib389/lib389/tests/cli/conf_backend_test.py
+2 -2
file changed
src/lib389/lib389/tests/cli/conf_chaining_test.py
+3 -4
file changed
src/lib389/lib389/tests/cli/idm_group_test.py
+3 -4
file changed
src/lib389/lib389/tests/cli/idm_user_test.py