#50995 Issue 50994 - Fix latest UI bugs found by QE
Closed 3 years ago by spichugi. Opened 4 years ago by mreynolds.
mreynolds/389-ds-base ui-fixes  into  master

file modified
+2 -1
@@ -313,7 +313,8 @@ 

  %package -n cockpit-389-ds

  Summary:          Cockpit UI Plugin for configuring and administering the 389 Directory Server

  BuildArch:        noarch

- Requires:         cockpit >= 198

+ Requires:         cockpit

+ Requires:         389-ds-base

  Requires:         python%{python3_pkgversion}

  Requires:         python%{python3_pkgversion}-lib389

  

@@ -61,7 +61,8 @@ 

  

  export class DSInstance extends React.Component {

      componentWillMount() {

-         this.checkPackageAndLoad();

+         this.loadInstanceList();

+         this.updateProgress(25);

      }

  

      constructor(props) {
@@ -94,7 +95,6 @@ 

          this.loadInstanceList = this.loadInstanceList.bind(this);

          this.loadBackups = this.loadBackups.bind(this);

          this.setServerId = this.setServerId.bind(this);

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

          this.updateProgress = this.updateProgress.bind(this);

          this.openCreateInstanceModal = this.openCreateInstanceModal.bind(this);

          this.closeCreateInstanceModal = this.closeCreateInstanceModal.bind(this);
@@ -114,7 +114,7 @@ 

                  progressValue: prevState.progressValue + value

              }),

              () => {

-                 if (this.state.progressValue >= 100) {

+                 if (this.state.progressValue > 100) {

                      this.setState(prevState => ({

                          pageLoadingState: {

                              ...prevState.pageLoadingState,
@@ -135,6 +135,7 @@ 

                  .done(status_data => {

                      let status_json = JSON.parse(status_data);

                      if (status_json.running) {

+                         this.updateProgress(25);

                          let cmd = [

                              "dsconf",

                              "-j",
@@ -148,17 +149,12 @@ 

                          cockpit

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

                                  .done(_ => {

+                                     this.updateProgress(25);

                                      this.setState(

                                          {

                                              serverId: serverId

                                          },

                                          () => {

-                                             this.setState(prevState => ({

-                                                 pageLoadingState: {

-                                                     ...prevState.pageLoadingState,

-                                                     state: "success"

-                                                 }

-                                             }));

                                              this.loadBackups();

                                          }

                                      );
@@ -174,7 +170,6 @@ 

                                              }

                                          );

                                      }

-                                     this.updateProgress(25);

                                  })

                                  .fail(err => {

                                      let errMsg = JSON.parse(err);
@@ -193,7 +188,6 @@ 

                                          }

                                      );

                                  });

-                         this.updateProgress(25);

                      } else {

                          this.setState(

                              {
@@ -229,25 +223,6 @@ 

                  });

      }

  

-     checkPackageAndLoad() {

If I understand correctly, you do this because of the progress bar. Am I right?
I am just a bit concerned that you are deprecating this code... If I understand correctly, it will still show the create instance button but it will fail to create it with some obscure message.
I think we should deal with it somehow...

-         let cmd = ["rpm", "-q", "389-ds-base"];

-         log_cmd("checkPackageAndLoad", "Check if 389-ds-base package is installed", cmd);

-         cockpit

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

-                 .done(_ => {

-                     this.updateProgress(25);

-                     this.loadInstanceList();

-                 })

-                 .fail(_ => {

-                     this.setState({

-                         pageLoadingState: {

-                             state: "noPackage",

-                             jsx: staticStates["noPackage"]

-                         }

-                     });

-                 });

-     }

- 

      loadInstanceList(serverId, action) {

          if (serverId === undefined) {

              this.setState(prevState => ({
@@ -262,6 +237,7 @@ 

          cockpit

                  .spawn(cmd, { superuser: true })

                  .done(data => {

+                     this.updateProgress(25);

                      let myObject = JSON.parse(data);

                      this.setState({

                          instList: myObject.insts,
@@ -289,7 +265,6 @@ 

                              });

                          }

                      }

-                     this.updateProgress(25);

                  })

                  .fail(_ => {

                      this.setState({
@@ -308,6 +283,7 @@ 

          const cmd = ["dsctl", "-j", this.state.serverId, "backups"];

          log_cmd("loadBackupsDSInstance", "Load Backups", cmd);

          cockpit.spawn(cmd, { superuser: true, err: "message" }).done(content => {

+             this.updateProgress(25);

              const config = JSON.parse(content);

              let rows = [];

              for (let row of config.items) {

@@ -224,7 +224,7 @@ 

              spinner =

                  <Row>

                      <div className="ds-margin-top-lg ds-modal-spinner">

-                         <Spinner loading inline size="lg" />Exporting database... <font size="1">(You can safely close this window)</font>

+                         <Spinner loading inline size="lg" />Exporting database... <font size="2">(You can safely close this window)</font>

                      </div>

                  </Row>;

          }
@@ -313,7 +313,7 @@ 

              spinner =

                  <Row>

                      <div className="ds-margin-top-lg ds-modal-spinner">

-                         <Spinner loading inline size="lg" />Importing LDIF file... <font size="1">(You can safely close this window)</font>

+                         <Spinner loading inline size="lg" />Importing LDIF file... <font size="2">(You can safely close this window)</font>

                      </div>

                  </Row>;

          }
@@ -412,7 +412,7 @@ 

                          <Form horizontal autoComplete="off">

                              <div className="ds-modal-spinner">

                                  <Spinner loading inline size="lg" /> Indexing <b>{msg}</b> ...

-                                 <p className="ds-margin-top-lg"><font size="1">(You can safely close this window)</font></p>

+                                 <p className="ds-margin-top-lg"><font size="2">(You can safely close this window)</font></p>

                              </div>

                          </Form>

                      </Modal.Body>

@@ -72,6 +72,7 @@ 

              exportSpinner: false,

              importSpinner: false,

              showConfirmLDIFImport: false,

+             importLDIFName: "",

              deleleLDIFName: "",

              modalChecked: false,

              modalSpinning: false,
@@ -196,15 +197,16 @@ 

          });

      }

  

-     importLDIF (ldif) {

+     importLDIF () {

          // Do import

          let import_cmd = [

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

-             "backend", "import", this.props.suffix, ldif, "--encrypted"

+             "backend", "import", this.props.suffix, this.state.importLDIFName, "--encrypted"

          ];

  

          this.setState({

              importSpinner: true,

+             showConfirmLDIFImport: false,

          });

  

          log_cmd("doImport", "Do online import", import_cmd);
@@ -375,6 +377,7 @@ 

                      );

                      this.setState({

                          showReindexModal: false,

+                         showReindexConfirm: false,

                      });

                  })

                  .fail(err => {
@@ -385,6 +388,7 @@ 

                      );

                      this.setState({

                          showReindexModal: false,

+                         showReindexConfirm: false,

                      });

                  });

      }
@@ -947,7 +951,7 @@ 

                  <DoubleConfirmModal

                      showModal={this.state.showReindexConfirm}

                      closeHandler={this.closeReindexConfirm}

-                     handleChange={this.handlChange}

+                     handleChange={this.handleChange}

                      actionHandler={this.doReindex}

                      spinning={this.state.modalSpinning}

                      item={this.props.suffix}

@@ -525,9 +525,9 @@ 

                              vlvItem.sorts.map(sort => {

                                  let indexState;

                                  if (sort.attrs.vlvenabled[0] == "0") {

-                                     indexState = <font size="1" color="#d01c8b"><b>Disabled</b></font>;

+                                     indexState = <font size="2" color="#d01c8b"><b>Disabled</b></font>;

                                  } else {

-                                     indexState = <font size="1" color="#4dac26"><b>Uses: </b>{sort.attrs.vlvuses[0]}</font>;

+                                     indexState = <font size="2" color="#4dac26"><b>Uses: </b>{sort.attrs.vlvuses[0]}</font>;

                                  }

                                  return (<p key={sort.dn + sort.attrs.vlvsort[0]}><label className="ds-divider-lrg">Sort</label>{sort.attrs.vlvsort[0]} ({indexState})</p>);

                              })

@@ -1348,8 +1348,8 @@ 

                      </TabPane>

                  </TabContent>

              </div>;

-         let cleanNavTitle = 'CleanAllRUV Tasks <font size="1">(' + cleanTasks.length + ')</font>';

-         let abortNavTitle = 'Abort CleanAllRUV Tasks <font size="1">(' + abortTasks.length + ')</font>';

+         let cleanNavTitle = 'CleanAllRUV Tasks <font size="2">(' + cleanTasks.length + ')</font>';

+         let abortNavTitle = 'Abort CleanAllRUV Tasks <font size="2">(' + abortTasks.length + ')</font>';

          let taskContent =

              <div>

                  <Nav bsClass="nav nav-tabs nav-tabs-pf">
@@ -1380,8 +1380,8 @@ 

                  </TabContent>

              </div>;

  

-         let conflictNavTitle = 'Conflict Entries <font size="1">(' + conflictEntries.length + ')</font>';

-         let glueNavTitle = 'Glue Entries <font size="1">(' + glueEntries.length + ')</font>';

+         let conflictNavTitle = 'Conflict Entries <font size="2">(' + conflictEntries.length + ')</font>';

+         let glueNavTitle = 'Glue Entries <font size="2">(' + glueEntries.length + ')</font>';

          let conflictContent =

              <div>

                  <Nav bsClass="nav nav-tabs nav-tabs-pf">
@@ -1431,10 +1431,10 @@ 

              </div>;

  

          let fullReportTitle = 'Sync Report';

-         let replAgmtNavTitle = 'Agreements <font size="1">(' + replAgmts.length + ')</font>';

-         let winsyncNavTitle = 'Winsync <font size="1">(' + replWinsyncAgmts.length + ')</font>';

-         let tasksNavTitle = 'Tasks <font size="1">(' + (cleanTasks.length + abortTasks.length) + ')</font>';

-         let conflictsNavTitle = 'Conflicts <font size="1">(' + (conflictEntries.length + glueEntries.length) + ')</font>';

+         let replAgmtNavTitle = 'Agreements <font size="2">(' + replAgmts.length + ')</font>';

+         let winsyncNavTitle = 'Winsync <font size="2">(' + replWinsyncAgmts.length + ')</font>';

+         let tasksNavTitle = 'Tasks <font size="2">(' + (cleanTasks.length + abortTasks.length) + ')</font>';

+         let conflictsNavTitle = 'Conflicts <font size="2">(' + (conflictEntries.length + glueEntries.length) + ')</font>';

  

          return (

              <div id="monitor-suffix-page" className="ds-tab-table">

@@ -16,6 +16,7 @@ 

  } from "patternfly-react";

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

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

+ import { DoubleConfirmModal } from "../notifications.jsx";

  import PluginBasicConfig from "./pluginBasicConfig.jsx";

  import PropTypes from "prop-types";

  import { log_cmd } from "../tools.jsx";
@@ -37,6 +38,8 @@ 

              configRows: [],

              attributes: [],

              objectClasses: [],

+             modalChecked: false,

+             modalSpinning: false,

  

              configName: "",

              configEnabled: false,
@@ -48,12 +51,13 @@ 

  

              newEntry: false,

              showConfigModal: false,

-             showConfirmDeleteConfig: false

+             showConfirmDelete: false

          };

  

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

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

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

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

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

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

          this.showAddConfigModal = this.showAddConfigModal.bind(this);
@@ -62,6 +66,8 @@ 

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

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

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

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

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

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

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

          this.editConfig = this.editConfig.bind(this);
@@ -85,6 +91,18 @@ 

          });

      }

  

+     handleTypeaheadChange(values) {

+         // When typaheads allow new values, an object is returned

+         // instead of string.  Grab the "label" in this case

+         let new_values = [];

+         for (let val of values) {

+             new_values.push(val.label);

+         }

+         this.setState({

+             subtrees: new_values

+         });

+     }

+ 

      loadConfigs() {

          this.setState({

              firstLoad: false
@@ -98,23 +116,20 @@ 

              "attr-uniq",

              "list"

          ];

-         this.props.toggleLoadingHandler();

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

          cockpit

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

                  .done(content => {

                      let myObject = JSON.parse(content);

                      this.setState({

-                         configRows: myObject.items.map(item => JSON.parse(item).attrs)

+                         configRows: myObject.items.map(item => item.attrs)

                      });

-                     this.props.toggleLoadingHandler();

                  })

                  .fail(err => {

                      if (err != 0) {

                          let errMsg = JSON.parse(err);

                          console.log("loadConfigs failed", errMsg.desc);

                      }

-                     this.props.toggleLoadingHandler();

                  });

      }

  
@@ -153,7 +168,6 @@ 

                  name

              ];

  

-             this.props.toggleLoadingHandler();

              log_cmd("openModal", "Fetch the Attribute Uniqueness Plugin config entry", cmd);

              cockpit

                      .spawn(cmd, {
@@ -200,7 +214,6 @@ 

                              }

                              this.setState({ subtrees: configSubtreesList });

                          }

-                         this.props.toggleLoadingHandler();

                      })

                      .fail(_ => {

                          this.setState({
@@ -213,7 +226,6 @@ 

                              topEntryOc: [],

                              subtreeEnriesOc: []

                          });

-                         this.props.toggleLoadingHandler();

                      });

          }

      }
