#50088 Issue 50041 - Add basic plugin UI/CLI wrappers
Merged 10 months ago by spichugi. Opened 10 months ago by spichugi.
spichugi/389-ds-base plugin_ui_cli  into  master

@@ -42,10 +42,13 @@ 

      "webpack-cli": "^3.1.0"

    },

    "dependencies": {

-     "@babel/polyfill": "^7.0.0",

-     "patternfly-react": "^2.22.1",

-     "node-sass": "^4.9.0",

-     "react": "^16.4.2",

-     "react-dom": "^16.4.2"

+     "patternfly": "3.58.0",

+     "patternfly-react": "2.24.5",

+     "react-bootstrap": "0.32.4",

+     "react": "16.6.1",

+     "react-dom": "16.6.1",

+     "prop-types": "15.6.2",

+     "table-resolver": "4.1.1",

+     "recompose": "0.30.0"

    }

  }

@@ -1179,14 +1179,17 @@ 

  }

  

  .ds-accordion {

+     box-shadow: unset;

+     background-image: unset;

      margin-top: 20px;

      color: #228bc0 !important;

-     background-color: white;

+     background-color: transparent !important;

      border: 0;

      position: relative;

      overflow: hidden;

      width: 700px;

      text-align: left;

+     font: inherit;

  }

  

  .ds-accordion:after {

@@ -1201,7 +1204,9 @@ 

      text-align: left;

  }

  

+ .ds-accordion:hover,

  .ds-accordion:focus {

+     background-color: transparent !important;

      outline: none !important;

      border: 0 !important;

      -moz-outline: none !important;

@@ -1209,6 +1214,10 @@ 

      box-shadow: 0 !important;

  }

  

+ .ds-accordion:active {

+     background-color: transparent !important;

+ }

+ 

  .ds-accordion-panel {

      padding-left: 25px;

      margin-top: 10px;

@@ -1431,10 +1440,57 @@ 

      margin-right: 10px;

  }

  

- .content-view-pf-pagination > div > span {

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

      margin-left: 8px;

+     position: relative;

+     top: -8px;

  }

  

  tbody:nth-child(even) {

      background: #f1f1f1;

  }

+ 

+ .ds-toolbar {

+     bottom: 10px;

+ }

+ 

+ .ds-plugin-spinner {

+     margin-top: 20px;

+     margin-bottom: 10px;

+     margin-left: 80px;

+ }

+ 

+ @media screen and (max-width: 1300px) {

+     .ds-plugin-spinner {

+         margin-left: 0px;

+     }

+ }

+ 

+ /* Link */

+ .navbar-default .navbar-nav > li > a:hover,

+ .navbar-default .navbar-nav > li > a:focus {

+     color: #228bc0;

+ }

+ 

+ /* Nav stacked (vertical) bar */

+ .nav-pills > li > a {

+     padding: 3px 15px;

+     font-size: 15px;

+     border-radius: 3px;

+ }

+ 

+ .ds-plugin-tab-header {

+     position: relative;

+     top: -16px;

+ }

+ 

+ @media screen and (max-width: 770px) {

+     .ds-plugin-tab-header {

+         margin-top: 0px;

+         margin-bottom: 0px;

+     }

+ }

+ 

+ .control-label {

+     text-align: left !important;

+ }

@@ -223,9 +223,6 @@ 

      for (i = 0; i < insts.length; i++) {

        $("#select-server").append('<option value="' + insts[i] + '">' + insts[i] +'</option>');

        $("#select-server select").val(insts[i]);

-       // We have to dispatch an event for the React components rerender

-       server_select_elem = document.getElementById('select-server');

-       server_select_elem.dispatchEvent(new Event('change'));

      }

  

      // Handle changing instance here

@@ -244,7 +241,10 @@ 

        server_id = insts[0];

        server_inst = insts[0].replace("slapd-", "");

        check_inst_alive();

-       load_config();

+       // We have to dispatch an event for the React components rerender

+       // It should also trigger the listener defined before

