#50088 Issue 50041 - Add basic plugin UI/CLI wrappers
Closed 3 years ago by spichugi. Opened 5 years 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";

+ 

+ class USN extends React.Component {

+     render() {

+         return (

+             <div>

+                 <PluginBasicConfig

+                     rows={this.props.rows}

+                     serverId={this.props.serverId}

+                     cn="USN"

+                     pluginName="Update Sequence Numbers"

+                     cmdName="usn"

+                     savePluginHandler={this.props.savePluginHandler}

+                     pluginListHandler={this.props.pluginListHandler}

+                     addNotification={this.props.addNotification}

+                     toggleLoadingHandler={this.props.toggleLoadingHandler}

+                 />

+             </div>

+         );

+     }

+ }

+ 

+ USN.propTypes = {

+     rows: PropTypes.array,

+     serverId: PropTypes.string,

+     savePluginHandler: PropTypes.func,

+     pluginListHandler: PropTypes.func,

+     addNotification: PropTypes.func,

+     toggleLoadingHandler: PropTypes.func

+ };

+ 

+ USN.defaultProps = {

+     rows: [],

+     serverId: "",

+     savePluginHandler: noop,

+     pluginListHandler: noop,

+     addNotification: noop,

+     toggleLoadingHandler: noop

+ };

+ 

+ export default USN;

@@ -2,8 +2,21 @@ 

  import React from "react";

  import PropTypes from "prop-types";

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

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

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

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

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

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

+ import AutoMembership from "./lib/plugins/autoMembership.jsx";

+ import DNA from "./lib/plugins/dna.jsx";

+ import LinkedAttributes from "./lib/plugins/linkedAttributes.jsx";

+ import ManagedEntries from "./lib/plugins/managedEntries.jsx";

+ import MemberOf from "./lib/plugins/memberOf.jsx";

+ import PassthroughAuthentication from "./lib/plugins/passthroughAuthentication.jsx";

+ import ReferentialIntegrity from "./lib/plugins/referentialIntegrity.jsx";

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

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

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

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

  import "./css/ds.css";

  