@@ -247,6 +259,15 @@ 

              acrossAllSubtrees ? "on" : "off"

          ];

  

+         if (subtrees.length == 0 && subtreeEnriesOc.length == 0) {

+             // There me a subtree or entry OC sets

+             this.props.addNotification(

+                 "error",

+                 `There must be at least one Subtree or Subtree Entries OC set`

+             );

+             return;

+         }

+ 

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

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

              cmd = [...cmd, "--attr-name"];
@@ -320,13 +341,29 @@ 

                          `Error during the config entry ${action} operation - ${errMsg.desc}`

                      );

                      this.loadConfigs();

-                     this.closeModal();

                      this.props.toggleLoadingHandler();

                  });

      }

  

-     deleteConfig(rowData) {

-         let configName = rowData.cn[0];

+     showConfirmDelete (name) {

+         this.setState({

+             showConfirmDelete: true,

+             modalChecked: false,

+             modalSpinning: false,

+             deleteName: name

+         });

+     }

+ 

+     closeConfirmDelete () {

+         this.setState({

+             showConfirmDelete: false,

+             modalChecked: false,

+             modalSpinning: false,

+             deleteName: ""

+         });

+     }

+ 

+     deleteConfig() {

          let cmd = [

              "dsconf",

              "-j",
@@ -334,10 +371,13 @@ 

              "plugin",

              "attr-uniq",

              "delete",

-             configName

+             this.state.deleteName

          ];

  

-         this.props.toggleLoadingHandler();

+         this.setState({

+             modalSpinning: true

+         });

+ 

          log_cmd("deleteConfig", "Delete the Attribute Uniqueness Plugin config entry", cmd);

          cockpit

                  .spawn(cmd, {
@@ -348,11 +388,11 @@ 

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

                      this.props.addNotification(

                          "success",

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

+                         `Config entry ${this.state.deleteName} was successfully deleted`

                      );

                      this.loadConfigs();

                      this.closeModal();

-                     this.props.toggleLoadingHandler();

+                     this.closeConfirmDelete();

                  })

                  .fail(err => {

                      let errMsg = JSON.parse(err);
@@ -361,8 +401,8 @@ 

                          `Error during the config entry removal operation - ${errMsg.desc}`

                      );

                      this.loadConfigs();

+                     this.closeConfirmDelete();

                      this.closeModal();

-                     this.props.toggleLoadingHandler();

                  });

      }

  
@@ -528,9 +568,7 @@ 

                                                      allowNew

                                                      multiple

                                                      onChange={values => {

-                                                         this.setState({

-                                                             subtrees: values

-                                                         });

+                                                         this.handleTypeaheadChange(values);

                                                      }}

                                                      selected={subtrees}

                                                      options={[""]}
@@ -672,7 +710,7 @@ 

                              <AttrUniqConfigTable

                                  rows={this.state.configRows}

                                  editConfig={this.showEditConfigModal}

-                                 deleteConfig={this.deleteConfig}

+                                 deleteConfig={this.showConfirmDelete}

                              />

                              <Button

                                  className="ds-margin-top"
@@ -684,6 +722,19 @@ 

                          </Col>

                      </Row>

                  </PluginBasicConfig>

+                 <DoubleConfirmModal

+                     showModal={this.state.showConfirmDelete}

+                     closeHandler={this.closeConfirmDelete}

+                     handleChange={this.handleCheckboxChange}

+                     actionHandler={this.deleteConfig}

+                     spinning={this.state.modalSpinning}

+                     item={this.state.deleteName}

+                     checked={this.state.modalChecked}

+                     mTitle="Delete Attribute Uniqueness Configuration"

+                     mMsg="Are you sure you want to delete this configuration?"

+                     mSpinningMsg="Deleting attribute uniqueness configuration..."

+                     mBtnName="Delete Configuration"

+                 />

              </div>

          );

      }

@@ -99,7 +99,6 @@ 

              "list",

              "definitions"

          ];

-         this.props.toggleLoadingHandler();

          log_cmd("loadDefinitions", "Get Auto Membership Plugin definitions", cmd);

          cockpit

                  .spawn(cmd, { superuser: true, err: "message" })
@@ -108,14 +107,12 @@ 

                      this.setState({

                          definitionRows: myObject.items.map(item => JSON.parse(item).attrs)

                      });

-                     this.props.toggleLoadingHandler();

                  })

                  .fail(err => {

                      let errMsg = JSON.parse(err);

                      if (err != 0) {

                          console.log("loadDefinitions failed", errMsg.desc);

                      }

-                     this.props.toggleLoadingHandler();

                  });

      }

  

@@ -138,7 +138,6 @@ 

              "list",

              "configs"

          ];

-         this.props.toggleLoadingHandler();

          log_cmd("loadConfigs", "Get DNA Plugin configs", cmd);

          cockpit

                  .spawn(cmd, { superuser: true, err: "message" })
@@ -147,14 +146,12 @@ 

                      this.setState({

                          configRows: myObject.items.map(item => JSON.parse(item).attrs)

                      });

-                     this.props.toggleLoadingHandler();

                  })

                  .fail(err => {

                      let errMsg = JSON.parse(err);

                      if (err != 0) {

                          console.log("loadConfigs failed", errMsg.desc);

                      }

-                     this.props.toggleLoadingHandler();

                  });

      }

  
@@ -169,7 +166,6 @@ 

              "shared-configs",

              basedn

          ];

-         this.props.toggleLoadingHandler();

          log_cmd("loadSharedConfigs", "Get DNA Plugin shared configs", cmd);

          cockpit

                  .spawn(cmd, { superuser: true, err: "message" })
@@ -178,14 +174,12 @@ 

                      this.setState({

                          sharedConfigRows: myObject.items.map(item => JSON.parse(item).attrs)

                      });

-                     this.props.toggleLoadingHandler();

                  })

                  .fail(err => {

                      let errMsg = JSON.parse(err);

                      if (err != 0) {

                          console.log("loadSharedConfigs failed", errMsg.desc);

                      }

-                     this.props.toggleLoadingHandler();

                  });

      }

  

@@ -77,7 +77,6 @@ 

              "linked-attr",

              "list"

          ];