+       server_select_elem = document.getElementById('select-server');

+       server_select_elem.dispatchEvent(new Event('change'));

      }

    }).fail(function(error){

      set_no_insts();

@@ -4,12 +4,28 @@ 

  

  var serverIdElem;

  

- function renderReactDOM() {

-     serverIdElem = document.getElementById("select-server");

-     const element = (

-         <Plugins serverId={serverIdElem.value.replace("slapd-", "")} />

+ function renderReactDOM(clear) {

+     // We should clear the React properties on the instance removal

+     if (clear) {

+         serverIdElem = "";

+     } else {

+         serverIdElem = document

+                 .getElementById("select-server")

+                 .value.replace("slapd-", "");

+     }

+     ReactDOM.render(

+         <Plugins serverId={serverIdElem} />,

+         document.getElementById("plugins")

      );

-     ReactDOM.render(element, document.getElementById("plugins"));

+ }

+ 

+ // We have to create the wrappers because this is no simple way

+ // to pass arguments to the listener's callback function

+ function renderReactWrapper() {

+     renderReactDOM(false);

+ }

+ function renderClearWrapper() {

+     renderReactDOM(true);

  }

  

  document.addEventListener("DOMContentLoaded", function() {

@@ -18,13 +34,16 @@ 

          if (serverIdElem != null && serverIdElem.value.startsWith("slapd-")) {

              document

                      .getElementById("select-server")

-                     .addEventListener("change", renderReactDOM);

+                     .addEventListener("change", renderReactWrapper);

              document

                      .getElementById("start-server-btn")

-                     .addEventListener("click", renderReactDOM);

+                     .addEventListener("click", renderReactWrapper);

              document

                      .getElementById("restart-server-btn")

-                     .addEventListener("click", renderReactDOM);

+                     .addEventListener("click", renderReactWrapper);

+             document

+                     .getElementById("remove-server-btn")

+                     .addEventListener("click", renderClearWrapper);

              renderReactDOM();

              clearInterval(init_config);

          }

@@ -30,8 +30,8 @@ 

    <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="css/ds.css" type="text/css" rel="stylesheet">

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

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

  </head>

  

  <body>

@@ -0,0 +1,56 @@ 

+ import React from "react";

+ import PropTypes from "prop-types";

+ import classNames from "classnames";

+ import { Icon, Button } from "patternfly-react";

+ 

+ class CustomCollapse extends React.Component {

+     constructor(props) {

+         super(props);

+ 

+         this.state = {

+             open: false

+         };

+     }

+ 

+     render() {

+         const { children, className, textOpened, textClosed } = this.props;

+         const { open } = this.state;

+ 

+         return (

+             <div>

+                 <Button

+                     className={classNames("ds-accordion", className)}

+                     onClick={() => {

+                         this.setState({ open: !open });

+                     }}

+                 >

+                     <Icon

+                         type="fa"

+                         size="1,5x"

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

+                     />{" "}

+                     {open ? textOpened : textClosed}

+                 </Button>

+                 <div className="ds-accordion-panel">{open && children}</div>

+             </div>

+         );

+     }

+ }

+ 

+ CustomCollapse.propTypes = {

+     children: PropTypes.any.isRequired,

+     /** Top-level custom class */

+     className: PropTypes.string,

+     /** Text for the link in opened state */

+     textOpened: PropTypes.string,

+     /** Text for the link in closed state */

+     textClosed: PropTypes.string

+ };

+ 

+ CustomCollapse.defaultProps = {

+     className: "",

+     textClosed: "Show Advanced Settings",

+     textOpened: "Hide Advanced Settings"

+ };

+ 

+ export default CustomCollapse;

@@ -1,27 +1,38 @@ 

  import React from "react";

  import PropTypes from "prop-types";

  import classNames from "classnames";

- import { noop, Spinner, FormControl } from "patternfly-react";

+ import {

+     noop,

+     Spinner,

+     Form,

+     ControlLabel,

+     FormControl

+ } from "patternfly-react";

  

  class CustomTableToolbar extends React.Component {

      render() {

          const {

+             children,

              className,

              placeholder,

              modelToSearch,

              searchFilterValue,

              handleValueChange,

+             disableLoadingSpinner,

              loading

          } = this.props;

  

-         const classes = classNames("form-group toolbar-pf-find", className);

+         const classes = classNames(

+             "form-group toolbar-pf-find ds-toolbar",

+             className

+         );

  

          return (

              <div className={classes}>

-                 <label>

-                     <div className="ds-float-left ds-right-indent">

-                         Search {modelToSearch}

-                     </div>

+                 <Form inline>

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

+                         Filter {modelToSearch}

+                     </ControlLabel>

                      <div className="ds-float-left">

                          <FormControl

                              type="text"

@@ -31,22 +42,27 @@ 

                              onChange={handleValueChange}

                          />

                      </div>

-                 </label>

-                 <div className="ds-float-right ds-right-indent">

-                     <Spinner loading={loading} size="md" />

-                 </div>

+                     <div className="toolbar-pf-action-right">{children}</div>

+                     <div className="toolbar-pf-action-right ds-right-indent">

+                         {!disableLoadingSpinner && (

+                             <Spinner loading={loading} size="md" />

+                         )}

+                     </div>

+                 </Form>

              </div>

          );

      }

  }

  

  CustomTableToolbar.propTypes = {

+     children: PropTypes.any,

      className: PropTypes.string,

      placeholder: PropTypes.string,

      modelToSearch: PropTypes.string,

      searchFilterValue: PropTypes.string,

      handleValueChange: PropTypes.func,

-     loading: PropTypes.bool

+     loading: PropTypes.bool,

+     disableLoadingSpinner: PropTypes.bool

  };

  

  CustomTableToolbar.defaultProps = {

@@ -55,7 +71,8 @@ 

      modelToSearch: "",

      searchFilterValue: "",

      handleValueChange: noop,

-     loading: false

+     loading: false,

+     disableLoadingSpinner: false

  };

  

  export default CustomTableToolbar;

@@ -0,0 +1,45 @@ 

+ import React from "react";

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

+ import PropTypes from "prop-types";

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

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

+ 

+ class AccountPolicy extends React.Component {

+     render() {

+         return (

+             <div>

+                 <PluginBasicConfig

+                     rows={this.props.rows}

+                     serverId={this.props.serverId}

+                     cn="Account Policy Plugin"

+                     pluginName="Account Policy"

+                     cmdName="accountpolicy"

+                     savePluginHandler={this.props.savePluginHandler}

+                     pluginListHandler={this.props.pluginListHandler}

+                     addNotification={this.props.addNotification}

+                     toggleLoadingHandler={this.props.toggleLoadingHandler}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ AccountPolicy.propTypes = {

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ AccountPolicy.defaultProps = {

+     rows: [],

+     serverId: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default AccountPolicy;

@@ -0,0 +1,45 @@ 

+ import React from "react";

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

+ import PropTypes from "prop-types";

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

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

+ 

+ class AttributeUniqueness extends React.Component {

+     render() {

+         return (

+             <div>

+                 <PluginBasicConfig

+                     rows={this.props.rows}

+                     serverId={this.props.serverId}

+                     cn="attribute uniqueness"

+                     pluginName="Attribute Uniqueness"

+                     cmdName="attruniq"

+                     savePluginHandler={this.props.savePluginHandler}

+                     pluginListHandler={this.props.pluginListHandler}

+                     addNotification={this.props.addNotification}

+                     toggleLoadingHandler={this.props.toggleLoadingHandler}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ AttributeUniqueness.propTypes = {

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ AttributeUniqueness.defaultProps = {

+     rows: [],

+     serverId: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default AttributeUniqueness;

@@ -0,0 +1,45 @@ 

+ import React from "react";

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

+ import PropTypes from "prop-types";

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

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

+ 

+ class AutoMembership extends React.Component {

+     render() {

+         return (

+             <div>

+                 <PluginBasicConfig

+                     rows={this.props.rows}

+                     serverId={this.props.serverId}

+                     cn="Auto Membership Plugin"

+                     pluginName="Auto Membership"

+                     cmdName="automember"

+                     savePluginHandler={this.props.savePluginHandler}

+                     pluginListHandler={this.props.pluginListHandler}

+                     addNotification={this.props.addNotification}

+                     toggleLoadingHandler={this.props.toggleLoadingHandler}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ AutoMembership.propTypes = {

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ AutoMembership.defaultProps = {

+     rows: [],

+     serverId: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default AutoMembership;

@@ -0,0 +1,45 @@ 

+ import React from "react";

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

+ import PropTypes from "prop-types";

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

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

+ 

+ class DNA extends React.Component {

+     render() {

+         return (

+             <div>

+                 <PluginBasicConfig

+                     rows={this.props.rows}

+                     serverId={this.props.serverId}

+                     cn="Distributed Numeric Assignment Plugin"

+                     pluginName="Distributed Numeric Assignment"

+                     cmdName="dna"

+                     savePluginHandler={this.props.savePluginHandler}

+                     pluginListHandler={this.props.pluginListHandler}

+                     addNotification={this.props.addNotification}

+                     toggleLoadingHandler={this.props.toggleLoadingHandler}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ DNA.propTypes = {

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ DNA.defaultProps = {

+     rows: [],

+     serverId: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default DNA;

@@ -0,0 +1,45 @@ 

+ import React from "react";

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

+ import PropTypes from "prop-types";

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

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

+ 

+ class LinkedAttributes extends React.Component {

+     render() {

+         return (

+             <div>

+                 <PluginBasicConfig

+                     rows={this.props.rows}

+                     serverId={this.props.serverId}

+                     cn="Linked Attributes"

+                     pluginName="Linked Attributes"

+                     cmdName="linkedattr"

+                     savePluginHandler={this.props.savePluginHandler}

+                     pluginListHandler={this.props.pluginListHandler}

+                     addNotification={this.props.addNotification}

+                     toggleLoadingHandler={this.props.toggleLoadingHandler}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ LinkedAttributes.propTypes = {

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ LinkedAttributes.defaultProps = {

+     rows: [],

+     serverId: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default LinkedAttributes;

@@ -0,0 +1,45 @@ 

+ import React from "react";

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

+ import PropTypes from "prop-types";

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

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

+ 

+ class ManagedEntries extends React.Component {

+     render() {

+         return (

+             <div>

+                 <PluginBasicConfig

+                     rows={this.props.rows}

+                     serverId={this.props.serverId}

+                     cn="Managed Entries"

+                     pluginName="Managed Entries"

+                     cmdName="managedentries"

+                     savePluginHandler={this.props.savePluginHandler}

+                     pluginListHandler={this.props.pluginListHandler}

+                     addNotification={this.props.addNotification}

+                     toggleLoadingHandler={this.props.toggleLoadingHandler}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ ManagedEntries.propTypes = {

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ ManagedEntries.defaultProps = {

+     rows: [],

+     serverId: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default ManagedEntries;

@@ -0,0 +1,45 @@ 

+ import React from "react";

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

+ import PropTypes from "prop-types";

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

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

+ 

+ class MemberOf extends React.Component {

+     render() {

+         return (

+             <div>

+                 <PluginBasicConfig

+                     rows={this.props.rows}

+                     serverId={this.props.serverId}

+                     cn="MemberOf Plugin"

+                     pluginName="MemberOf"

+                     cmdName="memberof"

+                     savePluginHandler={this.props.savePluginHandler}

+                     pluginListHandler={this.props.pluginListHandler}

+                     addNotification={this.props.addNotification}

+                     toggleLoadingHandler={this.props.toggleLoadingHandler}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ MemberOf.propTypes = {

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ MemberOf.defaultProps = {

+     rows: [],

+     serverId: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default MemberOf;

@@ -0,0 +1,45 @@ 

+ import React from "react";

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

+ import PropTypes from "prop-types";

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

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

+ 

+ class PassthroughAuthentication extends React.Component {

+     render() {

+         return (

+             <div>

+                 <PluginBasicConfig

+                     rows={this.props.rows}

+                     serverId={this.props.serverId}

+                     cn="Pass Through Authentication"

+                     pluginName="Pass Through Authentication"

+                     cmdName="passthroughauth"

+                     savePluginHandler={this.props.savePluginHandler}

+                     pluginListHandler={this.props.pluginListHandler}

+                     addNotification={this.props.addNotification}

+                     toggleLoadingHandler={this.props.toggleLoadingHandler}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ PassthroughAuthentication.propTypes = {

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ PassthroughAuthentication.defaultProps = {

+     rows: [],

+     serverId: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default PassthroughAuthentication;

@@ -0,0 +1,334 @@ 

+ import cockpit from "cockpit";

+ import React from "react";

+ import {

+     Button,

+     Row,

+     Col,

+     Form,

+     Switch,

+     noop,

+     FormGroup,

+     FormControl,

+     ControlLabel

+ } from "patternfly-react";

+ import PropTypes from "prop-types";

+ import CustomCollapse from "../customCollapse.jsx";

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

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

+ 

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

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

+ 

+         this.state = {

+             disableSwitch: false,

+             currentPluginEnabled: true,

+             currentPluginType: "",

+             currentPluginPath: "",

+             currentPluginInitfunc: "",

+             currentPluginId: "",

+             currentPluginVendor: "",

+             currentPluginVersion: "",

+             currentPluginDescription: ""

+         };

+     }

+ 

+     handleFieldChange(e) {

+         this.setState({

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

+         });

+     }

+ 

+     handleSwitchChange(value) {

+         const {

+             pluginName,

+             cmdName,

+             serverId,

+             pluginListHandler,

+             addNotification,

+             toggleLoadingHandler

+         } = this.props;

+         const new_status = this.state.currentPluginEnabled

+             ? "disable"

+             : "enable";

+         const cmd = [

+             "dsconf",

+             "-j",

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

+             "plugin",

+             cmdName,

+             new_status

+         ];

+ 

+         toggleLoadingHandler();

+         this.setState({ disableSwitch: true });

+         log_cmd(

+             "handleSwitchChange",

+             "Switch plugin states from the plugin tab",

+             cmd

+         );

+         cockpit

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

+                 .done(content => {

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

+                     pluginListHandler();

+                     addNotification(

+                         "success",

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

+                     );

+                     toggleLoadingHandler();

+                 })

+                 .fail(err => {

+                     addNotification(

+                         "error",

+                         `Error during ${pluginName} plugin modification - ${err}`

+                     );

+                     toggleLoadingHandler();

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

+                 });

+     }

+ 

+     updateFields() {

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

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

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

+             );

+ 

+             this.setState({

+                 currentPluginType: pluginRow["nsslapd-pluginType"][0],

+                 currentPluginPath: pluginRow["nsslapd-pluginPath"][0],

+                 currentPluginInitfunc: pluginRow["nsslapd-pluginInitfunc"][0],

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

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

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

+                 currentPluginDescription:

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

+             });

+         }

+         this.updateSwitch();

+     }

+ 

+     updateSwitch() {

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

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

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

+             );

+ 

+             var pluginEnabled;

+             if (pluginRow["nsslapd-pluginEnabled"][0] === "on") {

+                 pluginEnabled = true;

+             } else if (pluginRow["nsslapd-pluginEnabled"][0] === "off") {

+                 pluginEnabled = false;

+             } else {

+                 console.error(

+                     "openPluginModal failed",

+                     "wrong nsslapd-pluginenabled attribute value",

+                     pluginRow["nsslapd-pluginEnabled"][0]

+                 );

+             }

+ 

+             this.setState({

+                 currentPluginEnabled: pluginEnabled,

+                 disableSwitch: false

+             });

+         }

+     }

+ 

+     render() {

+         const {

+             currentPluginEnabled,

+             currentPluginType,

+             currentPluginPath,

+             currentPluginInitfunc,

+             currentPluginId,

+             currentPluginVendor,

+             currentPluginVersion,

+             currentPluginDescription,

+             disableSwitch

+         } = this.state;

+ 

+         const modalFieldsCol1 = {

+             currentPluginType: this.state.currentPluginType,

+             currentPluginPath: this.state.currentPluginPath,

+             currentPluginInitfunc: this.state.currentPluginInitfunc,

+             currentPluginId: this.state.currentPluginId

+         };

+         const modalFieldsCol2 = {

+             currentPluginVendor: this.state.currentPluginVendor,

+             currentPluginVersion: this.state.currentPluginVersion,

+             currentPluginDescription: this.state.currentPluginDescription

+         };

+         return (

+             <div>

+                 <Form inline>

+                     <Row>

+                         <Col sm={6}>

+                             <h3>

+                                 <ControlLabel className="ds-plugin-tab-header">

+                                     {this.props.pluginName}

+                                 </ControlLabel>

+                             </h3>

+                         </Col>

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

+                             <FormGroup

+                                 key="switchPluginStatus"

+                                 controlId="switchPluginStatus"

+                             >

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

+                                     Status

+                                 </ControlLabel>

+                                 <Switch

+                                     bsSize="normal"

+                                     title="normal"

+                                     id="bsSize-example"

+                                     value={currentPluginEnabled}

+                                     onChange={() =>

+                                         this.handleSwitchChange(

+                                             currentPluginEnabled

+                                         )

+                                     }

+                                     animate={false}

+                                     disabled={disableSwitch}

+                                 />

+                             </FormGroup>

+                         </Col>

+                     </Row>

+                 </Form>

+                 {this.props.children}

+                 <CustomCollapse>

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

+                                     )

+                                 )}

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

+                                     )

+                                 )}

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

+             </div>

+         );

+     }

+ }

+ 

+ PluginBasicConfig.propTypes = {

+     children: PropTypes.any.isRequired,

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     cn: PropTypes.string,

+     pluginName: PropTypes.string,

+     cmdName: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ PluginBasicConfig.defaultProps = {

+     rows: [],

+     serverId: "",

+     cn: "",

+     pluginName: "",

+     cmdName: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default PluginBasicConfig;

@@ -1,4 +1,3 @@ 

- import cockpit from "cockpit";

  import React from "react";

  import {

      Modal,

@@ -13,77 +12,9 @@ 

      Col

  } from "patternfly-react";

  import PropTypes from "prop-types";

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

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

  

- var cmd;

- 

  class PluginEditModal extends React.Component {

-     constructor(props) {

-         super(props);

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

-     }

- 

-     savePlugin() {

-         const {

-             currentPluginName,

-             currentPluginType,

-             currentPluginEnabled,

-             currentPluginPath,

-             currentPluginInitfunc,

-             currentPluginId,

-             currentPluginVendor,

-             currentPluginVersion,

-             currentPluginDescription,

-             pluginListHandler,

-             closeHandler,

-             addNotification

-         } = this.props;

-         cmd = [

-             "dsconf",

-             "-j",

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

-             "plugin",

-             "edit",

-             currentPluginName,

-             "--enabled",

-             currentPluginEnabled ? "on" : "off",

-             "--type",

-             currentPluginType,

-             "--path",

-             currentPluginPath,

-             "--initfunc",

-             currentPluginInitfunc,

-             "--id",

-             currentPluginId,

-             "--vendor",

-             currentPluginVendor,

-             "--version",

-             currentPluginVersion,

-             "--description",

-             currentPluginDescription

-         ];

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

-         cockpit

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

-                 .done(content => {

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

-                     addNotification(

-                         "success",

-                         `Plugin ${currentPluginName} was successfully modified`

-                     );

-                     pluginListHandler();

-                     closeHandler();

-                 })

-                 .fail(err => {

-                     addNotification(

-                         "error",

-                         `Error during plugin ${currentPluginName} modification - ${err}`

-                     );

-                     closeHandler();

-                 });

-     }

- 

      render() {

          const modalFields = {

              currentPluginType: this.props.currentPluginType,

@@ -99,8 +30,16 @@ 

              closeHandler,

              currentPluginName,

              currentPluginEnabled,

+             currentPluginType,

+             currentPluginPath,

+             currentPluginInitfunc,

+             currentPluginId,

+             currentPluginVendor,

+             currentPluginVersion,

+             currentPluginDescription,

              handleChange,

-             handleSwitchChange

+             handleSwitchChange,

+             savePluginHandler

          } = this.props;

          return (

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

@@ -132,7 +71,7 @@ 

                                      <Switch

                                          bsSize="normal"

                                          title="normal"

-                                         id="bsSize-example"

+                                         id="pluginEnableSwitch"

                                          value={currentPluginEnabled}

                                          onChange={() =>

                                              handleSwitchChange(

@@ -171,7 +110,20 @@ 

                          >

                              Cancel

                          </Button>

-                         <Button bsStyle="primary" onClick={this.savePlugin}>

+                         <Button

+                             bsStyle="primary"

+                             onClick={() => savePluginHandler({

+                                 name: currentPluginName,

+                                 enabled: currentPluginEnabled,

+                                 type: currentPluginType,

+                                 path: currentPluginPath,

+                                 initfunc: currentPluginInitfunc,

+                                 id: currentPluginId,

+                                 vendor: currentPluginVendor,

+                                 version: currentPluginVersion,

+                                 description: currentPluginDescription

+                             })}

+                         >

                              Save

                          </Button>

                      </Modal.Footer>

@@ -193,9 +145,8 @@ 

      currentPluginVendor: PropTypes.string,

      currentPluginVersion: PropTypes.string,

      currentPluginDescription: PropTypes.string,

-     serverId: PropTypes.string,

      closeHandler: PropTypes.func,

-     pluginListHandler: PropTypes.func,

+     savePluginHandler: PropTypes.func,

      showModal: PropTypes.bool

  };

  

@@ -211,9 +162,8 @@ 

      currentPluginVendor: "",

      currentPluginVersion: "",

      currentPluginDescription: "",

-     serverId: "",

      closeHandler: noop,

-     pluginListHandler: noop,

+     savePluginHandler: noop,

      showModal: false

  };

  

@@ -3,6 +3,7 @@ 

      PaginationRow,

      paginate,

      Table,

+     Button,

      noop,

      actionHeaderCellFormatter,

      customHeaderFormattersDefinition,

@@ -54,8 +55,6 @@ 

          this.customHeaderFormatters = customHeaderFormattersDefinition;

  

          this.handleSearchValueChange = this.handleSearchValueChange.bind(this);

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

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

          this.totalPages = this.totalPages.bind(this);

          this.onPageInput = this.onPageInput.bind(this);

          this.onSubmit = this.onSubmit.bind(this);

@@ -69,7 +68,6 @@ 

          this.filteredSearchedRows = this.filteredSearchedRows.bind(this);

  

          this.state = {

-             dropdownShown: false,

              searchFilterValue: "",

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

              // Sort the first column in an ascending way by default.

@@ -161,8 +159,8 @@ 

                          formatters: [

                              (value, { rowData }) => {

                                  return [

-                                     <Table.Actions key="0">

-                                         <Table.Button

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

+                                         <Button

                                              onClick={() => {

                                                  this.props.loadModalHandler(

                                                      rowData

@@ -170,8 +168,8 @@ 

                                              }}

                                          >

                                              Edit Plugin

-                                         </Table.Button>

-                                     </Table.Actions>

+                                         </Button>

+                                     </td>

                                  ];

                              }

                          ]

@@ -195,18 +193,6 @@ 

          this.setState({ searchFilterValue: event.target.value });

      }

  

-     hideDropdown() {

-         const { onExit } = this.props;

-         this.setState({ dropdownShown: false });

-         onExit();

-     }

- 

-     toggleDropdownShown() {

-         this.setState(prevState => ({

-             dropdownShown: !prevState.dropdownShown

-         }));

-     }

- 

      totalPages() {

          const { rows } = this.props;

          const { perPage } = this.state.pagination;

@@ -309,7 +295,7 @@ 

                      modelToSearch="Plugins"

                      searchFilterValue={this.state.searchFilterValue}

                      handleValueChange={this.handleSearchValueChange}

-                     loading={this.props.loading}

+                     disableLoadingSpinner

                  />

                  <Table.PfProvider

                      className="display ds-repl-table"

@@ -337,7 +323,9 @@ 

                      <Table.Body

                          rows={sortedPaginatedRows.rows}

                          rowKey="cn"

-                         onRow={this.onRow}

+                         onRow={() => ({

+                             role: "row"

+                         })}

                      />

                  </Table.PfProvider>

                  <PaginationRow

@@ -364,13 +352,11 @@ 

  PluginTable.propTypes = {

      rows: PropTypes.array,

      loadModalHandler: PropTypes.func,

-     loading: PropTypes.bool

  };

  

  PluginTable.defaultProps = {

      rows: [],

      loadModalHandler: noop,

-     loading: false

  };

  

  export default PluginTable;

@@ -0,0 +1,45 @@ 

+ import React from "react";

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

+ import PropTypes from "prop-types";

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

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

+ 

+ class ReferentialIntegrity extends React.Component {

+     render() {

+         return (

+             <div>

+                 <PluginBasicConfig

+                     rows={this.props.rows}

+                     serverId={this.props.serverId}

+                     cn="referential integrity postoperation"

+                     pluginName="Referential Integrity"

+                     cmdName="referint"

+                     savePluginHandler={this.props.savePluginHandler}

+                     pluginListHandler={this.props.pluginListHandler}

+                     addNotification={this.props.addNotification}

+                     toggleLoadingHandler={this.props.toggleLoadingHandler}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ ReferentialIntegrity.propTypes = {

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ ReferentialIntegrity.defaultProps = {

+     rows: [],

+     serverId: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default ReferentialIntegrity;

@@ -0,0 +1,45 @@ 

+ import React from "react";

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

+ import PropTypes from "prop-types";

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

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

+ 

+ class RetroChangelog extends React.Component {

+     render() {

+         return (

+             <div>

+                 <PluginBasicConfig

+                     rows={this.props.rows}

+                     serverId={this.props.serverId}

+                     cn="Retro Changelog Plugin"

+                     pluginName="Retro Changelog"

+                     cmdName="retrochangelog"

+                     savePluginHandler={this.props.savePluginHandler}

+                     pluginListHandler={this.props.pluginListHandler}

+                     addNotification={this.props.addNotification}

+                     toggleLoadingHandler={this.props.toggleLoadingHandler}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ RetroChangelog.propTypes = {

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ RetroChangelog.defaultProps = {

+     rows: [],

+     serverId: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default RetroChangelog;

@@ -0,0 +1,45 @@ 

+ import React from "react";

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

+ import PropTypes from "prop-types";

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

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

+ 

+ class RootDNAccessControl extends React.Component {

+     render() {

+         return (

+             <div>

+                 <PluginBasicConfig

+                     rows={this.props.rows}

+                     serverId={this.props.serverId}

+                     cn="RootDN Access Control"

+                     pluginName="RootDN Access Control"

+                     cmdName="rootdn"

+                     savePluginHandler={this.props.savePluginHandler}

+                     pluginListHandler={this.props.pluginListHandler}

+                     addNotification={this.props.addNotification}

+                     toggleLoadingHandler={this.props.toggleLoadingHandler}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ RootDNAccessControl.propTypes = {

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ RootDNAccessControl.defaultProps = {

+     rows: [],

+     serverId: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default RootDNAccessControl;

@@ -0,0 +1,45 @@ 

+ import React from "react";

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

+ import PropTypes from "prop-types";

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

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

+