@@ -12,6 +25,12 @@ 

  export class Plugins extends React.Component {

      componentWillMount() {

          this.pluginList();

+         this.setState(prevState => ({

+             pluginTabs: {

+                 ...prevState.pluginTabs,

+                 basicConfig: true

+             }

+         }));

      }

  

      componentDidUpdate(prevProps) {
@@ -30,13 +49,15 @@ 

          this.pluginList = this.pluginList.bind(this);

          this.removeNotification = this.removeNotification.bind(this);

          this.addNotification = this.addNotification.bind(this);

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

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

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

  

          this.state = {

              notifications: [],

              loading: false,

              showPluginModal: false,

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

-             // rows and row selection state

+             currentPluginTab: "",

              rows: [],

  

              // Plugin attributes
@@ -52,6 +73,10 @@ 

          };

      }

  

+     onChangeTab(event) {

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

+     }

+ 

      addNotification(type, message, timerdelay, persistent) {

          this.setState(prevState => ({

              notifications: [
@@ -93,6 +118,12 @@ 

          });

      }

  

+     toggleLoading() {

+         this.setState(prevState => ({

+             loading: !prevState.loading

+         }));

+     }

+ 

      openPluginModal(rowData) {

          var pluginEnabled;

          if (rowData["nsslapd-pluginEnabled"][0] === "on") {
@@ -121,9 +152,6 @@ 

      }

  

      pluginList() {

-         this.setState({

-             loading: true

-         });

          cmd = [

              "dsconf",

              "-j",
@@ -131,36 +159,294 @@ 

              "plugin",

              "list"

          ];

+         this.toggleLoading();

          log_cmd("pluginList", "Get plugins for table rows", cmd);

          cockpit

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

                  .done(content => {

                      var myObject = JSON.parse(content);

                      this.setState({

-                         rows: myObject.items,

-                         loading: false

+                         rows: myObject.items

                      });

+                     this.toggleLoading();

                  })

                  .fail(err => {

                      if (err != 0) {

                          console.error("pluginList failed", err);

                      }

-                     this.setState({

-                         loading: false

-                     });

+                     this.toggleLoading();

+                 });

+     }

+ 

+     savePlugin(data) {

+         cmd = [

+             "dsconf",

+             "-j",

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

+             "plugin",

+             "edit",

+             data.name,

+             "--type",

+             data.type,

+             "--path",

+             data.path,

+             "--initfunc",

+             data.initfunc,

+             "--id",

+             data.id,

+             "--vendor",

+             data.vendor,

+             "--version",

+             data.version,

+             "--description",

+             data.description

+         ];

+ 

+         if ("enabled" in data) {

+             cmd = [...cmd, "--enabled", data.enabled ? "on" : "off"];

+         }

+ 

+         this.toggleLoading();

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

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

+                     );

+                     this.closePluginModal();

+                     this.toggleLoading();

                  });

      }

  

      render() {

+         const selectPlugins = {

+             allPlugins: {

+                 name: "All Plugins",

+                 component: (

+                     <PluginTable

+                         rows={this.state.rows}

+                         loadModalHandler={this.openPluginModal}

+                     />

+                 )

+             },

+             accountPolicy: {

+                 name: "Account Policy",

+                 component: (

+                     <AccountPolicy

+                         rows={this.state.rows}

+                         serverId={this.props.serverId}

+                         savePluginHandler={this.savePlugin}

+                         pluginListHandler={this.pluginList}

+                         addNotification={this.addNotification}

+                         toggleLoadingHandler={this.toggleLoading}

+                     />

+                 )

+             },

+             attributeUniqueness: {

+                 name: "Attribute Uniqueness",

+                 component: (

+                     <AttributeUniqueness

+                         rows={this.state.rows}

+                         serverId={this.props.serverId}

+                         savePluginHandler={this.savePlugin}

+                         pluginListHandler={this.pluginList}

+                         addNotification={this.addNotification}

+                         toggleLoadingHandler={this.toggleLoading}

+                     />

+                 )

+             },

+             autoMembership: {

+                 name: "Auto Membership",

+                 component: (

+                     <AutoMembership

+                         rows={this.state.rows}

+                         serverId={this.props.serverId}

+                         savePluginHandler={this.savePlugin}

+                         pluginListHandler={this.pluginList}

+                         addNotification={this.addNotification}

+                         toggleLoadingHandler={this.toggleLoading}

+                     />

+                 )

+             },

+             dna: {

+                 name: "DNA",

+                 component: (

+                     <DNA

+                         rows={this.state.rows}

+                         serverId={this.props.serverId}

+                         savePluginHandler={this.savePlugin}

+                         pluginListHandler={this.pluginList}

+                         addNotification={this.addNotification}

+                         toggleLoadingHandler={this.toggleLoading}

+                     />

+                 )

+             },

+             linkedAttributes: {

+                 name: "Linked Attributes",

+                 component: (

+                     <LinkedAttributes

+                         rows={this.state.rows}

+                         serverId={this.props.serverId}

+                         savePluginHandler={this.savePlugin}

+                         pluginListHandler={this.pluginList}

+                         addNotification={this.addNotification}

+                         toggleLoadingHandler={this.toggleLoading}

+                     />

+                 )

+             },

+             memberOf: {

+                 name: "MemberOf",

+                 component: (

+                     <MemberOf

+                         rows={this.state.rows}

+                         serverId={this.props.serverId}

+                         savePluginHandler={this.savePlugin}

+                         pluginListHandler={this.pluginList}

+                         addNotification={this.addNotification}

+                         toggleLoadingHandler={this.toggleLoading}

+                     />

+                 )

+             },

+             managedEntries: {

+                 name: "Managed Entries",

+                 component: (

+                     <ManagedEntries

+                         rows={this.state.rows}

+                         serverId={this.props.serverId}

+                         savePluginHandler={this.savePlugin}

+                         pluginListHandler={this.pluginList}

+                         addNotification={this.addNotification}

+                         toggleLoadingHandler={this.toggleLoading}

+                     />

+                 )

+             },

+             passthroughAuthentication: {

+                 name: "Passthrough Authentication",

+                 component: (

+                     <PassthroughAuthentication

+                         rows={this.state.rows}

+                         serverId={this.props.serverId}

+                         savePluginHandler={this.savePlugin}

+                         pluginListHandler={this.pluginList}

+                         addNotification={this.addNotification}

+                         toggleLoadingHandler={this.toggleLoading}

+                     />

+                 )

+             },

+             referentialIntegrity: {

+                 name: "Referential Integrity",

+                 component: (

+                     <ReferentialIntegrity

+                         rows={this.state.rows}

+                         serverId={this.props.serverId}

+                         savePluginHandler={this.savePlugin}

+                         pluginListHandler={this.pluginList}

+                         addNotification={this.addNotification}

+                         toggleLoadingHandler={this.toggleLoading}

+                     />

+                 )

+             },

+             retroChangelog: {

+                 name: "Retro Changelog",

+                 component: (

+                     <RetroChangelog

+                         rows={this.state.rows}

+                         serverId={this.props.serverId}

+                         savePluginHandler={this.savePlugin}

+                         pluginListHandler={this.pluginList}

+                         addNotification={this.addNotification}

+                         toggleLoadingHandler={this.toggleLoading}

+                     />

+                 )

+             },

+             rootDnaAccessControl: {

+                 name: "RootDN Access Control",

+                 component: (

+                     <RootDNAccessControl

+                         rows={this.state.rows}

+                         serverId={this.props.serverId}

+                         savePluginHandler={this.savePlugin}

+                         pluginListHandler={this.pluginList}

+                         addNotification={this.addNotification}

+                         toggleLoadingHandler={this.toggleLoading}

+                     />

+                 )

+             },

+             usn: {

+                 name: "USN",

+                 component: (

+                     <USN

+                         rows={this.state.rows}

+                         serverId={this.props.serverId}

+                         savePluginHandler={this.savePlugin}

+                         pluginListHandler={this.pluginList}

+                         addNotification={this.addNotification}

+                         toggleLoadingHandler={this.toggleLoading}

+                     />

+                 )

+             }

+         };

          return (

              <div className="container-fluid">

                  <NotificationController

                      notifications={this.state.notifications}

                      removeNotificationAction={this.removeNotification}

                  />

-                 <h2>Plugins</h2>

+                 <Row className="clearfix">

+                     <Col sm={1}>

+                         <h2>Plugins</h2>

+                     </Col>

+                     <Col sm={10}>

+                         <Spinner

+                             className="ds-float-left ds-plugin-spinner"

+                             loading={this.state.loading}

+                             size="md"

+                         />

+                     </Col>

+                 </Row>

+                 <hr />

+                 <Tab.Container

+                     id="left-tabs-example"

+                     defaultActiveKey={Object.keys(selectPlugins)[0]}

+                 >

+                     <Row className="clearfix">

+                         <Col sm={2}>

+                             <Nav bsStyle="pills" stacked>

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

+                                     )

+                                 )}

+                             </Tab.Content>

+                         </Col>

+                     </Row>

+                 </Tab.Container>

                  <PluginEditModal

-                     addNotification={this.addNotification}

                      handleChange={this.handleFieldChange}

                      handleSwitchChange={this.handleSwitchChange}

                      currentPluginName={this.state.currentPluginName}
@@ -174,15 +460,9 @@ 

                      currentPluginDescription={

                          this.state.currentPluginDescription

                      }

-                     serverId={this.props.serverId}

                      closeHandler={this.closePluginModal}

-                     pluginListHandler={this.pluginList}

                      showModal={this.state.showPluginModal}

-                 />

-                 <PluginTable

-                     rows={this.state.rows}

-                     loadModalHandler={this.openPluginModal}

-                     loading={this.state.loading}

+                     savePluginHandler={this.savePlugin}

                  />

              </div>

          );