-         this.props.toggleLoadingHandler();

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

          cockpit

                  .spawn(cmd, { superuser: true, err: "message" })
@@ -86,14 +85,12 @@ 

                      this.setState({

                          configRows: myObject.items.map(item => JSON.parse(item).attrs)

                      });

-                     this.props.toggleLoadingHandler();

                  })

                  .fail(err => {

                      let errMsg = JSON.parse(err);

                      if (err != 0) {

                          console.log("loadConfigs failed", errMsg.desc);

                      }

-                     this.props.toggleLoadingHandler();

                  });

      }

  

@@ -93,7 +93,6 @@ 

              "list",

              "configs"

          ];

-         this.props.toggleLoadingHandler();

This looks a bit strange... You remove the loading toggler from managedEntries->loadConfigs, from attributeUniqueness but you don't remove it from some other places (i.e. linkedAttributes)
Also, I don't see the BZ it refers to in the commit message. Do I miss something?..

          log_cmd("loadConfigs", "Get Managed Entries Plugin configs", cmd);

          cockpit

                  .spawn(cmd, { superuser: true, err: "message" })
@@ -102,14 +101,12 @@ 

                      this.setState({

                          configRows: myObject.items.map(item => JSON.parse(item).attrs)

                      });

-                     this.props.toggleLoadingHandler();

                  })

                  .fail(err => {

                      let errMsg = JSON.parse(err);

                      if (err != 0) {

                          console.log("loadConfigs failed", errMsg.desc);

                      }

-                     this.props.toggleLoadingHandler();

                  });

      }

  

@@ -118,7 +118,6 @@ 

              "list",

              "pam-configs"

          ];

-         this.props.toggleLoadingHandler();

          log_cmd("loadPAMConfigs", "Get PAM Passthough Authentication Plugin pamConfigs", cmd);

          cockpit

                  .spawn(cmd, { superuser: true, err: "message" })
@@ -127,14 +126,12 @@ 

                      this.setState({

                          pamConfigRows: myObject.items.map(item => JSON.parse(item).attrs)

                      });

-                     this.props.toggleLoadingHandler();

                  })

                  .fail(err => {

                      let errMsg = JSON.parse(err);

                      if (err != 0) {

                          console.log("loadPAMConfigs failed", errMsg.desc);

                      }

-                     this.props.toggleLoadingHandler();

                  });

      }

  

@@ -258,7 +258,7 @@ 

                                              <MenuItem

                                                  eventKey="2"

                                                  onClick={() => {

-                                                     this.props.deleteConfig(rowData);

+                                                     this.props.deleteConfig(rowData.cn[0]);

                                                  }}

                                              >

                                                  Delete Config

@@ -311,17 +311,17 @@ 

                          ? ""

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

                  entryScope:

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

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

                          ? ""

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

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

                  excludeEntryScope:

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

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

                          ? ""

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

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

                  containerScope:

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

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

                          ? ""

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

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

                  referintConfigEntry:

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

                          ? ""

@@ -22,6 +22,7 @@ 

      componentWillMount() {

          if (this.props.wasActiveList.includes(5)) {

              if (this.state.firstLoad) {

+                 this.loadSuffixList();

                  this.updateSwitch();

              }

          }
@@ -35,6 +36,7 @@ 

          this.updateSwitch = this.updateSwitch.bind(this);

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

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

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

  

          this.state = {

              firstLoad: true,
@@ -42,11 +44,28 @@ 

              disableSwitch: false,

              cleanupModalShow: false,

              cleanupSuffix: "",

-             cleanupBackend: "",

-             cleanupMaxUSN: ""

+             cleanupMaxUSN: "",

+             suffixList: [],

          };

      }

  

+     loadSuffixList () {

+         const cmd = [

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

+             "backend", "suffix", "list", "--suffix"

+         ];

+         log_cmd("loadSuffixList", "Get a list of all the suffixes", cmd);

+         cockpit

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

+                 .done(content => {

+                     const suffixList = JSON.parse(content);

+                     this.setState({

+                         suffixList: suffixList.items,

+                         cleanupSuffix: suffixList.items[0]

+                     });

+                 });

+     }

+ 

      handleFieldChange(e) {

          this.setState({

              [e.target.id]: e.target.value
@@ -135,15 +154,14 @@ 

      toggleCleanupModal() {

          this.setState(prevState => ({

              cleanupModalShow: !prevState.cleanupModalShow,

-             cleanupSuffix: "",

-             cleanupBackend: "",

+             cleanupSuffix: prevState.suffixList[0],

              cleanupMaxUSN: ""

          }));

      }

  

      runCleanup() {

-         if (!this.state.cleanupSuffix && !this.state.cleanupBackend) {

-             this.props.addNotification("warning", "Suffix or backend name is required.");

+         if (!this.state.cleanupSuffix) {

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

          } else {

              let cmd = [

                  "dsconf",
@@ -157,9 +175,6 @@ 

              if (this.state.cleanupSuffix) {

                  cmd = [...cmd, "--suffix", this.state.cleanupSuffix];

              }

-             if (this.state.cleanupBackend) {

-                 cmd = [...cmd, "--backend", this.state.cleanupBackend];

-             }

              if (this.state.cleanupMaxUSN) {

                  cmd = [...cmd, "--max-usn", this.state.cleanupMaxUSN];

              }
@@ -201,10 +216,14 @@ 

              disableSwitch,

              cleanupModalShow,

              cleanupSuffix,

-             cleanupBackend,

-             cleanupMaxUSN

+             cleanupMaxUSN,

+             suffixList

          } = this.state;

  

+         let suffixes = suffixList.map((name) =>

+             <option key={name} value={name}>{name}</option>

+         );

+ 

          return (

              <div>

                  <Modal show={cleanupModalShow} onHide={this.toggleCleanupModal}>
@@ -225,42 +244,27 @@ 

                                  <Col sm={12}>

                                      <Form horizontal>

                                          <FormGroup controlId="cleanupSuffix" key="cleanupSuffix">

-                                             <Col sm={3}>

-                                                 <ControlLabel title="Gives the suffix or subtree in the Directory Server to run the cleanup operation against">

+                                             <Col sm={4}>

+                                                 <ControlLabel title="Gives the suffix in the Directory Server to run the cleanup operation against">

                                                      Cleanup Suffix

                                                  </ControlLabel>

                                              </Col>

-                                             <Col sm={9}>

-                                                 <FormControl

-                                                     type="text"

-                                                     value={cleanupSuffix}

-                                                     onChange={this.handleFieldChange}

-                                                 />

-                                             </Col>

-                                         </FormGroup>

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

-                                             <Col sm={3}>

-                                                 <ControlLabel title="Gives the Directory Server instance back end, or database, to run the cleanup operation against">

-                                                     Cleanup Backend

-                                                 </ControlLabel>

-                                             </Col>

-                                             <Col sm={9}>

-                                                 <FormControl

-                                                     type="text"

-                                                     value={cleanupBackend}

-                                                     onChange={this.handleFieldChange}

-                                                 />

+                                             <Col sm={8}>

+                                                 <select id="cleanupSuffix" onChange={this.handleFieldChange} defaultValue={cleanupSuffix}>

+                                                     {suffixes}

+                                                 </select>

                                              </Col>

                                          </FormGroup>

                                          <FormGroup controlId="cleanupMaxUSN" key="cleanupMaxUSN">

-                                             <Col sm={3}>

+                                             <Col sm={4}>

                                                  <ControlLabel title="Gives the highest USN value to delete when removing tombstone entries. All tombstone entries up to and including that number are deleted. Tombstone entries with higher USN values (that means more recent entries) are not deleted">

                                                      Cleanup Max USN

                                                  </ControlLabel>

                                              </Col>

-                                             <Col sm={9}>

+                                             <Col sm={8}>

                                                  <FormControl

-                                                     type="text"

+                                                     type="number"

+                                                     min="1"

                                                      value={cleanupMaxUSN}

                                                      onChange={this.handleFieldChange}

                                                  />

@@ -682,7 +682,7 @@ 

              cmd.push('--bind-passwd=' + this.state.agmtBindPW);

          }

          if (this.state.agmtBindDN != this.state._agmtBindDN) {

-             cmd.push('--bind-passwd=' + this.state.agmtBindDN);

+             cmd.push('--bind-dn=' + this.state.agmtBindDN);

          }

          if (this.state.agmtFracAttrs != this.state._agmtFracAttrs) {

              cmd.push('--frac-list=' + this.state.agmtFracAttrs.join(' '));

@@ -151,7 +151,7 @@ 

                  });

                  return;

              }

-             cmd.push("--cl-dir =" + this.state.clDir);

+             cmd.push("--cl-dir=" + this.state.clDir);

          }

          if (this.state.clMaxEntries != this.state._clMaxEntries) {

              cmd.push("--max-entries=" + this.state.clMaxEntries);

@@ -1402,7 +1402,7 @@ 

              spinner =

                  <Row>

                      <div className="ds-margin-top ds-modal-spinner">

-                         <Spinner loading inline size="lg" />Exporting database... <font size="1">(You can safely close this window)</font>

+                         <Spinner loading inline size="lg" />Exporting database... <font size="2">(You can safely close this window)</font>

                      </div>

                  </Row>;

          }

@@ -280,8 +280,8 @@ 

          if (this.props.disabled) {

              suffixClass = "ds-margin-top-xlg ds-disabled";

          }

-         let replAgmtNavTitle = 'Replication Agreements <font size="1">(' + this.props.agmtRows.length + ')</font>';

-         let winsyncNavTitle = 'Winsync Agreements <font size="1">(' + this.props.winsyncRows.length + ')</font>';

+         let replAgmtNavTitle = 'Replication Agreements <font size="2">(' + this.props.agmtRows.length + ')</font>';

+         let winsyncNavTitle = 'Winsync Agreements <font size="2">(' + this.props.winsyncRows.length + ')</font>';

  

          let enabledContent =

              <div className={suffixClass}>

@@ -162,7 +162,7 @@ 

                                                      eventKey="2"

                                                      className="ds-schema-dropdown"

                                                      onClick={() => {

-                                                         this.props.deleteHandler(rowData);

+                                                         this.props.deleteHandler(rowData.name[0]);

                                                      }}

                                                  >

                                                      Delete ObjectClass
@@ -374,7 +374,7 @@ 

                                                      eventKey="2"

                                                      className="ds-schema-dropdown"

                                                      onClick={() => {

-                                                         this.props.deleteHandler(rowData);

+                                                         this.props.deleteHandler(rowData.name[0]);

                                                      }}

                                                  >

                                                      Delete Attribute

@@ -492,8 +492,8 @@ 

      }

  

      render () {

-         let CATitle = 'Trusted Certificate Authorites <font size="1">(' + this.state.CACerts.length + ')</font>';

-         let ServerTitle = 'TLS Certificates <font size="1">(' + this.state.ServerCerts.length + ')</font>';

+         let CATitle = 'Trusted Certificate Authorites <font size="2">(' + this.state.CACerts.length + ')</font>';

+         let ServerTitle = 'TLS Certificates <font size="2">(' + this.state.ServerCerts.length + ')</font>';

  

          let certificatePage = '';

  

@@ -184,20 +184,21 @@ 

              <option key={name}>{name}</option>

          );

  

+         let eCiphers = '<h4>Enabled Ciphers <font size="2">(' + enabledList.length + ')</font></h4>';

+         let sCiphers = '<h4>Other Available Ciphers <font size="2">(' + supportedList.length + ')</font><h4>';

+ 

          if (this.state.saving) {

              cipherPage =

-                 <div className="ds-loading-spinner ds-center ds-margin-top-lg">

+                 <div className="ds-center ds-margin-top-lg">

                      <h4>Saving cipher preferences ...</h4>

                      <Spinner loading size="md" />

                  </div>;

          } else {

              cipherPage =

-                 <div className="container-fluid">

+                 <div>

                      <div className="ds-container">

                          <div className='ds-inline'>

-                             <div>

-                                 <h4>Enabled Ciphers</h4>

-                             </div>

+                             <div dangerouslySetInnerHTML={{__html: eCiphers}} />

                              <div>

                                  <select

                                      className="ds-cipher-width"
@@ -211,7 +212,7 @@ 

                          <div className="ds-divider-lrg" />

                          <div className='ds-inline'>

                              <div>

-                                 <h4>Other Available Ciphers</h4>

+                                 <div dangerouslySetInnerHTML={{__html: sCiphers}} />

                              </div>

                              <div>

                                  <select className="ds-cipher-width" size="16">
@@ -222,7 +223,7 @@ 

                      </div>

                      <hr />

                      <Row>

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

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

                              Cipher Suite

                          </Col>

                          <Col sm={9}>
@@ -238,7 +239,7 @@ 

                          </Col>

                      </Row>

                      <Row className="ds-margin-top">

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

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

                              Allow Specific Ciphers

                          </Col>

                          <Col sm={9}>
@@ -256,7 +257,7 @@ 

                          </Col>

                      </Row>

                      <Row className="ds-margin-top">

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

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

                              Deny Specific Ciphers

                          </Col>

                          <Col sm={9}>
@@ -286,7 +287,7 @@ 

          }

  

          return (

-             <div>

+             <div className="container-fluid">

                  {cipherPage}

              </div>

          );

@@ -123,6 +123,8 @@ 

          // Check if a setting was changed, if so enable the save button

          if (attr == 'mappingFallback' && this.state._mappingFallback != value) {

              disableSaveBtn = false;

+         } else if (attr == 'saslPriority' && this.state._saslPriority != value) {

+             disableSaveBtn = false;

          } else if (attr == 'maxBufSize' && this.state._maxBufSize != value) {

              disableSaveBtn = false;

          } else if (attr == 'allowedMechs' && this.state._allowedMechs.join(' ') != value.join(' ')) {
@@ -139,6 +141,8 @@ 

          // Now check for differences in values that we did not touch

          if (attr != 'mappingFallback' && this.state._mappingFallback != this.state.mappingFallback) {

              disableSaveBtn = false;

+         } else if (attr != 'saslPriority' && this.state._saslPriority != this.state.saslPriority) {

+             disableSaveBtn = false;

          } else if (attr != 'maxBufSize' && this.state._maxBufSize != this.state.maxBufSize) {

              disableSaveBtn = false;

          } else if (attr != 'allowedMechs' && this.state._allowedMechs.join(' ') != this.state.allowedMechs.join(' ')) {
@@ -173,6 +177,8 @@ 

              disableSaveBtn = false;

          } else if (attr == 'saslBase' && value != "") {

              disableSaveBtn = false;

+         } else if (attr == 'saslPriority' && value != "0") {

+             disableSaveBtn = false;

          } else if (attr == 'saslFilter' && value != "") {

              disableSaveBtn = false;

          }
@@ -192,7 +198,7 @@ 

              }

          }

  

-         // Handle TEst Text filed and buttons

+         // Handle Test Text field and buttons

          if (attr == 'saslTestText' && value != "" && this.state.saslMapRegex != "") {

              disableRegexTestBtn = false;

          }

@@ -33,8 +33,9 @@ 

  ];

  

  const rootdn_attrs = [

-     'nsslapd-rootpw',

      'nsslapd-rootpwstoragescheme',

+     'nsslapd-rootpw',

+     'confirmRootpw',

  ];

  

  const disk_attrs = [
@@ -173,15 +174,15 @@ 

  

          // Handle validating passwords are in sync

          if (attr == 'nsslapd-rootpw') {

-             if (value != this.state._confirmRootpw) {

+             if (value != this.state.confirmRootpw) {

                  disableSaveBtn = true;

                  errObj['nsslapd-rootpw'] = true;

              } else {

-                 errObj['nsslapdrootpw'] = false;

+                 errObj['nsslapd-rootpw'] = false;

              }

          }

          if (attr == 'confirmRootpw') {

-             if (value != this.state['_nsslapd-rootpw']) {

+             if (value != this.state['nsslapd-rootpw']) {

                  disableSaveBtn = true;

                  errObj['confirmRootpw'] = true;

              } else {
@@ -347,7 +348,7 @@ 

              'nsslapd-certdir': attrs['nsslapd-certdir'][0],

              'nsslapd-rootdn': attrs['nsslapd-rootdn'][0],

              'nsslapd-rootpw': attrs['nsslapd-rootpw'][0],

-             confirmRootpw: attrs['nsslapd-rootpw'][0],

+             'confirmRootpw': attrs['nsslapd-rootpw'][0],

              'nsslapd-rootpwstoragescheme': attrs['nsslapd-rootpwstoragescheme'][0],

              'nsslapd-anonlimitsdn': attrs['nsslapd-anonlimitsdn'][0],

              'nsslapd-disk-monitoring-threshold': attrs['nsslapd-disk-monitoring-threshold'][0],
@@ -376,7 +377,7 @@ 

              '_nsslapd-certdir': attrs['nsslapd-certdir'][0],

              '_nsslapd-rootdn': attrs['nsslapd-rootdn'][0],

              '_nsslapd-rootpw': attrs['nsslapd-rootpw'][0],

-             _confirmRootpw: attrs['nsslapd-rootpw'][0],

+             '_confirmRootpw': attrs['nsslapd-rootpw'][0],

              '_nsslapd-rootpwstoragescheme': attrs['nsslapd-rootpwstoragescheme'][0],

              '_nsslapd-anonlimitsdn': attrs['nsslapd-anonlimitsdn'][0],

              '_nsslapd-disk-monitoring-threshold': attrs['nsslapd-disk-monitoring-threshold'][0],
@@ -404,7 +405,7 @@ 

          ];

  

          for (let attr of rootdn_attrs) {

-             if (this.state['_' + attr] != this.state[attr]) {

+             if (attr != 'confirmRootpw' && this.state['_' + attr] != this.state[attr]) {

                  cmd.push(attr + "=" + this.state[attr]);

              }

          }
@@ -448,12 +449,12 @@ 

                              rootDNReloading: false,

                              'nsslapd-rootdn': attrs['nsslapd-rootdn'][0],

                              'nsslapd-rootpw': attrs['nsslapd-rootpw'][0],

-                             confirmRootpw: attrs['nsslapd-rootpw'][0],

+                             'confirmRootpw': attrs['nsslapd-rootpw'][0],

                              'nsslapd-rootpwstoragescheme': attrs['nsslapd-rootpwstoragescheme'][0],

                              // Record original values

                              '_nsslapd-rootdn': attrs['nsslapd-rootdn'][0],

                              '_nsslapd-rootpw': attrs['nsslapd-rootpw'][0],

-                             _confirmRootpw: attrs['nsslapd-rootpw'][0],

+                             '_confirmRootpw': attrs['nsslapd-rootpw'][0],

                              '_nsslapd-rootpwstoragescheme': attrs['nsslapd-rootpwstoragescheme'][0],

                              rootDNSaveDisabled: true

                          })

@@ -31,7 +31,7 @@ 

  

  export function log_cmd(js_func, desc, cmd_array) {

      if (console) {

-         let pw_args = ["--passwd", "--bind-pw"];

+         let pw_args = ["--passwd", "--bind-pw", "--nsslapd-rootpw"];

          let cmd_list = [];

          let converted_pw = false;

  

@@ -94,7 +94,7 @@ 

  

      toggleLoading() {

          this.setState(prevState => ({

-             loading: !prevState.loading

+             loading: !prevState.loading,

          }));

      }

  
@@ -138,15 +138,6 @@ 

      }

  

      pluginList() {

-         if (this.state.firstLoad) {

-             this.setState(prevState => ({

-                 firstLoad: false,

-                 pluginTabs: {

-                     ...prevState.pluginTabs,

-                     basicConfig: true

-                 }

-             }));

-         }

          cmd = [

              "dsconf",

              "-j",
@@ -154,22 +145,33 @@ 

              "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

-                     }, this.toggleLoading());

+                     if (this.state.firstLoad) {

+                         this.setState(prevState => ({

+                             pluginTabs: {

+                                 ...prevState.pluginTabs,

+                                 basicConfig: true

+                             },

+                             rows: myObject.items,

+                             firstLoad: false,

+                         }));

+                     } else {

+                         this.setState({

+                             rows: myObject.items

+                         });

+                     }

                  })

                  .fail(err => {

-                     if (err != 0) {

-                         let errMsg = JSON.parse(err);

-                         console.log("pluginList failed: ", errMsg.desc);

-                     }

-                     this.toggleLoading();

+                     let errMsg = JSON.parse(err);

+                     this.props.addNotification(

+                         "error",

+                         `${errMsg.desc} error during plugin loading`

+                     );

                  });

      }

  
@@ -321,6 +323,7 @@ 

                          addNotification={this.props.addNotification}

                          toggleLoadingHandler={this.toggleLoading}

                          wasActiveList={this.props.wasActiveList}

+                         key={this.props.wasActiveList}

                      />

                  )

              },