@@ -97,7 +97,6 @@ 

    $('#frac-exclude-list').find('option').remove();

    $('#frac-exclude-tot-list').find('option').remove();

    $('#frac-strip-list').find('option').remove();

-   load_schema_objects_to_select('attributetypes', 'select-attr-list');

    $("#select-attr-list").prop('selectedIndex',-1);

    $("#init-options").prop("selectedIndex", 0);

    $("#init-agmt-dropdown").show();
@@ -332,6 +331,17 @@ 

        repl_agmt_table.clear().draw();

      });

    } // suffix

+ 

+   // Load fractional replication agreement attr list here

+   var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket', 'schema', 'list'];

+   log_cmd('get_and_set_repl_agmts', 'Get all schema objects', cmd);

+   cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(schema_data) {

+     var schema_json = JSON.parse(schema_data);

+     load_schema_objects_to_select('attributetypes', 'select-attr-list', schema_json);

+   }).fail(function(oc_data) {

+       console.log("Get all schema objects failed: " + oc_data.message);

+       check_inst_alive(1);

+   });

  }

  

  

@@ -501,6 +501,25 @@ 

            });

          });

  

+         document.getElementById("remove-server-btn").addEventListener("click", function() {

+           popup_confirm("Are you sure you want to this remove instance: <b>" + server_id + "</b>", "Confirmation", function (yes) {

+             if (yes) {

+               var cmd = [DSCTL, server_id, "remove", "--do-it"];

+               $("#ds-remove-inst").html("<span class=\"spinner spinner-xs spinner-inline\"></span> Removing instance <b>" + server_id + "</b>...");

+               $("#remove-instance-form").modal('toggle');

+               log_cmd('#remove-server-btn (click)', 'Remove instance', cmd);

+               cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {

+                 $("#remove-instance-form").modal('toggle');

+                 popup_success("Instance has been deleted");

+                 get_insts();

+               }).fail(function(data) {

+                 $("#remove-instance-form").modal('toggle');

+                 popup_err("Failed to remove instance", data.message);

+               });

+             }

+           });

+         });

+ 

          clearInterval(init_config);

        }

    }, 250);
@@ -1286,7 +1305,7 @@ 

                             "Backups are written to the server's backup directory (nsslapd-bakdir)");

          return;

        }

-       

+ 

        // First check if backup name is already used

        var check_cmd = [DSCTL, '-j', server_id, 'backups'];

        log_cmd('#restore-server-btn (click)', 'Restore server instance', check_cmd);
@@ -1422,27 +1441,6 @@ 

        });

      });

  

- 

-     // Remove instance

-     $("#remove-server-btn").on("click", function () {

-       popup_confirm("Are you sure you want to this remove instance: <b>" + server_id + "</b>", "Confirmation", function (yes) {

-         if (yes) {

-           var cmd = [DSCTL, server_id, "remove", "--do-it"];

-           $("#ds-remove-inst").html("<span class=\"spinner spinner-xs spinner-inline\"></span> Removing instance <b>" + server_id + "</b>...");

-           $("#remove-instance-form").modal('toggle');

-           log_cmd('#remove-server-btn (click)', 'Remove instance', cmd);

-           cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {

-             $("#remove-instance-form").modal('toggle');

-             popup_success("Instance has been deleted");

-             get_insts();

-           }).fail(function(data) {

-             $("#remove-instance-form").modal('toggle');

-             popup_err("Failed to remove instance", data.message);

-           });

-         }

-       });

-     });

- 

      // Create instance form

      $("#create-server-btn").on("click", function() {

        clear_inst_form();

file modified
-12
@@ -28,12 +28,6 @@ 

  from lib389.cli_conf import backup as cli_backup

  from lib389.cli_conf import replication as cli_replication

  from lib389.cli_conf import chaining as cli_chaining

- 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

- from lib389.cli_conf.plugins import whoami as cli_whoami

- from lib389.cli_conf.plugins import referint as cli_referint

- from lib389.cli_conf.plugins import automember as cli_automember

  from lib389.cli_base import disconnect_instance, connect_instance

  from lib389.cli_base.dsrc import dsrc_to_ldap, dsrc_arg_concat

  from lib389.cli_base import setup_script_logger
@@ -78,23 +72,17 @@ 

  

  subparsers = parser.add_subparsers(help="resources to act upon")

  

- cli_automember.create_parser(subparsers)

  cli_backend.create_parser(subparsers)

  cli_backup.create_parser(subparsers)

  cli_chaining.create_parser(subparsers)

  cli_config.create_parser(subparsers)

  cli_directory_manager.create_parsers(subparsers)

  cli_health.create_parser(subparsers)

- cli_memberof.create_parser(subparsers)

  cli_plugin.create_parser(subparsers)

  cli_pwpolicy.create_parser(subparsers)

- cli_referint.create_parser(subparsers)

  cli_replication.create_parser(subparsers)

- cli_rootdn_ac.create_parser(subparsers)

  cli_sasl.create_parser(subparsers)

  cli_schema.create_parser(subparsers)

- cli_usn.create_parser(subparsers)

- cli_whoami.create_parser(subparsers)

  

  argcomplete.autocomplete(parser)

  

@@ -1,8 +1,54 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2016 Red Hat, Inc.

+ # Copyright (C) 2018 Red Hat, Inc.

  # All rights reserved.

  #

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

  # See LICENSE for details.

  # --- END COPYRIGHT BLOCK ---

  

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

+     """Display plugin configuration."""

+     plugin = args.plugin_cls(inst)

+     print(plugin.display())

+ 

+ 

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

+     plugin = args.plugin_cls(inst)

+     if plugin.status():

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

+     else:

+         plugin.enable()

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

+     else:

+         plugin.disable()

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

+     else:

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

+ 

+ 

+ def add_generic_plugin_parsers(subparser, plugin_cls):

+     show_parser = subparser.add_parser('show', help='display plugin configuration')

+     show_parser.set_defaults(func=generic_show, plugin_cls=plugin_cls)

+ 

+     enable_parser = subparser.add_parser('enable', help='enable plugin')

+     enable_parser.set_defaults(func=generic_enable, plugin_cls=plugin_cls)

+ 

+     disable_parser = subparser.add_parser('disable', help='disable plugin')

+     disable_parser.set_defaults(func=generic_disable, plugin_cls=plugin_cls)

+ 

+     status_parser = subparser.add_parser('status', help='display plugin status')

+     status_parser.set_defaults(func=generic_status, plugin_cls=plugin_cls)

+ 

+ 

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2016 Red Hat, Inc.

+ # Copyright (C) 2018 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -10,16 +10,22 @@ 

  from lib389.plugins import Plugin, Plugins

  from lib389.utils import ensure_dict_str

  from lib389.cli_base import (

-     _generic_list,

      _generic_get,

-     _generic_get_dn,

-     _generic_create,

-     _generic_delete,

      _get_arg,

-     _get_args,

-     _get_attributes,

-     _warn,

-     )

+ )

+ 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

+ from lib389.cli_conf.plugins import whoami as cli_whoami

+ from lib389.cli_conf.plugins import referint as cli_referint

+ from lib389.cli_conf.plugins import accountpolicy as cli_accountpolicy

+ from lib389.cli_conf.plugins import attruniq as cli_attruniq

+ from lib389.cli_conf.plugins import dna as cli_dna

+ from lib389.cli_conf.plugins import linkedattr as cli_linkedattr

+ from lib389.cli_conf.plugins import managedentries as cli_managedentries

+ from lib389.cli_conf.plugins import passthroughauth as cli_passthroughauth

+ from lib389.cli_conf.plugins import retrochangelog as cli_retrochangelog

+ from lib389.cli_conf.plugins import automember as cli_automember

  

  SINGULAR = Plugin

  MANY = Plugins
@@ -44,7 +50,7 @@ 

              if args and args.json:

                  json_result['items'].append(plugin_data)

              else:

-                 plugin_log.info(plugin_data)

+                 plugin_log.info(plugin_data["cn"][0])

          if args and args.json:

              print(json.dumps(json_result))

  
@@ -54,11 +60,6 @@ 

      _generic_get(inst, basedn, log.getChild('plugin_get'), MANY, rdn, args)

  

  

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

-     dn = _get_arg(args.dn, msg="Enter dn to retrieve")

-     _generic_get_dn(inst, basedn, log.getChild('plugin_get_dn'), MANY, dn, args)

- 

- 

  def vaidate_args(plugin, attr_arg_list):

      """Check if the attribute needs to be changed

      Return mods for the replace_many() method
@@ -97,100 +98,33 @@ 

          raise ValueError("Nothing to change")

  

  

- # Plugin enable

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

-     dn = _get_arg(args.dn, msg="Enter plugin dn to enable")

-     mc = MANY(inst, basedn)

-     o = mc.get(dn=dn)

-     if o.status():

-         print('Plugin already enabled')

-     else:

-         o.enable()

-         print('Enabled plugin')

- 

- 

- # Plugin disable

- def plugin_disable(inst, basedn, log, args, warn=True):

-     dn = _get_arg(args.dn, msg="Enter plugin dn to disable")

-     if warn:

-         _warn(dn, msg="Disabling %s %s" % (SINGULAR.__name__, dn))

-     mc = MANY(inst, basedn)

-     o = mc.get(dn=dn)

-     if not o.state():

-         print("Plugin already disabled")

-     else:

-         o.disable()

-         print('Disabled plugin')

- 

- 

- # Plugin configure?

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

-     pass

- 

- 

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

-     """Display plugin configuration."""

-     plugin = args.plugin_cls(inst)

-     print(plugin.display())

- 

- 

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

-     plugin = args.plugin_cls(inst)

-     if plugin.status():

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

-     else:

-         plugin.enable()

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

-     else:

-         plugin.disable()

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

-     else:

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

- 

- 

- def add_generic_plugin_parsers(subparser, plugin_cls):

-     show_parser = subparser.add_parser('show', help='display plugin configuration')

-     show_parser.set_defaults(func=generic_show, plugin_cls=plugin_cls)

- 

-     enable_parser = subparser.add_parser('enable', help='enable plugin')

-     enable_parser.set_defaults(func=generic_enable, plugin_cls=plugin_cls)

- 

-     disable_parser = subparser.add_parser('disable', help='disable plugin')

-     disable_parser.set_defaults(func=generic_disable, plugin_cls=plugin_cls)

- 

-     status_parser = subparser.add_parser('status', help='display plugin status')

-     status_parser.set_defaults(func=generic_status, plugin_cls=plugin_cls)

- 

- 

  def create_parser(subparsers):

      plugin_parser = subparsers.add_parser('plugin', help="Manage plugins available on the server")

  

-     subcommands = plugin_parser.add_subparsers(help="action")

+     subcommands = plugin_parser.add_subparsers(help="Plugins")

+ 

+     cli_memberof.create_parser(subcommands)

+     cli_automember.create_parser(subcommands)

+     cli_referint.create_parser(subcommands)

+     cli_rootdn_ac.create_parser(subcommands)

+     cli_usn.create_parser(subcommands)

+     cli_accountpolicy.create_parser(subcommands)

+     cli_attruniq.create_parser(subcommands)

+     cli_dna.create_parser(subcommands)

+     cli_linkedattr.create_parser(subcommands)

+     cli_managedentries.create_parser(subcommands)

+     cli_passthroughauth.create_parser(subcommands)

+     cli_retrochangelog.create_parser(subcommands)

+     cli_whoami.create_parser(subcommands)

  

      list_parser = subcommands.add_parser('list', help="List current configured (enabled and disabled) plugins")

      list_parser.set_defaults(func=plugin_list)

  

-     get_parser = subcommands.add_parser('get', help='get')

+     get_parser = subcommands.add_parser('get', help='Get the plugin data')

      get_parser.set_defaults(func=plugin_get)

      get_parser.add_argument('selector', nargs='?', help='The plugin to search for')

  

-     get_dn_parser = subcommands.add_parser('get_dn', help='get_dn')

-     get_dn_parser.set_defaults(func=plugin_get_dn)

-     get_dn_parser.add_argument('dn', nargs='?', help='The plugin dn to get')

- 

-     edit_parser = subcommands.add_parser('edit', help='get')

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

      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.')
@@ -202,11 +136,3 @@ 

      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.')

- 

-     enable_parser = subcommands.add_parser('enable', help='enable a plugin in the server')

-     enable_parser.set_defaults(func=plugin_enable)

-     enable_parser.add_argument('dn', nargs='?', help='The dn to enable')

- 

-     disable_parser = subcommands.add_parser('disable', help='disable the plugin configuration')

-     disable_parser.set_defaults(func=plugin_disable)

-     disable_parser.add_argument('dn', nargs='?', help='The dn to disable')

@@ -0,0 +1,16 @@ 

+ # --- BEGIN COPYRIGHT BLOCK ---

+ # Copyright (C) 2018 Red Hat, Inc.

+ # All rights reserved.

+ #

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

+ # See LICENSE for details.

+ # --- END COPYRIGHT BLOCK ---

+ 

+ from lib389.plugins import AccountPolicyPlugin

+ from lib389.cli_conf import add_generic_plugin_parsers

+ 

+ 

+ def create_parser(subparsers):

+     accountpolicy_parser = subparsers.add_parser('accountpolicy', help='Manage and configure Account Policy plugin')

+     subcommands = accountpolicy_parser.add_subparsers(help='action')

+     add_generic_plugin_parsers(subcommands, AccountPolicyPlugin)

@@ -0,0 +1,16 @@ 

+ # --- BEGIN COPYRIGHT BLOCK ---

+ # Copyright (C) 2018 Red Hat, Inc.

+ # All rights reserved.

+ #

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

+ # See LICENSE for details.

+ # --- END COPYRIGHT BLOCK ---

+ 

+ from lib389.plugins import AttributeUniquenessPlugin

+ from lib389.cli_conf import add_generic_plugin_parsers

+ 

+ 

+ def create_parser(subparsers):

+     attruniq_parser = subparsers.add_parser('attruniq', help='Manage and configure Attribute Uniqueness plugin')

+     subcommands = attruniq_parser.add_subparsers(help='action')

+     add_generic_plugin_parsers(subcommands, AttributeUniquenessPlugin)

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2016-2017 Red Hat, Inc.

+ # Copyright (C) 2018 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -8,18 +8,16 @@ 

  

  import ldap

  import json

- from lib389.plugins import AutoMembershipPlugin, AutoMembershipDefinition, AutoMembershipDefinitions

- from lib389.cli_conf.plugin import add_generic_plugin_parsers

+ from lib389.plugins import AutoMembershipPlugin, AutoMembershipDefinitions

+ from lib389.cli_conf import add_generic_plugin_parsers

  

  

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

-     """

-         List automember definition if instance name

-         is given else show all automember definitions.

- 

-         :param name: An instance 

-         :type name: lib389.DirSrv

+     """List automember definition if instance name

+     is given else show all automember definitions.

  

+     :param name: An instance

+     :type name: lib389.DirSrv

      """

      

      automembers = AutoMembershipDefinitions(inst)
@@ -34,15 +32,19 @@ 

          all_definitions = automembers.list()

          if args.json:

              result = {'type': 'list', 'items': []}

-         for definition in all_definitions:

-             if args.json:

-                 result['items'].append(definition)

-             else:

-                 log.info(definition.display())

+         if len(all_definitions) > 0:

+             for definition in all_definitions:

+                 if args.json:

+                     result['items'].append(definition)

+                 else:

+                     log.info(definition.display())

+         else:

+             log.info("No automember definitions were found")

  

          if args.json:

              print(json.dumps(result))

  

+ 

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

      """

          Create automember definition.
@@ -79,6 +81,7 @@ 

          log.info("Failed to create Automember definition: {}".format(str(e)))

          raise e

  

+ 

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

      """

          Edit automember definition
@@ -148,7 +151,7 @@ 

      show_parser = subcommands.add_parser('list', help='List automember definition.')

      show_parser.set_defaults(func=list_definition)

  

-     show_parser.add_argument("name", help='Set cn for group entry.')

+     show_parser.add_argument("--name", help='Set cn for group entry. If not specified show all automember definitions.')

  

      edit_parser = subcommands.add_parser('edit', help='Edit automember definition.')

      edit_parser.set_defaults(func=edit_definition)

@@ -0,0 +1,16 @@ 

+ # --- BEGIN COPYRIGHT BLOCK ---

+ # Copyright (C) 2018 Red Hat, Inc.

+ # All rights reserved.

+ #

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

+ # See LICENSE for details.

+ # --- END COPYRIGHT BLOCK ---

+ 

+ from lib389.plugins import DNAPlugin

+ from lib389.cli_conf import add_generic_plugin_parsers

+ 

+ 

+ def create_parser(subparsers):

+     dna_parser = subparsers.add_parser('dna', help='Manage and configure DNA plugin')

+     subcommands = dna_parser.add_subparsers(help='action')

+     add_generic_plugin_parsers(subcommands, DNAPlugin)

@@ -0,0 +1,16 @@ 

+ # --- BEGIN COPYRIGHT BLOCK ---

+ # Copyright (C) 2018 Red Hat, Inc.

+ # All rights reserved.

+ #

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

+ # See LICENSE for details.

+ # --- END COPYRIGHT BLOCK ---

+ 

+ from lib389.plugins import LinkedAttributesPlugin

+ from lib389.cli_conf import add_generic_plugin_parsers

+ 

+ 

+ def create_parser(subparsers):

+     linkedattr_parser = subparsers.add_parser('linkedattr', help='Manage and configure Linked Attributes plugin')

+     subcommands = linkedattr_parser.add_subparsers(help='action')

+     add_generic_plugin_parsers(subcommands, LinkedAttributesPlugin)

@@ -0,0 +1,16 @@ 

+ # --- BEGIN COPYRIGHT BLOCK ---

+ # Copyright (C) 2018 Red Hat, Inc.

+ # All rights reserved.

+ #

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

+ # See LICENSE for details.

+ # --- END COPYRIGHT BLOCK ---

+ 

+ from lib389.plugins import ManagedEntriesPlugin

+ from lib389.cli_conf import add_generic_plugin_parsers

+ 

+ 

+ def create_parser(subparsers):

+     managedentries_parser = subparsers.add_parser('managedentries', help='Manage and configure Managed Entries plugin')

+     subcommands = managedentries_parser.add_subparsers(help='action')

+     add_generic_plugin_parsers(subcommands, ManagedEntriesPlugin)

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2016-2017 Red Hat, Inc.

+ # Copyright (C) 2018 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -9,7 +9,7 @@ 

  import ldap

  

  from lib389.plugins import MemberOfPlugin

- from lib389.cli_conf.plugin import add_generic_plugin_parsers

+ from lib389.cli_conf import add_generic_plugin_parsers

  

  

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

@@ -0,0 +1,16 @@ 

+ # --- BEGIN COPYRIGHT BLOCK ---

+ # Copyright (C) 2018 Red Hat, Inc.

+ # All rights reserved.

+ #

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

+ # See LICENSE for details.

+ # --- END COPYRIGHT BLOCK ---

+ 

+ from lib389.plugins import PassThroughAuthenticationPlugin

+ from lib389.cli_conf import add_generic_plugin_parsers

+ 

+ 

+ def create_parser(subparsers):

+     passthroughauth_parser = subparsers.add_parser('passthroughauth', help='Manage and configure Pass-Through Authentication plugin')

+     subcommands = passthroughauth_parser.add_subparsers(help='action')

+     add_generic_plugin_parsers(subcommands, PassThroughAuthenticationPlugin)

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2016-2017 Red Hat, Inc.

+ # Copyright (C) 2018 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -9,7 +9,7 @@ 

  import ldap

  

  from lib389.plugins import ReferentialIntegrityPlugin

- from lib389.cli_conf.plugin import add_generic_plugin_parsers

+ from lib389.cli_conf import add_generic_plugin_parsers

  

  

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

@@ -0,0 +1,16 @@ 

+ # --- BEGIN COPYRIGHT BLOCK ---

+ # Copyright (C) 2018 Red Hat, Inc.

+ # All rights reserved.

+ #

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

+ # See LICENSE for details.

+ # --- END COPYRIGHT BLOCK ---

+ 

+ from lib389.plugins import RetroChangelogPlugin

+ from lib389.cli_conf import add_generic_plugin_parsers

+ 

+ 

+ def create_parser(subparsers):

+     retrochangelog_parser = subparsers.add_parser('retrochangelog', help='Manage and configure Retro Changelog plugin')

+     subcommands = retrochangelog_parser.add_subparsers(help='action')

+     add_generic_plugin_parsers(subcommands, RetroChangelogPlugin)

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2017 Red Hat, Inc.

+ # Copyright (C) 2018 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -9,7 +9,7 @@ 

  import ldap

  

  from lib389.plugins import RootDNAccessControlPlugin

- from lib389.cli_conf.plugin import add_generic_plugin_parsers

+ from lib389.cli_conf import add_generic_plugin_parsers

  

  

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

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2016-2017 Red Hat, Inc.

+ # Copyright (C) 2018 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -7,7 +7,7 @@ 

  # --- END COPYRIGHT BLOCK ---

  

  from lib389.plugins import USNPlugin

- from lib389.cli_conf.plugin import add_generic_plugin_parsers

+ from lib389.cli_conf import add_generic_plugin_parsers

  

  

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

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2016-2017 Red Hat, Inc.

+ # Copyright (C) 2018 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -7,7 +7,7 @@ 

  # --- END COPYRIGHT BLOCK ---

  

  from lib389.plugins import WhoamiPlugin

- from lib389.cli_conf.plugin import add_generic_plugin_parsers

+ from lib389.cli_conf import add_generic_plugin_parsers

  

  

  def create_parser(subparsers):

file modified
+37 -3
@@ -192,7 +192,7 @@ 

      :type dn: str

      """

  

-     def __init__(self, instance, dn="cn=managed entries,cn=plugins,cn=config"):

+     def __init__(self, instance, dn="cn=Managed Entries,cn=plugins,cn=config"):

          super(ManagedEntriesPlugin, self).__init__(instance, dn)

  

  
@@ -897,7 +897,7 @@ 

          super(AutoMembershipDefinition, self).__init__(instance, dn)

          self._rdn_attribute = 'cn'

          self._must_attributes = ['cn', 'autoMemberScope', 'autoMemberFilter', 'autoMemberGroupingAttr']

-         self._create_objectclasses = ['top', 'AutoMemberDefinition']

+         self._create_objectclasses = ['top', 'autoMemberDefinition']

          self._protected = False

  

      def get_groupattr(self):
@@ -958,6 +958,40 @@ 

          self._basedn = basedn

  

  

+ class AutoMembershipRegexRule(DSLdapObject):

+     """A single instance of Auto Membership Plugin Regex Rule config entry

+ 

+     :param instance: An instance

+     :type instance: lib389.DirSrv

+     :param dn: Entry DN

+     :type dn: str

+     """

+ 

+     def __init__(self, instance, dn=None):

+         super(AutoMembershipRegexRule, self).__init__(instance, dn)

+         self._rdn_attribute = 'cn'

+         self._must_attributes = ['cn', 'autoMemberTargetGroup']

+         self._create_objectclasses = ['top', 'autoMemberRegexRule']

+         self._protected = False

+ 

+ 

+ class AutoMembershipRegexRules(DSLdapObjects):

+     """A DSLdapObjects entity which represents Auto Membership Plugin Regex Rule config entry

+ 

+     :param instance: An instance

+     :type instance: lib389.DirSrv

+     :param basedn: Base DN for all account entries below

+     :type basedn: str

+     """

+ 

+     def __init__(self, instance, basedn):

+         super(AutoMembershipRegexRules, self).__init__(instance)

+         self._objectclasses = ['top', 'autoMemberRegexRule']

+         self._filterattrs = ['cn']

+         self._childobject = AutoMembershipRegexRule

+         self._basedn = basedn

+ 

+ 

  class ContentSynchronizationPlugin(Plugin):

      """A single instance of Content Synchronization plugin entry

  
@@ -1542,7 +1576,7 @@ 

  

  

  class Plugins(DSLdapObjects):

-     """A DSLdapObjects entity which represents MEP config entry

+     """A DSLdapObjects entity which represents plugin entry

  

      :param instance: An instance

      :type instance: lib389.DirSrv

Description: Add plugin UI tabs with basic data editing,
enable/disable and dynamic plugin switch.
Fix loading. Fix small CSS issues.

React. Add customCollapse element. Make customToolbar customizable.
Rework data flow in the component.

CLI. Put all the plugins to 'plugin' parser. Add wrappers for
all main plugins. Clean up plugin args (remove get_dn, generic enable and disable)

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

Reviewed by: ?

There is one known issue for 'Dynamic plugins' switch.
After initial loading, it shows the wrong image. It is patternfly-react component's bug so I'll think about how we can fix it...

Or we do a workaround (after we'll have the whole UI in React, the problem will become invisible).
Or we write our own switch component.
Or we use just a radio switch 'on-off' here (probably I'll do this for now).

If you have any thhoughts, please, share.

I think this will break the replication agmt modal. The issue should be fixed in schema.js

rebased onto 96baab94ade68f3e4215b7b55bba042fa20a432a

5 years ago

Okay, I was a bit hasty :D

Rebased.

P.S. I was thinking to put the loading part to schema.js but I think it can lead to some race conditions because of the cockpit async behavior... Anyway, it is not a big overhead and we will refactor it soon enough to React.

Few comments....

[1] The accordion used in the plugin page for showing advanced options has a "color" background making it look like a button, and it's center aligned (but it should left aligned to the left), and it should look exactly like what is on the Server tab Configuration page.

[2] When you select a plugin, I think you could put the plugin name in the right window above the on/off toggle. I know its redundant but it I think it will look nicer.

[3] Remove the Dynamic plugins configuration option altogether. I think eventually with Williams work these will be dynamic by default anyway. Also this feature has been known to crash the server, so dynamic plug-ins (as of today) should not be advertised.

[4] All Plugins -> The table pagination box is not aligned with "per page". I feel that "per page" needs to be raised up

[5] Not sure if it was a setup issue, but when I first looked at these changes the div/box of the advanced plugin options had a background color (grey). But now the background is mysteriously white. So while I don't know why for a brief moment it had a nice grey background, it definitely looked better than just a white ground with a black line border. Like I said it could be my setup, but right now its white and looks harsh :-)

[6] I think the page is also a little compressed. A HR under "Plugins" looks better. I tried adding it, only using one column, but then the "spinner" gets out of alignment.

Here is just an example showing the HR in action:

+++ b/src/cockpit/389-console/src/plugins.jsx
@@ -408,15 +408,9 @@ export class Plugins extends React.Component {
                     removeNotificationAction={this.removeNotification}
                 />
                 <Row className="clearfix">
-                    <Col sm={2}>
+                    <Col sm={12}>
                         <h2>Plugins</h2>
-                    </Col>
-                    <Col sm={10}>
-                        <Spinner
-                            className="ds-float-left ds-plugin-spinner"
-                            loading={this.state.loading}
-                            size="md"
-                        />
+                        <hr />
                     </Col>

I couldn't find a way to add the spinner back and get it to be right of the "Plugins" text, it always pops up below it. Sorry ran out of time to work on it (end of the day for me), but I think you get the idea.

Few comments....
[1] The accordion used in the plugin page for showing advanced options has a "color" background making it look like a button, and it's center aligned (but it should left aligned to the left), and it should look exactly like what is on the Server tab Configuration page.

Yeah, I faced a very weird issue here... If I use 'Button' patternfly component - it has this 'buttonish' look. I didn't find the CSS that I should unset.
If I use 'button' native component (which doesn't have the btn CSS classes - it starts to reload the page when I click on it (VERY WEIRD).
I'll try to investigate more... But for now, it's the best I could achieve.

[2] When you select a plugin, I think you could put the plugin name in the right window above the on/off toggle. I know its redundant but it I think it will look nicer.

I was thinking about that too. I'll try to put it but the code will be ugly :) I'll have to use 'margin' CSS for that (moving the word higher) because of the tab-content layout.

[3] Remove the Dynamic plugins configuration option altogether. I think eventually with Williams work these will be dynamic by default anyway. Also this feature has been known to crash the server, so dynamic plug-ins (as of today) should not be advertised.

Okay... It is a releaf.

[4] All Plugins -> The table pagination box is not aligned with "per page". I feel that "per page" needs to be raised up

I'll put some margin CSS there.

[5] Not sure if it was a setup issue, but when I first looked at these changes the div/box of the advanced plugin options had a background color (grey). But now the background is mysteriously white. So while I don't know why for a brief moment it had a nice grey background, it definitely looked better than just a white ground with a black line border. Like I said it could be my setup, but right now its white and looks harsh :-)

Hah, I was actually removing the grey background because I didn't like it. I'll put it back. We'll see, may be it was just a first look and it is actually good.

[6] I think the page is also a little compressed. A HR under "Plugins" looks better. I tried adding it, only using one column, but then the "spinner" gets out of alignment.

Sure. I'll check it.

Few comments....
[1] The accordion used in the plugin page for showing advanced options has a "color" background making it look like a button, and it's center aligned (but it should left aligned to the left), and it should look exactly like what is on the Server tab Configuration page.

Yeah, I faced a very weird issue here... If I use 'Button' patternfly component - it has this 'buttonish' look. I didn't find the CSS that I should unset.
If I use 'button' native component (which doesn't have the btn CSS classes - it starts to reload the page when I click on it (VERY WEIRD).
I'll try to investigate more... But for now, it's the best I could achieve.

Can't you use the same CSS/JS I used for the other accordions?

[2] When you select a plugin, I think you could put the plugin name in the right window above the on/off toggle. I know its redundant but it I think it will look nicer.

I was thinking about that too. I'll try to put it but the code will be ugly :)

Why ugly? And why is adding a header <h2>{name}</h2> hard? I seriously don't like how difficiult it is to use React to do really simple things (I really think we should of held off on incorporating React for the first UI release, but it's too late now) :-(

Can't you use the same CSS/JS I used for the other accordions?

It uses Jquery which we'll no longer use. All I need to do is to fix our 'ds-accordion' for this (so it overwrites the button CSS). And that's it (though to find the setting may not be easy, but it's there).

Why ugly? And why is adding a header

{name}

hard? I seriously don't like how difficiult it is to use React to do really simple things (I really think we should of held off on incorporating React for the first UI release, but it's too late now) :-(

React has nothing to do here. :) He only helps to build safe and fast things faster.

The issue is with the bootstrap layout (you can check the HTML element boxes with the browser's inspect tools - cursor)

The tab content is lower than 'Plugins' header. So if I want to put the plugin name header above the usual tab content I have to use 'margin' which is a bit ugly.

I'll rebase later today after all the reviewes and BZ work :)

Also in the plugin properties, the labels wrap "Plugin Description" Plugin initfunc", there should be enough room for this to fit without wrapping to two lines. You could even make it one column, instead of two, to definitely make sure there is enough room

rebased onto f2a66eb772ad5101d2dfacf08c977947011fb83f

5 years ago

rebased onto 322352cf224e214a21556b1fb68ecdae42a96bb6

5 years ago

rebased onto bef29b31f7f3a3eb8ec8aea184f2264973a1404b

5 years ago

The issues are fixed. Please, review.

P.S. the only thing that I didn't fix is [1] accordion - it's center aligned (but it should left aligned to the left).
I have it left aligned already...

rebased onto 86f7b898d448cb2b3606ff42795f2c2231d14381

5 years ago

Few comments:

[1] The plugin name should not be on the same line as the "Plugins" header, it should be beneath it. and not as large as "Plugins" as well, maybe h3?
[2] Trying to disable automember plugin throws error but I think its my env (so this can probably be ignored if it works for you)
[3] The plugin edit page should still be compressed. The labels are not left aligned, and have large gaps to the left. Remember we are trying to get the forms to be consistent. Please see how the Server tab configuration settings (labels and inputs) are laid out.

Thanks!

Also in the "All Plugins" table, please change "Edit Entry" to "Edit Plugin"

rebased onto e4fbaeadafa2441534a8aea4978c8727ee2b82bc

5 years ago

Looks good, one last request. Can you add a little vertical space between the accordion and the labels/inputs? It's a little too close together. Besides that LGTM ack!

rebased onto 00c3b7a

5 years ago

Good catch! Added the 'ds-accordion-panel' class.

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

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 42
+8 -5
file changed
src/cockpit/389-console/package.json
+58 -2
file changed
src/cockpit/389-console/src/css/ds.css
+4 -4
file changed
src/cockpit/389-console/src/ds.js
+27 -8
file changed
src/cockpit/389-console/src/index.es6
+1 -1
file changed
src/cockpit/389-console/src/index.html
+56
file added
src/cockpit/389-console/src/lib/customCollapse.jsx
+29 -12
file changed
src/cockpit/389-console/src/lib/customTableToolbar.jsx
+45
file added
src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
+45
file added
src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
+45
file added
src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
+45
file added
src/cockpit/389-console/src/lib/plugins/dna.jsx
+45
file added
src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
+45
file added
src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
+45
file added
src/cockpit/389-console/src/lib/plugins/memberOf.jsx
+45
file added
src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
+334
file added
src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
+26 -76
file changed
src/cockpit/389-console/src/lib/plugins/pluginModal.jsx
+9 -23
file changed
src/cockpit/389-console/src/lib/plugins/pluginTable.jsx
+45
file added
src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
+45
file added
src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
+45
file added
src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
+45
file added
src/cockpit/389-console/src/lib/plugins/usn.jsx
+299 -19
file changed
src/cockpit/389-console/src/plugins.jsx
+11 -1
file changed
src/cockpit/389-console/src/replication.js
+20 -22
file changed
src/cockpit/389-console/src/servers.js
+0 -12
file changed
src/lib389/cli/dsconf
+47 -1
file changed
src/lib389/lib389/cli_conf/__init__.py
+33 -107
file changed
src/lib389/lib389/cli_conf/plugin.py
+16
file added
src/lib389/lib389/cli_conf/plugins/accountpolicy.py
+16
file added
src/lib389/lib389/cli_conf/plugins/attruniq.py
+18 -15
file changed
src/lib389/lib389/cli_conf/plugins/automember.py
+16
file added
src/lib389/lib389/cli_conf/plugins/dna.py
+16
file added
src/lib389/lib389/cli_conf/plugins/linkedattr.py
+16
file added
src/lib389/lib389/cli_conf/plugins/managedentries.py
+2 -2
file changed
src/lib389/lib389/cli_conf/plugins/memberof.py
+16
file added
src/lib389/lib389/cli_conf/plugins/passthroughauth.py
+2 -2
file changed
src/lib389/lib389/cli_conf/plugins/referint.py
+16
file added
src/lib389/lib389/cli_conf/plugins/retrochangelog.py
+2 -2
file changed
src/lib389/lib389/cli_conf/plugins/rootdn_ac.py
+2 -2
file changed
src/lib389/lib389/cli_conf/plugins/usn.py
+2 -2
file changed
src/lib389/lib389/cli_conf/plugins/whoami.py
+37 -3
file changed
src/lib389/lib389/plugins.py