@@ -335,6 +338,7 @@ 

                          addNotification={this.props.addNotification}

                          toggleLoadingHandler={this.toggleLoading}

                          wasActiveList={this.props.wasActiveList}

+                         key={this.props.wasActiveList}

                      />

                  )

              },
@@ -349,6 +353,7 @@ 

                          addNotification={this.props.addNotification}

                          toggleLoadingHandler={this.toggleLoading}

                          wasActiveList={this.props.wasActiveList}

+                         key={this.props.wasActiveList}

                      />

                  )

              },
@@ -363,6 +368,7 @@ 

                          addNotification={this.props.addNotification}

                          toggleLoadingHandler={this.toggleLoading}

                          wasActiveList={this.props.wasActiveList}

+                         key={this.props.wasActiveList}

                      />

                  )

              },
@@ -377,6 +383,7 @@ 

                          addNotification={this.props.addNotification}

                          toggleLoadingHandler={this.toggleLoading}

                          wasActiveList={this.props.wasActiveList}

+                         key={this.props.wasActiveList}

                      />

                  )

              },
@@ -404,6 +411,7 @@ 

                          addNotification={this.props.addNotification}

                          toggleLoadingHandler={this.toggleLoading}

                          wasActiveList={this.props.wasActiveList}

+                         key={this.props.wasActiveList}

                      />

                  )

              },
@@ -431,6 +439,7 @@ 

                          addNotification={this.props.addNotification}

                          toggleLoadingHandler={this.toggleLoading}

                          wasActiveList={this.props.wasActiveList}

+                         key={this.props.wasActiveList}

                      />

                  )

              },
@@ -445,6 +454,7 @@ 

                          addNotification={this.props.addNotification}

                          toggleLoadingHandler={this.toggleLoading}

                          wasActiveList={this.props.wasActiveList}

+                         key={this.props.wasActiveList}

                      />

                  )

              },
@@ -472,67 +482,75 @@ 

                          addNotification={this.props.addNotification}

                          toggleLoadingHandler={this.toggleLoading}

                          wasActiveList={this.props.wasActiveList}

+                         key={this.props.wasActiveList}

                      />

                  )

              }

          };

+ 

          return (

              <div className="container-fluid">

-                 <Row className="clearfix">

-                     <Col sm={12}>

-                         <Spinner

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

-                             loading={this.state.loading}

-                             size="md"

-                         />

-                     </Col>

-                 </Row>

-                 <Tab.Container

-                     id="left-tabs-example"

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

-                 >

+                 <div className="ds-loading-spinner ds-center" hidden={!this.state.firstLoad}>

+                     <h4>Loading plugins ...</h4>

+                     <Spinner className="ds-margin-top" loading size="md" />

+                 </div>

+                 <div hidden={this.state.firstLoad}>

                      <Row className="clearfix">

-                         <Col sm={3}>

-                             <Nav bsStyle="pills" stacked>

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

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

-                                         {item.name}

-                                     </NavItem>

-                                 ))}

-                             </Nav>

-                         </Col>

-                         <Col sm={9}>

-                             <Tab.Content animation={false}>

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

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

-                                         {item.component}

-                                     </Tab.Pane>

-                                 ))}

-                             </Tab.Content>

+                         <Col sm={12}>

+                             <Spinner

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

+                                 loading={this.state.loading}

+                                 size="md"

+                             />

                          </Col>

                      </Row>

-                 </Tab.Container>

-                 <PluginEditModal

-                     handleChange={this.handleFieldChange}

-                     handleSwitchChange={this.handleSwitchChange}

-                     pluginData={{

-                         currentPluginName: this.state.currentPluginName,

-                         currentPluginType: this.state.currentPluginType,

-                         currentPluginEnabled: this.state.currentPluginEnabled,

-                         currentPluginPath: this.state.currentPluginPath,

-                         currentPluginInitfunc: this.state.currentPluginInitfunc,

-                         currentPluginId: this.state.currentPluginId,

-                         currentPluginVendor: this.state.currentPluginVendor,

-                         currentPluginVersion: this.state.currentPluginVersion,

-                         currentPluginDescription: this.state.currentPluginDescription,

-                         currentPluginDependsOnType: this.state.currentPluginDependsOnType,

-                         currentPluginDependsOnNamed: this.state.currentPluginDependsOnNamed,

-                         currentPluginPrecedence: this.state.currentPluginPrecedence

-                     }}

-                     closeHandler={this.closePluginModal}

-                     showModal={this.state.showPluginModal}

-                     savePluginHandler={this.savePlugin}

-                 />

+                     <Tab.Container

+                         id="left-tabs-example"

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

+                     >

+                         <Row className="clearfix">

+                             <Col sm={3}>

+                                 <Nav bsStyle="pills" stacked>

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

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

+                                             {item.name}

+                                         </NavItem>

+                                     ))}

+                                 </Nav>

+                             </Col>

+                             <Col sm={9}>

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

+                         handleChange={this.handleFieldChange}

+                         handleSwitchChange={this.handleSwitchChange}

+                         pluginData={{

+                             currentPluginName: this.state.currentPluginName,

+                             currentPluginType: this.state.currentPluginType,

+                             currentPluginEnabled: this.state.currentPluginEnabled,

+                             currentPluginPath: this.state.currentPluginPath,

+                             currentPluginInitfunc: this.state.currentPluginInitfunc,

+                             currentPluginId: this.state.currentPluginId,

+                             currentPluginVendor: this.state.currentPluginVendor,

+                             currentPluginVersion: this.state.currentPluginVersion,

+                             currentPluginDescription: this.state.currentPluginDescription,

+                             currentPluginDependsOnType: this.state.currentPluginDependsOnType,

+                             currentPluginDependsOnNamed: this.state.currentPluginDependsOnNamed,

+                             currentPluginPrecedence: this.state.currentPluginPrecedence

+                         }}

+                         closeHandler={this.closePluginModal}

+                         showModal={this.state.showPluginModal}

+                         savePluginHandler={this.savePlugin}

+                     />

+                 </div>

              </div>

          );

      }

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

      MatchingRulesTable

  } from "./lib/schema/schemaTables.jsx";

  import { ObjectClassModal, AttributeTypeModal } from "./lib/schema/schemaModals.jsx";

+ import { DoubleConfirmModal } from "./lib/notifications.jsx";

  import {

      Nav,

      NavItem,
@@ -50,6 +51,7 @@ 

              attributes: [],

              objectclasses: [],

              matchingrules: [],

+             deleteName: "",

  

              ocModalViewOnly: false,

              ocName: "",
@@ -97,20 +99,24 @@ 

          this.showAddObjectclassModal = this.showAddObjectclassModal.bind(this);

          this.openObjectclassModal = this.openObjectclassModal.bind(this);

          this.closeObjectclassModal = this.closeObjectclassModal.bind(this);

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

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

          this.addObjectclass = this.addObjectclass.bind(this);

          this.editObjectclass = this.editObjectclass.bind(this);

          this.cmdOperationObjectclass = this.cmdOperationObjectclass.bind(this);

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

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

  

          this.showViewAttributeModal = this.showViewAttributeModal.bind(this);

          this.showEditAttributeModal = this.showEditAttributeModal.bind(this);

          this.showAddAttributeModal = this.showAddAttributeModal.bind(this);

          this.openAttributeModal = this.openAttributeModal.bind(this);

          this.closeAttributeModal = this.closeAttributeModal.bind(this);

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

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

          this.addAttribute = this.addAttribute.bind(this);

          this.editAttribute = this.editAttribute.bind(this);

          this.cmdOperationAttribute = this.cmdOperationAttribute.bind(this);

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

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

      }

  

      toggleLoading(item) {
@@ -371,8 +377,25 @@ 

          this.setState({ objectclassModalShow: false });

      }

  

-     deleteObjectclass(rowData) {

-         let name = rowData.name[0];

+     closeConfirmOCDelete () {

+         // call doDeleteOC

+         this.setState({

+             showConfirmDeleteOC: false,

+             modalChecked: false,

+             modalSpinning: false,

+         });

+     }

+ 

+     showConfirmOCDelete(oc_name) {

+         this.setState({

+             showConfirmDeleteOC: true,

+             modalChecked: false,

+             modalSpinning: false,

+             deleteName: oc_name

+         });

+     }

+ 

+     doDeleteOC() {

          let cmd = [

              "dsconf",

              "-j",
@@ -380,10 +403,13 @@ 

              "schema",

              "objectclasses",

              "remove",

-             name

+             this.state.deleteName

          ];

  

-         this.toggleLoading("ocTable");

+         this.setState({

+             modalSpinning: true,

+         });

+ 

          log_cmd("deleteObjectclass", "Delete ObjectClass from schema", cmd);

          cockpit

                  .spawn(cmd, {
@@ -392,9 +418,9 @@ 

                  })

                  .done(content => {

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

-                     this.props.addNotification("success", `ObjectClass ${name} was successfully deleted`);

+                     this.props.addNotification("success", `ObjectClass ${this.state.deleteName} was successfully deleted`);

                      this.loadSchemaData();

-                     this.toggleLoading("ocTable");

+                     this.closeConfirmOCDelete();

                  })

                  .fail(err => {

                      let errMsg = JSON.parse(err);
@@ -403,7 +429,7 @@ 

                          `Error during ObjectClass removal operation - ${errMsg.desc}`

                      );

                      this.loadSchemaData();

-                     this.toggleLoading("ocTable");

+                     this.closeConfirmOCDelete();

                  });

      }

  
@@ -644,11 +670,29 @@ 

      }

  

      closeAttributeModal() {

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

+         this.setState({

+             attributeModalShow: false

+         });

+     }

+ 

+     closeConfirmAttrDelete () {

+         this.setState({

+             showConfirmAttrDelete: false,

+             modalChecked: false,

+             modalSpinning: false,

+         });

+     }

+ 

+     showConfirmAttrDelete(attr_name) {

+         this.setState({

+             showConfirmAttrDelete: true,

+             modalChecked: false,

+             modalSpinning: false,

+             deleteName: attr_name

+         });

      }

  

-     deleteAttribute(rowData) {

-         let name = rowData.name[0];

+     doDeleteAttr() {

          let cmd = [

              "dsconf",

              "-j",
@@ -656,10 +700,13 @@ 

              "schema",

              "attributetypes",

              "remove",

-             name

+             this.state.deleteName

          ];

  

-         this.toggleLoading("atTable");

+         this.setState({

+             modalSpinning: true,

+         });

+ 

          log_cmd("deleteAttribute", "Delete Attribute from schema", cmd);

          cockpit

                  .spawn(cmd, {
@@ -668,9 +715,9 @@ 

                  })

                  .done(content => {

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

-                     this.props.addNotification("success", `Attribute ${name} was successfully deleted`);

+                     this.props.addNotification("success", `Attribute ${this.state.deleteName} was successfully deleted`);

                      this.loadSchemaData();

-                     this.toggleLoading("atTable");

+                     this.closeConfirmAttrDelete();

                  })

                  .fail(err => {

                      let errMsg = JSON.parse(err);
@@ -679,7 +726,7 @@ 

                          `Error during Attribute removal operation - ${errMsg.desc}`

                      );

                      this.loadSchemaData();

-                     this.toggleLoading("atTable");

+                     this.closeConfirmAttrDelete();

                  });

      }

  
@@ -895,7 +942,7 @@ 

                                                  rows={this.state.filteredObjectclassRows}

                                                  viewModalHandler={this.showViewObjectclassModal}

                                                  editModalHandler={this.showEditObjectclassModal}

-                                                 deleteHandler={this.deleteObjectclass}

+                                                 deleteHandler={this.showConfirmOCDelete}

                                                  loading={this.state.ocTableLoading}

                                              />

                                              <Button
@@ -946,7 +993,7 @@ 

                                                  rows={this.state.filteredAttributesRows}

                                                  viewModalHandler={this.showViewAttributeModal}

                                                  editModalHandler={this.showEditAttributeModal}

-                                                 deleteHandler={this.deleteAttribute}

+                                                 deleteHandler={this.showConfirmAttrDelete}

                                                  syntaxes={this.state.syntaxes}

                                                  loading={this.state.atTableLoading}

                                              />
@@ -998,6 +1045,32 @@ 

                              </div>

                          </TabContainer>

                      </div>

+                     <DoubleConfirmModal

+                         showModal={this.state.showConfirmDeleteOC}

+                         closeHandler={this.closeConfirmOCDelete}

+                         handleChange={this.handleFieldChange}

+                         actionHandler={this.doDeleteOC}

+                         spinning={this.state.modalSpinning}

+                         item={this.state.deleteName}

+                         checked={this.state.modalChecked}

+                         mTitle="Delete An Objectclass"

+                         mMsg="Are you sure you want to delete this Objectclass?"

+                         mSpinningMsg="Deleting objectclass ..."

+                         mBtnName="Delete Objectclass"

+                     />

+                     <DoubleConfirmModal

+                         showModal={this.state.showConfirmAttrDelete}

+                         closeHandler={this.closeConfirmAttrDelete}

+                         handleChange={this.handleFieldChange}

+                         actionHandler={this.doDeleteAttr}

+                         spinning={this.state.modalSpinning}

+                         item={this.state.deleteName}

+                         checked={this.state.modalChecked}

+                         mTitle="Delete An Attribute"

+                         mMsg="Are you sure you want to delete this Attribute?"

+                         mSpinningMsg="Deleting attribute ..."

+                         mBtnName="Delete Attribute"

+                     />

                  </div>

              );

          }

@@ -84,6 +84,7 @@ 

          this.disableSecurity = this.disableSecurity.bind(this);

          this.saveSecurityConfig = this.saveSecurityConfig.bind(this);

          this.closeSecurityEnableModal = this.closeSecurityEnableModal.bind(this);

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

      }

  

      componentWillMount () {
@@ -103,6 +104,12 @@ 

          }

      }

  

+     reloadConfig () {

+         this.setState({

+             loaded: false

+         }, this.loadSecurityConfig);

+     }

+ 

      loadSupportedCiphers () {

          const cmd = [

              "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
@@ -482,7 +489,7 @@ 

          if (sslMin > sslMax) {

              this.props.addNotification(

                  "error",

-                 `The TLS minimum version but be less than or equal to the TLS maximum version`

+                 `The TLS minimum version must be less than or equal to the TLS maximum version`

              );

              // Reset page

              this.loadSecurityConfig();
@@ -620,7 +627,7 @@ 

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

                                  Secure Listen Host

                              </Col>

-                             <Col sm={4}>

+                             <Col sm={8}>

                                  <FormControl

                                      id="secureListenhost"

                                      type="text"
@@ -633,7 +640,7 @@ 

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

                                  Server Certificate Name

                              </Col>

-                             <Col sm={4}>

+                             <Col sm={8}>

                                  <Typeahead

                                      id="serverCertNameTypeahead"

                                      onChange={this.handleTypeaheadChange}
@@ -649,7 +656,7 @@ 

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

                                  Minimum TLS Version

                              </Col>

-                             <Col sm={4}>

+                             <Col sm={8}>

                                  <select id="sslVersionMin" className="btn btn-default dropdown ds-select" onChange={this.handleChange} value={this.state.sslVersionMin}>

                                      <option>TLS1.3</option>

                                      <option>TLS1.2</option>
@@ -765,7 +772,7 @@ 

                                  Security Settings

                                  <Icon className="ds-left-margin ds-refresh"

                                      type="fa" name="refresh" title="Refresh configuration settings"

-                                     onClick={this.loadSecurityConfig}

+                                     onClick={this.reloadConfig}

                                  />

                              </ControlLabel>

                          </Col>

@@ -227,6 +227,7 @@ 

                          serverId={this.props.serverId}

                          attrs={this.state.attrs}

                          enableTree={this.enableTree}

+                         addNotification={this.props.addNotification}

                      />

                  );

              } else if (this.state.node_name == "tuning-config") {
@@ -235,11 +236,16 @@ 

                          serverId={this.props.serverId}

                          attrs={this.state.attrs}

                          enableTree={this.enableTree}

+                         addNotification={this.props.addNotification}

                      />

                  );

              } else if (this.state.node_name == "sasl-config") {

                  server_element = (

-                     <ServerSASL serverId={this.props.serverId} enableTree={this.enableTree} />

+                     <ServerSASL

+                         serverId={this.props.serverId}

+                         enableTree={this.enableTree}

+                         addNotification={this.props.addNotification}

+                     />

                  );

              } else if (this.state.node_name == "security-config") {

                  server_element = (
@@ -255,6 +261,7 @@ 

                          serverId={this.props.serverId}

                          attrs={this.state.attrs}

                          enableTree={this.enableTree}

+                         addNotification={this.props.addNotification}

                      />

                  );

              } else if (this.state.node_name == "access-log-config") {
@@ -263,6 +270,7 @@ 

                          serverId={this.props.serverId}

                          attrs={this.state.attrs}

                          enableTree={this.enableTree}

+                         addNotification={this.props.addNotification}

                      />

                  );

              } else if (this.state.node_name == "audit-log-config") {
@@ -271,6 +279,7 @@ 

                          serverId={this.props.serverId}

                          attrs={this.state.attrs}

                          enableTree={this.enableTree}

+                         addNotification={this.props.addNotification}

                      />

                  );

              } else if (this.state.node_name == "auditfail-log-config") {
@@ -279,6 +288,7 @@ 

                          serverId={this.props.serverId}

                          attrs={this.state.attrs}

                          enableTree={this.enableTree}

+                         addNotification={this.props.addNotification}

                      />

                  );

              } else if (this.state.node_name == "error-log-config") {
@@ -287,6 +297,7 @@ 

                          serverId={this.props.serverId}

                          attrs={this.state.attrs}

                          enableTree={this.enableTree}

+                         addNotification={this.props.addNotification}

                      />

                  );

              }

@@ -167,5 +167,9 @@ 

              }

          ]

      },

-     plugins: plugins

+     plugins: plugins,

+     watchOptions: {

+         poll: true,

+         ignored: /node_modules/

+     }

  };

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

  from lib389.plugins import AttributeUniquenessPlugin, AttributeUniquenessPlugins

  from lib389.cli_conf import (add_generic_plugin_parsers, generic_object_edit, generic_object_add,

                               generic_enable, generic_disable, generic_status)

- from lib389._constants import DN_PLUGIN

  

  arg_to_attr = {

      'enabled': 'nsslapd-pluginenabled',
@@ -22,6 +21,7 @@ 

      'subtree_entries_oc': 'uniqueness-subtree-entries-oc'

  }

  

+ PLUGIN_DN = "cn=plugins,cn=config"

  

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

      log = log.getChild('attruniq_list')
@@ -30,11 +30,11 @@ 

      result_json = []

      for plugin in plugins.list():

          if args.json:

-             result_json.append(plugin.get_all_attrs_json())

+             result_json.append(json.loads(plugin.get_all_attrs_json()))

          else:

              result.append(plugin.rdn)

      if args.json:

-         log.info(json.dumps({"type": "list", "items": result_json}))

+         log.info(json.dumps({"type": "list", "items": result_json},  indent=4))

      else:

          if len(result) > 0:

              for i in result:
@@ -46,7 +46,11 @@ 

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

      log = log.getChild('attruniq_add')

      props = {'cn': args.NAME}

-     generic_object_add(AttributeUniquenessPlugin, inst, log, args, arg_to_attr, basedn=DN_PLUGIN, props=props)

+     # We require a subtree, or a target Objectclass

+     if args.subtree_entries_oc is None and args.subtree is None:

+         raise ValueError("A attribute uniqueness configuration requires a 'subtree' or 'subtree-entries-oc' to be set")

+ 

+     generic_object_add(AttributeUniquenessPlugin, inst, log, args, arg_to_attr, basedn=PLUGIN_DN, props=props)

  

  

  def attruniq_edit(inst, basedn, log, args):
@@ -64,8 +68,7 @@ 

      if not plugin.exists():

          raise ldap.NO_SUCH_OBJECT("Entry %s doesn't exists" % args.name)

      if args and args.json:

-         o_str = plugin.get_all_attrs_json()

-         log.info(o_str)

+         log.info(plugin.get_all_attrs_json())

      else:

          log.info(plugin.display())

  

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

      log.info('Attempting to add task entry...')

      if not plugin.status():

          log.error("'%s' is disabled. Fix up task can't be executed" % plugin.rdn)

-     task = plugin.cleanup(args.suffix, args.backend, args.maxusn)

+     task = plugin.cleanup(args.suffix, args.backend, args.max_usn)

      task.wait()

      exitcode = task.get_exit_code()

      if exitcode != 0:
@@ -66,6 +66,6 @@ 

      cleanup_group.add_argument('-n', '--backend',

                                 help='Gives the Directory Server instance back end, or database, to run the cleanup '

                                      'operation against. If the back end is not specified, then the suffix must be '

-                                     'specified.Backend instance in which USN tombstone entries (backend)')

-     cleanup_parser.add_argument('-m', '--maxusn', type=int, help='Gives the highest USN value to delete when '

+                                     'specified.  Backend instance in which USN tombstone entries (backend)')

+     cleanup_parser.add_argument('-m', '--max-usn', type=int, help='Gives the highest USN value to delete when '

                                                                   'removing tombstone entries (max_usn_to_delete)')

@@ -580,17 +580,38 @@ 

  

  

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

+     """Delete the manager entry is it exists, and remove it from replica

+     configuration if a suffix was provided.

+     """

+     deleted_manager_entry = False

      if is_a_dn(args.name):

          manager_dn = args.name

      else:

          manager_dn = "cn={},cn=config".format(args.name)

      manager = BootstrapReplicationManager(inst, dn=manager_dn)

-     manager.delete()

-     if args.suffix:

-         # Add supplier DN to config

+ 

+     try:

+         manager.delete()

+         deleted_manager_entry = True

+     except ldap.NO_SUCH_OBJECT:

+         # This is not okay if we did not specify a suffix

+         if args.suffix is None:

+             raise ValueError(f"The replication manager entry ({manager_dn}) does not exist.")

+ 

+     if args.suffix is not None:

+         # Delete supplier DN from the replication config

          replicas = Replicas(inst)

          replica = replicas.get(args.suffix)

-         replica.remove('nsds5ReplicaBindDN', manager_dn)

+         try:

+             replica.remove('nsds5ReplicaBindDN', manager_dn)

+         except ldap.NO_SUCH_ATTRIBUTE:

+             # The manager was not in the config

+             msg = f"The replication manager ({manager_dn}) does not exist in the suffix replication configuration"

+             if deleted_manager_entry:

+                 # We already deleted the manager entry, better say something

+                 msg += ", but the replication manager entry has been deleted from the global configuration."

+             raise ValueError(msg)

+ 

      log.info("Successfully deleted replication manager: " + manager_dn)

  

  

file modified
+4 -4
@@ -134,7 +134,7 @@ 

          'nsslapd-pluginDescription': 'Enforce unique attribute values',

      }

  

-     def __init__(self, instance, dn="cn=attribute uniqueness,cn=plugins,cn=config"):

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

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

          self._protected = False

          self._create_objectclasses = ['top', 'nsslapdplugin', 'extensibleObject']
@@ -192,13 +192,13 @@ 

          self._basedn = basedn

          # This is used to allow entry to instance to work

          self._list_attrlist = ['dn', 'nsslapd-pluginPath']

-         self._search_filter = "(nsslapd-pluginId=NSUniqueAttr)"

+         self._search_filter = "(nsslapd-pluginInitfunc=NSUniqueAttr_Init)"

This is absolutely horrible, but I'm also sure it works, so you know what, yolo.

          self._scope = ldap.SCOPE_SUBTREE

          self._server_controls = None

          self._client_controls = None

  

      def list(self):

-         """Get a list of all plugin instances where nsslapd-pluginId: NSUniqueAttr

+         """Get a list of all plugin instances where nsslapd-pluginInitfunc: NSUniqueAttr_Init

  

          :returns: A list of children entries

          """
@@ -206,7 +206,7 @@ 

          try:

              results = self._instance.search_ext_s(

                  base=self._basedn,

-                 scope=ldap.SCOPE_ONELEVEL,

+                 scope=self._scope,

                  filterstr=self._search_filter,

                  attrlist=self._list_attrlist,

                  serverctrls=self._server_controls, clientctrls=self._client_controls

Description:

This address many bugs, most of which are very small fixes:

  • [Bug 1816563] Referential integrity scope values are not saved in cockpit
  • [Bug 1816599] Initializing database from Ldif is broken in Cockpit
  • [Bug 1816708] Removing objectclass does not ask for confirmation
  • [Bug 1816712] Removing attribute does not ask for confirmation
  • [Bug 1816928] Other tabs become unclickable Or unresponsive if you click on Replication tab under Monitoring tab (WORKED FOR ME)
  • [Bug 1816956] Removing an attribute uniqueness does not ask for confirmation
  • [Bug 1816958] Run Fixup Task for USN under Plugins tab either Cleanup Suffix Or Cleanup Backend option should be there not the both
  • [Bug 1817062] Created attribute uniqueness is not visible after page refresh
  • [Bug 1817098] Instance fails to start after creating attribute uniqueness because of a missing attribute
  • [Bug 1817396] Various display problems on 'Server Settings'/'Security' Tab
  • [Bug 1817415] The 'Security Settings' refresh button does not work
  • [Bug 1817526] Cannot change Bind DN name in agreement
  • [Bug 1817554] cockpit crashes when creating new sasl mapping
  • [Bug 1817580] Reindex button is greyed out in Reindex Suffix
  • [Bug 1817585] Changing the SASL mapping priority prevents to create mapping
  • [Bug 1817983] Directory Manager Password can only be change when user click on Storage Scheme option
  • [Bug 1818016] Directory Manager Password is changing before the change in password storage scheme
  • [Bug 1818020] Confirm password field under Server setting's Directory manager tab not doing field check
  • [Bug 1818027] Cockpit broken when saving new changelog directory
  • [Bug 1818823] Can create replication manager without password and then it can't be deleted

Also made some improvement to the progress bar as it never reached 100%, and improved how the Plugin Tab is loaded. Additionally made some small cosmetic improvements as well.

relates: https://pagure.io/389-ds-base/issue/50994

I don't think this is correct. Attribute Unique is not based under a single cn like cn=attribute uniqueness with configs, it's multiple instances of the plugin, and they can be anywhere under cn=plugins. So this container may not exist (which could cause plugin create to fail), but also that it means instances of this plugin may not be found.

This in mind, attribute unique is absolutely cursed because of the way that it works like this, and it totally breaks down in the model that is lib389, and compounded by extensibleObject ....

So sadly, I think in this case, we can't make this change here to plugins.py, and if people do have a case where it's not finding the plugin, we'll need to work that out case by case .... :( :(

If I understand correctly, you do this because of the progress bar. Am I right?
I am just a bit concerned that you are deprecating this code... If I understand correctly, it will still show the create instance button but it will fail to create it with some obscure message.
I think we should deal with it somehow...

This looks a bit strange... You remove the loading toggler from managedEntries->loadConfigs, from attributeUniqueness but you don't remove it from some other places (i.e. linkedAttributes)
Also, I don't see the BZ it refers to in the commit message. Do I miss something?..

If you remove this, then it won't update the plugin list when we switch between the instances

If you remove this, then it won't update the plugin list when we switch between the instances

When we switch between instances EVERYTHING is reloaded, no?

This looks a bit strange... You remove the loading toggler from managedEntries->loadConfigs, from attributeUniqueness but you don't remove it from some other places (i.e. linkedAttributes)
Also, I don't see the BZ it refers to in the commit message. Do I miss something?..

I said in the commit that I improved the plugin loading, that's what this change is part of. I removed the toggling around the loading because it was redundant. We already do the toggling when we add/save or delete a managed entry config. Otherwise we toggle like 3 times on the managed entry config and the UI looks really sloppy as we are constantly spinning, then stops spinning, spins again, stops spinning, etc. It does it 3 times - well it used to that is.

This looks a bit strange... You remove the loading toggler from managedEntries->loadConfigs, from attributeUniqueness but you don't remove it from some other places (i.e. linkedAttributes)
Also, I don't see the BZ it refers to in the commit message. Do I miss something?..

I said in the commit that I improved the plugin loading, that's what this change is part of. I removed the toggling around the loading because it was redundant. We already do the toggling when we add/save or delete a managed entry config. Otherwise we toggle like 3 times on the managed entry config and the UI looks really sloppy as we are constantly spinning, then stops spinning, spins again, stops spinning, etc. It does it 3 times - well it used to that is.

Oh, and linked attributes does not exhibit this behaviour, it was only managed entries that did all this excessive toggling.

If I understand correctly, you do this because of the progress bar. Am I right?
I am just a bit concerned that you are deprecating this code... If I understand correctly, it will still show the create instance button but it will fail to create it with some obscure message.
I think we should deal with it somehow...

Ok I need to test the create instance button, but you can not have cockpit-389-ds without the 389-ds-base package. It's a hard dependency so there is no reason to check for it. Also people who build the products from source, and people on other platforms (debian for example) run into problems with the rpm check. Since it's not needed I removed it.

I don't think this is correct. Attribute Unique is not based under a single cn like cn=attribute uniqueness with configs, it's multiple instances of the plugin, and they can be anywhere under cn=plugins. So this container may not exist (which could cause plugin create to fail), but also that it means instances of this plugin may not be found.
This in mind, attribute unique is absolutely cursed because of the way that it works like this, and it totally breaks down in the model that is lib389, and compounded by extensibleObject ....
So sadly, I think in this case, we can't make this change here to plugins.py, and if people do have a case where it's not finding the plugin, we'll need to work that out case by case .... :( :(

Well currently dsconf does not work with attribute uniqueness at all if any of the plugin entries are disabled, because when its disabled all the plugin specific attributes, like vendor, funcs, ID's, etc are all none. This is how lib389 was finding the attr uniq plugin entries, so when its "off" it becomes invisible and there is no way to enable it.

Maybe we should enforce it being under a container? Regardless you are right, currently it's broken with or without my fix. I'll look into it some more...

When we switch between instances EVERYTHING is reloaded, no?

React reacts on the change of state. Do the child components - the only proper way now is manually define when we should react. Basically, the current structure (dsconf calls stuff) is the only explicit and simple way to do this.
After we will create one batch dsconf call which loads everything we can get rid of these things. But now... It's a lesser evil.

I said in the commit that I improved the plugin loading, that's what this change is part of. I removed the toggling around the loading because it was redundant. We already do the toggling when we add/save or delete a managed entry config. Otherwise we toggle like 3 times on the managed entry config and the UI looks really sloppy as we are constantly spinning, then stops spinning, spins again, stops spinning, etc. It does it 3 times - well it used to that is.

Sure, it makes sense and I agree. :)
My point is that it is inconsistent now. Other plugin components have the same toggling thingy.

When we switch between instances EVERYTHING is reloaded, no?

React reacts on the change of state. Do the child components - the only proper way now is manually define when we should react. Basically, the current structure (dsconf calls stuff) is the only explicit and simple way to do this.
After we will create one batch dsconf call which loads everything we can get rid of these things. But now... It's a lesser evil.

Previously when I switch instances I "thought" it reloaded everything but I did not confirm it. Anyway I will gladly add that code back...

Ok I need to test the create instance button, but you can not have cockpit-389-ds without the 389-ds-base package. It's a hard dependency so there is no reason to check for it. Also people who build the products from source, and people on other platforms (debian for example) run into problems with the rpm check. Since it's not needed I removed it.

I just tried to remove 389-ds-base and it hasn't removed cockpit-389-ds. So we probably should make it harder at that point.
But otherwise, I am okay with the change

I said in the commit that I improved the plugin loading, that's what this change is part of. I removed the toggling around the loading because it was redundant. We already do the toggling when we add/save or delete a managed entry config. Otherwise we toggle like 3 times on the managed entry config and the UI looks really sloppy as we are constantly spinning, then stops spinning, spins again, stops spinning, etc. It does it 3 times - well it used to that is.

Sure, it makes sense and I agree. :)
My point is that it is inconsistent now. Other plugin components have the same toggling thingy.

What I saw was that when we called pluginList somehow managed entry config loading was toggling three times. No other plugin did this, but I'll take a look at the other plugins like linked attributes and revise them if needed.

Ok I need to test the create instance button, but you can not have cockpit-389-ds without the 389-ds-base package. It's a hard dependency so there is no reason to check for it. Also people who build the products from source, and people on other platforms (debian for example) run into problems with the rpm check. Since it's not needed I removed it.

I just tried to remove 389-ds-base and it hasn't removed cockpit-389-ds. So we probably should make it harder at that point.
But otherwise, I am okay with the change

Hmm, you are right but I recall it being a dependency in the past. Perhaps the upstream (or source) spec file is off? I'll look into that...

1 new commit added

  • Updates
4 years ago

Okay @spichugi and @firstyear "everything" is fixed, please review...

Looks good to me for the parts I've reported and I haven't found anything new, so...
Ack from me!
I'll wait for @firstyear to confirm too :)

I don't think this is correct. Attribute Unique is not based under a single cn like cn=attribute uniqueness with configs, it's multiple instances of the plugin, and they can be anywhere under cn=plugins. So this container may not exist (which could cause plugin create to fail), but also that it means instances of this plugin may not be found.
This in mind, attribute unique is absolutely cursed because of the way that it works like this, and it totally breaks down in the model that is lib389, and compounded by extensibleObject ....
So sadly, I think in this case, we can't make this change here to plugins.py, and if people do have a case where it's not finding the plugin, we'll need to work that out case by case .... :( :(

Well currently dsconf does not work with attribute uniqueness at all if any of the plugin entries are disabled, because when its disabled all the plugin specific attributes, like vendor, funcs, ID's, etc are all none. This is how lib389 was finding the attr uniq plugin entries, so when its "off" it becomes invisible and there is no way to enable it.
Maybe we should enforce it being under a container? Regardless you are right, currently it's broken with or without my fix. I'll look into it some more...

Honestly that whole plugin just needs it's configuration syntax fixed, and until then lib389 will always struggle to manage it :(

This is absolutely horrible, but I'm also sure it works, so you know what, yolo.

Ack from my side re the plugins/python bits that I checked :)

This is absolutely horrible, but I'm also sure it works, so you know what, yolo.

Oh I know. At least the plugin function attribute is static and is not set whether the plugin is enabled or not. Lesser of two evils until we can rework the plugin. That's more of a 1.4.4 or 1.5.0 change...

rebased onto 862d044

4 years ago

Pull-Request has been merged by mreynolds

4 years ago

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

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

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 34
+2 -1
file changed
rpm/389-ds-base.spec.in
+7 -31
file changed
src/cockpit/389-console/src/ds.jsx
+3 -3
file changed
src/cockpit/389-console/src/lib/database/databaseModal.jsx
+7 -3
file changed
src/cockpit/389-console/src/lib/database/suffix.jsx
+2 -2
file changed
src/cockpit/389-console/src/lib/database/vlvIndexes.jsx
+8 -8
file changed
src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
+71 -20
file changed
src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
+0 -3
file changed
src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
+0 -6
file changed
src/cockpit/389-console/src/lib/plugins/dna.jsx
+0 -3
file changed
src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
+0 -3
file changed
src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
+0 -3
file changed
src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
+1 -1
file changed
src/cockpit/389-console/src/lib/plugins/pluginTables.jsx
+6 -6
file changed
src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
+40 -36
file changed
src/cockpit/389-console/src/lib/plugins/usn.jsx
+1 -1
file changed
src/cockpit/389-console/src/lib/replication/replAgmts.jsx
+1 -1
file changed
src/cockpit/389-console/src/lib/replication/replChangelog.jsx
+1 -1
file changed
src/cockpit/389-console/src/lib/replication/replModals.jsx
+2 -2
file changed
src/cockpit/389-console/src/lib/replication/replSuffix.jsx
+2 -2
file changed
src/cockpit/389-console/src/lib/schema/schemaTables.jsx
+2 -2
file changed
src/cockpit/389-console/src/lib/security/certificateManagement.jsx
+11 -10
file changed
src/cockpit/389-console/src/lib/security/ciphers.jsx
+7 -1
file changed
src/cockpit/389-console/src/lib/server/sasl.jsx
+10 -9
file changed
src/cockpit/389-console/src/lib/server/settings.jsx
+1 -1
file changed
src/cockpit/389-console/src/lib/tools.jsx
+89 -71
file changed
src/cockpit/389-console/src/plugins.jsx
+92 -19
file changed
src/cockpit/389-console/src/schema.jsx
+12 -5
file changed
src/cockpit/389-console/src/security.jsx
+12 -1
file changed
src/cockpit/389-console/src/server.jsx
+5 -1
file changed
src/cockpit/389-console/webpack.config.js
+9 -6
file changed
src/lib389/lib389/cli_conf/plugins/attruniq.py
+3 -3
file changed
src/lib389/lib389/cli_conf/plugins/usn.py
+25 -4
file changed
src/lib389/lib389/cli_conf/replication.py
+4 -4
file changed
src/lib389/lib389/plugins.py