#50893 Issue 50855 - UI: Port Server Tab to React
Closed 3 years ago by spichugi. Opened 4 years ago by mreynolds.
mreynolds/389-ds-base issue50855  into  master

@@ -20,7 +20,7 @@ 

  }

  

  .ds-left-align {

-     right: 0 !important;

+     text-align: left !important;

  }

  

  /* Main nav page index.html */
@@ -37,36 +37,11 @@ 

      margin-left: 65px;

  }

  

- .ds-oc-add-del-btn {

-     padding: 0 !important;

-     margin: 10px !important;

-     width: 130px;

-     height: 26px;

-     text-align: center;

- }

- 

- .ds-oc-must-buttons {

-     padding: 0 !important;

-     padding-left: 3px;

-     margin-top: 70px;

-     margin-bottom: 75px;

-     margin-left: 5px;

-     margin-right: 9px;

- }

- 

  /* This can probably be removed once we switch to PF React 4 - suffix action button */

  .ds-action-button {

      color: white;

  }

  

- .ds-oc-may-buttons {

-     padding0: !important;

-     padding-left: 3px;

-     margin-top: 4px;

-     margin-left: 5px;

-     margin-right: 9px;

- }

- 

  .ds-refresh {

      background-color: #0088ce;

      background-image: linear-gradient(to bottom,#39a5dc 0,#0088ce 100%);
@@ -124,6 +99,10 @@ 

      padding: 0px !important;

  }

  

+ .ds-width-sm {

+     max-width: 100px;

+ }

+ 

  .ds-tree {

      font-size: 0.5;

      background-color: #f8f8f8;
@@ -164,12 +143,6 @@ 

      padding-left: 5px;

  }

  

- .ds-input-auto-good {

-     width: 100%;

-     border-color: green;

-     padding-left: 5px;

- }

- 

  .ds-input-right {

      text-align: right;

  }
@@ -182,14 +155,6 @@ 

      max-width: 65px !important;

  }

  

- .ds-lag-report {

-     min-width: 800px !important;

- }

- 

- .ds-repl-mgr {

-     max-width: 600px !important;

- }

- 

  .ds-history-input {

      margin-top: !important;

      margin-right: 5px;
@@ -204,53 +169,10 @@ 

      width: 50px;

  }

  

- .ds-divider-med {

-     width: 15px;

- }

- 

- .ds-repl-managers-list {

-     width: 420px;

-     height: 200px;

- }

- 

- .ds-repl-managers-button {

-     margin-top: -15px;

-     margin-bottom: 10px;

-     margin-left: 0px;

- }

- 

- .ds-repl-manager-checkbox {

-     margin-top: 20px !important;

-     padding-top: 20px !important;

-     margin-left: 10px !important;

-     margin-right: 10px !important;

- }

- 

  .ds-label {

      vertical-align: middle !important; /* Fixes any weird issues in Firefox and IE */

  }

  

- /* Replication styles */

- .ds-agmt-dropdown {

-     padding: 0px !important;

-     line-height: 0 !important;

-     overflow: visible !important;

-     width: auto !important;

- }

- 

- .ds-oc-dropdown {

-     margin-top: 5px;

-     color: #181818 !important;

-     padding: 0px !important;

-     line-height: 0 !important;

-     height: 30px;

-     max-height: 200px !important;

-     width: 315px !important;

-     text-align: left;

-     outline: 0 !important;

-     overflow: auto;

- }

- 

  .ds-dblink-dropdown {

      width: 140px !important;

      text-align: left;
@@ -284,15 +206,6 @@ 

      line-height: 1;

  }

  

- .ds-sasl-table {

-     background-color: white !important;

-     padding: 0px;

-     border: 1px solid #909090;

-     table-layout: fixed;

-     clear: both;

-     word-wrap: break-word !important;

-     text-align: center;

- }

  

  .ds-db-table {

      border: 1px solid #d1d1d1;
@@ -357,36 +270,12 @@ 

      margin-left: 0 !important;

  }

  

- .ds-fractional-list {

-     width: 230px;

-     min-height: 100px !important;

-     max-height: 100px !important;

-     overflow-y: scroll;

- }

- 

- .ds-fractional-btn {

-     width: 150px;

-     height: 30px;

-     margin: 5px 5px 5px 10px;

- }

- 

- .ds-agmt-schedule-checkbox {

-     margin: 8px !important;

-     padding: 5px;

- }

- 

  .ds-config-checkbox {

      margin-top: 10px !important;

      margin-bottom: 10px !important;

      margin-right: 5px !important;

  }

  

- .ds-server-checkbox {

-     margin-top: 15px !important;

-     margin-right: 12px !important;

-     padding: 5px;

- }

- 

  .ds-send-expiring-checkbox {

      margin-top: 12px !important;

      margin-right: 12px !important;
@@ -411,12 +300,6 @@ 

      margin-bottom: 10px !important;

  }

  

- .ds-config-label-med {

-     margin-top: 10px;

-     width: 125px !important;

-     margin-bottom: 10px !important;

- }

- 

  .ds-config-sub-label {

      margin-top: 10px;

      width: 225px !important;
@@ -429,11 +312,6 @@ 

      margin-bottom: 10px !important;

  }

  

- .ds-cleanallruv-label {

-     width: 150px !important;

-     margin-bottom: 10px !important;

- }

- 

  .ds-expire-label {

      margin-top: 7px;

      margin-bottom: 7px;
@@ -444,30 +322,10 @@ 

      padding: 10px;

  }

  

- .ds-config-diskmon-label {

-     width: 210px !important;

-     padding-left: 30px;

-     margin-top: 10px;

- }

- 

- .ds-config-indent-sm-label {

-     margin-top: 10px;

-     margin-bottom: 10px;

-     width: 195px;

-     margin-left: 40px !important;

-     padding-right: 10px !important;

- }

- 

  .ds-expired-div {

      padding-left: 30px !important;

  }

  

- .ds-config-diskmon-checkbox {

-     margin: 12px !important;

-     padding: 5px;

-     margin-left: 30px !important;

- }

- 

  .ds-modal-row {

      margin-left: 20px;

      margin-right: 0px !important;
@@ -497,13 +355,6 @@ 

      min-height: 350px !important;

  }

  

- .ds-may-must-list {

-     width: 232px !important;

-     height: 150px !important;

-     margin: 0 !important;

-     padding: 0 !important;

- }

- 

  p {

      line-height: 1;

      white-space: normal;
@@ -676,10 +527,6 @@ 

      background-color: transparent !important;

  }

  

- .ds-accordion-panel {

-     margin-top: 10px;

- }

- 

  .ds-margin-top {

      margin-top: 10px !important;

  }
@@ -775,24 +622,11 @@ 

      margin-left: 5px !important;

  }

  

- .ds-td {

-     padding-left: 10px !important;

-     padding-top: 8px !important;

-     padding-bottom: 0px !important;

-     valign: middle !important;

- }

- 

- .ds-mgr-table {

-     max-width: 600px;

-     min-width: 600px;

- }

- 

  .ds-loglevel-table {

      max-width: 500px;

      min-width: 500px;

      table-layout: fixed;

      margin-top: 20px;

-     margin-left: 20px;

  }

  

  .ds-table-checkbox {
@@ -800,14 +634,6 @@ 

      text-align: center;

  }

  

- .ds-page-content {

-     margin-top: 30px !important;

- }

- 

- .ds-spacing-sm {

-     margin-right: 10px;

- }

- 

  .navbar-default {

      margin-bottom: 0px;

  }
@@ -832,28 +658,10 @@ 

      margin-left: 140px !important;

  }

  

- .ds-nested-modal {

-     margin-top: 100px;

-     margin-left: 100px;

-     min-width: 275px;

- }

- 

- .ds-attr-select {

-     min-height: 300px;

-     max-height: 300px;

-     width: 265px;

-     overflow: auto;

- }

- 

  .ds-center {

      text-align: center;

  }

  

- .ds-trailing-btn {

-     margin-left: 10px;

-     margin-top: -2px;

- }

- 

  .ds-loading-spinner {

      position: fixed;

      top: 25%;
@@ -889,6 +697,10 @@ 

      margin-top: -3px;

  }

  

+ .ds-lower-field {

+     margin-top: 3px;

+ }

+ 

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

      position: relative;

  }
@@ -995,7 +807,7 @@ 

  }

  

  .ds-suffix-header {

-     font-size: 18px;

+     font-size: 20px;

      margin-bottom: 15px;

      padding-top: 5px;

  }

@@ -9,6 +9,8 @@ 

  import { GlobalDatabaseConfig } from "./lib/database/databaseConfig.jsx";

  import { Suffix } from "./lib/database/suffix.jsx";

  import { Backups } from "./lib/database/backups.jsx";

+ import { GlobalPwPolicy } from "./lib/database/globalPwp.jsx";

+ import { LocalPwPolicy } from "./lib/database/localPwp.jsx";

  import {

      Modal,

      Icon,
@@ -28,6 +30,8 @@ 

  const DB_CONFIG = "dbconfig";

  const CHAINING_CONFIG = "chaining-config";

  const BACKUP_CONFIG = "backups";

+ const PWP_CONFIG = "pwpolicy";

+ const LOCAL_PWP_CONFIG = "localpwpolicy";

  const treeViewContainerStyles = {

      width: '295px',

  };
@@ -189,7 +193,6 @@ 

                                      importcacheauto: attrs['nsslapd-import-cache-autosize'],

                                      importcachesize: attrs['nsslapd-import-cachesize'],

                                  },

-                             loaded: true,

                              configUpdated: 1

                          }), this.setState({configUpdated: 0}));

                  })
@@ -360,18 +363,38 @@ 

                              id: "backups",

                          },

                          {

-                             "text": "Suffixes",

-                             "icon": "pficon-catalog",

-                             "state": {"expanded": true},

+                             text: "Password Policies",

+                             icon: "pficon-key",

                              selectable: false,

-                             "nodes": []

+                             state: {"expanded": true},

+                             "nodes": [

+                                 {

+                                     text: "Global Policy",

+                                     icon: "glyphicon glyphicon-globe",

+                                     selectable: true,

+                                     id: "pwpolicy",

+                                 },

+                                 {

+                                     text: "Local Policies",

+                                     icon: "pficon-home",

+                                     selectable: true,

+                                     id: "localpwpolicy",

+                                 },

+                             ]

+                         },

+                         {

+                             text: "Suffixes",

+                             icon: "pficon-catalog",

+                             state: {"expanded": true},

+                             selectable: false,

+                             nodes: []

                          }

                      ];

                      let current_node = this.state.node_name;

                      if (fullReset) {

                          current_node = DB_CONFIG;

                      }

-                     basicData[3].nodes = treeData;

+                     basicData[4].nodes = treeData;

  

                      this.setState(() => ({

                          nodes: basicData,
@@ -463,6 +486,8 @@ 

  

          if (selectedNode.id == "dbconfig" ||

              selectedNode.id == "chaining-config" ||

+             selectedNode.id == "pwpolicy" ||

+             selectedNode.id == "localpwpolicy" ||

              selectedNode.id == "backups") {

              // Nothing special to do, these configurations have already been loaded

              this.setState(prevState => {
@@ -557,8 +582,8 @@ 

              el.setAttribute('title', el.innerText);

          }

          this.setState({

-             disableTree: false

-         });

+             disableTree: false,

+         }, this.loadAttrs());

      }

  

      showSuffixModal () {
@@ -1070,17 +1095,18 @@ 

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

              "schema", "attributetypes", "list"

          ];

-         log_cmd("Suffixes", "Get attrs", attr_cmd);

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

          cockpit

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

                  .done(content => {

                      let attrContent = JSON.parse(content);

                      let attrs = [];

                      for (let content of attrContent['items']) {

-                         attrs.push(content.name);

+                         attrs.push(content.name[0]);

                      }

                      this.setState({

                          attributes: attrs,

+                         loaded: true

                      });

                  })

                  .fail(err => {
@@ -1101,6 +1127,7 @@ 

      render() {

          const { nodes } = this.state;

          let db_element = "";

+         let body = "";

          let disabled = "tree-view-container";

          if (this.state.disableTree) {

              disabled = "tree-view-container ds-disabled";
@@ -1127,6 +1154,22 @@ 

                          enableTree={this.enableTree}

                          key={this.state.chainingUpdated}

                      />;

+             } else if (this.state.node_name == PWP_CONFIG) {

+                 db_element =

+                     <GlobalPwPolicy

+                         serverId={this.props.serverId}

+                         addNotification={this.addNotification}

+                         attrs={this.state.attributes}

+                         enableTree={this.enableTree}

+                     />;

+             } else if (this.state.node_name == LOCAL_PWP_CONFIG) {

+                 db_element =

+                     <LocalPwPolicy

+                         serverId={this.props.serverId}

+                         addNotification={this.addNotification}

+                         attrs={this.state.attributes}

+                         enableTree={this.enableTree}

+                     />;

              } else if (this.state.node_name == BACKUP_CONFIG) {

                  db_element =

                      <Backups
@@ -1192,14 +1235,7 @@ 

                      }

                  }

              }

-         }

- 

-         return (

-             <div className="container-fluid">

-                 <NotificationController

-                     notifications={this.state.notifications}

-                     removeNotificationAction={this.removeNotification}

-                 />

+             body =

                  <div className="ds-container">

                      <div>

                          <div className="ds-tree">
@@ -1221,7 +1257,22 @@ 

                      <div className="ds-tree-content">

                          {db_element}

                      </div>

-                 </div>

+                 </div>;

+         } else {

+             body =

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

+                     <h4>Loading database configuration ...</h4>

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

+                 </div>;

+         }

+ 

+         return (

+             <div className="container-fluid">

+                 <NotificationController

+                     notifications={this.state.notifications}

+                     removeNotificationAction={this.removeNotification}

+                 />

+                 {body}

                  <CreateSuffixModal

                      showModal={this.state.showSuffixModal}

                      closeHandler={this.closeSuffixModal}

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

   * We can't load the config until all the html pages are load, so we'll use vars

   * to track the loading, and once all the pages are loaded, then we can load the config

   */

- var server_page_loaded = 0;

+ var server_page_loaded = 1;

  var security_page_loaded = 1;

  var db_page_loaded = 1;

  var repl_page_loaded = 1;
@@ -175,7 +175,6 @@ 

      return("${days} days, ${hours} hours, ${minutes} minutes, and ${seconds} seconds");

  }

  

- 

  function set_no_insts () {

      $("#select-server").empty();

      $("#select-server").append('<option value="No instances">No instances</option>');
@@ -221,7 +220,6 @@ 

            $(".all-pages").hide();

            $("#ds-navigation").show();

            $("#server-content").show();

-           $("#server-config").show();

          }

          $("#not-running").hide();

          $("#no-connect").hide();
@@ -378,9 +376,6 @@ 

    progress = 10;

    update_progress();

  

-   // Load the configuration for all the pages.

-   var dropdowns = ['local-pwp-suffix', 'select-repl-cfg-suffix'];

- 

    // Show the spinner, and reset the pages

    $("#loading-msg").html("Loading Directory Server configuration for <i><b>" + server_id + "</b></i>...");

    $("#everything").hide();
@@ -395,22 +390,8 @@ 

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

    log_cmd('load_config', 'get backend list', cmd);

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

-     // Update dropdowns

-     for (var idx in dropdowns) {

-       $("#" + dropdowns[idx]).find('option').remove();

-     }

-     var obj = JSON.parse(data);

-     for (var idx in obj['items']) {

-       for (var list in dropdowns){

-         $("#" + dropdowns[list]).append('<option value="' + obj['items'][idx] + '" selected="selected">' + obj['items'][idx] +'</option>');

-       }

-     }

- 

-     // Server page

-     get_and_set_config();

-     get_and_set_sasl();

-     get_and_set_localpwp();

      update_progress();

+     config_loaded = 1;

  

      // Initialize the tabs

      $(".ds-tab-list").css( 'color', '#777');
@@ -422,7 +403,6 @@ 

          $("#loading-page").hide();

          $("#everything").show();

          $("#server-content").show();

-         $("#server-config").show();

          clearInterval(loading_config);

          loading_cfg = 0;

  
@@ -444,6 +424,313 @@ 

    });

  }

  

+ 

+ 

+ // Create Instance

+ $("#create-inst-save").on("click", function() {

+     $(".ds-modal-error").hide();

+     $(".ds-inst-input").css("border-color", "initial");

+ 

+     /*

+      * Validate settings and update the INF settings

+      */

+     let setup_inf = create_inf_template;

+ 

+     // Server ID

+     let new_server_id = $("#create-inst-serverid").val();

+     if (new_server_id == ""){

+         report_err($("#create-inst-serverid"), 'You must provide an Instance name');

+         $("#create-inst-serverid").css("border-color", "red");

+         return;

+     } else {

+         new_server_id = new_server_id.replace(/^slapd-/i, "");  // strip "slapd-"

+         if (new_server_id.length > 128) {

+             report_err($("#create-inst-serverid"), 'Instance name is too long, it must not exceed 128 characters');

+             $("#create-inst-serverid").css("border-color", "red");

+             return;

+         }

+         if (new_server_id.match(/^[#%:A-Za-z0-9_\-]+$/g)) {

+             setup_inf = setup_inf.replace('INST_NAME', new_server_id);

+         } else {

+             report_err($("#create-inst-serverid"), 'Instance name can only contain letters, numbers, and:  # % : - _');

+             $("#create-inst-serverid").css("border-color", "red");

+             return;

+         }

+     }

+ 

+     // Port

+     let server_port = $("#create-inst-port").val();

+     if (server_port == ""){

+         report_err($("#create-inst-port"), 'You must provide a port number');

+         $("#create-inst-port").css("border-color", "red");

+         return;

+     } else if (!valid_port(server_port)) {

+         report_err($("#create-inst-port"), 'Port must be a number between 1 and 65534!');

+         $("#create-inst-port").css("border-color", "red");

+         return;

+     } else {

+         setup_inf = setup_inf.replace('PORT', server_port);

+     }

+ 

+     // Secure Port

+     let secure_port = $("#create-inst-secureport").val();

+     if (secure_port == ""){

+         report_err($("#create-inst-secureport"), 'You must provide a secure port number');

+         $("#create-inst-secureport").css("border-color", "red");

+         return;

+     } else if (!valid_port(secure_port)) {

+         report_err($("#create-inst-secureport"), 'Secure port must be a number!');

+         $("#create-inst-secureport").css("border-color", "red");

+         return;

+     } else {

+         setup_inf = setup_inf.replace('SECURE_PORT', secure_port);

+     }

+ 

+     // Root DN

+     let server_rootdn = $("#create-inst-rootdn").val();

+     if (server_rootdn == ""){

+         report_err($("#create-inst-rootdn"), 'You must provide a Directory Manager DN');

+         $("#create-inst-rootdn").css("border-color", "red");

+         return;

+     } else {

+         setup_inf = setup_inf.replace('ROOTDN', server_rootdn);

+     }

+ 

+     // Setup Self-Signed Certs

+     if ( $("#create-inst-tls").is(":checked") ){

+         setup_inf = setup_inf.replace('SELF_SIGN', 'True');

+     } else {

+         setup_inf = setup_inf.replace('SELF_SIGN', 'False');

+     }

+ 

+     // Root DN password

+     let root_pw = $("#rootdn-pw").val();

+     let root_pw_confirm = $("#rootdn-pw-confirm").val();

+     if (root_pw != root_pw_confirm) {

+         report_err($("#rootdn-pw"), 'Directory Manager passwords do not match!');

+         $("#rootdn-pw-confirm").css("border-color", "red");

+         return;

+     } else if (root_pw == ""){

+         report_err($("#rootdn-pw"), 'Directory Manager password can not be empty!');

+         $("#rootdn-pw-confirm").css("border-color", "red");

+         return;

+     } else if (root_pw.length < 8) {

+         report_err($("#rootdn-pw"), 'Directory Manager password must have at least 8 characters');

+         $("#rootdn-pw-confirm").css("border-color", "red");

+         return;

+     } else {

+         setup_inf = setup_inf.replace('ROOTPW', root_pw);

+     }

+ 

+     // Backend/Suffix

+     let backend_name = $("#backend-name").val();

+     let backend_suffix = $("#backend-suffix").val();

+     if ( (backend_name != "" && backend_suffix == "") || (backend_name == "" && backend_suffix != "") ) {

+         if (backend_name == ""){

+             report_err($("#backend-name"), 'If you specify a backend suffix, you must also specify a backend name');

+             $("#backend-name").css("border-color", "red");

+             return;

+         } else {

+             report_err($("#backend-suffix"), 'If you specify a backend name, you must also specify a backend suffix');

+             $("#backend-suffix").css("border-color", "red");

+             return;

+         }

+     }

+     if (backend_name != ""){

+         // We definitely have a backend name and suffix, next validate the suffix is a DN

+         if (valid_dn(backend_suffix)) {

+             // It's valid, add it

+             setup_inf += "\n[backend-" + backend_name + "]\nsuffix = " + backend_suffix + "\n";

+         } else {

+             // Not a valid DN

+             report_err($("#backend-suffix"), 'Invalid DN for Backend Suffix');

+             return;

+         }

+         if ( $("#create-sample-entries").is(":checked") ) {

+             setup_inf += '\nsample_entries = yes\n';

+         } else if ( $("#create-suffix-entry").is(":checked") ) {

+             setup_inf += '\ncreate_suffix_entry = yes\n';

+         }

+     }

+ 

+     /*

+      * Here are steps we take to create the instance

+      *

+      * [1] Get FQDN Name for nsslapd-localhost setting in setup file

+      * [2] Create a file for the inf setup parameters

+      * [3] Set strict permissions on that file

+      * [4] Populate the new setup file with settings (including cleartext password)

+      * [5] Create the instance

+      * [6] Remove setup file

+      */

+     cockpit.spawn(["hostname", "--fqdn"], { superuser: true, "err": "message" }).fail(function(ex, data) {

+         // Failed to get FQDN

+         popup_err("Failed to get hostname!", data);

+     }).done(function (data) {

+         /*

+          * We have FQDN, so set the hostname in inf file, and create the setup file

+          */

+         setup_inf = setup_inf.replace('FQDN', data);

+         let setup_file = "/tmp/389-setup-" + (new Date).getTime() + ".inf";

+         let rm_cmd = ['rm', setup_file];

+         let create_file_cmd = ['touch', setup_file];

+         cockpit.spawn(create_file_cmd, { superuser: true, "err": "message" }).fail(function(ex, data) {

+             // Failed to create setup file

+             popup_err("Failed to create installation file!", data);

+         }).done(function (){

+             /*

+              * We have our new setup file, now set permissions on that setup file before we add sensitive data

+              */

+             let chmod_cmd = ['chmod', '600', setup_file];

+             cockpit.spawn(chmod_cmd, { superuser: true, "err": "message" }).fail(function(ex, data) {

+                 // Failed to set permissions on setup file

+                 cockpit.spawn(rm_cmd, { superuser: true });  // Remove Inf file with clear text password

+                 $("#create-inst-spinner").hide();

+                 popup_err("Failed to set permission on setup file " + setup_file + ": ", data);

+             }).done(function () {

+                 /*

+                  * Success we have our setup file and it has the correct permissions.

+                  * Now populate the setup file...

+                  */

+                 let cmd = ["/bin/sh", "-c", '/usr/bin/echo -e "' + setup_inf + '" >> ' + setup_file];

+                 cockpit.spawn(cmd, { superuser: true, "err": "message" }).fail(function(ex, data) {

+                     // Failed to populate setup file

+                     popup_err("Failed to populate installation file!", data);

+                 }).done(function (){

+                     /*

+                      * Next, create the instance...

+                      */

+                      let cmd = [DSCREATE, 'from-file', setup_file];

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

+                          // Failed to create the new instance!

+                          cockpit.spawn(rm_cmd, { superuser: true });  // Remove Inf file with clear text password

+                          $("#create-inst-spinner").hide();

+                          popup_err("Failed to create instance!", data);

+                      }).done(function (){

+                          // Success!!!  Now cleanup everything up...

+                          cockpit.spawn(rm_cmd, { superuser: true });  // Remove Inf file with clear text password

+                          $("#create-inst-spinner").hide();

+                          $("#server-list-menu").attr('disabled', false);

+                          $("#no-instances").hide();

+                          get_insts();  // Refresh server list

+                          popup_success("Successfully created instance:  <b>slapd-" + new_server_id + "</b>");

+                          $("#create-inst-form").modal('toggle');

+                      });

+                  });

+                  $("#create-inst-spinner").show();

+              });

+         });

+     }).fail(function(data) {

+         console.log("failed: " + data.message);

+     });

+ });

+ 

+ var create_full_template =

+   "[general]\n" +

+   "config_version = 2\n" +

+   "defaults = 999999999\n" +

+   "full_machine_name = FQDN\n" +

+   "selinux = True\n" +

+   "strict_host_checking = True\n" +

+   "systemd = True\n" +

+   "[slapd]\n" +

+   "backup_dir = /var/lib/dirsrv/slapd-{instance_name}/bak\n" +

+   "bin_dir = /usr/bin\n" +

+   "cert_dir = /etc/dirsrv/slapd-{instance_name}\n" +

+   "config_dir = /etc/dirsrv/slapd-{instance_name}\n" +

+   "data_dir = /usr/share\n" +

+   "db_dir = /var/lib/dirsrv/slapd-{instance_name}/db\n" +

+   "user = dirsrv\n" +

+   "group = dirsrv\n" +

+   "initconfig_dir = /etc/sysconfig\n" +

+   "inst_dir = /usr/lib64/dirsrv/slapd-{instance_name}\n" +

+   "instance_name = localhost\n" +

+   "ldif_dir = /var/lib/dirsrv/slapd-{instance_name}/ldif\n" +

+   "lib_dir = /usr/lib64\n" +

+   "local_state_dir = /var\n" +

+   "lock_dir = /var/lock/dirsrv/slapd-{instance_name}\n" +

+   "log_dir = /var/log/dirsrv/slapd-{instance_name}\n" +

+   "port = PORT\n" +

+   "prefix = /usr\n" +

+   "root_dn = ROOTDN\n" +

+   "root_password = ROOTPW\n" +

+   "run_dir = /var/run/dirsrv\n" +

+   "sbin_dir = /usr/sbin\n" +

+   "schema_dir = /etc/dirsrv/slapd-{instance_name}/schema\n" +

+   "secure_port = SECURE_PORT\n" +

+   "self_sign_cert = True\n" +

+   "sysconf_dir = /etc\n" +

+   "tmp_dir = /tmp\n";

+ 

+ var create_inf_template =

+   "[general]\n" +

+   "config_version = 2\n" +

+   "full_machine_name = FQDN\n\n" +

+   "[slapd]\n" +

+   "user = dirsrv\n" +

+   "group = dirsrv\n" +

+   "instance_name = INST_NAME\n" +

+   "port = PORT\n" +

+   "root_dn = ROOTDN\n" +

+   "root_password = ROOTPW\n" +

+   "secure_port = SECURE_PORT\n" +

+   "self_sign_cert = SELF_SIGN\n";

+ 

+ 

+ function clear_inst_form() {

+   $(".ds-modal-error").hide();

+   $("#create-inst-serverid").val("");

+   $("#create-inst-port").val("389");

+   $("#create-inst-secureport").val("636");

+   $("#create-inst-rootdn").val("cn=Directory Manager");

+   $("#rootdn-pw").val("");

+   $("#rootdn-pw-confirm").val("");

+   $("#backend-suffix").val("dc=example,dc=com");

+   $("#backend-name").val("userRoot");

+   $("#create-sample-entries").prop('checked', false);

+   $("#create-inst-tls").prop('checked', true);

+   $(".ds-inst-input").css("border-color", "initial");

+ }

+ 

+ function do_backup(server_inst, backup_name) {

+   var cmd = [DSCTL, '-j', server_inst, 'status'];

+   $("#backup-spinner").show();

+   cockpit.spawn(cmd, { superuser: true}).

+   done(function(status_data) {

+     var status_json = JSON.parse(status_data);

+     if (status_json.running == true) {

+       var cmd = [DSCONF, "-j", server_inst, 'backup', 'create',  backup_name];

+       log_cmd('#ds-backup-btn (click)', 'Backup server instance', cmd);

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

+       done(function(data) {

+         $("#backup-spinner").hide();

+         popup_success("Backup has been created");

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

+       }).

+       fail(function(data) {

+         $("#backup-spinner").hide();

+         popup_err("Failed to backup the server", data.message);

+       })

+     } else {

+       var cmd = [DSCTL, server_inst, 'db2bak', backup_name];

+       log_cmd('#ds-backup-btn (click)', 'Backup server instance (offline)', cmd);

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

+       done(function(data) {

+         $("#backup-spinner").hide();

+         popup_success("Backup has been created");

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

+       }).

+       fail(function(data) {

+         $("#backup-spinner").hide();

+         popup_err("Failed to backup the server", data.message);

+       });

+     }

+   }).

+   fail(function() {

+     popup_err("Failed to check the server status", data.message);

+   });

+ }

+ 

  $(window.document).ready(function() {

    if(navigator.userAgent.toLowerCase().indexOf('firefoxf') > -1) {

      $("select@@@").focus( function() {
@@ -452,6 +739,134 @@ 

        this.style.setProperty( 'text-shadow', '0 0 0 #000', 'important' );

      });

    }

+ 

+   // Set an interval event to wait for all the pages to load, then load the config

+   var init_config = setInterval(function() {

+         /*

+          *  Stop, Start, and Restart server

+          */

+ 

+         let banner = document.getElementById("start-server-btn");

+         if (banner == null) {

+           // Not ready yet, return and try again

+           return;

+         }

+ 

+         get_insts();

+ 

+         /* Restore.  load restore table with current backups */

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

+           var cmd = [DSCTL, '-j', server_inst, 'backups'];

+           log_cmd('#restore-server-btn (click)', 'Restore server instance', cmd);

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

+             let backup_btn = "<button class=\"btn btn-default restore-btn\" type=\"button\">Restore</button>";

+             let del_btn =  "<button title=\"Delete backup directory\" class=\"btn btn-default ds-del-backup-btn\" type=\"button\"><span class='glyphicon glyphicon-trash'></span></button>";

+             let obj = JSON.parse(data);

+             backup_table.clear().draw( false );

+             for (var i = 0; i < obj.items.length; i++) {

+               let backup_name = obj.items[i][0];

+               let backup_date = obj.items[i][1];

+               let backup_size = obj.items[i][2];

+               backup_table.row.add([backup_name, backup_date, backup_size, backup_btn, del_btn]).draw( false );

+             }

+           }).fail(function(data) {

+             popup_err("Failed to get list of backups", data.message);

+           });

+         });

+ 

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

+           $("#backup-name").val("");

+         });

+ 

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

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

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

+           var cmd = [DSCTL, server_inst, 'start'];

+           log_cmd('#start-server-btn (click)', 'Start server instance', cmd);

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

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

+             load_config(true);

+             popup_success("Started instance \"" + server_id + "\"");

+           }).fail(function(data) {

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

+             popup_err("Failed to start instance \"" + server_id,  data.message);

+           });

+         });

+ 

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

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

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

+           var cmd = [DSCTL, server_inst, 'stop'];

+           log_cmd('#stop-server-btn (click)', 'Stop server instance', cmd);

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

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

+             popup_success("Stopped instance \"" + server_id + "\"");

+             check_inst_alive();

+           }).fail(function(data) {

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

+             popup_err("Error", "Failed to stop instance \"" + server_id+ "\"", data.message);

+             check_inst_alive();

+           });

+         });

+ 

+ 

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

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

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

+           var cmd = [DSCTL, server_inst, 'restart'];

+           log_cmd('#restart-server-btn (click)', 'Restart server instance', cmd);

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

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

+             load_config(true);

+             popup_success("Restarted instance \"" + server_id + "\"");

+           }).fail(function(data) {

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

+             popup_err("Failed to restart instance \"" + server_id + "\"", data.message);

+           });

+         });

+ 

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

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

+             if (yes) {

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

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

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

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

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

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

+                 popup_success("Instance has been deleted");

+                 get_insts();

+               }).fail(function(data) {

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

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

+               });

+             }

+           });

+         });

+         clearInterval(init_config);

+   }, 250);

+ 

+   $("#main-banner").load("banner.html");

+   check_for_389();

+ 

+   $("#server-tab").css( 'color', '#228bc0');  // Set first tab as highlighted

+ 

+   // Events

+   $(".ds-nav-choice").on('click', function (){

+     // This highlights each nav tab when clicked

+     $(".ds-tab-list").css( 'color', '#777');

+     var tab = $(this).attr("parent-id");

+     $("#" + tab).css( 'color', '#228bc0');

+   });

+ 

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

+     $(".all-pages").hide();

+     $("#server-tasks").show();

+   });

+   $("#server-tab").on("click", function() {

+     $(".all-pages").hide();

+     $("#server-content").show();

+   });

    $("#plugin-tab").on("click", function() {

      $(".all-pages").hide();

      $("#plugin-content").show();
@@ -464,10 +879,6 @@ 

      $(".all-pages").hide();

      $("#monitor-content").show();

    });

-   $("#security-tab").on("click", function() {

-     $(".all-pages").hide();

-     $("#security-content").show();

-   });

    $("#schema-tab").on("click", function() {

      $(".all-pages").hide();

      $("#schema-content").show();
@@ -476,4 +887,188 @@ 

      $(".all-pages").hide();

      $("#replication-content").show();

    });

+ 

+   // Create instance form

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

+     clear_inst_form();

+     set_ports();

+   });

+   $("#no-inst-create-btn").on("click", function () {

+     clear_inst_form();

+   });

+ 

+   // backup/restore table

+   var backup_table = $('#backup-table').DataTable( {

+     "paging": true,

+     "bAutoWidth": false,

+     "dom": '<"pull-left"f><"pull-right"l>tip',

+     "lengthMenu": [ 10, 25, 50, 100],

+     "language": {

+       "emptyTable": "No backups available for restore",

+       "search": "Search Backups"

+     },

+     "columnDefs": [ {

+       "targets": [3, 4],

+       "orderable": false

+     } ],

+     "columns": [

+       { "width": "120px" },

+       { "width": "80px" },

+       { "width": "30px" },

+       { "width": "40px" },

+       { "width": "30px" }

+     ],

+   });

+ 

+   $(".all-pages").hide();

+   $("#server-content").show();

+ 

+   // To remove text border on firefox on dropdowns)

+   if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {

+     $("select").focus( function() {

+       this.style.setProperty( 'outline', 'none', 'important' );

+       this.style.setProperty( 'color', 'rgba(0,0,0,0)', 'important' );

+       this.style.setProperty( 'text-shadow', '0 0 0 #000', 'important' );

+     });

+   }

+ 

+   $(".ds-tab-standalone").on('click', function (){

+     $(".ds-tab-list").css( 'color', '#777');

+     $(this).css( 'color', '#228bc0');

+   });

+ 

+   /* Backup server */

+   $("#ds-backup-btn").on('click', function () {

+     var backup_name = $("#backup-name").val();

+     if (backup_name == ""){

+       popup_msg("Error", "Backup must have a name");

+       return;

+     }

+     if (backup_name.indexOf(' ') >= 0) {

+       popup_msg("Error", "Backup name can not contain any spaces");

+       return;

+     }

+     if (backup_name.indexOf('/') >= 0) {

+       popup_msg("Error", "Backup name can not contain a forward slash. " +

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

+       return;

+     }

+ 

+     // First check if backup name is already used

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

+     log_cmd('#ds-backup-btn (click)', 'Restore server instance', check_cmd);

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

+       var obj = JSON.parse(data);

+       var found_backup = false;

+       for (var i = 0; i < obj.items.length; i++) {

+         if (obj.items[i][0] == backup_name) {

+           found_backup = true;

+           break;

+         }

+       }

+       if (found_backup) {

+         popup_confirm("A backup already exists with this name, replace it?", "Confirmation", function (yes) {

+           if (yes) {

+             do_backup(server_inst, backup_name);

+           } else {

+             return;

+           }

+         });

+       } else {

+         do_backup(server_inst, backup_name);

+       }

+     });

+   });

+ 

+   /* Restore server */

+   $(document).on('click', '.restore-btn', function(e) {

+     e.preventDefault();

+     var data = backup_table.row( $(this).parents('tr') ).data();

+     var restore_name = data[0];

+     popup_confirm("Are you sure you want to restore this backup:  <b>" + restore_name + "<b>", "Confirmation", function (yes) {

+       if (yes) {

+         var cmd = [DSCTL, '-j', server_inst, 'status'];

+         $("#restore-spinner").show();

+         cockpit.spawn(cmd, { superuser: true}).

+         done(function(status_data) {

+           var status_json = JSON.parse(status_data);

+           if (status_json.running == true) {

+             var cmd = [DSCONF, server_inst, 'backup', 'restore',  restore_name];

+             log_cmd('.restore-btn (click)', 'Restore server instance(online)', cmd);

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

+             done(function(data) {

+               $("#restore-spinner").hide();

+               popup_success("The backup has been restored");

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

+             }).

+             fail(function(data) {

+               $("#restore-spinner").hide();

+               popup_err("Failed to restore from the backup", data.message);

+             });

+           } else {

+             var cmd = [DSCTL, server_inst, 'bak2db', restore_name];

+             log_cmd('.restore-btn (click)', 'Restore server instance(offline)', cmd);

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

+             done(function(data) {

+               $("#restore-spinner").hide();

+               popup_success("The backup has been restored");

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

+             }).

+             fail(function(data) {

+               $("#restore-spinner").hide();

+               popup_err("Failed to restore from the backup", data.message);

+             });

+           }

+         }).

+         fail(function() {

+           popup_err("Failed to check the server status", data.message);

+         });

+       }

+     });

+   });

+ 

+   /* Delete backup directory */

+   $(document).on('click', '.ds-del-backup-btn', function(e) {

+     e.preventDefault();

+     var data = backup_table.row( $(this).parents('tr') ).data();

+     var restore_name = data[0];

+     var backup_row = $(this);

+     popup_confirm("Are you sure you want to delete this backup: <b>" + restore_name + "</b>", "Confirmation", function (yes) {

+       if (yes) {

+         var cmd = [DSCTL, server_inst, 'backups', '--delete', restore_name];

+         $("#restore-spinner").show();

+         log_cmd('.ds-del-backup-btn (click)', 'Delete backup', cmd);

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

+           $("#restore-spinner").hide();

+           backup_table.row( backup_row.parents('tr') ).remove().draw( false );

+           popup_success("The backup has been deleted");

+         }).fail(function(data) {

+           $("#restore-spinner").hide();

+           popup_err("Failed to delete the backup", data.message);

+         });

+       }

+     });

+   });

+ 

+   /* reload schema */

+   $("#schema-reload-btn").on("click", function () {

+     var schema_dir = $("#reload-dir").val();

+     if (schema_dir != ""){

+       var cmd = [DSCONF, server_inst, 'schema', 'reload', '--schemadir', schema_dir, '--wait'];

+     } else {

+       var cmd = [DSCONF, server_inst, 'schema', 'reload', '--wait'];

+     }

+     $("#reload-spinner").show();

+     log_cmd('#schema-reload-btn (click)', 'Reload schema files', cmd);

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

+       popup_success("Successfully reloaded schema");  // TODO use timed interval success msg (waiting for another PR top be merged before we can add it)

+       $("#schema-reload-form").modal('toggle');

+       $("#reload-spinner").hide();

+     }).fail(function(data) {

+       popup_err("Failed to reload schema files", data.message);

+       $("#reload-spinner").hide();

+     });

+   });

+ 

+ 

  });

@@ -4,8 +4,8 @@ 

  import { Database } from "./database.jsx";

  import { Monitor } from "./monitor.jsx";

  import { Schema } from "./schema.jsx";

- import { Security } from "./security.jsx";

  import { Replication } from "./replication.jsx";

+ import { Server } from "./server.jsx";

  

  var serverIdElem;

  
@@ -21,6 +21,11 @@ 

      let d = new Date();

      let tabKey = d.getTime();

  

+     // Server Tab

+     ReactDOM.render(

+         <Server serverId={serverIdElem} key={tabKey} />,

+         document.getElementById("server")

+     );

      // Plugins Tab

      ReactDOM.render(

          <Plugins serverId={serverIdElem} key={tabKey} />,
@@ -45,12 +50,6 @@ 

          document.getElementById("monitor")

      );

  

-     // Security tab

-     ReactDOM.render(

-         <Security serverId={serverIdElem} key={tabKey} />,

-         document.getElementById("security")

-     );

- 

      // Schema tab

      ReactDOM.render(

          <Schema serverId={serverIdElem} key={tabKey} />,

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

    <script src="static/c3.min.js"></script>

    <script src="static/d3.min.js"></script>

    <script src="ds.js"></script>

-   <script src="servers.js"></script>

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

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

    <link href="static/jquery.timepicker.min.css" type="text/css" rel="stylesheet">
@@ -56,39 +55,9 @@ 

            <ul class="nav navbar-nav navbar-primary">

  

              <!-- Server Config navtab -->

-             <li class="dropdown ds-nav-tab">

-               <a href="#0" class="ds-tab-list" data-toggle="dropdown" id="server-tab">

-                 Server Settings

-                 <b class="caret"></b>

-               </a>

-               <ul class="dropdown-menu dropup ds-nav-item">

-                 <li><a href="#0" class="ds-nav-choice" id="server-config-btn" parent-id="server-tab">Server Configuration</a></li>

-                 <li><a href="#0" class="ds-nav-choice" id="server-tuning-btn" parent-id="server-tab">Tuning & Limits</a></li>

-                 <li><a href="#0" class="ds-nav-choice" id="server-sasl-btn"   parent-id="server-tab">SASL</a></li>

-                 <li class="dropdown-submenu">

-                   <a tabindex="-1" href="#0">Password Policy</a>

-                   <ul class="dropdown-menu">

-                     <li><a href="#0" class="ds-nav-choice" id="server-gbl-pwp-btn"   parent-id="server-tab">Global Password Policy</a></li>

-                     <li><a href="#0" class="ds-nav-choice" id="server-local-pwp-btn" parent-id="server-tab">Local Password Policy</a></li>

-                   </ul>

-                 </li>

-                 <li><a href="#0" class="ds-nav-choice" id="server-ldapi-btn" parent-id="server-tab">LDAPI & Autobind</a></li>

-                 <li class="dropdown-submenu">

-                   <a tabindex="-1" href="#0">Logging</a>

-                   <ul class="dropdown-menu">

-                     <li><a href="#0" class="ds-nav-choice" id="server-log-access-btn"    parent-id="server-tab">Access Log</a></li>

-                     <li><a href="#0" class="ds-nav-choice" id="server-log-audit-btn"     parent-id="server-tab">Audit Log</a></li>

-                     <li><a href="#0" class="ds-nav-choice" id="server-log-auditfail-btn" parent-id="server-tab">Audit Failure Log</a></li>

-                     <li><a href="#0" class="ds-nav-choice" id="server-log-errors-btn"    parent-id="server-tab">Errors Log</a></li>

-                   </ul>

-                 </li>

-               </ul>

-             </li>

- 

-             <!-- Security navtab -->

              <li class="ds-nav-tab">

-               <a href="#0" class="ds-tab-list ds-tab-standalone" id="security-tab">

-                 Security

+               <a href="#0" class="ds-tab-list ds-tab-standalone" id="server-tab">

+                 Server Settings

                </a>

              </li>

  
@@ -485,17 +454,8 @@ 

          <h3>This server instance is running, but we can not connect to it.  Check LDAPI is properly configured on this instance.</h3>

        </div>

  

-       <div id="server-content" class="all-pages" hidden>

-           <div id="server-settings"></div>

-           <div id="server-tuning"></div>

-           <div id="server-sasl"></div>

-           <div id="server-pwp"></div>

-           <div id="server-ldapi"></div>

-           <div id="server-logs"></div>

-       </div>

- 

-       <div id="security-content" class="all-pages" hidden>

-           <div id="security"></div>

+       <div id="server-content" class="all-pages">

+           <div id="server"></div>

        </div>

  

        <div id="database-content" class="all-pages" hidden>

@@ -3,10 +3,11 @@ 

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

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

  import {

-     Row,

+     Checkbox,

      Col,

      ControlLabel,

      Form,

+     Row,

      Spinner,

      noop

  } from "patternfly-react";
@@ -355,28 +356,49 @@ 

                      </Form>

  

                      <div className="ds-container">

-                         <div>

-                             <h4 className="ds-sub-header">Database Cache Settings</h4>

-                             <hr />

-                             <div>

-                                 <label htmlFor="autoCacheChkbox" title="Set Database/Entry to be set automatically"><input

-                                     className="ds-left-indent" type="checkbox" id="autoCacheChkbox" checked={db_auto_checked}

-                                     onChange={this.select_auto_cache} /> Automatic Cache Tuning</label>

-                                 {db_cache_form}

-                             </div>

-                         </div>

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

-                         <div>

-                             <h4 className="ds-sub-header">Import Cache Settings</h4>

-                             <hr />

-                             <div>

-                                 <label htmlFor="autoImportCacheChkbox"title="Set Database/Entry to be set automatically"><input

-                                     className="ds-left-indent" type="checkbox" id="autoImportCacheChkbox"

-                                     onChange={this.select_auto_import_cache}

-                                     checked={import_auto_checked} /> Automatic Import Cache Tuning</label>

-                                 {import_cache_form}

-                             </div>

-                         </div>

+                         <Form className="container-fluid" horizontal>

+                             <Row>

+                                 <Col sm={5}>

+                                     <h4 className="ds-sub-header">Database Cache Settings</h4>

+                                     <hr />

+                                 </Col>

+                                 <Col sm={1} />

+                                 <Col sm={5}>

+                                     <h4 className="ds-sub-header">Import Cache Settings</h4>

+                                     <hr />

+                                 </Col>

+                             </Row>

+                             <Row>

+                                 <Col sm={5}>

+                                     <Checkbox title="Set Database/Entry to be set automatically"

+                                         id="autoCacheChkbox"

+                                         checked={db_auto_checked}

+                                         onChange={this.select_auto_cache}

+                                     >

+                                          Automatic Cache Tuning

+                                     </Checkbox>

+                                 </Col>

+                                 <Col sm={1} />

+                                 <Col sm={5}>

+                                     <Checkbox title="Set input to be set automatically"

+                                         id="autoImportCacheChkbox"

+                                         checked={import_auto_checked}

+                                         onChange={ this.select_auto_import_cache}

+                                     >

+                                          Automatic Import Cache Tuning

+                                     </Checkbox>

+                                 </Col>

+                             </Row>

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

+                                 <Col sm={6}>

+                                     {db_cache_form}

+                                 </Col>

+ 

+                                 <Col sm={6}>

+                                     {import_cache_form}

+                                 </Col>

+                             </Row>

+                         </Form>

                      </div>

                      <CustomCollapse>

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

@@ -946,8 +946,198 @@ 

      }

  }

  

+ export class PwpTable extends React.Component {

+     constructor(props) {

+         super(props);

+ 

+         this.state = {

+             rowKey: "targetdn",

+             columns: [

+                 {

+                     property: "targetdn",

+                     header: {

+                         label: "Target DN",

+                         props: {

+                             index: 0,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 0

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "pwp_type",

+                     header: {

+                         label: "Policy Type",

+                         props: {

+                             index: 1,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 1

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "basedn",

+                     header: {

+                         label: "Suffix",

+                         props: {

+                             index: 2,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 2

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+ 

+                 {

+                     property: "actions",

+                     header: {

+                         props: {

+                             index: 3,

+                             rowSpan: 1,

+                             colSpan: 1

+                         },

+                         formatters: [actionHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 3

+                         },

+                         formatters: [

+                             (value, { rowData }) => {

+                                 return [

+                                     <td key={rowData.targetdn}>

+                                         <DropdownButton id={rowData.targetdn}

+                                             className="ds-action-button"

+                                             bsStyle="primary" title="Actions">

+                                             <MenuItem eventKey="1" onClick={() => {

+                                                 this.props.editPolicy(

+                                                     rowData.targetdn,

+                                                 );

+                                             }}

+                                             >

+                                                 Edit Policy

+                                             </MenuItem>

+                                             <MenuItem divider />

+                                             <MenuItem eventKey="2" onClick={() => {

+                                                 this.props.deletePolicy(rowData.targetdn);

+                                             }}

+                                             >

+                                                 Delete Policy

+                                             </MenuItem>

+                                         </DropdownButton>

+                                     </td>

+                                 ];

+                             }

+                         ]

+                     }

+                 }

+             ],

+         };

+ 

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

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

+     } // Constructor

+ 

+     getColumns() {

+         return this.state.columns;

+     }

+ 

+     getSingleColumn () {

+         return [

+             {

+                 property: "msg",

+                 header: {

+                     label: "Local Password Policies",

+                     props: {

+                         index: 0,

+                         rowSpan: 1,

+                         colSpan: 1,

+                         sort: true

+                     },

+                     transforms: [],

+                     formatters: [],

+                     customFormatters: [sortableHeaderCellFormatter]

+                 },

+                 cell: {

+                     props: {

+                         index: 0

+                     },

+                     formatters: [tableCellFormatter]

+                 }

+             },

+         ];

+     }

+ 

+     render() {

+         let PwpTable;

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

+             PwpTable = <DSShortTable

+                 getColumns={this.getSingleColumn}

+                 rowKey={"msg"}

+                 rows={[{msg: "No Policies"}]}

+             />;

+         } else {

+             PwpTable =

+                 <DSTable

+                     noSearchBar

+                     getColumns={this.getColumns}

+                     rowKey={this.state.rowKey}

+                     rows={this.props.rows}

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

+                     toolBarPaginationPerPage={6}

+                 />;

+         }

+         return (

+             <div>

+                 {PwpTable}

+             </div>

+         );

+     }

+ }

+ 

  // Property types and defaults

  

+ PwpTable.propTypes = {

+     rows: PropTypes.array,

+     editPolicy: PropTypes.func,

+     deletePolicy: PropTypes.func

+ };

+ 

+ PwpTable.defaultProps = {

+     rows: [],

+     editPolicy: noop,

+     deletePolicy: noop

+ };

+ 

  BackupTable.propTypes = {

      rows: PropTypes.array,

      confirmRestore: PropTypes.func,

The added file is too large to be shown here, see it at: src/cockpit/389-console/src/lib/database/globalPwp.jsx
@@ -600,17 +600,11 @@ 

  

          let availMR = [];

          for (let mr of matchingRules) {

-             availMR.push({

-                 id: mr,

-                 label: mr

-             });

+             availMR.push(mr[0]);

          }

          let availAttrs = [];

          for (let attr of attributes) {

-             availAttrs.push({

-                 id: attr,

-                 label: attr

-             });

+             availAttrs.push(attr[0]);

          }

  

          return (

The added file is too large to be shown here, see it at: src/cockpit/389-console/src/lib/database/localPwp.jsx
@@ -0,0 +1,692 @@ 

+ import cockpit from "cockpit";

+ import React from "react";

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

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

+ import {

+     Form,

+     Col,

+     Nav,

+     NavItem,

+     Checkbox,

+     TabContainer,

+     TabContent,

+     TabPane,

+     TreeView,

+     Spinner,

+     Button

+ } from "patternfly-react";

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

+ 

+ const GLOBAL_POLICY = "global-policy";

+ 

+ export class ServerPwPolicy extends React.Component {

+     constructor(props) {

+         super(props);

+         this.state = {

+             loading: false,

+             loaded: false,

+             activeKey: 1,

+             notifications: [],

+             disableTree: false,

+             nodes: [],

+             node_name: "",

+             node_text: "",

+             dbtype: "",

+ 

+             // Tuning settings

+ 

+         }

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

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

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

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

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

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

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

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

+     }

+ 

+     componentWillMount() {

+         // Loading config TODO

+         if (!this.state.loaded) {

+             this.loadGlobal();

+         }

+     }

+ 

+     loadGlobal() {

+         let cmd = [

+             "dsconf", "-j", this.props.serverId, "pwpolicy", "get"

+         ];

+         log_cmd("loadGlobal", "Load global password policy", cmd);

+         cockpit

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

+                 .done(content => {

+                     let config = JSON.parse(content);

+                     let attrs = config.attrs;

+                     // Handle the checkbox values

+                     let pwpLocal = false;

+                     let pwpIsglobal = false;

+                     let pwMustChange = false;

+                     let pwHistory = false;

+                     let pwTrackUpdate = false;

+                     let pwExpire = false;

+                     let pwSendExpire = false;

+                     let pwLockout = false;

+                     let pwUnlock = false;

+                     let pwCheckSyntax = false;

+                     let pwPalindrome = false;

+                     let pwDictCheck = false;

+                     let pwAllowHashed = false;

+ 

+                     if (attrs['nsslapd-pwpolicy-local'][0] == "on") {

+                         pwpLocal = true;

+                     }

+                     if (attrs['passwordchange'][0] == "on") {

+                         pwMustChange = true;

+                     }

+                     if (attrs['passwordhistory'][0] == "on") {

+                         pwHistory = true;

+                     }

+                     if (attrs['passwordtrackupdatetime'][0] == "on") {

+                         pwTrackUpdate = true;

+                     }

+                     if (attrs['passwordisglobalpolicy'][0] == "on") {

+                         pwpIsglobal = true;

+                     }

+                     if (attrs['passwordsendexpiringtime'][0] == "on") {

+                         pwSendExpire = true;

+                     }

+                     if (attrs['passwordlockout'][0] == "on") {

+                         pwLockout = true;

+                     }

+                     if (attrs['passwordunlock'][0] == "on") {

+                         pwUnlock = true;

+                     }

+                     if (attrs['passwordexp'][0] == "on") {

+                         pwExpire = true;

+                     }

+                     if (attrs['passwordchecksyntax'][0] == "on") {

+                         pwCheckSyntax = true;

+                     }

+                     if (attrs['passwordpalindrome'][0] == "on") {

+                         pwPalindrome = true;

+                     }

+                     if (attrs['passworddictcheck'][0] == "on") {

+                         pwExpire = true;

+                     }

+                     if (attrs['nsslapd-allow-hashed-passwords'][0] == "on") {

+                         pwAllowHashed = true;

+                     }

+ 

+                     this.setState(() => (

+                         {

+                             loaded: true,

+                             loading: false,

+                             // Settings

+                             pwpLocal: pwpLocal,

+                             pwpIsglobal: pwpIsglobal,

+                             pwMustChange: pwMustChange,

+                             pwTrackUpdate: pwTrackUpdate,

+                             pwExpire: pwExpire,

+                             pwSendExpire: pwSendExpire,

+                             pwLockout: pwLockout,

+                             pwUnlock: pwUnlock,

+                             pwCheckSyntax: pwCheckSyntax,

+                             pwPalindrome: pwPalindrome,

+                             pwDictCheck: pwDictCheck,

+                             pwAllowHashed: pwAllowHashed,

+                             passwordstoragescheme: attrs['passwordstoragescheme'][0],

+                             pwInHistory: attrs['passwordinhistory'][0],

+                             pwWarning: attrs['passwordwarning'][0],

+                             pwMaxAge: attrs['passwordmaxage'][0],

+                             pwMinAge: attrs['passwordminage'][0],

+                             pwGraceLimit: attrs['passwordgracelimit'][0],

+                             pwLockoutDur: attrs['passwordlockoutduration'][0],

+                             pwMaxFailure: attrs['passwordmaxfailure'][0],

+                             pwResetFailureCount: attrs['passwordresetfailurecount'][0],

+                             pwMinLen: attrs['passwordminlength'][0],

+                             pwMinDigit: attrs['passwordmindigits'][0],

+                             pwMinAlpha: attrs['passwordminalphas'][0],

+                             pwMinUppers: attrs['passwordminuppers'][0],

+                             pwMinLowers: attrs['passwordminlowers'][0],

+                             pwMinSpecial: attrs['passwordminspecials'][0],

+                             pwMin8bit: attrs['passwordmin8bit'][0],

+                             pwMaxRepeats: attrs['passwordmaxrepeats'][0],

+                             pwMaxSeq: attrs['passwordmaxsequence'][0],

+                             pwMaxSeqSets: attrs['passwordmaxseqsets'][0],

+                             pwMaxClass: attrs['passwordmaxclasschars'][0],

+                             pwMinCat: attrs['passwordmincategories'][0],

+                             pwMinTokenLen: attrs['passwordmintokenlength'][0],

+                             pwBadWords: attrs['passwordbadwords'][0],

+                             pwUserAttrs: attrs['passworduserattributes'][0],

+                             pwDictPath: attrs['passworddictpath'][0],

+                             // Record original values

+                             _pwpLocal: pwpLocal,

+                             _pwpIsglobal: pwpIsglobal,

+                             _pwMustChange: pwMustChange,

+                             _pwTrackUpdate: pwTrackUpdate,

+                             _pwExpire: pwExpire,

+                             _pwSendExpire: pwSendExpire,

+                             _pwLockout: pwLockout,

+                             _pwUnlock: pwUnlock,

+                             _pwCheckSyntax: pwCheckSyntax,

+                             _pwPalindrome: pwPalindrome,

+                             _pwDictCheck: pwDictCheck,

+                             _pwAllowHashed: pwAllowHashed,

+                             _passwordstoragescheme: attrs['passwordstoragescheme'][0],

+                             _pwWarning: attrs['passwordwarning'][0],

+                             _pwInHistory: attrs['passwordinhistory'][0],

+                             _pwMaxAge: attrs['passwordmaxage'][0],

+                             _pwMinAge: attrs['passwordminage'][0],

+                             _pwGraceLimit: attrs['passwordgracelimit'][0],

+                             _pwLockoutDur: attrs['passwordlockoutduration'][0],

+                             _pwMaxFailure: attrs['passwordmaxfailure'][0],

+                             _pwResetFailureCount: attrs['passwordresetfailurecount'][0],

+                             _pwMinLen: attrs['passwordminlength'][0],

+                             _pwMinDigit: attrs['passwordmindigits'][0],

+                             _pwMinAlpha: attrs['passwordminalphas'][0],

+                             _pwMinUppers: attrs['passwordminuppers'][0],

+                             _pwMinLowers: attrs['passwordminlowers'][0],

+                             _pwMinSpecial: attrs['passwordminspecials'][0],

+                             _pwMin8bit: attrs['passwordmin8bit'][0],

+                             _pwMaxRepeats: attrs['passwordmaxrepeats'][0],

+                             _pwMaxSeq: attrs['passwordmaxsequence'][0],

+                             _pwMaxSeqSets: attrs['passwordmaxseqsets'][0],

+                             _pwMaxClass: attrs['passwordmaxclasschars'][0],

+                             _pwMinCat: attrs['passwordmincategories'][0],

+                             _pwMinTokenLen: attrs['passwordmintokenlength'][0],

+                             _pwBadWords: attrs['passwordbadwords'][0],

+                             _pwUserAttrs: attrs['passworduserattributes'][0],

+                             _pwDictPath: attrs['passworddictpath'][0],

+                         })

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.setState({

+                         loaded: true,

+                         loading: false,

+                     });

+                     this.addNotification(

+                         "error",

+                         `Error loading global password policy - ${errMsg.desc}`

+                     );

+                 });

+     }

+ 

+     loadLocal(name) {

+         // What makes this tough is that local polices can only have a single

+         // attribute, while global has them all.  So we have to check every

+         // single possible attribute if it's present.

+         this.setState({

+             loading: true,

+         });

+         let cmd = [

+             "dsconf", "-j", this.props.serverId, "localpwp", "get", name

+         ];

+         log_cmd("loadLocal", "Load local password policy", cmd);

+         cockpit

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

+                 .done(content => {

+                     let config = JSON.parse(content);

+                     let attrs = config.attrs;

+                     // Handle the checkbox values

+                     let pwMustChange = false;

+                     let pwHistory = false;

+                     let pwTrackUpdate = false;

+                     let pwExpire = false;

+                     let pwSendExpire = false;

+                     let pwLockout = false;

+                     let pwUnlock = false;

+                     let pwCheckSyntax = false;

+                     let pwPalindrome = false;

+                     let pwDictCheck = false;

+                     let pwAllowHashed = false;

+                     let pwStorageScheme = "PBKDF2_SHA256";

+                     let pwInHistory = "";

+                     let pwWarning = "";

+                     let pwMaxAge = "";

+                     let pwMinAge = "";

+                     let pwGraceLimit = "";

+                     let pwLockoutDur = "";

+                     let pwMaxFailure = "";

+                     let pwResetFailureCount = "";

+                     let pwMinLen = "";

+                     let pwMinDigit = "";

+                     let pwMinAlpha = "";

+                     let pwMinUppers = "";

+                     let pwMinLowers = "";

+                     let pwMinSpecial = "";

+                     let pwMin8bit = "";

+                     let pwMaxRepeats = "";

+                     let pwMaxSeq = "";

+                     let pwMaxSeqSets = "";

+                     let pwMaxClass = "";

+                     let pwMinCat = "";

+                     let pwMinTokenLen = "";

+                     let pwBadWords = "";

+                     let pwUserAttrs = "";

+                     let pwDictPath = "";

+ 

+                     if ('passwordchange' in attrs && attrs['passwordchange'][0] == "on") {

+                         pwMustChange = true;

+                     }

+                     if ('passwordhistory' in attrs && attrs['passwordhistory'][0] == "on") {

+                         pwHistory = true;

+                     }

+                     if ('passwordtrackupdatetime' in attrs && attrs['passwordtrackupdatetime'][0] == "on") {

+                         pwTrackUpdate = true;

+                     }

+                     if ('passwordsendexpiringtime' in attrs && attrs['passwordsendexpiringtime'][0] == "on") {

+                         pwSendExpire = true;

+                     }

+                     if ('passwordlockout' in attrs && attrs['passwordlockout'][0] == "on") {

+                         pwLockout = true;

+                     }

+                     if ('passwordunlock' in attrs && attrs['passwordunlock'][0] == "on") {

+                         pwUnlock = true;

+                     }

+                     if ('passwordexp' in attrs && attrs['passwordexp'][0] == "on") {

+                         pwExpire = true;

+                     }

+                     if ('passwordchecksyntax' in attrs && attrs['passwordchecksyntax'][0] == "on") {

+                         pwCheckSyntax = true;

+                     }

+                     if ('passwordpalindrome' in attrs && attrs['passwordpalindrome'][0] == "on") {

+                         pwPalindrome = true;

+                     }

+                     if ('passworddictcheck' in attrs && attrs['passworddictcheck'][0] == "on") {

+                         pwExpire = true;

+                     }

+                     if ('passwordstoragescheme' in attrs) {

+                         pwStorageScheme = attrs['passwordstoragescheme'][0];

+                     }

+                     if ('passwordinhistory' in attrs) {

+                         pwInHistory = attrs['passwordinhistory'][0];

+                     }

+                     if ('passwordwarning' in attrs) {

+                         pwWarning = attrs['passwordwarning'][0];

+                     }

+                     if ('passwordmaxage' in attrs) {

+                         pwMaxAge = attrs['passwordmaxage'][0];

+                     }

+                     if ('passwordminage' in attrs) {

+                         pwMinAge = attrs['passwordminage'][0];

+                     }

+                     if ('passwordgracelimit' in attrs) {

+                         pwMinAge = attrs['passwordgracelimit'][0];

+                     }

+                     if ('passwordlockoutduration' in attrs) {

+                         pwLockoutDur = attrs['passwordlockoutduration'][0];

+                     }

+                     if ('passwordmaxfailure' in attrs) {

+                         pwMaxFailure = attrs['passwordmaxfailure'][0];

+                     }

+                     if ('passwordresetfailurecount' in attrs) {

+                         pwResetFailureCount = attrs['passwordresetfailurecount'][0];

+                     }

+                     if ('passwordminlength' in attrs) {

+                         pwMinLen = attrs['passwordminlength'][0];

+                     }

+                     if ('passwordmindigits' in attrs) {

+                         pwMinDigit = attrs['passwordmindigits'][0];

+                     }

+                     if ('passwordminalphas' in attrs) {

+                         pwMinAlpha = attrs['passwordminalphas'][0];

+                     }

+                     if ('passwordminuppers' in attrs) {

+                         pwMinLen = attrs['passwordminuppers'][0];

+                     }

+                     if ('passwordminlowers' in attrs) {

+                         pwMinUppers = attrs['passwordminlowers'][0];

+                     }

+                     if ('passwordminspecials' in attrs) {

+                         pwMinSpecial = attrs['passwordminspecials'][0];

+                     }

+                     if ('passwordmin8bit' in attrs) {

+                         pwMin8bit = attrs['passwordmin8bit'][0];

+                     }

+                     if ('passwordmaxrepeats' in attrs) {

+                         pwMaxRepeats = attrs['passwordmaxrepeats'][0];

+                     }

+                     if ('passwordmaxsequence' in attrs) {

+                         pwMaxSeq = attrs['passwordmaxsequence'][0];

+                     }

+                     if ('passwordmaxseqsets' in attrs) {

+                         pwMaxSeqSets = attrs['passwordmaxseqsets'][0];

+                     }

+                     if ('passwordmaxclasschars' in attrs) {

+                         pwMaxClass = attrs['passwordmaxclasschars'][0];

+                     }

+                     if ('passwordmincategories' in attrs) {

+                         pwMinCat = attrs['passwordmincategories'][0];

+                     }

+                     if ('passwordmintokenlength' in attrs) {

+                         pwMinTokenLen = attrs['passwordmintokenlength'][0];

+                     }

+                     if ('passwordbadwords' in attrs) {

+                         pwBadWords = attrs['passwordbadwords'][0];

+                     }

+                     if ('passworduserattributes' in attrs) {

+                         pwUserAttrs = attrs['passworduserattributes'][0];

+                     }

+                     if ('passworddictpath' in attrs) {

+                         pwDictPath = attrs['passworddictpath'][0];

+                     }

+ 

+                     this.setState(() => (

+                         {

+                             loading: false,

+                             // Settings

+                             pwLocalMustChange: pwMustChange,

+                             pwLocalTrackUpdate: pwTrackUpdate,

+                             pwLocalExpire: pwExpire,

+                             pwLocalSendExpire: pwSendExpire,

+                             pwLocalLockout: pwLockout,

+                             pwLocalUnlock: pwUnlock,

+                             pwLocalCheckSyntax: pwCheckSyntax,

+                             pwLocalPalindrome: pwPalindrome,

+                             pwLocalDictCheck: pwDictCheck,

+                             pwLocalStorageScheme: pwStorageScheme,

+                             pwLocalInHistory: pwInHistory,

+                             pwLocalWarning: pwWarning,

+                             pwLocalMaxAge: pwMaxAge,

+                             pwLocalMinAge: pwMinAge,

+                             pwLocalGraceLimit: pwGraceLimit,

+                             pwLocalLockoutDur: pwLockoutDur,

+                             pwLocalMaxFailure: pwMaxFailure,

+                             pwLocalResetFailureCount: pwResetFailureCount,

+                             pwLocalMinLen: pwMinLen,

+                             pwLocalMinDigit: pwMinDigit,

+                             pwLocalMinAlpha: pwMinAlpha,

+                             pwLocalMinUppers: pwMinUppers,

+                             pwLocalMinLowers: pwMinLowers,

+                             pwLocalMinSpecial: pwMinSpecial,

+                             pwLocalMin8bit: pwMin8bit,

+                             pwLocalMaxRepeats: pwMaxRepeats,

+                             pwLocalMaxSeq: pwMaxSeq,

+                             pwLocalMaxSeqSets: pwMaxSeqSets,

+                             pwLocalMaxClass: pwMaxClass,

+                             pwLocalMinCat: pwMinCat,

+                             pwLocalMinTokenLen: pwMinTokenLen,

+                             pwLocalBadWords: pwBadWords,

+                             pwLocalUserAttrs: pwUserAttrs,

+                             pwLocalDictPath: pwDictPath,

+                             // Record original values

+                             _pwLocalMustChange: pwMustChange,

+                             _pwLocalTrackUpdate: pwTrackUpdate,

+                             _pwLocalExpire: pwExpire,

+                             _pwLocalSendExpire: pwSendExpire,

+                             _pwLocalLockout: pwLockout,

+                             _pwLocalUnlock: pwUnlock,

+                             _pwLocalCheckSyntax: pwCheckSyntax,

+                             _pwLocalPalindrome: pwPalindrome,

+                             _pwLocalDictCheck: pwDictCheck,

+                             _pwLocalStorageScheme: pwStorageScheme,

+                             _pwLocalInHistory: pwInHistory,

+                             _pwLocalWarning: pwWarning,

+                             _pwLocalMaxAge: pwMaxAge,

+                             _pwLocalMinAge: pwMinAge,

+                             _pwLocalGraceLimit: pwGraceLimit,

+                             _pwLocalLockoutDur: pwLockoutDur,

+                             _pwLocalMaxFailure: pwMaxFailure,

+                             _pwLocalResetFailureCount: pwResetFailureCount,

+                             _pwLocalMinLen: pwMinLen,

+                             _pwLocalMinDigit: pwMinDigit,

+                             _pwLocalMinAlpha: pwMinAlpha,

+                             _pwLocalMinUppers: pwMinUppers,

+                             _pwLocalMinLowers: pwMinLowers,

+                             _pwLocalMinSpecial: pwMinSpecial,

+                             _pwLocalMin8bit: pwMin8bit,

+                             _pwLocalMaxRepeats: pwMaxRepeats,

+                             _pwLocalMaxSeq: pwMaxSeq,

+                             _pwLocalMaxSeqSets: pwMaxSeqSets,

+                             _pwLocalMaxClass: pwMaxClass,

+                             _pwLocalMinCat: pwMinCat,

+                             _pwLocalMinTokenLen: pwMinTokenLen,

+                             _pwLocalBadWords: pwBadWords,

+                             _pwLocalUserAttrs: pwUserAttrs,

+                             _pwLocalDictPath: pwDictPath,

+                         })

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.setState({

+                         loaded: true,

+                         loading: false,

+                     });

+                     this.addNotification(

+                         "error",

+                         `Error loading local password policy - ${errMsg.desc}`

+                     );

+                 });

+     }

+ 

+     loadSuffixTree() {

+         let cmd = [

+             "dsconf", "-j", this.props.serverId, "backend", "get-tree",

+         ];

+         log_cmd("loadSuffixTree", "Start building the suffix tree for local pwp", cmd);

+         cockpit

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

+                 .done(content => {

+                     let treeData = [];

+                     if (content != "") {

+                         treeData = JSON.parse(content);

+                     }

+                     let basicData = [

+                         {

+                             text: "Global Password Policy",

+                             selectable: true,

+                             selected: true,

+                             icon: "pficon-home",

+                             id: "global-policy",

+ 

+                         },

+                         {

+                             text: "Suffixes",

+                             icon: "pficon-catalog",

+                             state: {"expanded": true},

+                             selectable: false,

+                             id: "pwp-suffixes",

+                             nodes: []

+                         }

+                     ];

+                     let current_node = this.state.node_name;

+                     basicData[3].nodes = treeData;

+ 

+                     this.setState(() => ({

+                         nodes: basicData,

+                         node_name: current_node,

+                     }), this.update_tree_nodes);

+                 });

+     }

+ 

+     selectNode(selectedNode) {

+         if (selectedNode.selected) {

+             return;

+         }

+         this.setState({

+             disableTree: true // Disable the tree to allow node to be fully loaded

+         });

+ 

+         if (selectedNode.id == GLOBAL_POLICY) {

+             // Nothing special to do, this has already been loaded

+             this.setState(prevState => {

+                 return {

+                     nodes: this.nodeSelector(prevState.nodes, selectedNode),

+                     node_name: selectedNode.id,

+                     node_text: selectedNode.text,

+                     dbtype: selectedNode.type,

+                     bename: "",

+                 };

+             });

+         } else {

+             if (selectedNode.id in this.state) {

+                 // This suffix is already cached, just use what we have...

+                 this.setState(prevState => {

+                     return {

+                         nodes: this.nodeSelector(prevState.nodes, selectedNode),

+                         node_name: selectedNode.id,

+                         node_text: selectedNode.text,

+                         dbtype: selectedNode.type,

+                         bename: selectedNode.be,

+                     };

+                 });

+             } else {

+                 // Load this suffix

+                 if (selectedNode.type == "suffix" || selecetedNode.type == "subsuffix") {

+                     this.loadSuffix(selectedNode.id);

+                 }

+                 this.setState(prevState => {

+                     return {

+                         nodes: this.nodeSelector(prevState.nodes, selectedNode),

+                         node_name: selectedNode.id,

+                         node_text: selectedNode.text,

+                         dbtype: selectedNode.type,

+                         bename: selectedNode.be,

+                     };

+                 });

+             }

+         }

+     }

+ 

+     nodeSelector(nodes, targetNode) {

+         return nodes.map(node => {

+             if (node.nodes) {

+                 return {

+                     ...node,

+                     nodes: this.nodeSelector(node.nodes, targetNode),

+                     selected: node.id === targetNode.id ? !node.selected : false

+                 };

+             } else if (node.id === targetNode.id) {

+                 return { ...node, selected: !node.selected };

+             } else if (node.id !== targetNode.id && node.selected) {

+                 return { ...node, selected: false };

+             } else {

+                 return node;

+             }

+         });

+     }

+ 

+     addNotification(type, message, timerdelay, persistent) {

+         this.setState(prevState => ({

+             notifications: [

+                 ...prevState.notifications,

+                 {

+                     key: prevState.notifications.length + 1,

+                     type: type,

+                     persistent: persistent,

+                     timerdelay: timerdelay,

+                     message: message,

+                 }

+             ]

+         }));

+     }

+ 

+     removeNotification(notificationToRemove) {

+         this.setState({

+             notifications: this.state.notifications.filter(

+                 notification => notificationToRemove.key !== notification.key

+             )

+         });

+     }

+ 

+     update_tree_nodes() {

+         // Set title to the text value of each suffix node.  We need to do this

+         // so we can read long suffixes in the UI tree div

+         let elements = document.getElementsByClassName('treeitem-row');

+         for (let el of elements) {

+             el.setAttribute('title', el.innerText);

+         }

+         this.setState({

+             disableTree: false

+         });

+     }

+ 

+     render() {

+         let pwp_element = "";

+         let disabled = "tree-view-container";

+         if (this.state.disableTree) {

+             disabled = "tree-view-container ds-disabled";

+         }

+         let reloadSpinner = "";

+         if (this.state.loading) {

+             reloadSpinner = <Spinner loading size="md" />;

+         }

+ 

+         if (this.state.loaded) {

+             if (this.state.node_name == GLOBAL_POLICY || this.state.node_name == "") {

+                 pwp_element =

+                     <GlobalPwp

+                         serverId={this.props.serverId}

+                         addNotification={this.addNotification}

+                         reload={this.loadGlobalConfig}

+                         data={this.state.globalDBConfig}

+                         enableTree={this.enableTree}

+                         key={this.state.configUpdated}

+                     />;

+             } else if (this.state.dbtype == "suffix" || this.state.dbtype == "subsuffix") {

+                 if (this.state.suffixLoading) {

+                     pwp_element =

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

+                             <h4>Loading password policy for <b>{this.state.node_text} ...</b></h4>

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

+                         </div>;

+                 } else {

+                     db_element =

+                         <Suffix

+ 

+                         />;

+                 }

+             } else {

+                 // Chaining

+                 pwp_element =

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

+                         <h4>You can not have local password policies on a database link: <b>{this.state.node_text}</b></h4>

+                     </div>;

+             }

+         }

+ 

+         return (

+             <div className="container-fluid">

+                 <NotificationController

+                     notifications={this.state.notifications}

+                     removeNotificationAction={this.removeNotification}

+                 />

+                 <Row>

+                     <Col sm={12} className="ds-word-wrap">

+                         <ControlLabel className="ds-suffix-header">

+                             Password Policy

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

+                                 type="fa" name="refresh" title="Refresh password policies"

+                                 onClick={() => {

+                                     this.loadSuffixTree(1);

+                                 }}

+                             />

+                         </ControlLabel>

+                         {reloadSpinner}

+                     </Col>

+                 </Row>

+                 <hr />

+                 <div className="ds-container">

+                     <div className="ds-tree">

+                         <div className={disabled} id="pwp-tree"

+                             style={treeViewContainerStyles}>

+                             <TreeView

+                                 nodes={nodes}

+                                 highlightOnHover

+                                 highlightOnSelect

+                                 selectNode={this.selectNode}

+                             />

+                         </div>

+                     </div>

+                     <div className="ds-tree-content">

+                         {pwp_element}

+                     </div>

+                 </div>

+             </div>

+         );

+     }

+ }

@@ -77,10 +77,10 @@ 

          let dateAttrs = ['last-update-start', 'last-update-end',

              'last-init-start', 'last-init-end'];

          for (let attr of dateAttrs) {

-             if (agmt[attr] == "19700101000000Z") {

+             if (agmt[attr][0] == "19700101000000Z") {

                  convertedDate[attr] = "Unavailable";

              } else {

-                 convertedDate[attr] = get_date_string(agmt[attr]);

+                 convertedDate[attr] = get_date_string(agmt[attr][0]);

              }

          }

          let initButton = null;
@@ -223,10 +223,10 @@ 

          let dateAttrs = ['last-update-start', 'last-update-end',

              'last-init-start', 'last-init-end'];

          for (let attr of dateAttrs) {

-             if (agmt[attr] == "19700101000000Z") {

+             if (agmt[attr][0] == "19700101000000Z") {

                  agmt[attr] = "Unavailable";

              } else {

-                 agmt[attr] = get_date_string(agmt[attr]);

+                 agmt[attr] = get_date_string(agmt[attr][0]);

              }

          }

  

@@ -105,10 +105,7 @@ 

                              configEntry["altstateattrname"] === undefined

                                  ? []

                                  : [

-                                     {

-                                         id: configEntry["altstateattrname"][0],

-                                         label: configEntry["altstateattrname"][0]

-                                     }

+                                     configEntry["altstateattrname"][0]

                                  ],

                              alwaysRecordLogin: !(

                                  configEntry["alwaysrecordlogin"] === undefined ||
@@ -118,37 +115,25 @@ 

                              configEntry["alwaysrecordloginattr"] === undefined

                                  ? []

                                  : [

-                                     {

-                                         id: configEntry["alwaysrecordloginattr"][0],

-                                         label: configEntry["alwaysrecordloginattr"][0]

-                                     }

+                                     configEntry["alwaysrecordloginattr"][0]

                                  ],

                              limitAttrName:

                              configEntry["limitattrname"] === undefined

                                  ? []

                                  : [

-                                     {

-                                         id: configEntry["limitattrname"][0],

-                                         label: configEntry["limitattrname"][0]

-                                     }

+                                     configEntry["limitattrname"][0]

                                  ],

                              specAttrName:

                              configEntry["specattrname"] === undefined

                                  ? []

                                  : [

-                                     {

-                                         id: configEntry["specattrname"][0],

-                                         label: configEntry["specattrname"][0]

-                                     }

+                                     configEntry["specattrname"][0]

                                  ],

                              stateAttrName:

                              configEntry["stateattrname"] === undefined

                                  ? []

                                  : [

-                                     {

-                                         id: configEntry["stateattrname"][0],

-                                         label: configEntry["stateattrname"][0]

-                                     }

+                                     configEntry["stateattrname"][0],

                                  ]

                          });

                          this.props.toggleLoadingHandler();
@@ -200,7 +185,7 @@ 

  

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

          if (altStateAttrName.length != 0) {

-             cmd = [...cmd, altStateAttrName[0].label];

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

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

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

          } else {
@@ -209,7 +194,7 @@ 

  

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

          if (alwaysRecordLoginAttr.length != 0) {

-             cmd = [...cmd, alwaysRecordLoginAttr[0].label];

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

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

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

          } else {
@@ -218,7 +203,7 @@ 

  

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

          if (limitAttrName.length != 0) {

-             cmd = [...cmd, limitAttrName[0].label];

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

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

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

          } else {
@@ -227,7 +212,7 @@ 

  

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

          if (specAttrName.length != 0) {

-             cmd = [...cmd, specAttrName[0].label];

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

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

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

          } else {
@@ -236,7 +221,7 @@ 

  

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

          if (stateAttrName.length != 0) {

-             cmd = [...cmd, stateAttrName[0].label];

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

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

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

          } else {
@@ -366,10 +351,7 @@ 

                      const attrContent = JSON.parse(content);

                      let attrs = [];

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

-                         attrs.push({

-                             id: content.name,

-                             label: content.name

-                         });

+                         attrs.push(content.name[0]);

                      }

                      this.setState({

                          attributes: attrs
@@ -641,7 +623,7 @@ 

                      <Row>

                          <Col sm={10}>

                              <Form horizontal>

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

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

                                      <Col

                                          componentClass={ControlLabel}

                                          sm={3}

@@ -170,19 +170,13 @@ 

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

                                  ? []

                                  : [

-                                     {

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

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

-                                     }

+                                     configEntry["uniqueness-top-entry-oc"][0]

                                  ],

                              subtreeEnriesOc:

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

                                  ? []

                                  : [

-                                     {

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

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

-                                     }

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

                                  ]

                          });

  
@@ -192,7 +186,7 @@ 

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

                                  configAttrNamesList = [

                                      ...configAttrNamesList,

-                                     { id: value, label: value }

+                                     value

                                  ];

                              }

                              this.setState({ attrNames: configAttrNamesList });
@@ -203,7 +197,7 @@ 

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

                                  configSubtreesList = [

                                      ...configSubtreesList,

-                                     { id: value, label: value }

+                                     value

                                  ];

                              }

                              this.setState({ subtrees: configSubtreesList });
@@ -260,7 +254,7 @@ 

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

              if (attrNames.length != 0) {

                  for (let value of attrNames) {

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

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

                  }

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

                  cmd = [...cmd, ""];
@@ -273,7 +267,7 @@ 

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

              if (subtrees.length != 0) {

                  for (let value of subtrees) {

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

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

                  }

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

                  cmd = [...cmd, ""];
@@ -284,7 +278,7 @@ 

  

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

          if (topEntryOc.length != 0) {

-             cmd = [...cmd, topEntryOc[0].label];

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

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

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

          } else {
@@ -293,7 +287,7 @@ 

  

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

          if (subtreeEnriesOc.length != 0) {

-             cmd = [...cmd, subtreeEnriesOc[0].label];

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

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

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

          } else {
@@ -398,10 +392,7 @@ 

                      const attrContent = JSON.parse(content);

                      let attrs = [];

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

-                         attrs.push({

-                             id: content.name,

-                             label: content.name

-                         });

+                         attrs.push(content.name[0]);

                      }

                      this.setState({

                          attributes: attrs
@@ -429,10 +420,7 @@ 

                      const ocContent = JSON.parse(content);

                      let ocs = [];

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

-                         ocs.push({

-                             id: content.name,

-                             label: content.name

-                         });

+                         ocs.push(content.name[0]);

                      }

                      this.setState({

                          objectClasses: ocs

@@ -214,10 +214,7 @@ 

                              let groupingAttr = definitionEntry["automembergroupingattr"][0];

                              this.setState({

                                  groupingAttrMember: [

-                                     {

-                                         id: groupingAttr.split(":")[0],

-                                         label: groupingAttr.split(":")[0]

-                                     }

+                                     groupingAttr.split(":")[0]

                                  ],

                                  groupingAttrEntry: groupingAttr.split(":")[1]

                              });
@@ -546,7 +543,7 @@ 

                  this.setState({ regexExclusive: [] });

              } else {

                  for (let value of regexEntry["automemberexclusiveregex"]) {

-                     exclusiveRegexList = [...exclusiveRegexList, { id: value, label: value }];

+                     exclusiveRegexList = [...exclusiveRegexList, value];

                  }

                  this.setState({ regexExclusive: exclusiveRegexList });

              }
@@ -554,7 +551,7 @@ 

                  this.setState({ regexInclusive: [] });

              } else {

                  for (let value of regexEntry["automemberinclusiveregex"]) {

-                     inclusiveRegexList = [...inclusiveRegexList, { id: value, label: value }];

+                     inclusiveRegexList = [...inclusiveRegexList, value];

                  }

                  this.setState({ regexInclusive: inclusiveRegexList });

              }
@@ -606,11 +603,11 @@ 

                                  automembertargetgroup: [regexTargetGroup],

                                  automemberexclusiveregex:

                                      regexExclusive.length !== 0

-                                         ? regexExclusive.map(regex => regex.label)

+                                         ? regexExclusive.map(regex => regex)

                                          : [],

                                  automemberinclusiveregex:

                                      regexInclusive.length !== 0

-                                         ? regexInclusive.map(regex => regex.label)

+                                         ? regexInclusive.map(regex => regex)

                                          : [],

                                  needsadd: true

                              }
@@ -633,11 +630,11 @@ 

                                  automembertargetgroup: [regexTargetGroup],

                                  automemberexclusiveregex:

                                      regexExclusive.length !== 0

-                                         ? regexExclusive.map(regex => regex.label)

+                                         ? regexExclusive.map(regex => regex)

                                          : [],

                                  automemberinclusiveregex:

                                      regexInclusive.length !== 0

-                                         ? regexInclusive.map(regex => regex.label)

+                                         ? regexInclusive.map(regex => regex)

                                          : [],

                                  needsupdate: true

                              }
@@ -697,10 +694,7 @@ 

                      const attrContent = JSON.parse(content);

                      let attrs = [];

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

-                         attrs.push({

-                             id: content.name,

-                             label: content.name

-                         });

+                         attrs.push(content.name[0]);

                      }

                      this.setState({

                          attributes: attrs

@@ -292,7 +292,7 @@ 

                              this.setState({ type: [] });

                          } else {

                              for (let value of configEntry["dnatype"]) {

-                                 dnaTypeList = [...dnaTypeList, { id: value, label: value }];

+                                 dnaTypeList = [...dnaTypeList, value];

                              }

                              this.setState({ type: dnaTypeList });

                          }
@@ -401,7 +401,7 @@ 

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

              if (type.length != 0) {

                  for (let value of type) {

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

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

                  }

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

                  cmd = [...cmd, ""];
@@ -726,10 +726,7 @@ 

                      const attrContent = JSON.parse(content);

                      let attrs = [];

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

-                         attrs.push({

-                             id: content.name,

-                             label: content.name

-                         });

+                         attrs.push(content.name[0]);

                      }

                      this.setState({

                          attributes: attrs

@@ -137,19 +137,13 @@ 

                              configEntry["linktype"] === undefined

                                  ? []

                                  : [

-                                     {

-                                         id: configEntry["linktype"][0],

-                                         label: configEntry["linktype"][0]

-                                     }

+                                     configEntry["linktype"][0]

                                  ],

                              managedType:

                              configEntry["managedtype"] === undefined

                                  ? []

                                  : [

-                                     {

-                                         id: configEntry["managedtype"][0],

-                                         label: configEntry["managedtype"][0]

-                                     }

+                                     configEntry["managedtype"][0]

                                  ],

                              linkScope:

                              configEntry["linkscope"] === undefined
@@ -195,7 +189,7 @@ 

  

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

          if (linkType.length != 0) {

-             cmd = [...cmd, linkType[0].label];

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

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

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

          } else {
@@ -204,7 +198,7 @@ 

  

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

          if (managedType.length != 0) {

-             cmd = [...cmd, managedType[0].label];

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

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

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

          } else {
@@ -310,10 +304,7 @@ 

                      const attrContent = JSON.parse(content);

                      let attrs = [];

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

-                         attrs.push({

-                             id: content.name,

-                             label: content.name

-                         });

+                         attrs.push(content.name[0]);

                      }

                      this.setState({

                          attributes: attrs

@@ -351,19 +351,13 @@ 

                              configEntry["meprdnattr"] === undefined

                                  ? []

                                  : [

-                                     {

-                                         id: configEntry["meprdnattr"][0],

-                                         label: configEntry["meprdnattr"][0]

-                                     }

+                                     configEntry["meprdnattr"][0]

                                  ],

                              templateStaticAttr:

                              configEntry["mepstaticattr"] === undefined

                                  ? []

                                  : [

-                                     {

-                                         id: configEntry["mepstaticattr"][0],

-                                         label: configEntry["mepstaticattr"][0]

-                                     }

+                                     configEntry["mepstaticattr"][0]

                                  ]

                          });

                          if (configEntry["mepmappedattr"] === undefined) {
@@ -372,7 +366,7 @@ 

                              for (let value of configEntry["mepmappedattr"]) {

                                  templateMappedAttrObjectList = [

                                      ...templateMappedAttrObjectList,

-                                     { id: value, label: value }

+                                     value

                                  ];

                              }

                              this.setState({
@@ -415,7 +409,7 @@ 

  

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

          if (templateRDNAttr.length != 0) {

-             cmd = [...cmd, templateRDNAttr[0].label];

+             cmd = [...cmd, templateRDNAttr[0]];

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

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

          } else {
@@ -424,7 +418,7 @@ 

  

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

          if (templateStaticAttr.length != 0) {

-             cmd = [...cmd, templateStaticAttr[0].label];

+             cmd = [...cmd, templateStaticAttr[0]];

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

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

          } else {
@@ -434,7 +428,7 @@ 

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

          if (templateMappedAttr.length != 0) {

              for (let value of templateMappedAttr) {

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

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

              }

          } else {

              cmd = [...cmd, "delete"];
@@ -536,10 +530,7 @@ 

                      const attrContent = JSON.parse(content);

                      let attrs = [];

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

-                         attrs.push({

-                             id: content.name,

-                             label: content.name

-                         });

+                         attrs.push(content.name[0]);

                      }

                      this.setState({

                          attributes: attrs

@@ -183,10 +183,7 @@ 

                              configEntry["memberofautoaddoc"] === undefined

                                  ? []

                                  : [

-                                     {

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

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

-                                     }

+                                     configEntry["memberofautoaddoc"][0],

                                  ],

                              configAllBackends: !(

                                  configEntry["memberofallbackends"] === undefined ||
@@ -215,7 +212,7 @@ 

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

                                  configAttrObjectList = [

                                      ...configAttrObjectList,

-                                     { id: value, label: value }

+                                     value

                                  ];

                              }

                              this.setState({ configAttr: configAttrObjectList });
@@ -226,7 +223,7 @@ 

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

                                  configGroupAttrObjectList = [

                                      ...configGroupAttrObjectList,

-                                     { id: value, label: value }

+                                     value

                                  ];

                              }

                              this.setState({
@@ -298,7 +295,7 @@ 

  

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

              if (configAutoAddOC.length != 0) {

-                 cmd = [...cmd, configAutoAddOC[0].label];

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

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

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

              } else {
@@ -309,13 +306,13 @@ 

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

              if (configAttr.length != 0) {

                  for (let value of configAttr) {

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

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

                  }

              }

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

              if (configGroupAttr.length != 0) {

                  for (let value of configGroupAttr) {

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

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

                  }

              }

  
@@ -422,10 +419,7 @@ 

                      pluginRow["memberofautoaddoc"] === undefined

                          ? []

                          : [

-                             {

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

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

-                             }

+                             pluginRow["memberofautoaddoc"][0],

                          ],

                  memberOfAllBackends: !(

                      pluginRow["memberofallbackends"] === undefined ||
@@ -454,7 +448,7 @@ 

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

                      memberOfAttrObjectList = [

                          ...memberOfAttrObjectList,

-                         { id: value, label: value }

+                         value

                      ];

                  }

                  this.setState({ memberOfAttr: memberOfAttrObjectList });
@@ -465,7 +459,7 @@ 

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

                      memberOfGroupAttrObjectList = [

                          ...memberOfGroupAttrObjectList,

-                         { id: value, label: value }

+                         value

                      ];

                  }

                  this.setState({
@@ -491,10 +485,7 @@ 

                      const ocContent = JSON.parse(content);

                      let ocs = [];

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

-                         ocs.push({

-                             id: content.name,

-                             label: content.name

-                         });

+                         ocs.push(content.name[0]);

                      }

                      this.setState({

                          objectClasses: ocs
@@ -522,10 +513,7 @@ 

                      const atContent = JSON.parse(content);

                      let attrs = [];

                      for (let content of atContent["items"]) {

-                         attrs.push({

-                             id: content.name,

-                             label: content.name

-                         });

+                         attrs.push(content.name[0]);

                      }

                      this.setState({

                          attributeTypes: attrs
@@ -585,7 +573,7 @@ 

  

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

          if (memberOfAutoAddOC.length != 0) {

-             specificPluginCMD = [...specificPluginCMD, memberOfAutoAddOC[0].label];

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

          } else {

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

          }
@@ -594,7 +582,7 @@ 

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

          if (memberOfAttr.length != 0) {

              for (let value of memberOfAttr) {

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

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

              }

          } else {

              specificPluginCMD = [...specificPluginCMD, "delete"];
@@ -603,7 +591,7 @@ 

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

          if (memberOfGroupAttr.length != 0) {

              for (let value of memberOfGroupAttr) {

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

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

              }

          } else {

              specificPluginCMD = [...specificPluginCMD, "delete"];
@@ -729,12 +717,7 @@ 

                                                      }}

                                                      selected={configAttr}

                                                      newSelectionPrefix="Add a member: "

-                                                     options={[

-                                                         {

-                                                             id: "memberOf",

-                                                             label: "memberOf"

-                                                         }

-                                                     ]}

+                                                     options={["memberOf"]}

                                                      placeholder="Type a member attribute..."

                                                  />

                                              </Col>
@@ -927,18 +910,9 @@ 

                                              selected={memberOfAttr}

                                              newSelectionPrefix="Add a member attrbiute: "

                                              options={[

-                                                 {

-                                                     id: "member",

-                                                     label: "member"

-                                                 },

-                                                 {

-                                                     id: "memberCertificate",

-                                                     label: "memberCertificate"

-                                                 },

-                                                 {

-                                                     id: "uniqueMember",

-                                                     label: "uniqueMember"

-                                                 }

+                                                 "member",

+                                                 "memberCertificate",

+                                                 "uniqueMember"

                                              ]}

                                              placeholder="Type a member attribute..."

                                          />

@@ -249,7 +249,7 @@ 

                              for (let value of pamConfigEntry["pamexcludesuffix"]) {

                                  pamExcludeSuffixList = [

                                      ...pamExcludeSuffixList,

-                                     { id: value, label: value }

+                                     value

                                  ];

                              }

                              this.setState({
@@ -263,7 +263,7 @@ 

                              for (let value of pamConfigEntry["pamincludesuffix"]) {

                                  pamIncludeSuffixList = [

                                      ...pamIncludeSuffixList,

-                                     { id: value, label: value }

+                                     value

                                  ];

                              }

                              this.setState({
@@ -275,7 +275,7 @@ 

                              this.setState({ pamIDAttr: [] });

                          } else {

                              for (let value of pamConfigEntry["pamidattr"]) {

-                                 pamIDAttrList = [...pamIDAttrList, { id: value, label: value }];

+                                 pamIDAttrList = [...pamIDAttrList, value];

                              }

                              this.setState({ pamIDAttr: pamIDAttrList });

                          }
@@ -399,7 +399,7 @@ 

          cmd = [...cmd, "--exclude-suffix"];

          if (pamExcludeSuffix.length != 0) {

              for (let value of pamExcludeSuffix) {

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

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

              }

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

              cmd = [...cmd, ""];
@@ -409,7 +409,7 @@ 

          cmd = [...cmd, "--include-suffix"];

          if (pamIncludeSuffix.length != 0) {

              for (let value of pamIncludeSuffix) {

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

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

              }

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

              cmd = [...cmd, ""];
@@ -419,7 +419,7 @@ 

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

          if (pamIDAttr.length != 0) {

              for (let value of pamIDAttr) {

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

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

              }

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

              cmd = [...cmd, ""];
@@ -642,10 +642,7 @@ 

                      const attrContent = JSON.parse(content);

                      let attrs = [];

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

-                         attrs.push({

-                             id: content.name,

-                             label: content.name

-                         });

+                         attrs.push(content.name[0]);

                      }

                      this.setState({

                          attributes: attrs

@@ -140,7 +140,7 @@ 

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

                                  membershipAttrList = [

                                      ...membershipAttrList,

-                                     { id: value, label: value }

+                                     value

                                  ];

                              }

                              this.setState({
@@ -206,7 +206,7 @@ 

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

          if (configMembershipAttr.length != 0) {

              for (let value of configMembershipAttr) {

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

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

              }

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

              cmd = [...cmd, ""];
@@ -334,7 +334,7 @@ 

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

              } else {

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

-                     membershipAttrList = [...membershipAttrList, { id: value, label: value }];

+                     membershipAttrList = [...membershipAttrList, value];

                  }

                  this.setState({ membershipAttr: membershipAttrList });

              }
@@ -357,10 +357,7 @@ 

                      const attrContent = JSON.parse(content);

                      let attrs = [];

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

-                         attrs.push({

-                             id: content.name,

-                             label: content.name

-                         });

+                         attrs.push(content.name[0]);

                      }

                      this.setState({

                          attributes: attrs
@@ -418,7 +415,7 @@ 

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

          if (membershipAttr.length != 0) {

              for (let value of membershipAttr) {

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

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

              }

          } else {

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

@@ -70,10 +70,7 @@ 

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

                          ? []

                          : [

-                             {

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

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

-                             }

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

                          ],

                  directory:

                      pluginRow["nsslapd-changelogdir"] === undefined
@@ -107,10 +104,7 @@ 

                      const attrContent = JSON.parse(content);

                      let attrs = [];

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

-                         attrs.push({

-                             id: content.name,

-                             label: content.name

-                         });

+                         attrs.push(content.name[0]);

                      }

                      this.setState({

                          attributes: attrs
@@ -142,7 +136,7 @@ 

              "--is-replicated",

              isReplicated ? "TRUE" : "FALSE",

              "--attribute",

-             attribute.length != 0 ? attribute[0].label : "delete",

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

              "--directory",

              directory || "delete",

              "--max-age",

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

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

              } else {

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

-                     allowHostList = [...allowHostList, { id: value, label: value }];

+                     allowHostList = [...allowHostList, value];

                  }

                  this.setState({ allowHost: allowHostList });

              }
@@ -74,7 +74,7 @@ 

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

              } else {

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

-                     denyHostList = [...denyHostList, { id: value, label: value }];

+                     denyHostList = [...denyHostList, value];

                  }

                  this.setState({ denyHost: denyHostList });

              }
@@ -82,7 +82,7 @@ 

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

              } else {

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

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

+                     allowIPList = [...allowIPList, value];

                  }

                  this.setState({ allowIP: allowIPList });

              }
@@ -90,7 +90,7 @@ 

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

              } else {

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

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

+                     denyIPList = [...denyIPList, value];

                  }

                  this.setState({ denyIP: denyIPList });

              }
@@ -127,7 +127,7 @@ 

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

          if (allowHost.length != 0) {

              for (let value of allowHost) {

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

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

              }

          } else {

              specificPluginCMD = [...specificPluginCMD, "delete"];
@@ -135,7 +135,7 @@ 

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

          if (denyHost.length != 0) {

              for (let value of denyHost) {

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

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

              }

          } else {

              specificPluginCMD = [...specificPluginCMD, "delete"];
@@ -143,7 +143,7 @@ 

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

          if (allowIP.length != 0) {

              for (let value of allowIP) {

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

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

              }

          } else {

              specificPluginCMD = [...specificPluginCMD, "delete"];
@@ -151,7 +151,7 @@ 

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

          if (allowHost.length != 0) {

              for (let value of allowHost) {

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

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

              }

          } else {

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

@@ -186,8 +186,7 @@ 

  

          if (this.state.saving) {

              cipherPage =

-                 <div className="ds-loading-spinner ds-center">

-                     <p />

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

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

                      <Spinner loading size="md" />

                  </div>;
@@ -274,10 +273,9 @@ 

                              />

                          </Col>

                      </Row>

-                     <p />

                      <Button

                          bsStyle="primary"

-                         className="ds-margin-top"

+                         className="ds-margin-top-lg"

                          onClick={() => {

                              this.saveCipherPref();

                          }}

@@ -0,0 +1,763 @@ 

+ import cockpit from "cockpit";

+ import React from "react";

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

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

+ import {

+     Button,

+     Checkbox,

+     Col,

+     ControlLabel,

+     Form,

+     FormControl,

+     Icon,

+     Nav,

+     NavItem,

+     Row,

+     Spinner,

+     TabContainer,

+     TabContent,

+     TabPane,

+ } from "patternfly-react";

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

+ import PropTypes from "prop-types";

+ 

+ const accesslog_levels = [

+     4,

+     256,

+     512

+ ];

+ 

+ const settings_attrs = [

+     'nsslapd-accesslog',

+     'nsslapd-accesslog-level',

+     'nsslapd-accesslog-logbuffering',

+     'nsslapd-accesslog-logging-enabled',

+     'accesslevel-4',

+     'accesslevel-256',

+     'accesslevel-512',

+ ];

+ 

+ const rotation_attrs = [

+     'nsslapd-accesslog-logrotationsync-enabled',

+     'nsslapd-accesslog-logrotationsynchour',

+     'nsslapd-accesslog-logrotationsyncmin',

+     'nsslapd-accesslog-logrotationtime',

+     'nsslapd-accesslog-logrotationtimeunit',

+     'nsslapd-accesslog-maxlogsize',

+     'nsslapd-accesslog-maxlogsperdir',

+ ];

+ 

+ const exp_attrs = [

+     'nsslapd-accesslog-logexpirationtime',

+     'nsslapd-accesslog-logexpirationtimeunit',

+     'nsslapd-accesslog-logmaxdiskspace',

+     'nsslapd-accesslog-logminfreediskspace',

+ ];

+ 

+ export class ServerAccessLog extends React.Component {

+     constructor(props) {

+         super(props);

+         this.state = {

+             loading: false,

+             loaded: false,

+             activeKey: 1,

+             notifications: [],

+             saveSettingsDisabled: true,

+             saveRotationDisabled: true,

+             saveExpDisabled: true,

+             attrs: this.props.attrs,

+         };

+ 

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

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

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

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

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

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

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

+     }

+ 

+     componentWillMount() {

+         // Loading config

+         if (!this.state.loaded) {

+             this.loadConfig();

+         }

+     }

+ 

+     componentDidMount() {

+         this.props.enableTree();

+     }

+ 

+     handleNavSelect(key) {

+         this.setState({ activeKey: key });

+     }

+ 

+     addNotification(type, message, timerdelay, persistent) {

+         this.setState(prevState => ({

+             notifications: [

+                 ...prevState.notifications,

+                 {

+                     key: prevState.notifications.length + 1,

+                     type: type,

+                     persistent: persistent,

+                     timerdelay: timerdelay,

+                     message: message,

+                 }

+             ]

+         }));

+     }

+ 

+     removeNotification(notificationToRemove) {

+         this.setState({

+             notifications: this.state.notifications.filter(

+                 notification => notificationToRemove.key !== notification.key

+             )

+         });

+     }

+ 

+     handleChange(e, nav_tab) {

+         let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;

+         let attr = e.target.id;

+         let disableSaveBtn = true;

+         let disableBtnName = "";

+         let config_attrs = [];

+         if (nav_tab == "settings") {

+             config_attrs = settings_attrs;

+             disableBtnName = "saveSettingsDisabled";

+         } else if (nav_tab == "rotation") {

+             config_attrs = rotation_attrs;

+             disableBtnName = "saveRotationDisabled";

+         } else {

+             config_attrs = exp_attrs;

+             disableBtnName = "saveExpDisabled";

+         }

+ 

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

+         for (let config_attr of config_attrs) {

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

+                 disableSaveBtn = false;

+                 break;

+             }

+         }

+ 

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

+         for (let config_attr of config_attrs) {

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

+                 disableSaveBtn = false;

+                 break;

+             }

+         }

+ 

+         this.setState({

+             [attr]: value,

+             [disableBtnName]: disableSaveBtn,

+         });

+     }

+ 

+     saveConfig(nav_tab) {

+         let level_change = false;

+         let new_level = 0;

+         this.setState({

+             loading: true

+         });

+ 

+         let config_attrs = [];

+         if (nav_tab == "settings") {

+             config_attrs = settings_attrs;

+         } else if (nav_tab == "rotation") {

+             config_attrs = rotation_attrs;

+         } else {

+             config_attrs = exp_attrs;

+         }

+ 

+         let cmd = [

+             'dsconf', '-j', this.props.serverId, 'config', 'replace'

+         ];

+ 

+         for (let attr of config_attrs) {

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

+                 if (attr.startsWith("accesslevel-")) {

+                     level_change = true;

+                     continue;

+                 }

+                 let val = this.state[attr];

+                 if (typeof val === "boolean") {

+                     if (val) {

+                         val = "on";

+                     } else {

+                         val = "off";

+                     }

+                 }

+                 cmd.push(attr + "=" + val);

+             }

+         }

+ 

+         if (level_change) {

+             for (let level of accesslog_levels) {

+                 if (this.state['accesslevel-' + level.toString()]) {

+                     new_level += level;

+                 }

+             }

+             cmd.push("nsslapd-accesslog-level" + "=" + new_level.toString());

+         }

+ 

+         log_cmd("saveConfig", "Saving access log settings", cmd);

+         cockpit

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

+                 .done(content => {

+                     this.reloadConfig();

+                     this.setState({

+                         loading: false

+                     });

+                     this.addNotification(

+                         "success",

+                         "Successfully updated Access Log settings"

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.reloadConfig();

+                     this.setState({

+                         loading: false

+                     });

+                     this.addNotification(

+                         "error",

+                         `Error saving Access Log settings - ${errMsg.desc}`

+                     );

+                 });

+     }

+ 

+     reloadConfig() {

+         this.setState({

+             loading: true

+         });

+         let cmd = [

+             "dsconf", "-j", this.props.serverId, "config", "get"

+         ];

+         log_cmd("reloadConfig", "load Access Log configuration", cmd);

+         cockpit

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

+                 .done(content => {

+                     let config = JSON.parse(content);

+                     let attrs = config.attrs;

+                     let enabled = false;

+                     let buffering = false;

+                     let level_val = parseInt(attrs['nsslapd-accesslog-level'][0]);

+                     let loglevel = {};

+ 

+                     if (attrs['nsslapd-accesslog-logging-enabled'][0] == "on") {

+                         enabled = true;

+                     }

+                     if (attrs['nsslapd-accesslog-logbuffering'][0] == "on") {

+                         buffering = true;

+                     }

+                     for (let level of accesslog_levels) {

+                         if (level & level_val) {

+                             loglevel[level.toString()] = true;

+                         } else {

+                             loglevel[level.toString()] = false;

+                         }

+                     }

+ 

+                     this.setState(() => (

+                         {

+                             loading: false,

+                             loaded: true,

+                             saveSettingsDisabled: true,

+                             saveRotationDisabled: true,

+                             saveExpDisabled: true,

+                             'nsslapd-accesslog': attrs['nsslapd-accesslog'][0],

+                             'nsslapd-accesslog-level': attrs['nsslapd-accesslog-level'][0],

+                             'accesslevel-4': loglevel['4'],

+                             'accesslevel-256': loglevel['256'],

+                             'accesslevel-512': loglevel['512'],

+                             'nsslapd-accesslog-logbuffering': buffering,

+                             'nsslapd-accesslog-logexpirationtime': attrs['nsslapd-accesslog-logexpirationtime'][0],

+                             'nsslapd-accesslog-logexpirationtimeunit': attrs['nsslapd-accesslog-logexpirationtimeunit'][0],

+                             'nsslapd-accesslog-logging-enabled': enabled,

+                             'nsslapd-accesslog-logmaxdiskspace': attrs['nsslapd-accesslog-logmaxdiskspace'][0],

+                             'nsslapd-accesslog-logminfreediskspace': attrs['nsslapd-accesslog-logminfreediskspace'][0],

+                             'nsslapd-accesslog-logrotationsync-enabled': attrs['nsslapd-accesslog-logrotationsync-enabled'][0],

+                             'nsslapd-accesslog-logrotationsynchour': attrs['nsslapd-accesslog-logrotationsynchour'][0],

+                             'nsslapd-accesslog-logrotationsyncmin': attrs['nsslapd-accesslog-logrotationsyncmin'][0],

+                             'nsslapd-accesslog-logrotationtime': attrs['nsslapd-accesslog-logrotationtime'][0],

+                             'nsslapd-accesslog-logrotationtimeunit': attrs['nsslapd-accesslog-logrotationtimeunit'][0],

+                             'nsslapd-accesslog-maxlogsize': attrs['nsslapd-accesslog-maxlogsize'][0],

+                             'nsslapd-accesslog-maxlogsperdir': attrs['nsslapd-accesslog-maxlogsperdir'][0],

+                             // Record original values

+                             '_nsslapd-accesslog': attrs['nsslapd-accesslog'][0],

+                             '_nsslapd-accesslog-level': attrs['nsslapd-accesslog-level'][0],

+                             '_accesslevel-4': loglevel['4'],

+                             '_accesslevel-256': loglevel['256'],

+                             '_accesslevel-512': loglevel['512'],

+                             '_nsslapd-accesslog-logbuffering': buffering,

+                             '_nsslapd-accesslog-logexpirationtime': attrs['nsslapd-accesslog-logexpirationtime'][0],

+                             '_nsslapd-accesslog-logexpirationtimeunit': attrs['nsslapd-accesslog-logexpirationtimeunit'][0],

+                             '_nsslapd-accesslog-logging-enabled': enabled,

+                             '_nsslapd-accesslog-logmaxdiskspace': attrs['nsslapd-accesslog-logmaxdiskspace'][0],

+                             '_nsslapd-accesslog-logminfreediskspace': attrs['nsslapd-accesslog-logminfreediskspace'][0],

+                             '_nsslapd-accesslog-logrotationsync-enabled': attrs['nsslapd-accesslog-logrotationsync-enabled'][0],

+                             '_nsslapd-accesslog-logrotationsynchour': attrs['nsslapd-accesslog-logrotationsynchour'][0],

+                             '_nsslapd-accesslog-logrotationsyncmin': attrs['nsslapd-accesslog-logrotationsyncmin'][0],

+                             '_nsslapd-accesslog-logrotationtime': attrs['nsslapd-accesslog-logrotationtime'][0],

+                             '_nsslapd-accesslog-logrotationtimeunit': attrs['nsslapd-accesslog-logrotationtimeunit'][0],

+                             '_nsslapd-accesslog-maxlogsize': attrs['nsslapd-accesslog-maxlogsize'][0],

+                             '_nsslapd-accesslog-maxlogsperdir': attrs['nsslapd-accesslog-maxlogsperdir'][0],

+                         })

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.addNotification(

+                         "error",

+                         `Error loading Access Log configuration - ${errMsg.desc}`

+                     );

+                     this.setState({

+                         loading: false,

+                         loaded: true,

+                     });

+                 });

+     }

+ 

+     loadConfig() {

+         let attrs = this.state.attrs;

+         let enabled = false;

+         let buffering = false;

+         let level_val = parseInt(attrs['nsslapd-accesslog-level'][0]);

+         let loglevel = {};

+ 

+         if (attrs['nsslapd-accesslog-logging-enabled'][0] == "on") {

+             enabled = true;

+         }

+         if (attrs['nsslapd-accesslog-logbuffering'][0] == "on") {

+             buffering = true;

+         }

+         for (let level of accesslog_levels) {

+             if (level & level_val) {

+                 loglevel[level.toString()] = true;

+             } else {

+                 loglevel[level.toString()] = false;

+             }

+         }

+ 

+         this.setState({

+             loading: false,

+             loaded: true,

+             saveSettingsDisabled: true,

+             saveRotationDisabled: true,

+             saveExpDisabled: true,

+             'nsslapd-accesslog': attrs['nsslapd-accesslog'][0],

+             'nsslapd-accesslog-level': attrs['nsslapd-accesslog-level'][0],

+             'accesslevel-4': loglevel['4'],

+             'accesslevel-256': loglevel['256'],

+             'accesslevel-512': loglevel['512'],

+             'nsslapd-accesslog-logbuffering': buffering,

+             'nsslapd-accesslog-logexpirationtime': attrs['nsslapd-accesslog-logexpirationtime'][0],

+             'nsslapd-accesslog-logexpirationtimeunit': attrs['nsslapd-accesslog-logexpirationtimeunit'][0],

+             'nsslapd-accesslog-logging-enabled': enabled,

+             'nsslapd-accesslog-logmaxdiskspace': attrs['nsslapd-accesslog-logmaxdiskspace'][0],

+             'nsslapd-accesslog-logminfreediskspace': attrs['nsslapd-accesslog-logminfreediskspace'][0],

+             'nsslapd-accesslog-logrotationsync-enabled': attrs['nsslapd-accesslog-logrotationsync-enabled'][0],

+             'nsslapd-accesslog-logrotationsynchour': attrs['nsslapd-accesslog-logrotationsynchour'][0],

+             'nsslapd-accesslog-logrotationsyncmin': attrs['nsslapd-accesslog-logrotationsyncmin'][0],

+             'nsslapd-accesslog-logrotationtime': attrs['nsslapd-accesslog-logrotationtime'][0],

+             'nsslapd-accesslog-logrotationtimeunit': attrs['nsslapd-accesslog-logrotationtimeunit'][0],

+             'nsslapd-accesslog-maxlogsize': attrs['nsslapd-accesslog-maxlogsize'][0],

+             'nsslapd-accesslog-maxlogsperdir': attrs['nsslapd-accesslog-maxlogsperdir'][0],

+             // Record original values

+             '_nsslapd-accesslog': attrs['nsslapd-accesslog'][0],

+             '_nsslapd-accesslog-level': attrs['nsslapd-accesslog-level'][0],

+             '_accesslevel-4': loglevel['4'],

+             '_accesslevel-256': loglevel['256'],

+             '_accesslevel-512': loglevel['512'],

+             '_nsslapd-accesslog-logbuffering': buffering,

+             '_nsslapd-accesslog-logexpirationtime': attrs['nsslapd-accesslog-logexpirationtime'][0],

+             '_nsslapd-accesslog-logexpirationtimeunit': attrs['nsslapd-accesslog-logexpirationtimeunit'][0],

+             '_nsslapd-accesslog-logging-enabled': enabled,

+             '_nsslapd-accesslog-logmaxdiskspace': attrs['nsslapd-accesslog-logmaxdiskspace'][0],

+             '_nsslapd-accesslog-logminfreediskspace': attrs['nsslapd-accesslog-logminfreediskspace'][0],

+             '_nsslapd-accesslog-logrotationsync-enabled': attrs['nsslapd-accesslog-logrotationsync-enabled'][0],

+             '_nsslapd-accesslog-logrotationsynchour': attrs['nsslapd-accesslog-logrotationsynchour'][0],

+             '_nsslapd-accesslog-logrotationsyncmin': attrs['nsslapd-accesslog-logrotationsyncmin'][0],

+             '_nsslapd-accesslog-logrotationtime': attrs['nsslapd-accesslog-logrotationtime'][0],

+             '_nsslapd-accesslog-logrotationtimeunit': attrs['nsslapd-accesslog-logrotationtimeunit'][0],

+             '_nsslapd-accesslog-maxlogsize': attrs['nsslapd-accesslog-maxlogsize'][0],

+             '_nsslapd-accesslog-maxlogsperdir': attrs['nsslapd-accesslog-maxlogsperdir'][0],

+         });

+     }

+ 

+     render() {

+         let body =

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

+                 <TabContainer id="access-log-settings" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>

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

+                         <Nav bsClass="nav nav-tabs nav-tabs-pf">

+                             <NavItem eventKey={1}>

+                                 <div dangerouslySetInnerHTML={{__html: 'Settings'}} />

+                             </NavItem>

+                             <NavItem eventKey={2}>

+                                 <div dangerouslySetInnerHTML={{__html: 'Rotation Policy'}} />

+                             </NavItem>

+                             <NavItem eventKey={3}>

+                                 <div dangerouslySetInnerHTML={{__html: 'Deletion Policy'}} />

+                             </NavItem>

+                         </Nav>

+ 

+                         <TabContent className="ds-margin-top-lg">

+                             <TabPane eventKey={1}>

+                                 <Form>

+                                     <Row className="ds-margin-top" title="Enable access logging (nsslapd-accesslog-logging-enabled).">

+                                         <Col sm={3}>

+                                             <Checkbox

+                                                 id="nsslapd-accesslog-logging-enabled"

+                                                 defaultChecked={this.state['nsslapd-accesslog-logging-enabled']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "settings");

+                                                 }}

+                                             >

+                                                 Enable Access Logging

+                                             </Checkbox>

+                                         </Col>

+                                     </Row>

+                                     <div className="ds-margin-left">

+                                         <Row className="ds-margin-top" title="Enable access logging (nsslapd-accesslog).">

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

+                                                 Access Log Location

+                                             </Col>

+                                             <Col sm={6}>

+                                                 <FormControl

+                                                     id="nsslapd-accesslog"

+                                                     type="text"

+                                                     value={this.state['nsslapd-accesslog']}

+                                                     onChange={(e) => {

+                                                         this.handleChange(e, "settings");

+                                                     }}

+                                                 />

+                                             </Col>

+                                         </Row>

+                                         <Row className="ds-margin-top" title="Disable access log buffering for faster troubleshooting, but this will impact server performance (nsslapd-accesslog-logbuffering).">

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

+                                                 <Checkbox

+                                                     id="nsslapd-accesslog-logbuffering"

+                                                     defaultChecked={this.state['nsslapd-accesslog-logbuffering']}

+                                                     onChange={(e) => {

+                                                         this.handleChange(e, "settings");

+                                                     }}

+                                                 >

+                                                     Access Log Buffering Enabled

+                                                 </Checkbox>

+                                             </Col>

+                                         </Row>

+                                         <table className="table table-striped table-bordered table-hover ds-loglevel-table ds-margin-top-lg" id="accesslog-level-table">

+                                             <thead>

+                                                 <tr>

+                                                     <th className="ds-table-checkbox" />

+                                                     <th>Logging Level</th>

+                                                 </tr>

+                                             </thead>

+                                             <tbody>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="accesslevel-256"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['accesslevel-256']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Default Logging

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="accesslevel-4"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['accesslevel-4']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Internal Operations

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="accesslevel-512"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['accesslevel-512']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Entry Access and Referrals

+                                                     </td>

+                                                 </tr>

+                                             </tbody>

+                                         </table>

+                                     </div>

+                                     <Button

+                                         disabled={this.state.saveSettingsDisabled}

+                                         bsStyle="primary"

+                                         className="ds-margin-top-med"

+                                         onClick={() => {

+                                             this.saveConfig("settings");

+                                         }}

+                                     >

+                                         Save Settings

+                                     </Button>

+                                 </Form>

+                             </TabPane>

+                         </TabContent>

+ 

+                         <TabContent className="ds-margin-top-lg">

+                             <TabPane eventKey={2}>

+                                 <Form horizontal>

+                                     <Row className="ds-margin-top-xlg" title="The maximum number of logs that are archived (nsslapd-accesslog-maxlogsperdir).">

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

+                                             Maximum Number Of Logs

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-accesslog-maxlogsperdir"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-accesslog-maxlogsperdir']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top-lg" title="The maximum size of each log file in megabytes (nsslapd-errorlog-maxlogsize).">

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

+                                             Maximum Log Size (in MB)

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-accesslog-maxlogsize"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-accesslog-maxlogsize']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <hr />

+                                     <Row className="ds-margin-top" title="Rotate the log based this number of time units (nsslapd-accesslog-logrotationtime).">

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

+                                             Create New Log Every...

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-accesslog-logrotationtime"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-accesslog-logrotationtime']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                         <Col sm={2}>

+                                             <select

+                                                 className="btn btn-default dropdown"

+                                                 id="nsslapd-accesslog-logrotationtimeunit"

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                                 value={this.state['nsslapd-accesslog-logrotationtimeunit']}

+                                             >

+                                                 <option>minute</option>

+                                                 <option>hour</option>

+                                                 <option>day</option>

+                                                 <option>week</option>

+                                                 <option>month</option>

+                                             </select>

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="The hour whenthe log should be rotated (nsslapd-accesslog-logrotationsynchour).">

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

+                                             Hour

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-accesslog-logrotationsynchour"

+                                                 type="number"

+                                                 min="0"

+                                                 max="23"

+                                                 value={this.state['nsslapd-accesslog-logrotationsynchour']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="The minute within the hour to rotate the log (nsslapd-accesslog-logrotationsyncmin).">

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

+                                             Minute

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-accesslog-logrotationsyncmin"

+                                                 type="number"

+                                                 min="0"

+                                                 max="59"

+                                                 value={this.state['nsslapd-accesslog-logrotationsyncmin']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Button

+                                         disabled={this.state.saveRotationDisabled}

+                                         bsStyle="primary"

+                                         className="ds-margin-top-med"

+                                         onClick={() => {

+                                             this.saveConfig("rotation");

+                                         }}

+                                     >

+                                         Save Rotation Settings

+                                     </Button>

+                                 </Form>

+                             </TabPane>

+                         </TabContent>

+ 

+                         <TabContent className="ds-margin-top-lg">

+                             <TabPane eventKey={3}>

+                                 <Form horizontal>

+                                     <Row className="ds-margin-top-xlg" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-accesslog-logmaxdiskspace).">

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

+                                             Total Log Archive Exceeds (in MB)

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-accesslog-logmaxdiskspace"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-accesslog-logmaxdiskspace']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-accesslog-logminfreediskspace).">

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

+                                             Free Disk Space (in MB)

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-accesslog-logminfreediskspace"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-accesslog-logminfreediskspace']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-accesslog-logexpirationtime).">

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

+                                             Log File is Older Than...

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-accesslog-logexpirationtime"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-accesslog-logexpirationtime']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             />

+                                         </Col>

+                                         <Col sm={2}>

+                                             <select

+                                                 className="btn btn-default dropdown"

+                                                 id="nsslapd-accesslog-logexpirationtimeunit"

+                                                 value={this.state['nsslapd-accesslog-logexpirationtimeunit']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             >

+                                                 <option>day</option>

+                                                 <option>week</option>

+                                                 <option>month</option>

+                                             </select>

+                                         </Col>

+                                     </Row>

+                                     <Button

+                                         disabled={this.state.saveExpDisabled}

+                                         bsStyle="primary"

+                                         className="ds-margin-top-med"

+                                         onClick={() => {

+                                             this.saveConfig("exp");

+                                         }}

+                                     >

+                                         Save Deletion Settings

+                                     </Button>

+                                 </Form>

+                             </TabPane>

+                         </TabContent>

+                     </div>

+                 </TabContainer>

+             </div>;

+ 

+         if (this.state.loading || !this.state.loaded) {

+             body = <Spinner loading size="md" />;

+         }

+ 

+         return (

+             <div id="server-accesslog-page">

+                 <NotificationController

+                     notifications={this.state.notifications}

+                     removeNotificationAction={this.removeNotification}

+                 />

+                 <Row>

+                     <Col sm={5}>

+                         <ControlLabel className="ds-suffix-header ds-margin-top-lg">

+                             Access Log Settings

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

+                                 type="fa" name="refresh" title="Refresh the Access Log settings"

+                                 onClick={this.reloadConfig}

+                                 disabled={this.state.loading}

+                             />

+                         </ControlLabel>

+                     </Col>

+                 </Row>

+                 {body}

+             </div>

+         );

+     }

+ }

+ 

+ // Property types and defaults

+ 

+ ServerAccessLog.propTypes = {

+     serverId: PropTypes.string,

+     attrs: PropTypes.object,

+ };

+ 

+ ServerAccessLog.defaultProps = {

+     serverId: "",

+     attrs: {},

+ };

@@ -0,0 +1,623 @@ 

+ import cockpit from "cockpit";

+ import React from "react";

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

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

+ import {

+     Button,

+     Checkbox,

+     Col,

+     ControlLabel,

+     Form,

+     FormControl,

+     Icon,

+     Nav,

+     NavItem,

+     Row,

+     Spinner,

+     TabContainer,

+     TabContent,

+     TabPane,

+ } from "patternfly-react";

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

+ import PropTypes from "prop-types";

+ 

+ const settings_attrs = [

+     'nsslapd-auditlog',

+     'nsslapd-auditlog-logging-enabled',

+ ];

+ 

+ const rotation_attrs = [

+     'nsslapd-auditlog-logrotationsync-enabled',

+     'nsslapd-auditlog-logrotationsynchour',

+     'nsslapd-auditlog-logrotationsyncmin',

+     'nsslapd-auditlog-logrotationtime',

+     'nsslapd-auditlog-logrotationtimeunit',

+     'nsslapd-auditlog-maxlogsize',

+     'nsslapd-auditlog-maxlogsperdir',

+ ];

+ 

+ const exp_attrs = [

+     'nsslapd-auditlog-logexpirationtime',

+     'nsslapd-auditlog-logexpirationtimeunit',

+     'nsslapd-auditlog-logmaxdiskspace',

+     'nsslapd-auditlog-logminfreediskspace',

+ ];

+ 

+ export class ServerAuditLog extends React.Component {

+     constructor(props) {

+         super(props);

+         this.state = {

+             loading: false,

+             loaded: false,

+             activeKey: 1,

+             notifications: [],

+             saveSettingsDisabled: true,

+             saveRotationDisabled: true,

+             saveExpDisabled: true,

+             attrs: this.props.attrs,

+         };

+ 

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

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

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

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

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

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

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

+     }

+ 

+     componentWillMount() {

+         // Loading config

+         if (!this.state.loaded) {

+             this.loadConfig();

+         }

+     }

+ 

+     componentDidMount() {

+         this.props.enableTree();

+     }

+ 

+     handleNavSelect(key) {

+         this.setState({ activeKey: key });

+     }

+ 

+     addNotification(type, message, timerdelay, persistent) {

+         this.setState(prevState => ({

+             notifications: [

+                 ...prevState.notifications,

+                 {

+                     key: prevState.notifications.length + 1,

+                     type: type,

+                     persistent: persistent,

+                     timerdelay: timerdelay,

+                     message: message,

+                 }

+             ]

+         }));

+     }

+ 

+     removeNotification(notificationToRemove) {

+         this.setState({

+             notifications: this.state.notifications.filter(

+                 notification => notificationToRemove.key !== notification.key

+             )

+         });

+     }

+ 

+     handleChange(e, nav_tab) {

+         let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;

+         let attr = e.target.id;

+         let disableSaveBtn = true;

+         let disableBtnName = "";

+         let config_attrs = [];

+         if (nav_tab == "settings") {

+             config_attrs = settings_attrs;

+             disableBtnName = "saveSettingsDisabled";

+         } else if (nav_tab == "rotation") {

+             config_attrs = rotation_attrs;

+             disableBtnName = "saveRotationDisabled";

+         } else {

+             config_attrs = exp_attrs;

+             disableBtnName = "saveExpDisabled";

+         }

+ 

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

+         for (let config_attr of config_attrs) {

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

+                 disableSaveBtn = false;

+                 break;

+             }

+         }

+ 

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

+         for (let config_attr of config_attrs) {

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

+                 disableSaveBtn = false;

+                 break;

+             }

+         }

+ 

+         this.setState({

+             [attr]: value,

+             [disableBtnName]: disableSaveBtn,

+         });

+     }

+ 

+     saveConfig(nav_tab) {

+         this.setState({

+             loading: true

+         });

+ 

+         let config_attrs = [];

+         if (nav_tab == "settings") {

+             config_attrs = settings_attrs;

+         } else if (nav_tab == "rotation") {

+             config_attrs = rotation_attrs;

+         } else {

+             config_attrs = exp_attrs;

+         }

+ 

+         let cmd = [

+             'dsconf', '-j', this.props.serverId, 'config', 'replace'

+         ];

+ 

+         for (let attr of config_attrs) {

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

+                 let val = this.state[attr];

+                 if (typeof val === "boolean") {

+                     if (val) {

+                         val = "on";

+                     } else {

+                         val = "off";

+                     }

+                 }

+                 cmd.push(attr + "=" + val);

+             }

+         }

+ 

+         log_cmd("saveConfig", "Saving audit log settings", cmd);

+         cockpit

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

+                 .done(content => {

+                     this.reloadConfig();

+                     this.setState({

+                         loading: false

+                     });

+                     this.addNotification(

+                         "success",

+                         "Successfully updated Audit Log settings"

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.reloadConfig();

+                     this.setState({

+                         loading: false

+                     });

+                     this.addNotification(

+                         "error",

+                         `Error saving Audit Log settings - ${errMsg.desc}`

+                     );

+                 });

+     }

+ 

+     loadConfig() {

+         let attrs = this.state.attrs;

+         let enabled = false;

+ 

+         if (attrs['nsslapd-auditlog-logging-enabled'][0] == "on") {

+             enabled = true;

+         }

+ 

+         this.setState({

+             loading: false,

+             loaded: true,

+             saveSettingsDisabled: true,

+             saveRotationDisabled: true,

+             saveExpDisabled: true,

+             'nsslapd-auditlog': attrs['nsslapd-auditlog'][0],

+             'nsslapd-auditlog-logexpirationtime': attrs['nsslapd-auditlog-logexpirationtime'][0],

+             'nsslapd-auditlog-logexpirationtimeunit': attrs['nsslapd-auditlog-logexpirationtimeunit'][0],

+             'nsslapd-auditlog-logging-enabled': enabled,

+             'nsslapd-auditlog-logmaxdiskspace': attrs['nsslapd-auditlog-logmaxdiskspace'][0],

+             'nsslapd-auditlog-logminfreediskspace': attrs['nsslapd-auditlog-logminfreediskspace'][0],

+             'nsslapd-auditlog-logrotationsync-enabled': attrs['nsslapd-auditlog-logrotationsync-enabled'][0],

+             'nsslapd-auditlog-logrotationsynchour': attrs['nsslapd-auditlog-logrotationsynchour'][0],

+             'nsslapd-auditlog-logrotationsyncmin': attrs['nsslapd-auditlog-logrotationsyncmin'][0],

+             'nsslapd-auditlog-logrotationtime': attrs['nsslapd-auditlog-logrotationtime'][0],

+             'nsslapd-auditlog-logrotationtimeunit': attrs['nsslapd-auditlog-logrotationtimeunit'][0],

+             'nsslapd-auditlog-maxlogsize': attrs['nsslapd-auditlog-maxlogsize'][0],

+             'nsslapd-auditlog-maxlogsperdir': attrs['nsslapd-auditlog-maxlogsperdir'][0],

+             // Record original values

+             '_nsslapd-auditlog': attrs['nsslapd-auditlog'][0],

+             '_nsslapd-auditlog-logexpirationtime': attrs['nsslapd-auditlog-logexpirationtime'][0],

+             '_nsslapd-auditlog-logexpirationtimeunit': attrs['nsslapd-auditlog-logexpirationtimeunit'][0],

+             '_nsslapd-auditlog-logging-enabled': enabled,

+             '_nsslapd-auditlog-logmaxdiskspace': attrs['nsslapd-auditlog-logmaxdiskspace'][0],

+             '_nsslapd-auditlog-logminfreediskspace': attrs['nsslapd-auditlog-logminfreediskspace'][0],

+             '_nsslapd-auditlog-logrotationsync-enabled': attrs['nsslapd-auditlog-logrotationsync-enabled'][0],

+             '_nsslapd-auditlog-logrotationsynchour': attrs['nsslapd-auditlog-logrotationsynchour'][0],

+             '_nsslapd-auditlog-logrotationsyncmin': attrs['nsslapd-auditlog-logrotationsyncmin'][0],

+             '_nsslapd-auditlog-logrotationtime': attrs['nsslapd-auditlog-logrotationtime'][0],

+             '_nsslapd-auditlog-logrotationtimeunit': attrs['nsslapd-auditlog-logrotationtimeunit'][0],

+             '_nsslapd-auditlog-maxlogsize': attrs['nsslapd-auditlog-maxlogsize'][0],

+             '_nsslapd-auditlog-maxlogsperdir': attrs['nsslapd-auditlog-maxlogsperdir'][0],

+         });

+     }

+ 

+     reloadConfig() {

+         this.setState({

+             loading: true,

+         });

+         let cmd = [

+             "dsconf", "-j", this.props.serverId, "config", "get"

+         ];

+         log_cmd("reloadConfig", "load Audit Log configuration", cmd);

+         cockpit

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

+                 .done(content => {

+                     let config = JSON.parse(content);

+                     let attrs = config.attrs;

+                     let enabled = false;

+ 

+                     if (attrs['nsslapd-auditlog-logging-enabled'][0] == "on") {

+                         enabled = true;

+                     }

+ 

+                     this.setState(() => (

+                         {

+                             loading: false,

+                             loaded: true,

+                             saveSettingsDisabled: true,

+                             saveRotationDisabled: true,

+                             saveExpDisabled: true,

+                             'nsslapd-auditlog': attrs['nsslapd-auditlog'][0],

+                             'nsslapd-auditlog-logexpirationtime': attrs['nsslapd-auditlog-logexpirationtime'][0],

+                             'nsslapd-auditlog-logexpirationtimeunit': attrs['nsslapd-auditlog-logexpirationtimeunit'][0],

+                             'nsslapd-auditlog-logging-enabled': enabled,

+                             'nsslapd-auditlog-logmaxdiskspace': attrs['nsslapd-auditlog-logmaxdiskspace'][0],

+                             'nsslapd-auditlog-logminfreediskspace': attrs['nsslapd-auditlog-logminfreediskspace'][0],

+                             'nsslapd-auditlog-logrotationsync-enabled': attrs['nsslapd-auditlog-logrotationsync-enabled'][0],

+                             'nsslapd-auditlog-logrotationsynchour': attrs['nsslapd-auditlog-logrotationsynchour'][0],

+                             'nsslapd-auditlog-logrotationsyncmin': attrs['nsslapd-auditlog-logrotationsyncmin'][0],

+                             'nsslapd-auditlog-logrotationtime': attrs['nsslapd-auditlog-logrotationtime'][0],

+                             'nsslapd-auditlog-logrotationtimeunit': attrs['nsslapd-auditlog-logrotationtimeunit'][0],

+                             'nsslapd-auditlog-maxlogsize': attrs['nsslapd-auditlog-maxlogsize'][0],

+                             'nsslapd-auditlog-maxlogsperdir': attrs['nsslapd-auditlog-maxlogsperdir'][0],

+                             // Record original values

+                             '_nsslapd-auditlog': attrs['nsslapd-auditlog'][0],

+                             '_nsslapd-auditlog-logexpirationtime': attrs['nsslapd-auditlog-logexpirationtime'][0],

+                             '_nsslapd-auditlog-logexpirationtimeunit': attrs['nsslapd-auditlog-logexpirationtimeunit'][0],

+                             '_nsslapd-auditlog-logging-enabled': enabled,

+                             '_nsslapd-auditlog-logmaxdiskspace': attrs['nsslapd-auditlog-logmaxdiskspace'][0],

+                             '_nsslapd-auditlog-logminfreediskspace': attrs['nsslapd-auditlog-logminfreediskspace'][0],

+                             '_nsslapd-auditlog-logrotationsync-enabled': attrs['nsslapd-auditlog-logrotationsync-enabled'][0],

+                             '_nsslapd-auditlog-logrotationsynchour': attrs['nsslapd-auditlog-logrotationsynchour'][0],

+                             '_nsslapd-auditlog-logrotationsyncmin': attrs['nsslapd-auditlog-logrotationsyncmin'][0],

+                             '_nsslapd-auditlog-logrotationtime': attrs['nsslapd-auditlog-logrotationtime'][0],

+                             '_nsslapd-auditlog-logrotationtimeunit': attrs['nsslapd-auditlog-logrotationtimeunit'][0],

+                             '_nsslapd-auditlog-maxlogsize': attrs['nsslapd-auditlog-maxlogsize'][0],

+                             '_nsslapd-auditlog-maxlogsperdir': attrs['nsslapd-auditlog-maxlogsperdir'][0],

+                         })

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.addNotification(

+                         "error",

+                         `Error loading Audit Log configuration - ${errMsg.desc}`

+                     );

+                     this.setState({

+                         loading: false,

+                         loaded: true,

+                     });

+                 });

+     }

+ 

+     render() {

+         let body =

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

+                 <TabContainer id="audit-log-settings" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>

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

+                         <Nav bsClass="nav nav-tabs nav-tabs-pf">

+                             <NavItem eventKey={1}>

+                                 <div dangerouslySetInnerHTML={{__html: 'Settings'}} />

+                             </NavItem>

+                             <NavItem eventKey={2}>

+                                 <div dangerouslySetInnerHTML={{__html: 'Rotation Policy'}} />

+                             </NavItem>

+                             <NavItem eventKey={3}>

+                                 <div dangerouslySetInnerHTML={{__html: 'Deletion Policy'}} />

+                             </NavItem>

+                         </Nav>

+ 

+                         <TabContent className="ds-margin-top-lg">

+                             <TabPane eventKey={1}>

+                                 <Form>

+                                     <Row className="ds-margin-top" title="Enable access logging (nsslapd-auditlog-logging-enabled).">

+                                         <Col sm={3}>

+                                             <Checkbox

+                                                 id="nsslapd-auditlog-logging-enabled"

+                                                 defaultChecked={this.state['nsslapd-auditlog-logging-enabled']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "settings");

+                                                 }}

+                                             >

+                                                 Enable Audit Logging

+                                             </Checkbox>

+                                         </Col>

+                                     </Row>

+                                     <div className="ds-margin-left">

+                                         <Row className="ds-margin-top" title="Enable access logging (nsslapd-auditlog).">

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

+                                                 Audit Log Location

+                                             </Col>

+                                             <Col sm={6}>

+                                                 <FormControl

+                                                     id="nsslapd-auditlog"

+                                                     type="text"

+                                                     value={this.state['nsslapd-auditlog']}

+                                                     onChange={(e) => {

+                                                         this.handleChange(e, "settings");

+                                                     }}

+                                                 />

+                                             </Col>

+                                         </Row>

+                                     </div>

+                                     <Button

+                                         disabled={this.state.saveSettingsDisabled}

+                                         bsStyle="primary"

+                                         className="ds-margin-top-med"

+                                         onClick={() => {

+                                             this.saveConfig("settings");

+                                         }}

+                                     >

+                                         Save Settings

+                                     </Button>

+                                 </Form>

+                             </TabPane>

+                         </TabContent>

+ 

+                         <TabContent className="ds-margin-top-lg">

+                             <TabPane eventKey={2}>

+                                 <Form horizontal>

+                                     <Row className="ds-margin-top-xlg" title="The maximum number of logs that are archived (nsslapd-auditlog-maxlogsperdir).">

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

+                                             Maximum Number Of Logs

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditlog-maxlogsperdir"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-auditlog-maxlogsperdir']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top-lg" title="The maximum size of each log file in megabytes (nsslapd-auditlog-maxlogsize).">

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

+                                             Maximum Log Size (in MB)

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditlog-maxlogsize"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-auditlog-maxlogsize']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <hr />

+                                     <Row className="ds-margin-top" title="Rotate the log based this number of time units (nsslapd-auditlog-logrotationtime).">

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

+                                             Create New Log Every...

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditlog-logrotationtime"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-auditlog-logrotationtime']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                         <Col sm={2}>

+                                             <select

+                                                 className="btn btn-default dropdown"

+                                                 id="nsslapd-auditlog-logrotationtimeunit"

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                                 value={this.state['nsslapd-auditlog-logrotationtimeunit']}

+                                             >

+                                                 <option>minute</option>

+                                                 <option>hour</option>

+                                                 <option>day</option>

+                                                 <option>week</option>

+                                                 <option>month</option>

+                                             </select>

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="The hour whenthe log should be rotated (nsslapd-auditlog-logrotationsynchour).">

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

+                                             Hour

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditlog-logrotationsynchour"

+                                                 type="number"

+                                                 min="0"

+                                                 max="23"

+                                                 value={this.state['nsslapd-auditlog-logrotationsynchour']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="The minute within the hour to rotate the log (nsslapd-auditlog-logrotationsyncmin).">

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

+                                             Minute

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditlog-logrotationsyncmin"

+                                                 type="number"

+                                                 min="0"

+                                                 max="59"

+                                                 value={this.state['nsslapd-auditlog-logrotationsyncmin']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Button

+                                         disabled={this.state.saveRotationDisabled}

+                                         bsStyle="primary"

+                                         className="ds-margin-top-med"

+                                         onClick={() => {

+                                             this.saveConfig("rotation");

+                                         }}

+                                     >

+                                         Save Rotation Settings

+                                     </Button>

+                                 </Form>

+                             </TabPane>

+                         </TabContent>

+ 

+                         <TabContent className="ds-margin-top-lg">

+                             <TabPane eventKey={3}>

+                                 <Form horizontal>

+                                     <Row className="ds-margin-top-xlg" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-auditlog-logmaxdiskspace).">

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

+                                             Total Log Archive Exceeds (in MB)

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditlog-logmaxdiskspace"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-auditlog-logmaxdiskspace']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-auditlog-logminfreediskspace).">

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

+                                             Free Disk Space (in MB)

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditlog-logminfreediskspace"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-auditlog-logminfreediskspace']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-auditlog-logexpirationtime).">

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

+                                             Log File is Older Than...

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditlog-logexpirationtime"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-auditlog-logexpirationtime']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             />

+                                         </Col>

+                                         <Col sm={2}>

+                                             <select

+                                                 className="btn btn-default dropdown"

+                                                 id="nsslapd-auditlog-logexpirationtimeunit"

+                                                 value={this.state['nsslapd-auditlog-logexpirationtimeunit']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             >

+                                                 <option>day</option>

+                                                 <option>week</option>

+                                                 <option>month</option>

+                                             </select>

+                                         </Col>

+                                     </Row>

+                                     <Button

+                                         disabled={this.state.saveExpDisabled}

+                                         bsStyle="primary"

+                                         className="ds-margin-top-med"

+                                         onClick={() => {

+                                             this.saveConfig("exp");

+                                         }}

+                                     >

+                                         Save Deletion Settings

+                                     </Button>

+                                 </Form>

+                             </TabPane>

+                         </TabContent>

+                     </div>

+                 </TabContainer>

+             </div>;

+ 

+         if (this.state.loading || !this.state.loaded) {

+             body = <Spinner loading size="md" />;

+         }

+ 

+         return (

+             <div id="server-auditlog-page">

+                 <NotificationController

+                     notifications={this.state.notifications}

+                     removeNotificationAction={this.removeNotification}

+                 />

+                 <Row>

+                     <Col sm={5}>

+                         <ControlLabel className="ds-suffix-header ds-margin-top-lg">

+                             Audit Log Settings

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

+                                 type="fa" name="refresh" title="Refresh the Access Log settings"

+                                 onClick={this.reloadConfig}

+                                 disabled={this.state.loading}

+                             />

+                         </ControlLabel>

+                     </Col>

+                 </Row>

+                 {body}

+             </div>

+         );

+     }

+ }

+ 

+ // Property types and defaults

+ 

+ ServerAuditLog.propTypes = {

+     serverId: PropTypes.string,

+     attrs: PropTypes.object,

+ };

+ 

+ ServerAuditLog.defaultProps = {

+     serverId: "",

+     attrs: {},

+ };

@@ -0,0 +1,621 @@ 

+ import cockpit from "cockpit";

+ import React from "react";

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

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

+ import {

+     Button,

+     Checkbox,

+     Col,

+     ControlLabel,

+     Form,

+     FormControl,

+     Icon,

+     Nav,

+     NavItem,

+     Row,

+     Spinner,

+     TabContainer,

+     TabContent,

+     TabPane,

+ } from "patternfly-react";

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

+ import PropTypes from "prop-types";

+ 

+ const settings_attrs = [

+     'nsslapd-auditfaillog',

+     'nsslapd-auditfaillog-logging-enabled',

+ ];

+ 

+ const rotation_attrs = [

+     'nsslapd-auditfaillog-logrotationsync-enabled',

+     'nsslapd-auditfaillog-logrotationsynchour',

+     'nsslapd-auditfaillog-logrotationsyncmin',

+     'nsslapd-auditfaillog-logrotationtime',

+     'nsslapd-auditfaillog-logrotationtimeunit',

+     'nsslapd-auditfaillog-maxlogsize',

+     'nsslapd-auditfaillog-maxlogsperdir',

+ ];

+ 

+ const exp_attrs = [

+     'nsslapd-auditfaillog-logexpirationtime',

+     'nsslapd-auditfaillog-logexpirationtimeunit',

+     'nsslapd-auditfaillog-logmaxdiskspace',

+     'nsslapd-auditfaillog-logminfreediskspace',

+ ];

+ 

+ export class ServerAuditFailLog extends React.Component {

+     constructor(props) {

+         super(props);

+         this.state = {

+             loading: false,

+             loaded: false,

+             activeKey: 1,

+             notifications: [],

+             saveSettingsDisabled: true,

+             saveRotationDisabled: true,

+             saveExpDisabled: true,

+             attrs: this.props.attrs,

+         };

+ 

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

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

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

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

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

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

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

+     }

+ 

+     componentWillMount() {

+         // Loading config

+         if (!this.state.loaded) {

+             this.loadConfig();

+         }

+     }

+ 

+     componentDidMount() {

+         this.props.enableTree();

+     }

+ 

+     handleNavSelect(key) {

+         this.setState({ activeKey: key });

+     }

+ 

+     addNotification(type, message, timerdelay, persistent) {

+         this.setState(prevState => ({

+             notifications: [

+                 ...prevState.notifications,

+                 {

+                     key: prevState.notifications.length + 1,

+                     type: type,

+                     persistent: persistent,

+                     timerdelay: timerdelay,

+                     message: message,

+                 }

+             ]

+         }));

+     }

+ 

+     removeNotification(notificationToRemove) {

+         this.setState({

+             notifications: this.state.notifications.filter(

+                 notification => notificationToRemove.key !== notification.key

+             )

+         });

+     }

+ 

+     handleChange(e, nav_tab) {

+         let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;

+         let attr = e.target.id;

+         let disableSaveBtn = true;

+         let disableBtnName = "";

+         let config_attrs = [];

+         if (nav_tab == "settings") {

+             config_attrs = settings_attrs;

+             disableBtnName = "saveSettingsDisabled";

+         } else if (nav_tab == "rotation") {

+             config_attrs = rotation_attrs;

+             disableBtnName = "saveRotationDisabled";

+         } else {

+             config_attrs = exp_attrs;

+             disableBtnName = "saveExpDisabled";

+         }

+ 

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

+         for (let config_attr of config_attrs) {

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

+                 disableSaveBtn = false;

+                 break;

+             }

+         }

+ 

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

+         for (let config_attr of config_attrs) {

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

+                 disableSaveBtn = false;

+                 break;

+             }

+         }

+ 

+         this.setState({

+             [attr]: value,

+             [disableBtnName]: disableSaveBtn,

+         });

+     }

+ 

+     saveConfig(nav_tab) {

+         this.setState({

+             loading: true

+         });

+ 

+         let config_attrs = [];

+         if (nav_tab == "settings") {

+             config_attrs = settings_attrs;

+         } else if (nav_tab == "rotation") {

+             config_attrs = rotation_attrs;

+         } else {

+             config_attrs = exp_attrs;

+         }

+ 

+         let cmd = [

+             'dsconf', '-j', this.props.serverId, 'config', 'replace'

+         ];

+ 

+         for (let attr of config_attrs) {

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

+                 let val = this.state[attr];

+                 if (typeof val === "boolean") {

+                     if (val) {

+                         val = "on";

+                     } else {

+                         val = "off";

+                     }

+                 }

+                 cmd.push(attr + "=" + val);

+             }

+         }

+ 

+         log_cmd("saveConfig", "Saving audit log settings", cmd);

+         cockpit

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

+                 .done(content => {

+                     this.reloadConfig();

+                     this.setState({

+                         loading: false

+                     });

+                     this.addNotification(

+                         "success",

+                         "Successfully updated Audit Failure Log settings"

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.reloadConfig();

+                     this.setState({

+                         loading: false

+                     });

+                     this.addNotification(

+                         "error",

+                         `Error saving Audit Failure Log settings - ${errMsg.desc}`

+                     );

+                 });

+     }

+ 

+     reloadConfig() {

+         this.setState({

+             loading: true,

+         });

+         let cmd = [

+             "dsconf", "-j", this.props.serverId, "config", "get"

+         ];

+         log_cmd("loadConfig", "load Audit Failure Log configuration", cmd);

+         cockpit

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

+                 .done(content => {

+                     let config = JSON.parse(content);

+                     let attrs = config.attrs;

+                     let enabled = false;

+ 

+                     if (attrs['nsslapd-auditfaillog-logging-enabled'][0] == "on") {

+                         enabled = true;

+                     }

+ 

+                     this.setState(() => (

+                         {

+                             loading: false,

+                             loaded: true,

+                             saveSettingsDisabled: true,

+                             saveRotationDisabled: true,

+                             saveExpDisabled: true,

+                             'nsslapd-auditfaillog': attrs['nsslapd-auditfaillog'][0],

+                             'nsslapd-auditfaillog-logexpirationtime': attrs['nsslapd-auditfaillog-logexpirationtime'][0],

+                             'nsslapd-auditfaillog-logexpirationtimeunit': attrs['nsslapd-auditfaillog-logexpirationtimeunit'][0],

+                             'nsslapd-auditfaillog-logging-enabled': enabled,

+                             'nsslapd-auditfaillog-logmaxdiskspace': attrs['nsslapd-auditfaillog-logmaxdiskspace'][0],

+                             'nsslapd-auditfaillog-logminfreediskspace': attrs['nsslapd-auditfaillog-logminfreediskspace'][0],

+                             'nsslapd-auditfaillog-logrotationsync-enabled': attrs['nsslapd-auditfaillog-logrotationsync-enabled'][0],

+                             'nsslapd-auditfaillog-logrotationsynchour': attrs['nsslapd-auditfaillog-logrotationsynchour'][0],

+                             'nsslapd-auditfaillog-logrotationsyncmin': attrs['nsslapd-auditfaillog-logrotationsyncmin'][0],

+                             'nsslapd-auditfaillog-logrotationtime': attrs['nsslapd-auditfaillog-logrotationtime'][0],

+                             'nsslapd-auditfaillog-logrotationtimeunit': attrs['nsslapd-auditfaillog-logrotationtimeunit'][0],

+                             'nsslapd-auditfaillog-maxlogsize': attrs['nsslapd-auditfaillog-maxlogsize'][0],

+                             'nsslapd-auditfaillog-maxlogsperdir': attrs['nsslapd-auditfaillog-maxlogsperdir'][0],

+                             // Record original values

+                             '_nsslapd-auditfaillog': attrs['nsslapd-auditfaillog'][0],

+                             '_nsslapd-auditfaillog-logexpirationtime': attrs['nsslapd-auditfaillog-logexpirationtime'][0],

+                             '_nsslapd-auditfaillog-logexpirationtimeunit': attrs['nsslapd-auditfaillog-logexpirationtimeunit'][0],

+                             '_nsslapd-auditfaillog-logging-enabled': enabled,

+                             '_nsslapd-auditfaillog-logmaxdiskspace': attrs['nsslapd-auditfaillog-logmaxdiskspace'][0],

+                             '_nsslapd-auditfaillog-logminfreediskspace': attrs['nsslapd-auditfaillog-logminfreediskspace'][0],

+                             '_nsslapd-auditfaillog-logrotationsync-enabled': attrs['nsslapd-auditfaillog-logrotationsync-enabled'][0],

+                             '_nsslapd-auditfaillog-logrotationsynchour': attrs['nsslapd-auditfaillog-logrotationsynchour'][0],

+                             '_nsslapd-auditfaillog-logrotationsyncmin': attrs['nsslapd-auditfaillog-logrotationsyncmin'][0],

+                             '_nsslapd-auditfaillog-logrotationtime': attrs['nsslapd-auditfaillog-logrotationtime'][0],

+                             '_nsslapd-auditfaillog-logrotationtimeunit': attrs['nsslapd-auditfaillog-logrotationtimeunit'][0],

+                             '_nsslapd-auditfaillog-maxlogsize': attrs['nsslapd-auditfaillog-maxlogsize'][0],

+                             '_nsslapd-auditfaillog-maxlogsperdir': attrs['nsslapd-auditfaillog-maxlogsperdir'][0],

+                         })

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.addNotification(

+                         "error",

+                         `Error loading Audit Failure Log configuration - ${errMsg.desc}`

+                     );

+                     this.setState({

+                         loading: false,

+                     });

+                 });

+     }

+ 

+     loadConfig() {

+         let attrs = this.state.attrs;

+         let enabled = false;

+ 

+         if (attrs['nsslapd-auditfaillog-logging-enabled'][0] == "on") {

+             enabled = true;

+         }

+ 

+         this.setState({

+             loaded: true,

+             saveSettingsDisabled: true,

+             saveRotationDisabled: true,

+             saveExpDisabled: true,

+             'nsslapd-auditfaillog': attrs['nsslapd-auditfaillog'][0],

+             'nsslapd-auditfaillog-logexpirationtime': attrs['nsslapd-auditfaillog-logexpirationtime'][0],

+             'nsslapd-auditfaillog-logexpirationtimeunit': attrs['nsslapd-auditfaillog-logexpirationtimeunit'][0],

+             'nsslapd-auditfaillog-logging-enabled': enabled,

+             'nsslapd-auditfaillog-logmaxdiskspace': attrs['nsslapd-auditfaillog-logmaxdiskspace'][0],

+             'nsslapd-auditfaillog-logminfreediskspace': attrs['nsslapd-auditfaillog-logminfreediskspace'][0],

+             'nsslapd-auditfaillog-logrotationsync-enabled': attrs['nsslapd-auditfaillog-logrotationsync-enabled'][0],

+             'nsslapd-auditfaillog-logrotationsynchour': attrs['nsslapd-auditfaillog-logrotationsynchour'][0],

+             'nsslapd-auditfaillog-logrotationsyncmin': attrs['nsslapd-auditfaillog-logrotationsyncmin'][0],

+             'nsslapd-auditfaillog-logrotationtime': attrs['nsslapd-auditfaillog-logrotationtime'][0],

+             'nsslapd-auditfaillog-logrotationtimeunit': attrs['nsslapd-auditfaillog-logrotationtimeunit'][0],

+             'nsslapd-auditfaillog-maxlogsize': attrs['nsslapd-auditfaillog-maxlogsize'][0],

+             'nsslapd-auditfaillog-maxlogsperdir': attrs['nsslapd-auditfaillog-maxlogsperdir'][0],

+             // Record original values

+             '_nsslapd-auditfaillog': attrs['nsslapd-auditfaillog'][0],

+             '_nsslapd-auditfaillog-logexpirationtime': attrs['nsslapd-auditfaillog-logexpirationtime'][0],

+             '_nsslapd-auditfaillog-logexpirationtimeunit': attrs['nsslapd-auditfaillog-logexpirationtimeunit'][0],

+             '_nsslapd-auditfaillog-logging-enabled': enabled,

+             '_nsslapd-auditfaillog-logmaxdiskspace': attrs['nsslapd-auditfaillog-logmaxdiskspace'][0],

+             '_nsslapd-auditfaillog-logminfreediskspace': attrs['nsslapd-auditfaillog-logminfreediskspace'][0],

+             '_nsslapd-auditfaillog-logrotationsync-enabled': attrs['nsslapd-auditfaillog-logrotationsync-enabled'][0],

+             '_nsslapd-auditfaillog-logrotationsynchour': attrs['nsslapd-auditfaillog-logrotationsynchour'][0],

+             '_nsslapd-auditfaillog-logrotationsyncmin': attrs['nsslapd-auditfaillog-logrotationsyncmin'][0],

+             '_nsslapd-auditfaillog-logrotationtime': attrs['nsslapd-auditfaillog-logrotationtime'][0],

+             '_nsslapd-auditfaillog-logrotationtimeunit': attrs['nsslapd-auditfaillog-logrotationtimeunit'][0],

+             '_nsslapd-auditfaillog-maxlogsize': attrs['nsslapd-auditfaillog-maxlogsize'][0],

+             '_nsslapd-auditfaillog-maxlogsperdir': attrs['nsslapd-auditfaillog-maxlogsperdir'][0],

+         });

+     }

+ 

+     render() {

+         let body =

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

+                 <TabContainer id="auditfail-log-settings" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>

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

+                         <Nav bsClass="nav nav-tabs nav-tabs-pf">

+                             <NavItem eventKey={1}>

+                                 <div dangerouslySetInnerHTML={{__html: 'Settings'}} />

+                             </NavItem>

+                             <NavItem eventKey={2}>

+                                 <div dangerouslySetInnerHTML={{__html: 'Rotation Policy'}} />

+                             </NavItem>

+                             <NavItem eventKey={3}>

+                                 <div dangerouslySetInnerHTML={{__html: 'Deletion Policy'}} />

+                             </NavItem>

+                         </Nav>

+ 

+                         <TabContent className="ds-margin-top-lg">

+                             <TabPane eventKey={1}>

+                                 <Form>

+                                     <Row className="ds-margin-top" title="Enable access logging (nsslapd-auditfaillog-logging-enabled).">

+                                         <Col sm={3}>

+                                             <Checkbox

+                                                 id="nsslapd-auditfaillog-logging-enabled"

+                                                 defaultChecked={this.state['nsslapd-auditfaillog-logging-enabled']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "settings");

+                                                 }}

+                                             >

+                                                 Enable Audit Failure Logging

+                                             </Checkbox>

+                                         </Col>

+                                     </Row>

+                                     <div className="ds-margin-left">

+                                         <Row className="ds-margin-top" title="Enable access logging (nsslapd-auditfaillog).">

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

+                                                 Audit Failure Log Location

+                                             </Col>

+                                             <Col sm={6}>

+                                                 <FormControl

+                                                     id="nsslapd-auditfaillog"

+                                                     type="text"

+                                                     value={this.state['nsslapd-auditfaillog']}

+                                                     onChange={(e) => {

+                                                         this.handleChange(e, "settings");

+                                                     }}

+                                                 />

+                                             </Col>

+                                         </Row>

+                                     </div>

+                                     <Button

+                                         disabled={this.state.saveSettingsDisabled}

+                                         bsStyle="primary"

+                                         className="ds-margin-top-med"

+                                         onClick={() => {

+                                             this.saveConfig("settings");

+                                         }}

+                                     >

+                                         Save Settings

+                                     </Button>

+                                 </Form>

+                             </TabPane>

+                         </TabContent>

+ 

+                         <TabContent className="ds-margin-top-lg">

+                             <TabPane eventKey={2}>

+                                 <Form horizontal>

+                                     <Row className="ds-margin-top-xlg" title="The maximum number of logs that are archived (nsslapd-auditfaillog-maxlogsperdir).">

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

+                                             Maximum Number Of Logs

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditfaillog-maxlogsperdir"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-auditfaillog-maxlogsperdir']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top-lg" title="The maximum size of each log file in megabytes (nsslapd-auditfaillog-maxlogsize).">

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

+                                             Maximum Log Size (in MB)

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditfaillog-maxlogsize"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-auditfaillog-maxlogsize']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <hr />

+                                     <Row className="ds-margin-top" title="Rotate the log based this number of time units (nsslapd-auditfaillog-logrotationtime).">

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

+                                             Create New Log Every...

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditfaillog-logrotationtime"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-auditfaillog-logrotationtime']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                         <Col sm={2}>

+                                             <select

+                                                 className="btn btn-default dropdown"

+                                                 id="nsslapd-auditfaillog-logrotationtimeunit"

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                                 value={this.state['nsslapd-auditfaillog-logrotationtimeunit']}

+                                             >

+                                                 <option>minute</option>

+                                                 <option>hour</option>

+                                                 <option>day</option>

+                                                 <option>week</option>

+                                                 <option>month</option>

+                                             </select>

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="The hour whenthe log should be rotated (nsslapd-auditfaillog-logrotationsynchour).">

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

+                                             Hour

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditfaillog-logrotationsynchour"

+                                                 type="number"

+                                                 min="0"

+                                                 max="23"

+                                                 value={this.state['nsslapd-auditfaillog-logrotationsynchour']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="The minute within the hour to rotate the log (nsslapd-auditfaillog-logrotationsyncmin).">

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

+                                             Minute

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditfaillog-logrotationsyncmin"

+                                                 type="number"

+                                                 min="0"

+                                                 max="59"

+                                                 value={this.state['nsslapd-auditfaillog-logrotationsyncmin']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Button

+                                         disabled={this.state.saveRotationDisabled}

+                                         bsStyle="primary"

+                                         className="ds-margin-top-med"

+                                         onClick={() => {

+                                             this.saveConfig("rotation");

+                                         }}

+                                     >

+                                         Save Rotation Settings

+                                     </Button>

+                                 </Form>

+                             </TabPane>

+                         </TabContent>

+ 

+                         <TabContent className="ds-margin-top-lg">

+                             <TabPane eventKey={3}>

+                                 <Form horizontal>

+                                     <Row className="ds-margin-top-xlg" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-auditfaillog-logmaxdiskspace).">

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

+                                             Total Log Archive Exceeds (in MB)

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditfaillog-logmaxdiskspace"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-auditfaillog-logmaxdiskspace']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-auditfaillog-logminfreediskspace).">

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

+                                             Free Disk Space (in MB)

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditfaillog-logminfreediskspace"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-auditfaillog-logminfreediskspace']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-auditfaillog-logexpirationtime).">

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

+                                             Log File is Older Than...

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-auditfaillog-logexpirationtime"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-auditfaillog-logexpirationtime']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             />

+                                         </Col>

+                                         <Col sm={2}>

+                                             <select

+                                                 className="btn btn-default dropdown"

+                                                 id="nsslapd-auditfaillog-logexpirationtimeunit"

+                                                 value={this.state['nsslapd-auditfaillog-logexpirationtimeunit']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             >

+                                                 <option>day</option>

+                                                 <option>week</option>

+                                                 <option>month</option>

+                                             </select>

+                                         </Col>

+                                     </Row>

+                                     <Button

+                                         disabled={this.state.saveExpDisabled}

+                                         bsStyle="primary"

+                                         className="ds-margin-top-med"

+                                         onClick={() => {

+                                             this.saveConfig("exp");

+                                         }}

+                                     >

+                                         Save Deletion Settings

+                                     </Button>

+                                 </Form>

+                             </TabPane>

+                         </TabContent>

+                     </div>

+                 </TabContainer>

+             </div>;

+ 

+         if (this.state.loading || !this.state.loaded) {

+             body = <Spinner loading size="md" />;

+         }

+ 

+         return (

+             <div id="server-auditfaillog-page">

+                 <NotificationController

+                     notifications={this.state.notifications}

+                     removeNotificationAction={this.removeNotification}

+                 />

+                 <Row>

+                     <Col sm={5}>

+                         <ControlLabel className="ds-suffix-header ds-margin-top-lg">

+                             Audit Failure Log Settings

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

+                                 type="fa" name="refresh" title="Refresh the Access Log settings"

+                                 onClick={this.reloadConfig}

+                                 disabled={this.state.loading}

+                             />

+                         </ControlLabel>

+                     </Col>

+                 </Row>

+                 {body}

+             </div>

+         );

+     }

+ }

+ 

+ // Property types and defaults

+ 

+ ServerAuditFailLog.propTypes = {

+     serverId: PropTypes.string,

+     attrs: PropTypes.object,

+ };

+ 

+ ServerAuditFailLog.defaultProps = {

+     serverId: "",

+     attrs: {},

+ };

@@ -0,0 +1,956 @@ 

+ import cockpit from "cockpit";

+ import React from "react";

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

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

+ import {

+     Button,

+     Checkbox,

+     Col,

+     ControlLabel,

+     Form,

+     FormControl,

+     Icon,

+     Nav,

+     NavItem,

+     Row,

+     Spinner,

+     TabContainer,

+     TabContent,

+     TabPane,

+ } from "patternfly-react";

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

+ import PropTypes from "prop-types";

+ 

+ const errorlog_levels = [

+     1, 2, 4, 8, 16, 32, 64, 128, 2048,

+     4096, 8192, 32768, 65536, 262144,

+ ];

+ 

+ const settings_attrs = [

+     'nsslapd-errorlog',

+     'nsslapd-errorlog-level',

+     'nsslapd-errorlog-logging-enabled',

+     'errorlevel-1',

+     'errorlevel-2',

+     'errorlevel-4',

+     'errorlevel-8',

+     'errorlevel-16',

+     'errorlevel-32',

+     'errorlevel-64',

+     'errorlevel-128',

+     'errorlevel-2048',

+     'errorlevel-4096',

+     'errorlevel-8192',

+     'errorlevel-32768',

+     'errorlevel-65536',

+     'errorlevel-262144',

+ ];

+ 

+ const rotation_attrs = [

+     'nsslapd-errorlog-logrotationsync-enabled',

+     'nsslapd-errorlog-logrotationsynchour',

+     'nsslapd-errorlog-logrotationsyncmin',

+     'nsslapd-errorlog-logrotationtime',

+     'nsslapd-errorlog-logrotationtimeunit',

+     'nsslapd-errorlog-maxlogsize',

+     'nsslapd-errorlog-maxlogsperdir',

+ ];

+ 

+ const exp_attrs = [

+     'nsslapd-errorlog-logexpirationtime',

+     'nsslapd-errorlog-logexpirationtimeunit',

+     'nsslapd-errorlog-logmaxdiskspace',

+     'nsslapd-errorlog-logminfreediskspace',

+ ];

+ 

+ export class ServerErrorLog extends React.Component {

+     constructor(props) {

+         super(props);

+         this.state = {

+             loading: false,

+             loaded: false,

+             activeKey: 1,

+             notifications: [],

+             saveSettingsDisabled: true,

+             saveRotationDisabled: true,

+             saveExpDisabled: true,

+             attrs: this.props.attrs,

+         };

+ 

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

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

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

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

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

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

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

+     }

+ 

+     componentWillMount() {

+         // Loading config

+         if (!this.state.loaded) {

+             this.loadConfig();

+         }

+     }

+ 

+     componentDidMount() {

+         this.props.enableTree();

+     }

+ 

+     handleNavSelect(key) {

+         this.setState({ activeKey: key });

+     }

+ 

+     addNotification(type, message, timerdelay, persistent) {

+         this.setState(prevState => ({

+             notifications: [

+                 ...prevState.notifications,

+                 {

+                     key: prevState.notifications.length + 1,

+                     type: type,

+                     persistent: persistent,

+                     timerdelay: timerdelay,

+                     message: message,

+                 }

+             ]

+         }));

+     }

+ 

+     removeNotification(notificationToRemove) {

+         this.setState({

+             notifications: this.state.notifications.filter(

+                 notification => notificationToRemove.key !== notification.key

+             )

+         });

+     }

+ 

+     handleChange(e, nav_tab) {

+         let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;

+         let attr = e.target.id;

+         let disableSaveBtn = true;

+         let disableBtnName = "";

+         let config_attrs = [];

+         if (nav_tab == "settings") {

+             config_attrs = settings_attrs;

+             disableBtnName = "saveSettingsDisabled";

+         } else if (nav_tab == "rotation") {

+             config_attrs = rotation_attrs;

+             disableBtnName = "saveRotationDisabled";

+         } else {

+             config_attrs = exp_attrs;

+             disableBtnName = "saveExpDisabled";

+         }

+ 

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

+         for (let config_attr of config_attrs) {

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

+                 disableSaveBtn = false;

+                 break;

+             }

+         }

+ 

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

+         for (let config_attr of config_attrs) {

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

+                 disableSaveBtn = false;

+                 break;

+             }

+         }

+ 

+         this.setState({

+             [attr]: value,

+             [disableBtnName]: disableSaveBtn,

+         });

+     }

+ 

+     saveConfig(nav_tab) {

+         let level_change = false;

+         let new_level = 0;

+         this.setState({

+             loading: true

+         });

+ 

+         let config_attrs = [];

+         if (nav_tab == "settings") {

+             config_attrs = settings_attrs;

+         } else if (nav_tab == "rotation") {

+             config_attrs = rotation_attrs;

+         } else {

+             config_attrs = exp_attrs;

+         }

+ 

+         let cmd = [

+             'dsconf', '-j', this.props.serverId, 'config', 'replace'

+         ];

+ 

+         for (let attr of config_attrs) {

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

+                 if (attr.startsWith("errorlevel-")) {

+                     level_change = true;

+                     continue;

+                 }

+                 let val = this.state[attr];

+                 if (typeof val === "boolean") {

+                     if (val) {

+                         val = "on";

+                     } else {

+                         val = "off";

+                     }

+                 }

+                 cmd.push(attr + "=" + val);

+             }

+         }

+ 

+         if (level_change) {

+             for (let level of errorlog_levels) {

+                 if (this.state['errorlevel-' + level.toString()]) {

+                     new_level += level;

+                 }

+             }

+             cmd.push("nsslapd-errorlog-level" + "=" + new_level.toString());

+         }

+ 

+         log_cmd("saveConfig", "Saving error log settings", cmd);

+         cockpit

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

+                 .done(content => {

+                     this.reloadConfig();

+                     this.setState({

+                         loading: false

+                     });

+                     this.addNotification(

+                         "success",

+                         "Successfully updated Error Log settings"

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.reloadConfig();

+                     this.setState({

+                         loading: false

+                     });

+                     this.addNotification(

+                         "error",

+                         `Error saving Error Log settings - ${errMsg.desc}`

+                     );

+                 });

+     }

+ 

+     loadConfig() {

+         let attrs = this.state.attrs;

+         let enabled = false;

+         let level_val = parseInt(attrs['nsslapd-errorlog-level'][0]);

+         let loglevel = {};

+ 

+         if (attrs['nsslapd-errorlog-logging-enabled'][0] == "on") {

+             enabled = true;

+         }

+         for (let level of errorlog_levels) {

+             if (level & level_val) {

+                 loglevel[level.toString()] = true;

+             } else {

+                 loglevel[level.toString()] = false;

+             }

+         }

+ 

+         this.setState({

+             loading: false,

+             loaded: true,

+             saveSettingsDisabled: true,

+             saveRotationDisabled: true,

+             saveExpDisabled: true,

+             'nsslapd-errorlog': attrs['nsslapd-errorlog'][0],

+             'nsslapd-errorlog-level': attrs['nsslapd-errorlog-level'][0],

+             'errorlevel-1': loglevel['1'],

+             'errorlevel-2': loglevel['2'],

+             'errorlevel-4': loglevel['4'],

+             'errorlevel-8': loglevel['8'],

+             'errorlevel-16': loglevel['16'],

+             'errorlevel-32': loglevel['32'],

+             'errorlevel-64': loglevel['64'],

+             'errorlevel-128': loglevel['128'],

+             'errorlevel-2048': loglevel['2048'],

+             'errorlevel-4096': loglevel['4096'],

+             'errorlevel-8192': loglevel['8192'],

+             'errorlevel-32768': loglevel['32768'],

+             'errorlevel-65536': loglevel['65536'],

+             'errorlevel-262144': loglevel['262144'],

+             'nsslapd-errorlog-logexpirationtime': attrs['nsslapd-errorlog-logexpirationtime'][0],

+             'nsslapd-errorlog-logexpirationtimeunit': attrs['nsslapd-errorlog-logexpirationtimeunit'][0],

+             'nsslapd-errorlog-logging-enabled': enabled,

+             'nsslapd-errorlog-logmaxdiskspace': attrs['nsslapd-errorlog-logmaxdiskspace'][0],

+             'nsslapd-errorlog-logminfreediskspace': attrs['nsslapd-errorlog-logminfreediskspace'][0],

+             'nsslapd-errorlog-logrotationsync-enabled': attrs['nsslapd-errorlog-logrotationsync-enabled'][0],

+             'nsslapd-errorlog-logrotationsynchour': attrs['nsslapd-errorlog-logrotationsynchour'][0],

+             'nsslapd-errorlog-logrotationsyncmin': attrs['nsslapd-errorlog-logrotationsyncmin'][0],

+             'nsslapd-errorlog-logrotationtime': attrs['nsslapd-errorlog-logrotationtime'][0],

+             'nsslapd-errorlog-logrotationtimeunit': attrs['nsslapd-errorlog-logrotationtimeunit'][0],

+             'nsslapd-errorlog-maxlogsize': attrs['nsslapd-errorlog-maxlogsize'][0],

+             'nsslapd-errorlog-maxlogsperdir': attrs['nsslapd-errorlog-maxlogsperdir'][0],

+             // Record original values

+             '_nsslapd-errorlog': attrs['nsslapd-errorlog'][0],

+             '_nsslapd-errorlog-level': attrs['nsslapd-errorlog-level'][0],

+             '_errorlevel-1': loglevel['1'],

+             '_errorlevel-2': loglevel['2'],

+             '_errorlevel-4': loglevel['4'],

+             '_errorlevel-8': loglevel['8'],

+             '_errorlevel-16': loglevel['16'],

+             '_errorlevel-32': loglevel['32'],

+             '_errorlevel-64': loglevel['64'],

+             '_errorlevel-128': loglevel['128'],

+             '_errorlevel-2048': loglevel['2048'],

+             '_errorlevel-4096': loglevel['4096'],

+             '_errorlevel-8192': loglevel['8192'],

+             '_errorlevel-32768': loglevel['32768'],

+             '_errorlevel-65536': loglevel['65536'],

+             '_errorlevel-262144': loglevel['262144'],

+             '_nsslapd-errorlog-logexpirationtime': attrs['nsslapd-errorlog-logexpirationtime'][0],

+             '_nsslapd-errorlog-logexpirationtimeunit': attrs['nsslapd-errorlog-logexpirationtimeunit'][0],

+             '_nsslapd-errorlog-logging-enabled': enabled,

+             '_nsslapd-errorlog-logmaxdiskspace': attrs['nsslapd-errorlog-logmaxdiskspace'][0],

+             '_nsslapd-errorlog-logminfreediskspace': attrs['nsslapd-errorlog-logminfreediskspace'][0],

+             '_nsslapd-errorlog-logrotationsync-enabled': attrs['nsslapd-errorlog-logrotationsync-enabled'][0],

+             '_nsslapd-errorlog-logrotationsynchour': attrs['nsslapd-errorlog-logrotationsynchour'][0],

+             '_nsslapd-errorlog-logrotationsyncmin': attrs['nsslapd-errorlog-logrotationsyncmin'][0],

+             '_nsslapd-errorlog-logrotationtime': attrs['nsslapd-errorlog-logrotationtime'][0],

+             '_nsslapd-errorlog-logrotationtimeunit': attrs['nsslapd-errorlog-logrotationtimeunit'][0],

+             '_nsslapd-errorlog-maxlogsize': attrs['nsslapd-errorlog-maxlogsize'][0],

+             '_nsslapd-errorlog-maxlogsperdir': attrs['nsslapd-errorlog-maxlogsperdir'][0],

+         });

+     }

+ 

+     reloadConfig() {

+         this.setState({

+             loading: true,

+         });

+         let cmd = [

+             "dsconf", "-j", this.props.serverId, "config", "get"

+         ];

+         log_cmd("reloadConfig", "load Error Log configuration", cmd);

+         cockpit

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

+                 .done(content => {

+                     let config = JSON.parse(content);

+                     let attrs = config.attrs;

+                     let enabled = false;

+                     let level_val = parseInt(attrs['nsslapd-errorlog-level'][0]);

+                     let loglevel = {};

+ 

+                     if (attrs['nsslapd-errorlog-logging-enabled'][0] == "on") {

+                         enabled = true;

+                     }

+                     for (let level of errorlog_levels) {

+                         if (level & level_val) {

+                             loglevel[level.toString()] = true;

+                         } else {

+                             loglevel[level.toString()] = false;

+                         }

+                     }

+ 

+                     this.setState(() => (

+                         {

+                             loading: false,

+                             loaded: true,

+                             saveSettingsDisabled: true,

+                             saveRotationDisabled: true,

+                             saveExpDisabled: true,

+                             'nsslapd-errorlog': attrs['nsslapd-errorlog'][0],

+                             'nsslapd-errorlog-level': attrs['nsslapd-errorlog-level'][0],

+                             'errorlevel-1': loglevel['1'],

+                             'errorlevel-2': loglevel['2'],

+                             'errorlevel-4': loglevel['4'],

+                             'errorlevel-8': loglevel['8'],

+                             'errorlevel-16': loglevel['16'],

+                             'errorlevel-32': loglevel['32'],

+                             'errorlevel-64': loglevel['64'],

+                             'errorlevel-128': loglevel['128'],

+                             'errorlevel-2048': loglevel['2048'],

+                             'errorlevel-4096': loglevel['4096'],

+                             'errorlevel-8192': loglevel['8192'],

+                             'errorlevel-32768': loglevel['32768'],

+                             'errorlevel-65536': loglevel['65536'],

+                             'errorlevel-262144': loglevel['262144'],

+                             'nsslapd-errorlog-logexpirationtime': attrs['nsslapd-errorlog-logexpirationtime'][0],

+                             'nsslapd-errorlog-logexpirationtimeunit': attrs['nsslapd-errorlog-logexpirationtimeunit'][0],

+                             'nsslapd-errorlog-logging-enabled': enabled,

+                             'nsslapd-errorlog-logmaxdiskspace': attrs['nsslapd-errorlog-logmaxdiskspace'][0],

+                             'nsslapd-errorlog-logminfreediskspace': attrs['nsslapd-errorlog-logminfreediskspace'][0],

+                             'nsslapd-errorlog-logrotationsync-enabled': attrs['nsslapd-errorlog-logrotationsync-enabled'][0],

+                             'nsslapd-errorlog-logrotationsynchour': attrs['nsslapd-errorlog-logrotationsynchour'][0],

+                             'nsslapd-errorlog-logrotationsyncmin': attrs['nsslapd-errorlog-logrotationsyncmin'][0],

+                             'nsslapd-errorlog-logrotationtime': attrs['nsslapd-errorlog-logrotationtime'][0],

+                             'nsslapd-errorlog-logrotationtimeunit': attrs['nsslapd-errorlog-logrotationtimeunit'][0],

+                             'nsslapd-errorlog-maxlogsize': attrs['nsslapd-errorlog-maxlogsize'][0],

+                             'nsslapd-errorlog-maxlogsperdir': attrs['nsslapd-errorlog-maxlogsperdir'][0],

+                             // Record original values

+                             '_nsslapd-errorlog': attrs['nsslapd-errorlog'][0],

+                             '_nsslapd-errorlog-level': attrs['nsslapd-errorlog-level'][0],

+                             '_errorlevel-1': loglevel['1'],

+                             '_errorlevel-2': loglevel['2'],

+                             '_errorlevel-4': loglevel['4'],

+                             '_errorlevel-8': loglevel['8'],

+                             '_errorlevel-16': loglevel['16'],

+                             '_errorlevel-32': loglevel['32'],

+                             '_errorlevel-64': loglevel['64'],

+                             '_errorlevel-128': loglevel['128'],

+                             '_errorlevel-2048': loglevel['2048'],

+                             '_errorlevel-4096': loglevel['4096'],

+                             '_errorlevel-8192': loglevel['8192'],

+                             '_errorlevel-32768': loglevel['32768'],

+                             '_errorlevel-65536': loglevel['65536'],

+                             '_errorlevel-262144': loglevel['262144'],

+                             '_nsslapd-errorlog-logexpirationtime': attrs['nsslapd-errorlog-logexpirationtime'][0],

+                             '_nsslapd-errorlog-logexpirationtimeunit': attrs['nsslapd-errorlog-logexpirationtimeunit'][0],

+                             '_nsslapd-errorlog-logging-enabled': enabled,

+                             '_nsslapd-errorlog-logmaxdiskspace': attrs['nsslapd-errorlog-logmaxdiskspace'][0],

+                             '_nsslapd-errorlog-logminfreediskspace': attrs['nsslapd-errorlog-logminfreediskspace'][0],

+                             '_nsslapd-errorlog-logrotationsync-enabled': attrs['nsslapd-errorlog-logrotationsync-enabled'][0],

+                             '_nsslapd-errorlog-logrotationsynchour': attrs['nsslapd-errorlog-logrotationsynchour'][0],

+                             '_nsslapd-errorlog-logrotationsyncmin': attrs['nsslapd-errorlog-logrotationsyncmin'][0],

+                             '_nsslapd-errorlog-logrotationtime': attrs['nsslapd-errorlog-logrotationtime'][0],

+                             '_nsslapd-errorlog-logrotationtimeunit': attrs['nsslapd-errorlog-logrotationtimeunit'][0],

+                             '_nsslapd-errorlog-maxlogsize': attrs['nsslapd-errorlog-maxlogsize'][0],

+                             '_nsslapd-errorlog-maxlogsperdir': attrs['nsslapd-errorlog-maxlogsperdir'][0],

+                         })

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.addNotification(

+                         "error",

+                         `Error loading Error Log configuration - ${errMsg.desc}`

+                     );

+                     this.setState({

+                         loading: false,

+                         loaded: true,

+                     });

+                 });

+     }

+ 

+     render() {

+         let body =

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

+                 <TabContainer id="error-log-settings" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>

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

+                         <Nav bsClass="nav nav-tabs nav-tabs-pf">

+                             <NavItem eventKey={1}>

+                                 <div dangerouslySetInnerHTML={{__html: 'Settings'}} />

+                             </NavItem>

+                             <NavItem eventKey={2}>

+                                 <div dangerouslySetInnerHTML={{__html: 'Rotation Policy'}} />

+                             </NavItem>

+                             <NavItem eventKey={3}>

+                                 <div dangerouslySetInnerHTML={{__html: 'Deletion Policy'}} />

+                             </NavItem>

+                         </Nav>

+ 

+                         <TabContent className="ds-margin-top-lg">

+                             <TabPane eventKey={1}>

+                                 <Form>

+                                     <Row className="ds-margin-top" title="Enable access logging (nsslapd-errorlog-logging-enabled).">

+                                         <Col sm={3}>

+                                             <Checkbox

+                                                 id="nsslapd-errorlog-logging-enabled"

+                                                 defaultChecked={this.state['nsslapd-errorlog-logging-enabled']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "settings");

+                                                 }}

+                                             >

+                                                 Enable Error Logging

+                                             </Checkbox>

+                                         </Col>

+                                     </Row>

+                                     <div className="ds-margin-left">

+                                         <Row className="ds-margin-top" title="Enable access logging (nsslapd-errorlog).">

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

+                                                 Error Log Location

+                                             </Col>

+                                             <Col sm={6}>

+                                                 <FormControl

+                                                     id="nsslapd-errorlog"

+                                                     type="text"

+                                                     value={this.state['nsslapd-errorlog']}

+                                                     onChange={(e) => {

+                                                         this.handleChange(e, "settings");

+                                                     }}

+                                                 />

+                                             </Col>

+                                         </Row>

+                                         <table className="table table-striped table-bordered table-hover ds-loglevel-table ds-margin-top-lg" id="errorlog-level-table">

+                                             <thead>

+                                                 <tr>

+                                                     <th className="ds-table-checkbox" />

+                                                     <th>Logging Level</th>

+                                                 </tr>

+                                             </thead>

+                                             <tbody>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-1"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-1']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Trace Function Calls

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-2"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-2']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Packet Handling

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-4"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-4']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Heavy Trace Output

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-8"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-8']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Connection Management

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-16"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-16']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Packets Sent & Received

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-32"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-32']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Search Filter Processing

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-64"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-64']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Config File Processing

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-128"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-128']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Access Control List Processing

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-256"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-2048']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Log Entry Parsing

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-4096"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-4096']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Housekeeping

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-8192"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-8192']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Replication

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-32768"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-32768']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Entry Cache

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-65536"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-65536']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Plugin

+                                                     </td>

+                                                 </tr>

+                                                 <tr>

+                                                     <td className="ds-table-checkbox">

+                                                         <input

+                                                             id="errorlevel-262144"

+                                                             onChange={(e) => {

+                                                                 this.handleChange(e, "settings");

+                                                             }}

+                                                             checked={this.state['errorlevel-262144']}

+                                                             type="checkbox"

+                                                         />

+                                                     </td>

+                                                     <td className="ds-left-align">

+                                                         Access Control Summary

+                                                     </td>

+                                                 </tr>

+                                             </tbody>

+                                         </table>

+                                     </div>

+                                     <Button

+                                         disabled={this.state.saveSettingsDisabled}

+                                         bsStyle="primary"

+                                         className="ds-margin-top-med"

+                                         onClick={() => {

+                                             this.saveConfig("settings");

+                                         }}

+                                     >

+                                         Save Settings

+                                     </Button>

+                                 </Form>

+                             </TabPane>

+                         </TabContent>

+ 

+                         <TabContent className="ds-margin-top-lg">

+                             <TabPane eventKey={2}>

+                                 <Form horizontal>

+                                     <Row className="ds-margin-top-xlg" title="The maximum number of logs that are archived (nsslapd-errorlog-maxlogsperdir).">

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

+                                             Maximum Number Of Logs

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-errorlog-maxlogsperdir"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-errorlog-maxlogsperdir']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top-lg" title="The maximum size of each log file in megabytes (nsslapd-errorlog-maxlogsize).">

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

+                                             Maximum Log Size (in MB)

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-errorlog-maxlogsize"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-errorlog-maxlogsize']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <hr />

+                                     <Row className="ds-margin-top" title="Rotate the log based this number of time units (nsslapd-errorlog-logrotationtime).">

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

+                                             Create New Log Every...

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-errorlog-logrotationtime"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-errorlog-logrotationtime']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                         <Col sm={2}>

+                                             <select

+                                                 className="btn btn-default dropdown"

+                                                 id="nsslapd-errorlog-logrotationtimeunit"

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                                 value={this.state['nsslapd-errorlog-logrotationtimeunit']}

+                                             >

+                                                 <option>minute</option>

+                                                 <option>hour</option>

+                                                 <option>day</option>

+                                                 <option>week</option>

+                                                 <option>month</option>

+                                             </select>

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="The hour whenthe log should be rotated (nsslapd-errorlog-logrotationsynchour).">

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

+                                             Hour

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-errorlog-logrotationsynchour"

+                                                 type="number"

+                                                 min="0"

+                                                 max="23"

+                                                 value={this.state['nsslapd-errorlog-logrotationsynchour']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="The minute within the hour to rotate the log (nsslapd-errorlog-logrotationsyncmin).">

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

+                                             Minute

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-errorlog-logrotationsyncmin"

+                                                 type="number"

+                                                 min="0"

+                                                 max="59"

+                                                 value={this.state['nsslapd-errorlog-logrotationsyncmin']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "rotation");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Button

+                                         disabled={this.state.saveRotationDisabled}

+                                         bsStyle="primary"

+                                         className="ds-margin-top-med"

+                                         onClick={() => {

+                                             this.saveConfig("rotation");

+                                         }}

+                                     >

+                                         Save Rotation Settings

+                                     </Button>

+                                 </Form>

+                             </TabPane>

+                         </TabContent>

+ 

+                         <TabContent className="ds-margin-top-lg">

+                             <TabPane eventKey={3}>

+                                 <Form horizontal>

+                                     <Row className="ds-margin-top-xlg" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-errorlog-logmaxdiskspace).">

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

+                                             Total Log Archive Exceeds (in MB)

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-errorlog-logmaxdiskspace"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-errorlog-logmaxdiskspace']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-errorlog-logminfreediskspace).">

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

+                                             Free Disk Space (in MB)

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-errorlog-logminfreediskspace"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-errorlog-logminfreediskspace']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             />

+                                         </Col>

+                                     </Row>

+                                     <Row className="ds-margin-top" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-errorlog-logexpirationtime).">

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

+                                             Log File is Older Than...

+                                         </Col>

+                                         <Col sm={2}>

+                                             <FormControl

+                                                 id="nsslapd-errorlog-logexpirationtime"

+                                                 type="number"

+                                                 min="1"

+                                                 max="2147483647"

+                                                 value={this.state['nsslapd-errorlog-logexpirationtime']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             />

+                                         </Col>

+                                         <Col sm={2}>

+                                             <select

+                                                 className="btn btn-default dropdown"

+                                                 id="nsslapd-errorlog-logexpirationtimeunit"

+                                                 value={this.state['nsslapd-errorlog-logexpirationtimeunit']}

+                                                 onChange={(e) => {

+                                                     this.handleChange(e, "exp");

+                                                 }}

+                                             >

+                                                 <option>day</option>

+                                                 <option>week</option>

+                                                 <option>month</option>

+                                             </select>

+                                         </Col>

+                                     </Row>

+                                     <Button

+                                         disabled={this.state.saveExpDisabled}

+                                         bsStyle="primary"

+                                         className="ds-margin-top-med"

+                                         onClick={() => {

+                                             this.saveConfig("exp");

+                                         }}

+                                     >

+                                         Save Deletion Settings

+                                     </Button>

+                                 </Form>

+                             </TabPane>

+                         </TabContent>

+                     </div>

+                 </TabContainer>

+             </div>;

+ 

+         if (this.state.loading || !this.state.loaded) {

+             body = <Spinner loading size="md" />;

+         }

+ 

+         return (

+             <div id="server-errorlog-page">

+                 <NotificationController

+                     notifications={this.state.notifications}

+                     removeNotificationAction={this.removeNotification}

+                 />

+                 <Row>

+                     <Col sm={5}>

+                         <ControlLabel className="ds-suffix-header ds-margin-top-lg">

+                             Error Log Settings

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

+                                 type="fa" name="refresh" title="Refresh the Access Log settings"

+                                 onClick={this.reloadConfig}

+                                 disabled={this.state.loading}

+                             />

+                         </ControlLabel>

+                     </Col>

+                 </Row>

+                 {body}

+             </div>

+         );

+     }

+ }

+ 

+ // Property types and defaults

+ 

+ ServerErrorLog.propTypes = {

+     serverId: PropTypes.string,

+     attrs: PropTypes.object,

+ };

+ 

+ ServerErrorLog.defaultProps = {

+     serverId: "",

+     attrs: {},

+ };

@@ -0,0 +1,331 @@ 

+ import cockpit from "cockpit";

+ import React from "react";

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

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

+ import {

+     Button,

+     Checkbox,

+     Col,

+     ControlLabel,

+     Form,

+     FormControl,

+     Icon,

+     Row,

+     Spinner,

+ } from "patternfly-react";

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

+ import PropTypes from "prop-types";

+ 

+ const ldapi_attrs = [

+     'nsslapd-ldapimaptoentries',

+     'nsslapd-ldapifilepath',

+     'nsslapd-ldapimaprootdn',

+     'nsslapd-ldapientrysearchbase',

+     'nsslapd-ldapigidnumbertype',

+     'nsslapd-ldapiuidnumbertype',

+ ];

+ 

+ export class ServerLDAPI extends React.Component {

+     constructor(props) {

+         super(props);

+         this.state = {

+             loading: false,

+             loaded: false,

+             notifications: [],

+             saveDisabled: true,

+             attrs: this.props.attrs,

+             // settings

+ 

+         };

+ 

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

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

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

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

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

+     }

+ 

+     componentWillMount() {

+         // Loading config

+         if (!this.state.loaded) {

+             this.loadConfig();

+         }

+     }

+ 

+     componentDidMount() {

+         this.props.enableTree();

+     }

+ 

+     addNotification(type, message, timerdelay, persistent) {

+         this.setState(prevState => ({

+             notifications: [

+                 ...prevState.notifications,

+                 {

+                     key: prevState.notifications.length + 1,

+                     type: type,

+                     persistent: persistent,

+                     timerdelay: timerdelay,

+                     message: message,

+                 }

+             ]

+         }));

+     }

+ 

+     removeNotification(notificationToRemove) {

+         this.setState({

+             notifications: this.state.notifications.filter(

+                 notification => notificationToRemove.key !== notification.key

+             )

+         });

+     }

+ 

+     handleChange(e) {

+         let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;

+         let attr = e.target.id;

+         let disableSaveBtn = true;

+ 

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

+         for (let ldapi_attr of ldapi_attrs) {

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

+                 disableSaveBtn = false;

+                 break;

+             }

+         }

+ 

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

+         for (let ldapi_attr of ldapi_attrs) {

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

+                 disableSaveBtn = false;

+                 break;

+             }

+         }

+ 

+         this.setState({

+             [attr]: value,

+             saveDisabled: disableSaveBtn,

+         });

+     }

+ 

+     loadConfig() {

+         let attrs = this.state.attrs;

+         let mapToEntries = false;

+ 

+         if ('nsslapd-ldapimaptoentries' in attrs) {

+             if (attrs['nsslapd-ldapimaptoentries'][0] == "on") {

+                 mapToEntries = true;

+             }

+         }

+         this.setState({

+             loading: false,

+             loaded: true,

+             saveDisabled: true,

+             'nsslapd-ldapimaptoentries': mapToEntries,

+             'nsslapd-ldapifilepath': attrs['nsslapd-ldapifilepath'][0],

+             'nsslapd-ldapimaprootdn': attrs['nsslapd-ldapimaprootdn'][0],

+             'nsslapd-ldapientrysearchbase': attrs['nsslapd-ldapientrysearchbase'][0],

+             'nsslapd-ldapigidnumbertype': attrs['nsslapd-ldapigidnumbertype'][0],

+             'nsslapd-ldapiuidnumbertype': attrs['nsslapd-ldapiuidnumbertype'][0],

+             // Record original values

+             '_nsslapd-ldapimaptoentries': mapToEntries,

+             '_nsslapd-ldapimaprootdn': attrs['nsslapd-ldapimaprootdn'][0],

+             '_nsslapd-ldapifilepath': attrs['nsslapd-ldapifilepath'][0],

+             '_nsslapd-ldapientrysearchbase': attrs['nsslapd-ldapientrysearchbase'][0],

+             '_nsslapd-ldapigidnumbertype': attrs['nsslapd-ldapigidnumbertype'][0],

+             '_nsslapd-ldapiuidnumbertype': attrs['nsslapd-ldapiuidnumbertype'][0],

+         });

+     }

+ 

+     saveConfig() {

+         this.setState({

+             loading: true

+         });

+ 

+         let cmd = [

+             'dsconf', '-j', this.props.serverId, 'config', 'replace'

+         ];

+ 

+         for (let attr of ldapi_attrs) {

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

+                 let val = this.state[attr];

+                 if (typeof val === "boolean") {

+                     if (val) {

+                         val = "on";

+                     } else {

+                         val = "off";

+                     }

+                 }

+                 cmd.push(attr + "=" + val);

+             }

+         }

+ 

+         log_cmd("saveConfig", "Saving LDAPI settings", cmd);

+         cockpit

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

+                 .done(content => {

+                     this.loadConfig();

+                     this.setState({

+                         loading: false

+                     });

+                     this.addNotification(

+                         "success",

+                         "Successfully updated LDAPI configuration"

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.loadConfig();

+                     this.setState({

+                         loading: false

+                     });

+                     this.addNotification(

+                         "error",

+                         `Error updating LDAPI configuration - ${errMsg.desc}`

+                     );

+                 });

+     }

+ 

+     render() {

+         let mapUserAttrs = "";

+ 

+         if (this.state['nsslapd-ldapimaptoentries']) {

+             mapUserAttrs =

+                 <div>

+                     <Row title="The Directory Server attribute to map system UIDs to user entries (nsslapd-ldapiuidnumbertype)." className="ds-margin-top">

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

+                             LDAPI UID Number Attribute

+                         </Col>

+                         <Col sm={4}>

+                             <FormControl

+                                 id="nsslapd-ldapiuidnumbertype"

+                                 type="text"

+                                 value={this.state['nsslapd-ldapiuidnumbertype']}

+                                 onChange={this.handleChange}

+                                 placeholder="e.g.  uidNumber"

+                             />

+                         </Col>

+                     </Row>

+                     <Row title="The Directory Server attribute to map system GUIDs to user entries (nsslapd-ldapigidnumbertype)." className="ds-margin-top">

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

+                             LDAPI GID Number Attribute

+                         </Col>

+                         <Col sm={4}>

+                             <FormControl

+                                 id="nsslapd-ldapigidnumbertype"

+                                 type="text"

+                                 value={this.state['nsslapd-ldapigidnumbertype']}

+                                 onChange={this.handleChange}

+                                 placeholder="e.g.  gidNumber"

+                             />

+                         </Col>

+                     </Row>

+                     <Row title="The subtree to search for user entries to use for autobind. (nsslapd-ldapientrysearchbase)." className="ds-margin-top">

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

+                             LDAPI Entry Search Base

+                         </Col>

+                         <Col sm={4}>

+                             <FormControl

+                                 id="nsslapd-ldapientrysearchbase"

+                                 type="text"

+                                 value={this.state['nsslapd-ldapientrysearchbase']}

+                                 onChange={this.handleChange}

+ 

+                             />

+                         </Col>

+                     </Row>

+                 </div>;

+         }

+ 

+         let body =

+             <div>

+                 <Form horizontal>

+                     <Row title="The Unix socket file (nsslapd-ldapifilepath).  The UI requires this exact path so it is a read-only setting." className="ds-margin-top-lg">

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

+                             LDAPI Socket File Path

+                         </Col>

+                         <Col sm={4}>

+                             <FormControl

+                                 id="nsslapd-ldapifilepath"

+                                 type="text"

+                                 value={this.state['nsslapd-ldapifilepath']}

+                                 disabled

+                             />

+                         </Col>

+                     </Row>

+                     <Row title="Map the Unix root entry to this Directory Manager DN (nsslapd-ldapimaprootdn).  The UI requires this to be set to the current root DN so it is a read-only setting" className="ds-margin-top">

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

+                             DN to map "root" To

+                         </Col>

+                         <Col sm={4}>

+                             <FormControl

+                                 id="nsslapd-ldapimaprootdn"

+                                 type="text"

+                                 value={this.state['nsslapd-ldapimaprootdn']}

+                                 disabled

+                             />

+                         </Col>

+                     </Row>

+                     <Row

+                         title="Map regular system users to Directory Server entries (nsslapd-ldapimaptoentries)."

+                         className="ds-margin-top"

+                     >

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

+                             <Checkbox

+                                 checked={this.state['nsslapd-ldapimaptoentries']}

+                                 id="nsslapd-ldapimaptoentries"

+                                 onChange={this.handleChange} className="ds-margin-left-sm"

+                             >

+                                 Map System Users to Database Entries

+                             </Checkbox>

+                         </Col>

+                     </Row>

+                     {mapUserAttrs}

+                     <Button

+                         disabled={this.state.saveDisabled}

+                         bsStyle="primary"

+                         className="ds-margin-top-med"

+                         onClick={this.saveConfig}

+                     >

+                         Save Settings

+                     </Button>

+                 </Form>

+             </div>;

+ 

+         if (this.state.lading || !this.state.loaded) {

+             body = <Spinner loading size="md" />;

+         }

+ 

+         return (

+             <div id="server-ldapi-page">

+                 <NotificationController

+                     notifications={this.state.notifications}

+                     removeNotificationAction={this.removeNotification}

+                 />

+                 <Row>

+                     <Col sm={5}>

+                         <ControlLabel className="ds-suffix-header ds-margin-top-lg">

+                             LDAPI & AutoBind Settings

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

+                                 type="fa" name="refresh" title="Refresh the LDAPI settings"

+                                 onClick={this.loadConfig}

+                                 disabled={this.state.loading}

+                             />

+                         </ControlLabel>

+                     </Col>

+                 </Row>

+                 {body}

+             </div>

+         );

+     }

+ }

+ 

+ // Property types and defaults

+ 

+ ServerLDAPI.propTypes = {

+     serverId: PropTypes.string,

+     attrs: PropTypes.object,

+ };

+ 

+ ServerLDAPI.defaultProps = {

+     serverId: "",

+     attrs: {},

+ };

@@ -0,0 +1,790 @@ 

+ import cockpit from "cockpit";

+ import React from "react";

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

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

+ import {

+     Button,

+     Checkbox,

+     Col,

+     ControlLabel,

+     Form,

+     FormControl,

+     Icon,

+     Row,

+     Spinner,

+ } from "patternfly-react";

+ import { SASLTable } from "./serverTables.jsx";

+ import { SASLMappingModal } from "./serverModals.jsx";

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

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

+ 

+ export class ServerSASL extends React.Component {

+     constructor(props) {

+         super(props);

+         this.state = {

+             configLoading: false,

+             tableLoading: false,

+             loaded: false,

+             activeKey: 1,

+             errObj: {},

+             notifications: [],

+             saveDisabled: true,

+             supportedMechs: [],

+ 

+             // Main settings

+             allowedMechs: [],

+             mappingFallback: false,

+             maxBufSize: "",

+             // Mapping modal

+             showMapping: false,

+             saveMappingDisabled: true,

+             testRegexDisabled: true,

+             testBtnDisabled: true,

+             saslMapName: "",

+             saslMapRegex: "",

+             saslTestText: "",

+             saslBase: "",

+             saslFilter: "",

+             saslPriority: "100",

+             saslModalType: "Create",

+             saslErrObj: {},

+             showConfirmDelete: false,

+             modalChecked: false,

+         };

+ 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+     }

+ 

+     componentWillMount() {

+         // Loading config

+         if (!this.state.loaded) {

+             this.loadConfig();

+         }

+     }

+ 

+     componentDidMount() {

+         this.props.enableTree();

+     }

+ 

+     addNotification(type, message, timerdelay, persistent) {

+         this.setState(prevState => ({

+             notifications: [

+                 ...prevState.notifications,

+                 {

+                     key: prevState.notifications.length + 1,

+                     type: type,

+                     persistent: persistent,

+                     timerdelay: timerdelay,

+                     message: message,

+                 }

+             ]

+         }));

+     }

+ 

+     removeNotification(notificationToRemove) {

+         this.setState({

+             notifications: this.state.notifications.filter(

+                 notification => notificationToRemove.key !== notification.key

+             )

+         });

+     }

+ 

+     handleTestRegex() {

+         let test_string = this.state.saslTestText;

+         let regex = this.state.saslMapRegex;

+         let cleaned_regex = regex.replace(/\\\(/g, '(').replace(/\\\)/g, ')');

+         let sasl_regex = RegExp(cleaned_regex);

+         if (sasl_regex.test(test_string)) {

+             this.addNotification(

+                 "success",

+                 "The test string matches the Regular Expression"

+             );

+         } else {

+             this.addNotification(

+                 "warning",

+                 "The test string does not match the Regular Expression"

+             );

+         }

+     }

+ 

+     handleChange(e) {

+         let attr = "";

+         let value = "";

+         let isArray = false;

+         let chkBox = false;

+         let disableSaveBtn = true;

+         let valueErr = false;

+         let errObj = this.state.errObj;

+ 

+         // Could be a typeahead change, check if "e" is an Array

+         if (Array.isArray(e)) {

+             isArray = true;

+             attr = "allowedMechs";

+             value = e;

+         } else {

+             value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;

+             attr = e.target.id;

+             if (e.target.type === 'checkbox') {

+                 chkBox = true;

+             }

+         }

+ 

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

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

+             disableSaveBtn = false;

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

+             disableSaveBtn = false;

+         } else if (attr == 'allowedMechs' && this.state._allowedMechs.join(' ') != value.join(' ')) {

+             if (this.state._allowedMechs.length > value.length) {

+                 // The way allow mechanisms work if that once you set it initially

+                 // you can't edit it without removing all the current mecahisms.  So

+                 // if we remove one, just remove them all and make the user start over.

+                 // MARK THIS DOES NOT WORK

+                 value = [];

+             }

+             disableSaveBtn = false;

+         }

+ 

+         // 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 != 'maxBufSize' && this.state._maxBufSize != this.state.maxBufSize) {

+             disableSaveBtn = false;

+         } else if (attr != 'allowedMechs' && this.state._allowedMechs.join(' ') != this.state.allowedMechs.join(' ')) {

+             disableSaveBtn = false;

+         }

+ 

+         if (!isArray && !chkBox && value == "") {

+             valueErr = true;

+             disableSaveBtn = true;

+         }

+ 

+         errObj[attr] = valueErr;

+         this.setState({

+             [attr]: value,

+             saveDisabled: disableSaveBtn,

+             errObj: errObj,

+         });

+     }

+ 

+     handleModalAddChange(e) {

+         let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;

+         let attr = e.target.id;

+         let disableSaveBtn = true;

+         let disableRegexTestBtn = true;

+         let valueErr = false;

+         let errObj = this.state.errObj;

+ 

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

+         if (attr == 'saslMapName' && value != "") {

+             disableSaveBtn = false;

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

+             disableSaveBtn = false;

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

+             disableSaveBtn = false;

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

+             disableSaveBtn = false;

+         }

+         if (!disableSaveBtn) {

+             // Make sure every other field is set

+             if (attr != 'saslMapName' && this.state.saslMapName == "") {

+                 disableSaveBtn = true;

+             }

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

+                 disableSaveBtn = true;

+             }

+             if (attr != 'saslBase' && this.state.saslBase == "") {

+                 disableSaveBtn = true;

+             }

+             if (attr != 'saslFilter' && this.state.saslFilter == "") {

+                 disableSaveBtn = true;

+             }

+         }

+ 

+         // Handle TEst Text filed and buttons

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

+             disableRegexTestBtn = false;

+         }

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

+             disableRegexTestBtn = false;

+         }

+ 

+         errObj[attr] = valueErr;

+         this.setState({

+             [attr]: value,

+             saveMappingDisabled: disableSaveBtn,

+             testBtnDisabled: disableRegexTestBtn,

+             errObj: errObj,

+         });

+     }

+ 

+     handleModalChange(e) {

+         let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;

+         let attr = e.target.id;

+         let disableSaveBtn = true;

+         let disableRegexTestBtn = true;

+         let valueErr = false;

+         let errObj = this.state.errObj;

+ 

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

+         if (attr == 'saslMapName' && this.state._saslMapName != value) {

+             disableSaveBtn = false;

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

+             disableSaveBtn = false;

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

+             disableSaveBtn = false;

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

+             disableSaveBtn = false;

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

+             disableSaveBtn = false;

+         }

+ 

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

+         if (attr != 'saslMapName' && this.state._saslMapName != this.state.saslMapName) {

+             disableSaveBtn = false;

+         } else if (attr != 'saslMapRegex' && this.state._saslMapRegex != this.state.saslMapRegex) {

+             disableSaveBtn = false;

+         } else if (attr != 'saslBase' && this.state._saslBase != this.state.saslBase) {

+             disableSaveBtn = false;

+         } else if (attr != 'saslFilter' && this.state._saslFilter != this.state.saslFilter) {

+             disableSaveBtn = false;

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

+             disableSaveBtn = false;

+         }

+ 

+         // Handle TEst Text filed and buttons

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

+             disableRegexTestBtn = false;

+         }

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

+             disableRegexTestBtn = false;

+         }

+ 

+         if (value == "" && attr != "saslTestText") {

+             valueErr = true;

+             disableSaveBtn = true;

+         }

+ 

+         errObj[attr] = valueErr;

+         this.setState({

+             [attr]: value,

+             saveMappingDisabled: disableSaveBtn,

+             testBtnDisabled: disableRegexTestBtn,

+             errObj: errObj,

+         });

+     }

+ 

+     loadConfig() {

+         let cmd = [

+             "dsconf", "-j", this.props.serverId, "config", 'get'

+         ];

+         log_cmd("loadConfig", "Get SASL settings", cmd);

+         cockpit

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

+                 .done(content => {

+                     let config = JSON.parse(content);

+                     let attrs = config.attrs;

+                     let allowedMechsVal = attrs['nsslapd-allowed-sasl-mechanisms'][0];

+                     let allowedMechs = [];

+                     let fallback = false;

+ 

+                     if (attrs['nsslapd-sasl-mapping-fallback'][0] == "on") {

+                         fallback = true;

+                     }

+                     if (allowedMechsVal != "") {

+                         // Could be space or comma separated

+                         if (allowedMechsVal.indexOf(',') > -1) {

+                             allowedMechsVal = allowedMechsVal.trim();

+                             allowedMechs = allowedMechsVal.split(',');

+                         } else {

+                             allowedMechs = allowedMechsVal.split();

+                         }

+                     }

+ 

+                     this.setState({

+                         maxBufSize: attrs['nsslapd-sasl-max-buffer-size'][0],

+                         allowedMechs: allowedMechs,

+                         mappingFallback: fallback,

+                         saveDisabled: true,

+                         // Store original values

+                         _maxBufSize: attrs['nsslapd-sasl-max-buffer-size'][0],

+                         _allowedMechs: allowedMechs,

+                         _mappingFallback: fallback,

+                     }, this.loadMechs());

+                 });

+     }

+ 

+     loadMechs() {

+         let cmd = [

+             "dsconf", "-j", this.props.serverId, "sasl", 'get-mechs'

+         ];

+         log_cmd("loadMechs", "Get supported SASL mechanisms", cmd);

+         cockpit

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

+                 .done(content => {

+                     const config = JSON.parse(content);

+                     this.setState({

+                         supportedMechs: config.items

+                     }, this.loadSASLMappings());

+                 });

+     }

+ 

+     loadSASLMappings() {

+         let cmd = ["dsconf", '-j', this.props.serverId, 'sasl', 'list', '--details'];

+         log_cmd('get_and_set_sasl', 'Get SASL mappings', cmd);

+         cockpit

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

+                 .done(content => {

+                     let saslMapObj = JSON.parse(content);

+                     let mappings = [];

+                     for (let mapping of saslMapObj['items']) {

+                         if (!mapping['attrs'].hasOwnProperty('nssaslmappriority')) {

+                             mapping['attrs'].nssaslmappriority = ['100'];

+                         }

+                         mappings.push(mapping['attrs']);

+                     }

+                     this.setState({

+                         mappings: mappings,

+                         loaded: true,

+                         tableLoading: false,

+                         configLoading: false,

+                     });

+                 });

+     }

+ 

+     showCreateMapping() {

+         this.setState({

+             showMappingModal: true,

+             saveMappingDisabled: true,

+             testRegexDisabled: true,

+             saslModalType: "Create",

+             saslMapName: "",

+             saslMapRegex: "",

+             saslTestText: "",

+             saslBase: "",

+             saslFilter: "",

+             saslPriority: "100",

+             saslErrObj: {},

+         });

+     }

+ 

+     closeMapping() {

+         this.setState({

+             showMappingModal: false,

+         });

+     }

+ 

+     showEditMapping(name, regex, base, filter, priority) {

+         this.setState({

+             showMappingModal: true,

+             saveMappingDisabled: true,

+             testRegexDisabled: true,

+             saslModalType: "Edit",

+             saslMapName: name,

+             saslMapRegex: regex,

+             saslTestText: "",

+             saslBase: base,

+             saslFilter: filter,

+             saslPriority: priority,

+             // Note original values

+             _saslMapName: name,

+             _saslMapRegex: regex,

+             _saslTestText: "",

+             _saslBase: base,

+             _saslFilter: filter,

+             _saslPriority: priority,

+             saslErrObj: {},

+         });

+     }

+ 

+     showConfirmDelete(name) {

+         this.setState({

+             saslMapName: name,

+             modalChecked: false,

+             showConfirmDelete: true,

+         });

+     }

+ 

+     closeConfirmDelete() {

+         this.setState({

+             modalChecked: false,

+             showConfirmDelete: false,

+         });

+     }

+ 

+     createMapping() {

+         this.setState({

+             tableLoading: true,

+         });

+         let cmd = [

+             'dsconf', '-j', this.props.serverId,

+             'sasl', 'create',

+             '--cn=' + this.state.saslMapName,

+             '--nsSaslMapFilterTemplate=' + this.state.saslFilter,

+             '--nsSaslMapRegexString=' + this.state.saslMapRegex,

+             '--nsSaslMapBaseDNTemplate=' + this.state.saslBase,

+             '--nsSaslMapPriority=' + this.state.saslPriority

+         ];

+ 

+         log_cmd("createMapping", "Create sasl mapping", cmd);

+         cockpit

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

+                 .done(content => {

+                     this.loadConfig();

+                     this.closeMapping();

+                     this.addNotification(

+                         "success",

+                         "Successfully create new SASL Mapping"

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.loadConfig();

+                     this.addNotification(

+                         "error",

+                         `Error creating new SASL Mapping - ${errMsg.desc}`

+                     );

+                 });

+     }

+ 

+     editMapping(name) {

+         // Start spinning

+         let new_mappings = this.state.mappings;

+         for (let saslMap of new_mappings) {

+             if (saslMap.cn[0] == name) {

+                 saslMap.nssaslmapregexstring = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmapregexstring[0]} loading size="sm" />];

+                 saslMap.nssaslmapbasedntemplate = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmapbasedntemplate[0]} loading size="sm" />];

+                 saslMap.nssaslmapfiltertemplate = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmapfiltertemplate[0]} loading size="sm" />];

+                 saslMap.nssaslmappriority = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmappriority[0]} loading size="sm" />];

+             }

+         }

+ 

+         this.setState({

+             mappings: new_mappings

+         });

+ 

+         // Delete and create

+         let delete_cmd = [

+             'dsconf', '-j', this.props.serverId,

+             'sasl', 'delete', this.state._saslMapName

+         ];

+         let create_cmd = [

+             'dsconf', '-j', this.props.serverId,

+             'sasl', 'create',

+             '--cn=' + this.state.saslMapName,

+             '--nsSaslMapFilterTemplate=' + this.state.saslFilter,

+             '--nsSaslMapRegexString=' + this.state.saslMapRegex,

+             '--nsSaslMapBaseDNTemplate=' + this.state.saslBase,

+             '--nsSaslMapPriority=' + this.state.saslPriority

+         ];

+ 

+         log_cmd("editMapping", "deleting sasl mapping", delete_cmd);

+         cockpit

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

+                 .done(content => {

+                     log_cmd("editMapping", "Create new sasl mapping", create_cmd);

+                     cockpit

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

+                             .done(content => {

+                                 this.closeMapping();

+                                 this.loadConfig();

+                                 this.addNotification(

+                                     "success",

+                                     "Successfully updated SASL Mapping"

+                                 );

+                             })

+                             .fail(err => {

+                                 let errMsg = JSON.parse(err);

+                                 this.closeMapping();

+                                 this.loadConfig();

+                                 this.addNotification(

+                                     "error",

+                                     `Error updating SASL Mapping - ${errMsg.desc}`

+                                 );

+                             });

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.loadConfig();

+                     this.closeMapping();

+                     this.addNotification(

+                         "error",

+                         `Error replacing SASL Mapping - ${errMsg.desc}`

+                     );

+                 });

+     }

+ 

+     deleteMapping() {

+         // Start spinning

+         let new_mappings = this.state.mappings;

+         for (let saslMap of new_mappings) {

+             if (saslMap.cn[0] == this.state.saslMapName) {

+                 saslMap.nssaslmapregexstring = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmapregexstring[0]} loading size="sm" />];

+                 saslMap.nssaslmapbasedntemplate = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmapbasedntemplate[0]} loading size="sm" />];

+                 saslMap.nssaslmapfiltertemplate = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmapfiltertemplate[0]} loading size="sm" />];

+                 saslMap.nssaslmappriority = [<Spinner className="ds-lower-field" key={new_mappings[0].nssaslmappriority[0]} loading size="sm" />];

+             }

+         }

+         this.setState({

+             mappings: new_mappings

+         });

+ 

+         let cmd = [

+             'dsconf', '-j', this.props.serverId,

+             'sasl', 'delete', this.state.saslMapName

+         ];

+         log_cmd("deleteMapping", "Delete sasl mapping", cmd);

+         cockpit

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

+                 .done(content => {

+                     this.closeConfirmDelete();

+                     this.loadConfig();

+                     this.addNotification(

+                         "success",

+                         "Successfully deleted SASL Mapping"

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.loadConfig();

+                     this.closeConfirmDelete();

+                     this.addNotification(

+                         "error",

+                         `Error deleting SASL Mapping - ${errMsg.desc}`

+                     );

+                 });

+     }

+ 

+     saveConfig() {

+         // Start spinning

+         this.setState({

+             configLoading: true,

+         });

+ 

+         // Build up the command list

+         let cmd = [

+             'dsconf', '-j', this.props.serverId, 'config', 'replace'

+         ];

+ 

+         let mech_str_new = this.state.allowedMechs.join(' ');

+         let mech_str_orig = this.state._allowedMechs.join(' ');

+         if (mech_str_orig != mech_str_new) {

+             cmd.push("nsslapd-allowed-sasl-mechanisms=" + mech_str_new);

+         }

+         if (this.state._mappingFallback != this.state.mappingFallback) {

+             let value = "off";

+             if (this.state.mappingFallback) {

+                 value = "on";

+             }

+             cmd.push("nsslapd-sasl-mapping-fallback=" + value);

+         }

+         if (this.state._maxBufSize != this.state.maxBufSize) {

+             cmd.push("nsslapd-sasl-max-buffer-size=" + this.state.maxBufSize);

+         }

+ 

+         log_cmd("saveConfig", "Applying SASL config change", cmd);

+         cockpit

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

+                 .done(content => {

+                     this.loadConfig();

+                     this.addNotification(

+                         "success",

+                         "Successfully updated SASL configuration.  These " +

+                             "changes require the server to be restarted to take effect."

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.loadConfig();

+                     this.addNotification(

+                         "error",

+                         `Error updating SASL configuration - ${errMsg.desc}`

+                     );

+                 });

+     }

+ 

+     render() {

+         let configSpinner = "";

+         let tableSpinner = " ";

+         let body = "";

+         if (this.state.tableLoading) {

+             tableSpinner = <Spinner loading size="sm" />;

+         }

+         if (this.state.configLoading) {

+             configSpinner = <Spinner loading size="md" />;

+         }

+ 

+         if (!this.state.loaded) {

+             body =

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

+                     <h4>Loading SASL configuration ...</h4>

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

+                 </div>;

+         } else {

+             body =

+                 <div className="ds-margin-left-sm">

+                     <Row>

+                         <Col sm={3} className="ds-word-wrap">

+                             <ControlLabel className="ds-suffix-header ds-margin-top-lg">

+                                 SASL Settings

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

+                                     type="fa" name="refresh" title="Refresh SASL configuration"

+                                     onClick={() => {

+                                         this.loadConfig();

+                                     }}

+                                 />

+                             </ControlLabel>

+                         </Col>

+                         <Col sm={1} className="ds-margin-top-lg">

+                             {configSpinner}

+                         </Col>

+                     </Row>

+                     <hr />

+                     <Form>

+                         <Row title="The maximum SASL buffer size in bytes (nsslapd-sasl-max-buffer-size)." className="ds-margin-top">

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

+                                 Max SASL Buffer Size

+                             </Col>

+                             <Col sm={4}>

+                                 <FormControl

+                                     id="maxBufSize"

+                                     type="number"

+                                     min="-1"

+                                     max="2147483647"

+                                     value={this.state.maxBufSize}

+                                     onChange={this.handleChange}

+                                 />

+                             </Col>

+                         </Row>

+                         <Row

+                             title="A list of SASL mechanisms the server will only accept (nsslapd-allowed-sasl-mechanisms).  The default is all mechanisms are allowed."

+                             className="ds-margin-top"

+                         >

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

+                                 Allowed SASL Mechanisms

+                             </Col>

+                             <Col sm={4}>

+                                 <Typeahead

+                                     id="allowedMechs"

+                                     onChange={value => {

+                                         this.handleChange(value);

+                                     }}

+                                     multiple

+                                     options={this.state.supportedMechs}

+                                     selected={this.state.allowedMechs}

+                                     placeholder="Type SASL mechanism to allow"

+                                     ref={(typeahead) => { this.typeahead = typeahead }}

+                                 />

+                             </Col>

+                         </Row>

+                         <Row

+                             title="Check all sasl mappings until one succeeds or they all fail (nsslapd-sasl-mapping-fallback)."

+                             className="ds-margin-top"

+                         >

+                             <Checkbox

+                                 checked={this.state.mappingFallback}

+                                 id="mappingFallback"

+                                 onChange={this.handleChange} className="ds-margin-left-sm"

+                             >

+                                 Allow SASL Mapping Fallback

+                             </Checkbox>

+                         </Row>

+                     </Form>

+                     <Button

+                         disabled={this.state.saveDisabled}

+                         bsStyle="primary"

+                         className="ds-margin-top-med"

+                         onClick={this.saveConfig}

+                     >

+                         Save Settings

+                     </Button>

+                     <hr />

+                     <Row>

+                         <h4 className="ds-center ds-logo-style">

+                             <div className="ds-inline">

+                                 <ControlLabel>

+                                     <b>SASL Mappings</b>

+                                 </ControlLabel>

+                             </div>

+                             <div className="ds-left-indent ds-inline">

+                                 <ControlLabel>

+                                     {tableSpinner}

+                                 </ControlLabel>

+                             </div>

+                         </h4>

+                     </Row>

+                     <SASLTable

+                         rows={this.state.mappings}

+                         editMapping={this.showEditMapping}

+                         deleteMapping={this.showConfirmDelete}

+                         className="ds-margin-top"

+                     />

+                     <Button

+                         bsStyle="primary"

+                         className="ds-margin-top-med"

+                         onClick={this.showCreateMapping}

+                     >

+                         Create New Mapping

+                     </Button>

+                 </div>;

+         }

+ 

+         return (

+             <div id="server-sasl-page">

+                 <NotificationController

+                     notifications={this.state.notifications}

+                     removeNotificationAction={this.removeNotification}

+                 />

+                 {body}

+                 <SASLMappingModal

+                     showModal={this.state.showMappingModal}

+                     testBtnDisabled={this.state.testBtnDisabled}

+                     saveDisabled={this.state.saveMappingDisabled}

+                     closeHandler={this.closeMapping}

+                     handleChange={this.state.saslModalType == "Create" ? this.handleModalAddChange : this.handleModalChange}

+                     handleTestRegex={this.handleTestRegex}

+                     saveHandler={this.state.saslModalType == "Create" ? this.createMapping : this.editMapping}

+                     error={this.state.saslErrObj}

+                     type={this.state.saslModalType}

+                     name={this.state.saslMapName}

+                     regex={this.state.saslMapRegex}

+                     testText={this.state.saslTestText}

+                     base={this.state.saslBase}

+                     filter={this.state.saslFilter}

+                     priority={this.state.saslPriority}

+                     spinning={this.state.tableLoading}

+                 />

+                 <DoubleConfirmModal

+                     showModal={this.state.showConfirmDelete}

+                     closeHandler={this.closeConfirmDelete}

+                     handleChange={this.handleModalChange}

+                     actionHandler={this.deleteMapping}

+                     item={this.state.saslMapName}

+                     checked={this.state.modalChecked}

+                     spinning={this.state.tableLoading}

+                     mTitle="Delete SASL Mapping"

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

+                     mSpinningMsg="Deleting SASL Mapping ..."

+                     mBtnName="Delete Mapping"

+                 />

+             </div>

+         );

+     }

+ }

@@ -0,0 +1,224 @@ 

+ import React from "react";

+ import {

+     Button,

+     // Checkbox,

+     Col,

+     ControlLabel,

+     Form,

+     FormControl,

+     Icon,

+     Modal,

+     Row,

+     Spinner,

+     noop

+ } from "patternfly-react";

+ import PropTypes from "prop-types";

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

+ 

+ export class SASLMappingModal extends React.Component {

+     render() {

+         let title = this.props.type;

+         let btnText = "Create";

+         let spinning = "";

+         if (title != "Create") {

+             btnText = "Save";

+         }

+         if (this.props.spinning) {

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

+         }

+ 

+         return (

+             <Modal show={this.props.showModal} onHide={this.props.closeHandler}>

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

+                     <Modal.Header>

+                         <button

+                             className="close"

+                             onClick={this.props.closeHandler}

+                             aria-hidden="true"

+                             aria-label="Close"

+                         >

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

+                         </button>

+                         <Modal.Title>

+                             {title} SASL Mapping

+                         </Modal.Title>

+                     </Modal.Header>

+                     <Modal.Body>

+                         <Form horizontal>

+                             <Row

+                                 className="ds-margin-top"

+                                 title="SASL Mapping entry name"

+                             >

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

+                                     SASL Mapping Name

+                                 </Col>

+                                 <Col sm={5}>

+                                     <FormControl

+                                         id="saslMapName"

+                                         type="text"

+                                         onChange={this.props.handleChange}

+                                         className={this.props.error.saslMapName ? "ds-input-bad" : ""}

+                                         defaultValue={this.props.name}

+                                     />

+                                 </Col>

+                             </Row>

+                             <Row

+                                 className="ds-margin-top"

+                                 title="SASL mapping Regular Expression"

+                             >

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

+                                     SASL Mapping Regex

+                                 </Col>

+                                 <Col sm={5}>

+                                     <FormControl

+                                         id="saslMapRegex"

+                                         type="text"

+                                         onChange={this.props.handleChange}

+                                         className={this.props.error.saslMapRegex ? "ds-input-bad" : ""}

+                                         defaultValue={this.props.regex}

+                                     />

+                                 </Col>

+                             </Row>

+                             <Row

+                                 className="ds-margin-top"

+                                 title="Test Regular Expression"

+                             >

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

+                                     <font size="2">* Test Regex</font>

+                                 </Col>

+                                 <Col sm={5}>

+                                     <FormControl

+                                         id="saslTestText"

+                                         type="text"

+                                         onChange={this.props.handleChange}

+                                         defaultValue={this.props.testText}

+                                         placeholder="Enter text to test regex"

+                                     />

+                                 </Col>

+                                 <Col sm={1}>

+                                     <Button

+                                         disabled={this.props.testBtnDisabled}

+                                         bsStyle="primary"

+                                         onClick={this.props.handleTestRegex}

+                                     >

+                                         Test It

+                                     </Button>

+                                 </Col>

+                             </Row>

+                             <Row

+                                 className="ds-margin-top"

+                                 title="The search base or a specific entry DN to match against the constructed DN"

+                             >

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

+                                     SASL Mapping Base

+                                 </Col>

+                                 <Col sm={5}>

+                                     <FormControl

+                                         id="saslBase"

+                                         type="text"

+                                         onChange={this.props.handleChange}

+                                         className={this.props.error.saslBase ? "ds-input-bad" : ""}

+                                         defaultValue={this.props.base}

+                                     />

+                                 </Col>

+                             </Row>

+                             <Row

+                                 className="ds-margin-top"

+                                 title="SASL mapping search filter"

+                             >

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

+                                     SASL Mapping Filter

+                                 </Col>

+                                 <Col sm={5}>

+                                     <FormControl

+                                         id="saslFilter"

+                                         type="text"

+                                         onChange={this.props.handleChange}

+                                         className={this.props.error.saslFilter ? "ds-input-bad" : ""}

+                                         defaultValue={this.props.filter}

+                                     />

+                                 </Col>

+                             </Row>

+                             <Row

+                                 className="ds-margin-top"

+                                 title="Set the mapping priority for which mappins should be tried first"

+                             >

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

+                                     SASL Mapping Priority

+                                 </Col>

+                                 <Col sm={5}>

+                                     <FormControl

+                                         id="saslPriority"

+                                         type="number"

+                                         min="1"

+                                         max="100"

+                                         value={this.props.priority}

+                                         onChange={this.props.handleChange}

+                                         className={this.props.error.saslPriority ? "ds-input-bad" : ""}

+                                     />

+                                 </Col>

+                             </Row>

+                         </Form>

+                         {spinning}

+                     </Modal.Body>

+                     <Modal.Footer>

+                         <Button

+                             bsStyle="default"

+                             className="btn-cancel"

+                             onClick={this.props.closeHandler}

+                         >

+                             Cancel

+                         </Button>

+                         <Button

+                             bsStyle="primary"

+                             disabled={this.props.saveDisabled}

+                             onClick={() => {

+                                 this.props.saveHandler(this.props.name);

+                             }}

+                         >

+                             {btnText} Mapping

+                         </Button>

+                     </Modal.Footer>

+                 </div>

+             </Modal>

+         );

+     }

+ }

+ 

+ // Types and defaults

+ 

+ SASLMappingModal.propTypes = {

+     showModal: PropTypes.bool,

+     testBtnDisabled: PropTypes.bool,

+     saveDisabled: PropTypes.bool,

+     closeHandler: PropTypes.func,

+     handleChange: PropTypes.func,

+     handleTestRegex: PropTypes.func,

+     saveHandler: PropTypes.func,

+     error: PropTypes.object,

+     name: PropTypes.string,

+     regex: PropTypes.string,

+     testText: PropTypes.string,

+     base: PropTypes.string,

+     filter: PropTypes.string,

+     priority: PropTypes.string,

+     spinning: PropTypes.bool,

+ };

+ 

+ SASLMappingModal.defaultProps = {

+     showModal: false,

+     testBtnDisabled: true,

+     saveDisabled: true,

+     closeHandler: noop,

+     handleChange: noop,

+     handleTestRegex: noop,

+     saveHandler: noop,

+     error: {},

+     name: "",

+     regex: "",

+     testText: "",

+     base: "",

+     filter: "",

+     priority: "",

+     spinning: PropTypes.bool,

+ };

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

+ import React from "react";

+ import {

+     DropdownButton,

+     MenuItem,

+     actionHeaderCellFormatter,

+     sortableHeaderCellFormatter,

+     tableCellFormatter,

+     noop

+ } from "patternfly-react";

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

+ import PropTypes from "prop-types";

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

+ 

+ export class SASLTable extends React.Component {

+     constructor(props) {

+         super(props);

+ 

+         this.state = {

+             rowKey: "cn",

+             columns: [

+                 {

+                     property: "cn",

+                     header: {

+                         label: "Mapping Name",

+                         props: {

+                             index: 0,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 0

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "nssaslmapregexstring",

+                     header: {

+                         label: "Regular Expression",

+                         props: {

+                             index: 1,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 1

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "nssaslmapbasedntemplate",

+                     header: {

+                         label: "Search Base DN",

+                         props: {

+                             index: 2,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 2

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "nssaslmapfiltertemplate",

+                     header: {

+                         label: "Search Filter",

+                         props: {

+                             index: 3,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 3

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "nssaslmappriority",

+                     header: {

+                         label: "Priority",

+                         props: {

+                             index: 4,

+                             rowSpan: 1,

+                             colSpan: 1,

+                             sort: true

+                         },

+                         transforms: [],

+                         formatters: [],

+                         customFormatters: [sortableHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 4

+                         },

+                         formatters: [tableCellFormatter]

+                     }

+                 },

+                 {

+                     property: "actions",

+                     header: {

+                         props: {

+                             index: 5,

+                             rowSpan: 1,

+                             colSpan: 1

+                         },

+                         formatters: [actionHeaderCellFormatter]

+                     },

+                     cell: {

+                         props: {

+                             index: 5

+                         },

+                         formatters: [

+                             (value, { rowData }) => {

+                                 return [

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

+                                         <DropdownButton id={rowData.cn[0]}

+                                             className="ds-action-button"

+                                             bsStyle="primary" title="Actions">

+                                             <MenuItem eventKey="1" onClick={() => {

+                                                 this.props.editMapping(

+                                                     rowData.cn[0],

+                                                     rowData.nssaslmapregexstring[0],

+                                                     rowData.nssaslmapbasedntemplate[0],

+                                                     rowData.nssaslmapfiltertemplate[0],

+                                                     rowData.nssaslmappriority[0]

+                                                 );

+                                             }}

+                                             >

+                                                 Edit Mapping

+                                             </MenuItem>

+                                             <MenuItem divider />

+                                             <MenuItem eventKey="2" onClick={() => {

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

+                                             }}

+                                             >

+                                                 Delete Mapping

+                                             </MenuItem>

+                                         </DropdownButton>

+                                     </td>

+                                 ];

+                             }

+                         ]

+                     }

+                 }

+             ],

+         };

+ 

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

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

+     } // Constructor

+ 

+     getColumns() {

+         return this.state.columns;

+     }

+ 

+     getSingleColumn () {

+         return [

+             {

+                 property: "msg",

+                 header: {

+                     label: "SASL Mappings",

+                     props: {

+                         index: 0,

+                         rowSpan: 1,

+                         colSpan: 1,

+                         sort: true

+                     },

+                     transforms: [],

+                     formatters: [],

+                     customFormatters: [sortableHeaderCellFormatter]

+                 },

+                 cell: {

+                     props: {

+                         index: 0

+                     },

+                     formatters: [tableCellFormatter]

+                 }

+             },

+         ];

+     }

+ 

+     render() {

+         let SASLTable;

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

+             SASLTable = <DSShortTable

+                 getColumns={this.getSingleColumn}

+                 rowKey={"msg"}

+                 rows={[{msg: "No Mappings"}]}

+             />;

+         } else {

+             SASLTable =

+                 <DSTable

+                     noSearchBar

+                     getColumns={this.getColumns}

+                     rowKey={this.state.rowKey}

+                     rows={this.props.rows}

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

+                     toolBarPaginationPerPage={6}

+                 />;

+         }

+         return (

+             <div>

+                 {SASLTable}

+             </div>

+         );

+     }

+ }

+ 

+ SASLTable.propTypes = {

+     rows: PropTypes.array,

+     editMapping: PropTypes.func,

+     deleteMapping: PropTypes.func

+ };

+ 

+ SASLTable.defaultProps = {

+     rows: [],

+     editMapping: noop,

+     deleteMapping: noop

+ };

The added file is too large to be shown here, see it at: src/cockpit/389-console/src/lib/server/settings.jsx
@@ -0,0 +1,556 @@ 

+ import cockpit from "cockpit";

+ import React from "react";

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

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

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

+ import {

+     Button,

+     Col,

+     ControlLabel,

+     Form,

+     FormControl,

+     Icon,

+     Checkbox,

+     Row,

+     Spinner,

+ } from "patternfly-react";

+ import PropTypes from "prop-types";

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

+ 

+ const tuning_attrs = [

+     'nsslapd-ndn-cache-enabled',

+     'nsslapd-ignore-virtual-attrs',

+     'nsslapd-connection-nocanon',

+     'nsslapd-enable-turbo-mode',

+     'nsslapd-threadnumber',

+     'nsslapd-maxdescriptors',

+     'nsslapd-timelimit',

+     'nsslapd-sizelimit',

+     'nsslapd-pagedsizelimit',

+     'nsslapd-idletimeout',

+     'nsslapd-ioblocktimeout',

+     'nsslapd-outbound-ldap-io-timeout',

+     'nsslapd-maxbersize',

+     'nsslapd-maxsasliosize',

+     'nsslapd-listen-backlog-size',

+     'nsslapd-max-filter-nest-level',

+     'nsslapd-ndn-cache-max-size',

+ ];

+ 

+ export class ServerTuning extends React.Component {

+     constructor(props) {

+         super(props);

+         this.state = {

+             loading: false,

+             loaded: false,

+             activeKey: 1,

+             notifications: [],

+             saveDisabled: true,

+             errObj: {},

+             attrs: this.props.attrs,

+         };

+ 

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

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

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

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

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

+     }

+ 

+     componentWillMount() {

+         // Loading config

+         if (!this.state.loaded) {

+             this.loadConfig();

+         }

+     }

+ 

+     componentDidMount() {

+         this.props.enableTree();

+     }

+ 

+     addNotification(type, message, timerdelay, persistent) {

+         this.setState(prevState => ({

+             notifications: [

+                 ...prevState.notifications,

+                 {

+                     key: prevState.notifications.length + 1,

+                     type: type,

+                     persistent: persistent,

+                     timerdelay: timerdelay,

+                     message: message,

+                 }

+             ]

+         }));

+     }

+ 

+     removeNotification(notificationToRemove) {

+         this.setState({

+             notifications: this.state.notifications.filter(

+                 notification => notificationToRemove.key !== notification.key

+             )

+         });

+     }

+ 

+     handleChange(e) {

+         let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;

+         let attr = e.target.id;

+         let disableSaveBtn = true;

+         let valueErr = false;

+         let errObj = this.state.errObj;

+ 

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

+         for (let tuning_attr of tuning_attrs) {

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

+                 disableSaveBtn = false;

+                 break;

+             }

+         }

+ 

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

+         for (let tuning_attr of tuning_attrs) {

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

+                 disableSaveBtn = false;

+                 break;

+             }

+         }

+ 

+         if (value == "" && e.target.type !== 'checkbox') {

+             valueErr = true;

+             disableSaveBtn = true;

+         }

+         errObj[attr] = valueErr;

+         this.setState({

+             [attr]: value,

+             saveDisabled: disableSaveBtn,

+             errObj: errObj,

+         });

+     }

+ 

+     loadConfig(reloading) {

+         if (reloading) {

+             this.setState({

+                 loading: true

+             });

+         }

+ 

+         let attrs = this.state.attrs;

+         let ndnEnabled = false;

+         let ignoreVirtAttrs = false;

+         let connNoCannon = false;

+         let turboMode = false;

+ 

+         if (attrs['nsslapd-ndn-cache-enabled'][0] == "on") {

+             ndnEnabled = true;

+         }

+         if (attrs['nsslapd-ignore-virtual-attrs'][0] == "on") {

+             ignoreVirtAttrs = true;

+         }

+         if (attrs['nsslapd-connection-nocanon'][0] == "on") {

+             connNoCannon = true;

+         }

+         if (attrs['nsslapd-enable-turbo-mode'][0] == "on") {

+             turboMode = true;

+         }

+         this.setState({

+             loaded: true,

+             loading: false,

+             // Settings

+             'nsslapd-ndn-cache-enabled': ndnEnabled,

+             'nsslapd-ignore-virtual-attrs': ignoreVirtAttrs,

+             'nsslapd-connection-nocanon': connNoCannon,

+             'nsslapd-enable-turbo-mode': turboMode,

+             'nsslapd-threadnumber': attrs['nsslapd-threadnumber'][0],

+             'nsslapd-maxdescriptors': attrs['nsslapd-maxdescriptors'][0],

+             'nsslapd-timelimit': attrs['nsslapd-timelimit'][0],

+             'nsslapd-sizelimit': attrs['nsslapd-sizelimit'][0],

+             'nsslapd-pagedsizelimit': attrs['nsslapd-pagedsizelimit'][0],

+             'nsslapd-idletimeout': attrs['nsslapd-idletimeout'][0],

+             'nsslapd-ioblocktimeout': attrs['nsslapd-ioblocktimeout'][0],

+             'nsslapd-outbound-ldap-io-timeout': attrs['nsslapd-outbound-ldap-io-timeout'][0],

+             'nsslapd-maxbersize': attrs['nsslapd-maxbersize'][0],

+             'nsslapd-maxsasliosize': attrs['nsslapd-maxsasliosize'][0],

+             'nsslapd-listen-backlog-size': attrs['nsslapd-listen-backlog-size'][0],

+             'nsslapd-max-filter-nest-level': attrs['nsslapd-max-filter-nest-level'][0],

+             'nsslapd-ndn-cache-max-size': attrs['nsslapd-ndn-cache-max-size'][0],

+             // Record original values

+             '_nsslapd-ndn-cache-enabled': ndnEnabled,

+             '_nsslapd-ignore-virtual-attrs': ignoreVirtAttrs,

+             '_nsslapd-connection-nocanon': connNoCannon,

+             '_nsslapd-enable-turbo-mode': turboMode,

+             '_nsslapd-threadnumber': attrs['nsslapd-threadnumber'][0],

+             '_nsslapd-maxdescriptors': attrs['nsslapd-maxdescriptors'][0],

+             '_nsslapd-timelimit': attrs['nsslapd-timelimit'][0],

+             '_nsslapd-sizelimit': attrs['nsslapd-sizelimit'][0],

+             '_nsslapd-pagedsizelimit': attrs['nsslapd-pagedsizelimit'][0],

+             '_nsslapd-idletimeout': attrs['nsslapd-idletimeout'][0],

+             '_nsslapd-ioblocktimeout': attrs['nsslapd-ioblocktimeout'][0],

+             '_nsslapd-outbound-ldap-io-timeout': attrs['nsslapd-outbound-ldap-io-timeout'][0],

+             '_nsslapd-maxbersize': attrs['nsslapd-maxbersize'][0],

+             '_nsslapd-maxsasliosize': attrs['nsslapd-maxsasliosize'][0],

+             '_nsslapd-listen-backlog-size': attrs['nsslapd-listen-backlog-size'][0],

+             '_nsslapd-max-filter-nest-level': attrs['nsslapd-max-filter-nest-level'][0],

+             '_nsslapd-ndn-cache-max-size': attrs['nsslapd-ndn-cache-max-size'][0],

+         });

+     }

+ 

+     saveConfig() {

+         let cmd = [

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

+             'config', 'replace'

+         ];

+ 

+         for (let attr of tuning_attrs) {

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

+                 let val = this.state[attr];

+                 if (typeof val === "boolean") {

+                     if (val) {

+                         val = "on";

+                     } else {

+                         val = "off";

+                     }

+                 }

+                 cmd.push(attr + "=" + val);

+             }

+         }

+ 

+         log_cmd("saveConfig", "Saving Tuning configuration", cmd);

+         cockpit

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

+                 .done(content => {

+                     this.loadConfig(1);

+                     this.addNotification(

+                         "success",

+                         "Successfully updated Advanced configuration"

+                     );

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.loadConfig(1);

+                     this.addNotification(

+                         "error",

+                         `Error updating Advanced configuration - ${errMsg.desc}`

+                     );

+                 });

+     }

+ 

+     render () {

+         let reloadSpinner = "";

+         let body = "";

+ 

+         if (this.state.loading) {

+             reloadSpinner = <Spinner loading size="md" />;

+         }

+         if (!this.state.loaded) {

+             body =

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

+                     <h4>Loading tuning configuration ...</h4>

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

+                 </div>;

+         } else {

+             body =

+                 <div>

+                     <Row>

+                         <Col sm={4}>

+                             <ControlLabel className="ds-suffix-header ds-margin-top-lg ds-margin-left-sm">

+                                 Tuning & Limits

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

+                                     type="fa" name="refresh" title="Refresh tuning settings"

+                                     onClick={() => {

+                                         this.loadConfig(1);

+                                     }}

+                                 />

+                             </ControlLabel>

+                         </Col>

+                         <Col sm={8} className="ds-margin-top-lg">

+                             {reloadSpinner}

+                         </Col>

+                     </Row>

+                     <hr />

+                     <Form horizontal>

+                         <Row title="The number of worker threads that handle database operations (nsslapd-threadnumber)." className="ds-margin-top">

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

+                                 Number Of Worker Threads

+                             </Col>

+                             <Col sm={3}>

+                                 <FormControl

+                                     id="nsslapd-threadnumber"

+                                     type="number"

+                                     min="-1"

+                                     max="1048576"

+                                     value={this.state['nsslapd-threadnumber']}

+                                     onChange={this.handleChange}

+                                     className={this.state.errObj['nsslapd-threadnumber'] ? "ds-input-bad" : ""}

+                                 />

+                             </Col>

+                         </Row>

+                         <Row title="The maximum number of file descriptors the server will use (nsslapd-maxdescriptors)." className="ds-margin-top">

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

+                                 Maximum File Descriptors

+                             </Col>

+                             <Col sm={3}>

+                                 <FormControl

+                                     id="nsslapd-maxdescriptors"

+                                     type="number"

+                                     min="1024"

+                                     max="1048576"

+                                     value={this.state['nsslapd-maxdescriptors']}

+                                     onChange={this.handleChange}

+                                     className={this.state.errObj['nsslapd-maxdescriptors'] ? "ds-input-bad" : ""}

+                                 />

+                             </Col>

+                         </Row>

+                         <Row title="The maximum number of seconds allocated for a search request (nsslapd-timelimit)." className="ds-margin-top">

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

+                                 Search Time Limit

+                             </Col>

+                             <Col sm={3}>

+                                 <FormControl

+                                     id="nsslapd-timelimit"

+                                     type="number"

+                                     min="-1"

+                                     max="2147483647"

+                                     value={this.state['nsslapd-timelimit']}

+                                     onChange={this.handleChange}

+                                     className={this.state.errObj['nsslapd-timelimit'] ? "ds-input-bad" : ""}

+                                 />

+                             </Col>

+                         </Row>

+                         <Row title="The maximum number of entries to return from a search operation (nsslapd-sizelimit)." className="ds-margin-top">

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

+                                 Search Size Limit

+                             </Col>

+                             <Col sm={3}>

+                                 <FormControl

+                                     id="nsslapd-sizelimit"

+                                     type="number"

+                                     min="-1"

+                                     max="2147483647"

+                                     value={this.state['nsslapd-sizelimit']}

+                                     onChange={this.handleChange}

+                                     className={this.state.errObj['nsslapd-sizelimit'] ? "ds-input-bad" : ""}

+                                 />

+                             </Col>

+                         </Row>

+                         <Row title="The maximum number of entries to return from a paged search operation (nsslapd-pagedsizelimit)." className="ds-margin-top">

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

+                                 Paged Search Size Limit

+                             </Col>

+                             <Col sm={3}>

+                                 <FormControl

+                                     id="nsslapd-pagedsizelimit"

+                                     type="number"

+                                     min="-1"

+                                     max="2147483647"

+                                     value={this.state['nsslapd-pagedsizelimit']}

+                                     onChange={this.handleChange}

+                                     className={this.state.errObj['nsslapd-pagedsizelimit'] ? "ds-input-bad" : ""}

+                                 />

+                             </Col>

+                         </Row>

+                         <Row title="Sets the amount of time in seconds after which an idle LDAP client connection is closed by the server (nsslapd-idletimeout)." className="ds-margin-top">

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

+                                 Idle Connection Timeout

+                             </Col>

+                             <Col sm={3}>

+                                 <FormControl

+                                     id="nsslapd-idletimeout"

+                                     type="number"

+                                     min="0"

+                                     max="2147483647"

+                                     value={this.state['nsslapd-idletimeout']}

+                                     onChange={this.handleChange}

+                                     className={this.state.errObj['nsslapd-idletimeout'] ? "ds-input-bad" : ""}

+                                 />

+                             </Col>

+                         </Row>

+                         <Row title="Sets the amount of time in milliseconds after which the connection to a stalled LDAP client is closed (nsslapd-ioblocktimeout)." className="ds-margin-top">

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

+                                 I/O Block Timeout

+                             </Col>

+                             <Col sm={3}>

+                                 <FormControl

+                                     id="nsslapd-ioblocktimeout"

+                                     type="number"

+                                     min="0"

+                                     max="2147483647"

+                                     value={this.state['nsslapd-ioblocktimeout']}

+                                     onChange={this.handleChange}

+                                     className={this.state.errObj['nsslapd-ioblocktimeout'] ? "ds-input-bad" : ""}

+                                 />

+                             </Col>

+                         </Row>

+                     </Form>

+                     <CustomCollapse className="ds-margin-left-sm ds-margin-right">

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

+                             <Form horizontal>

+                                 <Row className="ds-margin-top" title="Sets the I/O wait time for all outbound LDAP connections (nsslapd-outbound-ldap-io-timeout).">

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

+                                         Outbound IO Timeout

+                                     </Col>

+                                     <Col sm={3}>

+                                         <FormControl

+                                             type="number"

+                                             min="0"

+                                             max="2147483647"

+                                             id="nsslapd-outbound-ldap-io-timeout"

+                                             value={this.state['nsslapd-outbound-ldap-io-timeout']}

+                                             onChange={this.handleChange}

+                                         />

+                                     </Col>

+                                 </Row>

+                                 <Row className="ds-margin-top" title="The maximum size in bytes allowed for an incoming message (nsslapd-maxbersize).">

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

+                                         Maximum BER Size

+                                     </Col>

+                                     <Col sm={3}>

+                                         <FormControl

+                                             type="number"

+                                             min="1"

+                                             max="2147483647"

+                                             id="nsslapd-maxbersize"

+                                             value={this.state['nsslapd-maxbersize']}

+                                             onChange={this.handleChange}

+                                         />

+                                     </Col>

+                                 </Row>

+                                 <Row className="ds-margin-top" title="The maximum allowed SASL IO packet size that the server will accept (nsslapd-maxsasliosize).">

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

+                                         Maximum SASL IO Size

+                                     </Col>

+                                     <Col sm={3}>

+                                         <FormControl

+                                             type="number"

+                                             min="-1"

+                                             max="2147483647"

+                                             id="nsslapd-maxsasliosize"

+                                             value={this.state['nsslapd-maxsasliosize']}

+                                             onChange={this.handleChange}

+                                         />

+                                     </Col>

+                                 </Row>

+                                 <Row className="ds-margin-top" title="The maximum length for how long the connection queue for the socket can grow before refusing connections (nsslapd-listen-backlog-size).">

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

+                                         Listen Backlog Size

+                                     </Col>

+                                     <Col sm={3}>

+                                         <FormControl

+                                             type="number"

+                                             min="64"

+                                             id="nsslapd-listen-backlog-size"

+                                             value={this.state['nsslapd-listen-backlog-size']}

+                                             onChange={this.handleChange}

+                                         />

+                                     </Col>

+                                 </Row>

+                                 <Row className="ds-margin-top" title="Sets how deep a nested search filter is analysed (nsslapd-max-filter-nest-level).">

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

+                                         Maximum Nested Filter Level

+                                     </Col>

+                                     <Col sm={3}>

+                                         <FormControl

+                                             type="number"

+                                             min="0"

+                                             id="nsslapd-max-filter-nest-level"

+                                             value={this.state['nsslapd-max-filter-nest-level']}

+                                             onChange={this.handleChange}

+                                         />

+                                     </Col>

+                                 </Row>

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

+                                     <Col componentClass={ControlLabel} sm={4} title="Enable the normalized DN cache.  Each thread has its own cache (nsslapd-ndn-cache-enabled).">

+                                         <Checkbox

+                                             checked={this.state['nsslapd-ndn-cache-enabled']}

+                                             id="nsslapd-ndn-cache-enabled"

+                                             onChange={this.handleChange}

+                                         >

+                                             Enable Normalized DN Cache

+                                         </Checkbox>

+                                     </Col>

+                                     <Col sm={4}>

+                                         <div className="ds-inline">

+                                             <FormControl

+                                                 id="nsslapd-ndn-cache-max-size"

+                                                 type="number"

+                                                 min="1048576"

+                                                 max="2147483647"

+                                                 className="ds-input-right"

+                                                 value={this.state['nsslapd-ndn-cache-max-size']}

+                                                 onChange={this.handleChange}

+                                                 title="Per thread NDN cache size in bytes (nsslapd-ndn-cache-max-size)."

+                                             />

+                                         </div>

+                                         <div className="ds-inline ds-left-margin ds-lower-field">

+                                             <font size="2">bytes</font>

+                                         </div>

+                                     </Col>

+                                 </Row>

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

+                                     <Col sm={4} componentClass={ControlLabel} title="Disable DNS reverse entries for outgoing connections (nsslapd-connection-nocanon).">

+                                         <Checkbox

+                                             id="nsslapd-connection-nocanon"

+                                             defaultChecked={this.state['nsslapd-connection-nocanon']}

+                                             onChange={this.handleChange}

+                                         >

+                                             Disable Reverse DNS Lookups

+                                         </Checkbox>

+                                     </Col>

+                                 </Row>

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

+                                     <Col sm={4} componentClass={ControlLabel} title="Sets the worker threads to continuously read a connection without passing it back to the polling mechanism. (nsslapd-enable-turbo-mode).">

+                                         <Checkbox

+                                             id="nsslapd-enable-turbo-mode"

+                                             defaultChecked={this.state['nsslapd-enable-turbo-mode']}

+                                             onChange={this.handleChange}

+                                         >

+                                             Enable Connection Turbo Mode

+                                         </Checkbox>

+                                     </Col>

+                                 </Row>

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

+                                     <Col sm={4} componentClass={ControlLabel} title="Disable the virtual attribute lookup in a search entry (nsslapd-ignore-virtual-attrs).">

+                                         <Checkbox

+                                             id="nsslapd-ignore-virtual-attrs"

+                                             defaultChecked={this.state['nsslapd-ignore-virtual-attrs']}

+                                             onChange={this.handleChange}

+                                         >

+                                             Disable Virtual Attribute Lookups

+                                         </Checkbox>

+                                     </Col>

+                                 </Row>

+                             </Form>

+                         </div>

+                     </CustomCollapse>

+                     <Button

+                         disabled={this.state.saveDisabled}

+                         bsStyle="primary"

+                         className="ds-margin-top-lg ds-margin-left"

+                         onClick={this.saveConfig}

+                     >

+                         Save

+                     </Button>

+                 </div>;

+         }

+ 

+         return (

+             <div id="tuning-content">

+                 <NotificationController

+                     notifications={this.state.notifications}

+                     removeNotificationAction={this.removeNotification}

+                 />

+                 {body}

+             </div>

+         );

+     }

+ }

+ 

+ // Property types and defaults

+ 

+ ServerTuning.propTypes = {

+     serverId: PropTypes.string,

+     attrs: PropTypes.object,

+ };

+ 

+ ServerTuning.defaultProps = {

+     serverId: "",

+     attrs: {},

+ };

@@ -979,7 +979,7 @@ 

                      let attrContent = JSON.parse(content);

                      let attrs = [];

                      for (let content of attrContent['items']) {

-                         attrs.push(content.name);

+                         attrs.push(content.name[0]);

                      }

                      this.setState({

                          attributes: attrs,

@@ -2,24 +2,26 @@ 

  import React from "react";

  import Switch from "react-switch";

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

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

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

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

  import { CertificateManagement } from "./lib/security/certificateManagement.jsx";

  import { SecurityEnableModal } from "./lib/security/securityModals.jsx";

  import { Ciphers } from "./lib/security/ciphers.jsx";

  import {

+     Button,

+     Checkbox,

+     Col,

+     ControlLabel,

+     Form,

+     FormControl,

+     Icon,

      Nav,

      NavItem,

+     Row,

+     Spinner,

      TabContainer,

      TabContent,

      TabPane,

-     Col,

-     Row,

-     ControlLabel,

-     Button,

-     Checkbox,

-     Icon,

-     Spinner

  } from "patternfly-react";

  import PropTypes from "prop-types";

  import "./css/ds.css";
@@ -46,7 +48,6 @@ 

              securityEnabled: false,

              requireSecureBinds: false,

              secureListenhost: false,

-             securePort: '636',

              clientAuth: false,

              checkHostname: false,

              validateCert: '',
@@ -54,11 +55,11 @@ 

              sslVersionMax: '',

              allowWeakCipher: false,

              nssslpersonalityssl: '',

+             nstlsallowclientrenegotiation: true,

              // Original config Settings

              _securityEnabled: false,

              _requireSecureBinds: false,

              _secureListenhost: false,

-             _securePort: '636',

              _clientAuth: false,

              _checkHostname: false,

              _validateCert: '',
@@ -66,6 +67,7 @@ 

              _sslVersionMax: '',

              _allowWeakCipher: false,

              _nssslpersonalityssl: '',

+             _nstlsallowclientrenegotiation: true,

          };

  

          this.handleChange = this.handleChange.bind(this);
@@ -116,6 +118,10 @@ 

          }

      }

  

+     componentDidMount () {

+         this.props.enableTree();

+     }

+ 

      loadSupportedCiphers () {

          const cmd = [

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

                      let validateCert = "warn";

                      let cipherPref = "default";

                      let allowWeak = false;

+                     let renegot = true;

  

+                     if ('nstlsallowclientrenegotiation' in config.items) {

+                         if (config.items['nstlsallowclientrenegotiation'] == "off") {

+                             renegot = false;

+                         }

+                     }

                      if ('nsslapd-security' in attrs) {

                          if (attrs['nsslapd-security'].toLowerCase() == "on") {

                              secEnabled = true;
@@ -320,7 +332,6 @@ 

                              securityEnabled: secEnabled,

                              requireSecureBinds: secReqSecBinds,

                              secureListenhost: attrs['nsslapd-securelistenhost'],

-                             securePort: attrs['nsslapd-secureport'],

                              clientAuth: clientAuth,

                              checkHostname: attrs['nsslapd-ssl-check-hostname'],

                              validateCert: validateCert,
@@ -328,10 +339,11 @@ 

                              sslVersionMax: attrs['sslversionmax'],

                              allowWeakCipher: allowWeak,

                              cipherPref: cipherPref,

+                             nstlsallowclientrenegotiation: renegot,

+                             _nstlsallowclientrenegotiation: renegot,

                              _securityEnabled: secEnabled,

                              _requireSecureBinds: secReqSecBinds,

                              _secureListenhost: attrs['nsslapd-securelistenhost'],

-                             _securePort: attrs['nsslapd-secureport'],

                              _clientAuth: clientAuth,

                              _checkHostname: attrs['nsslapd-ssl-check-hostname'],

                              _validateCert: validateCert,
@@ -513,18 +525,6 @@ 

          if (this.state._clientAuth != this.state.clientAuth) {

              cmd.push("--tls-client-auth=" + this.state.clientAuth);

          }

-         if (this.state._securePort != this.state.securePort) {

-             if (!valid_port(this.state.securePort)) {

-                 this.addNotification(

-                     "error",

-                     `The Secure Port is invalid, it must be a number between 1 and 65535`

-                 );

-                 // Reset page

-                 this.loadSecurityConfig();

-                 return;

-             }

-             cmd.push("--secure-port=" + this.state.securePort);

-         }

          if (this.state._secureListenhost != this.state.secureListenhost) {

              cmd.push("--listen-host=" + this.state.secureListenhost);

          }
@@ -550,6 +550,14 @@ 

              cmd.push("--require-secure-authentication=" + val);

          }

  

+         if (this.state._nstlsallowclientrenegotiation != this.state.nstlsallowclientrenegotiation) {

+             let val = "off";

+             if (this.state.nstlsallowclientrenegotiation) {

+                 val = "on";

+             }

+             cmd.push("--tls-client-renegotiation=" + val);

+         }

+ 

          if (cmd.length > 5) {

              log_cmd("saveSecurityConfig", "Applying security config change", cmd);

              let msg = "Successfully updated security configuration.  You must restart the Directory Server for these changes to take effect.";
@@ -626,26 +634,23 @@ 

              let configPage = "";

              if (this.state.securityEnabled) {

                  configPage =

-                     <div>

-                         <Row className="ds-margin-top" title="The server's secure port number (nsslapd-secureport).">

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

-                                 Server Secure Port

-                             </Col>

-                             <Col sm={4}>

-                                 <input id="securePort" className="ds-input-auto" onChange={this.handleChange} type="text" value={this.state.securePort} />

-                             </Col>

-                         </Row>

+                     <Form horizontal>

                          <Row className="ds-margin-top" title="This parameter can be used to restrict the Directory Server instance to a single IP interface (hostname, or IP address).  This parameter specifically sets what interface to use for TLS traffic.  Requires restart. (nsslapd-securelistenhost).">

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

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

                                  Secure Listen Host

                              </Col>

                              <Col sm={4}>

-                                 <input id="secureListenhost" className="ds-input-auto" type="text" onChange={this.handleChange} value={this.state.secureListenhost} />

+                                 <FormControl

+                                     id="secureListenhost"

+                                     type="text"

+                                     value={this.state.secureListenhost}

+                                     onChange={this.handleChange}

+                                 />

                              </Col>

                          </Row>

                          <Row className="ds-margin-top" title="The name, or nickname, of the server certificate inthe NSS datgabase the server should use (nsSSLPersonalitySSL).">

-                             <Col sm={3}>

-                                 <ControlLabel>Server Certificate Name</ControlLabel>

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

+                                 Server Certificate Name

                              </Col>

                              <Col sm={4}>

                                  <Typeahead
@@ -660,7 +665,7 @@ 

                              </Col>

                          </Row>

                          <Row className="ds-margin-top" title="The minimum SSL/TLS version the server will accept (sslversionmin).">

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

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

                                  Minimum TLS Version

                              </Col>

                              <Col sm={4}>
@@ -674,7 +679,7 @@ 

                              </Col>

                          </Row>

                          <Row className="ds-margin-top" title="The maximum SSL/TLS version the server will accept (sslversionmax).">

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

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

                                  Maximum TLS Version

                              </Col>

                              <Col sm={4}>
@@ -688,7 +693,7 @@ 

                              </Col>

                          </Row>

                          <Row className="ds-margin-top" title="Sets how the Directory Server enforces TLS client authentication (nsSSLClientAuth).">

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

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

                                  Client Authentication

                              </Col>

                              <Col sm={4}>
@@ -700,7 +705,7 @@ 

                              </Col>

                          </Row>

                          <Row className="ds-margin-top" title="Validate server's certificate expiration date (nsslapd-validate-cert).">

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

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

                                  Validate Certificate

                              </Col>

                              <Col sm={4}>
@@ -711,9 +716,8 @@ 

                                  </select>

                              </Col>

                          </Row>

-                         <p />

-                         <Row>

-                             <Col sm={5}>

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

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

                                  <Checkbox

                                      id="requireSecureBinds"

                                      defaultChecked={this.state.requireSecureBinds}
@@ -725,7 +729,7 @@ 

                              </Col>

                          </Row>

                          <Row>

-                             <Col sm={5}>

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

                                  <Checkbox

                                      id="checkHostname"

                                      defaultChecked={this.state.checkHostname}
@@ -737,7 +741,7 @@ 

                              </Col>

                          </Row>

                          <Row>

-                             <Col sm={5}>

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

                                  <Checkbox

                                      id="allowWeakCipher"

                                      defaultChecked={this.state.allowWeakCipher}
@@ -748,17 +752,28 @@ 

                                  </Checkbox>

                              </Col>

                          </Row>

-                         <p />

+                         <Row>

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

+                                 <Checkbox

+                                     id="nstlsallowclientrenegotiation"

+                                     defaultChecked={this.state.nstlsallowclientrenegotiation}

+                                     onChange={this.handleChange}

+                                     title="Allow client-initiated renegotiation (nsTLSAllowClientRenegotiation)."

+                                 >

+                                     Allow Client Renegotiation

+                                 </Checkbox>

+                             </Col>

+                         </Row>

                          <Button

                              bsStyle="primary"

-                             className="ds-margin-top-med"

+                             className="ds-margin-top-lg"

                              onClick={() => {

                                  this.saveSecurityConfig();

                              }}

                          >

                              Save Configuration

                          </Button>

-                     </div>;

+                     </Form>;

              }

  

              securityPage =
@@ -767,6 +782,18 @@ 

                          notifications={this.state.notifications}

                          removeNotificationAction={this.removeNotification}

                      />

+                     <Row>

+                         <Col sm={11}>

+                             <ControlLabel className="ds-suffix-header">

+                                 Security Settings

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

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

+                                     onClick={this.loadSecurityConfig}

+                                 />

+                             </ControlLabel>

+                         </Col>

+                     </Row>

+ 

                      <div className="ds-tab-table">

                          <TabContainer id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>

                              <div>
@@ -785,23 +812,17 @@ 

                                      <TabPane eventKey={1}>

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

                                              <Row>

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

-                                                     Security Enabled

-                                                 </Col>

-                                                 <Col sm={1}>

+                                                 <Col sm={11}>

+                                                     <ControlLabel>

+                                                         Security Enabled

+                                                     </ControlLabel>

                                                      <Switch

-                                                         className="ds-switch"

+                                                         className="ds-switch ds-margin-left-sm ds-lower-field"

                                                          onChange={this.handleSwitchChange}

                                                          checked={this.state.securityEnabled}

                                                          height={20}

                                                      />

                                                  </Col>

-                                                 <Col>

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

-                                                         type="fa" name="refresh" title="Refresh security settings"

-                                                         onClick={this.loadSecurityConfig}

-                                                     />

-                                                 </Col>

                                              </Row>

                                              <hr />

                                              {configPage}

@@ -0,0 +1,345 @@ 

+ import cockpit from "cockpit";

+ import React from "react";

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

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

+ import {

+     TreeView,

+     Spinner,

+ } from "patternfly-react";

+ import PropTypes from "prop-types";

+ import { ServerSettings } from "./lib/server/settings.jsx";

+ import { ServerTuning } from "./lib/server/tuning.jsx";

+ import { ServerSASL } from "./lib/server/sasl.jsx";

+ import { ServerLDAPI } from "./lib/server/ldapi.jsx";

+ import { ServerAccessLog } from "./lib/server/accessLog.jsx";

+ import { ServerAuditLog } from "./lib/server/auditLog.jsx";

+ import { ServerAuditFailLog } from "./lib/server/auditfailLog.jsx";

+ import { ServerErrorLog } from "./lib/server/errorLog.jsx";

+ import { Security } from "./security.jsx";

+ 

+ const treeViewContainerStyles = {

+     width: '295px',

+ };

+ 

+ export class Server extends React.Component {

+     constructor(props) {

+         super(props);

+         this.state = {

+             nodes: [],

+             node_name: "",

+             node_text: "",

+             attrs: [],

+             loaded: false,

+             disableTree: false,

+         };

+ 

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

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

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

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

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

+     }

+ 

+     componentWillMount() {

+         this.loadConfig();

+     }

+ 

+     enableTree() {

+         this.setState({

+             disableTree: false,

+         });

+     }

+ 

+     loadConfig () {

+         let cmd = [

+             "dsconf", "-j", this.props.serverId, "config", "get"

+         ];

+         log_cmd("loadConfig", "Load server configuration", cmd);

+         cockpit

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

+                 .done(content => {

+                     let config = JSON.parse(content);

+                     let attrs = config.attrs;

+                     this.setState({

+                         loaded: true,

+                         attrs: attrs

+                     }, this.loadTree());

+                 })

+                 .fail(err => {

+                     let errMsg = JSON.parse(err);

+                     this.setState({

+                         loaded: true,

+                     });

+                     this.addNotification(

+                         "error",

+                         `Error loading server configuration - ${errMsg.desc}`

+                     );

+                 });

+     }

+ 

+     addNotification(type, message, timerdelay, persistent) {

+         this.setState(prevState => ({

+             notifications: [

+                 ...prevState.notifications,

+                 {

+                     key: prevState.notifications.length + 1,

+                     type: type,

+                     persistent: persistent,

+                     timerdelay: timerdelay,

+                     message: message,

+                 }

+             ]

+         }));

+     }

+ 

+     removeNotification(notificationToRemove) {

+         this.setState({

+             notifications: this.state.notifications.filter(

+                 notification => notificationToRemove.key !== notification.key

+             )

+         });

+     }

+ 

+     loadTree() {

+         let basicData = [

+             {

+                 text: "Server Settings",

+                 selectable: true,

+                 selected: true,

+                 icon: "pficon-settings",

+                 state: {"expanded": true},

+                 id: "settings-config",

+                 nodes: []

+             },

+             {

+                 text: "Tuning & Limits",

+                 selectable: true,

+                 icon: "fa fa-tachometer",

+                 id: "tuning-config",

+                 nodes: []

+             },

+             {

+                 text: "Security",

+                 selectable: true,

+                 icon: "pficon-locked",

+                 id: "security-config",

+                 nodes: []

+             },

+             {

+                 text: "SASL Settings & Mappings",

+                 selectable: true,

+                 icon: "glyphicon glyphicon-map-marker",

+                 id: "sasl-config",

+                 nodes: []

+             },

+             {

+                 text: "LDAPI & Autobind",

+                 selectable: true,

+                 icon: "glyphicon glyphicon-flash",

+                 id: "ldapi-config",

+                 nodes: []

+             },

+             {

+                 text: "Logging",

+                 icon: "pficon-catalog",

+                 selectable: false,

+                 id: "logging-config",

+                 state: {"expanded": true},

+                 nodes: [

+                     {

+                         text: "Access Log",

+                         icon: "glyphicon glyphicon-book",

+                         selectable: true,

+                         id: "access-log-config",

+                         type: "log",

+                     },

+                     {

+                         text: "Audit Log",

+                         icon: "glyphicon glyphicon-book",

+                         selectable: true,

+                         id: "audit-log-config",

+                         type: "log",

+                     },

+                     {

+                         text: "Audit Failure Log",

+                         icon: "glyphicon glyphicon-book",

+                         selectable: true,

+                         id: "auditfail-log-config",

+                         type: "log",

+                     },

+                     {

+                         text: "Errors Log",

+                         icon: "glyphicon glyphicon-book",

+                         selectable: true,

+                         id: "error-log-config",

+                         type: "log",

+                     },

+                 ]

+             },

+         ];

+         this.setState({

+             nodes: basicData,

+             node_name: this.state.node_name,

+         });

+     }

+ 

+     selectNode(selectedNode) {

+         if (selectedNode.selected) {

+             return;

+         }

+         this.setState({

+             disableTree: true, // Disable the tree to allow node to be fully loaded

+         });

+ 

+         this.setState(prevState => {

+             return {

+                 nodes: this.nodeSelector(prevState.nodes, selectedNode),

+                 node_name: selectedNode.id,

+                 node_text: selectedNode.text,

+                 bename: "",

+             };

+         });

+     }

+ 

+     nodeSelector(nodes, targetNode) {

+         return nodes.map(node => {

+             if (node.nodes) {

+                 return {

+                     ...node,

+                     nodes: this.nodeSelector(node.nodes, targetNode),

+                     selected: node.id === targetNode.id ? !node.selected : false

+                 };

+             } else if (node.id === targetNode.id) {

+                 return { ...node, selected: !node.selected };

+             } else if (node.id !== targetNode.id && node.selected) {

+                 return { ...node, selected: false };

+             } else {

+                 return node;

+             }

+         });

+     }

+ 

+     render() {

+         const { nodes } = this.state;

+         let serverPage =

+             <div className="ds-loading-spinner ds-center">

+                 <p />

+                 <h4>Loading server configuration ...</h4>

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

+             </div>;

+ 

+         let server_element = "";

+         let disabled = "tree-view-container";

+         if (this.state.disableTree) {

+             disabled = "tree-view-container ds-disabled";

+         }

+ 

+         if (this.state.loaded) {

+             if (this.state.node_name == "settings-config" || this.state.node_name == "") {

+                 server_element =

+                     <ServerSettings

+                         serverId={this.props.serverId}

+                         attrs={this.state.attrs}

+                         enableTree={this.enableTree}

+                     />;

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

+                 server_element =

+                     <ServerTuning

+                         serverId={this.props.serverId}

+                         attrs={this.state.attrs}

+                         enableTree={this.enableTree}

+                     />;

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

+                 server_element =

+                     <ServerSASL

+                         serverId={this.props.serverId}

+                         enableTree={this.enableTree}

+                     />;

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

+                 server_element =

+                     <Security

+                         serverId={this.props.serverId}

+                         enableTree={this.enableTree}

+                     />;

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

+                 server_element =

+                     <ServerLDAPI

+                         serverId={this.props.serverId}

+                         attrs={this.state.attrs}

+                         enableTree={this.enableTree}

+                     />;

+             } else if (this.state.node_name == "access-log-config") {

+                 server_element =

+                     <ServerAccessLog

+                         serverId={this.props.serverId}

+                         attrs={this.state.attrs}

+                         enableTree={this.enableTree}

+                     />;

+             } else if (this.state.node_name == "audit-log-config") {

+                 server_element =

+                     <ServerAuditLog

+                         serverId={this.props.serverId}

+                         attrs={this.state.attrs}

+                         enableTree={this.enableTree}

+                     />;

+             } else if (this.state.node_name == "auditfail-log-config") {

+                 server_element =

+                     <ServerAuditFailLog

+                         serverId={this.props.serverId}

+                         attrs={this.state.attrs}

+                         enableTree={this.enableTree}

+                     />;

+             } else if (this.state.node_name == "error-log-config") {

+                 server_element =

+                     <ServerErrorLog

+                         serverId={this.props.serverId}

+                         attrs={this.state.attrs}

+                         enableTree={this.enableTree}

+                     />;

+             }

+ 

+             serverPage =

+                 <div className="container-fluid">

+                     <NotificationController

+                         notifications={this.state.notifications}

+                         removeNotificationAction={this.removeNotification}

+                     />

+                     <div className="ds-container">

+                         <div>

+                             <div className="ds-tree">

+                                 <div className={disabled} id="server-tree"

+                                     style={treeViewContainerStyles}>

+                                     <TreeView

+                                         nodes={nodes}

+                                         highlightOnHover

+                                         highlightOnSelect

+                                         selectNode={this.selectNode}

+                                         key={this.state.node_text}

+                                     />

+                                 </div>

+                             </div>

+                         </div>

+                         <div className="ds-tree-content">

+                             {server_element}

+                         </div>

+                     </div>

+                 </div>;

+         }

+ 

+         return (

+             <div>

+                 {serverPage}

+             </div>

+         );

+     }

+ }

+ 

+ // Property types and defaults

+ 

+ Server.propTypes = {

+     serverId: PropTypes.string

+ };

+ 

+ Server.defaultProps = {

+     serverId: ""

+ };

@@ -1,1269 +0,0 @@ 

- 

- <div id="server-content-buttons">

- 

-   <!--

-         General Config Settings

-   -->

-   <div id="server-config" class="all-pages ds-margin-left">

-     <h3 class="ds-config-header">Server Configuration Settings</h3>

-     <div class="ds-container">

-       <div class="ds-inline">

-         <div>

-           <label for="nsslapd-localhost" class="ds-config-label" title="The server's local hostname (nsslapd-localhost).">Server Hostname</label><input

-             type="text" class="ds-input" id="nsslapd-localhost" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-port" class="ds-config-label" title="The server's port number (nsslapd-port).">Server Port</label><input

-             type="text" class="ds-input" id="nsslapd-port" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-listenhost" class="ds-config-label" title=

-             "This parameter can be used to restrict the Directory Server instance to a single IP interface (hostname, or IP address).  Requires restart. (nsslapd-listenhost).">Listen Host Address</label><input

-              type="text" class="ds-input" id="nsslapd-listenhost" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-bakdir" class="ds-config-label" title="The location where database backups are stored (nsslapd-bakdir).">Database Backup Directory</label><input

-             type="text" class="ds-input" id="nsslapd-bakdir" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-ldifdir" class="ds-config-label" title="The location where the server's LDIF files are located (nsslapd-ldifdir).">LDIF File Directory</label><input

-             type="text" class="ds-input" id="nsslapd-ldifdir" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-schemadir" class="ds-config-label" title="The location for the servers custom schema files. (nsslapd-schemadir)">Schema Directory</label><input

-             type="text" class="ds-input" id="nsslapd-schemadir" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-certdir" class="ds-config-label" title="The location of the server's certificates (nsslapd-certdir).">Certificate Directory</label><input

-             type="text" class="ds-input" id="nsslapd-certdir" size="40"/>

-         </div>

-       </div>

-       <div class="ds-divider"></div>

-       <div class="ds-inline">

-         <div>

-           <input type="checkbox" class="ds-config-checkbox" id="nsslapd-disk-monitoring"><label

-             for="nsslapd-disk-monitoring" class="ds-label" title="Enable disk space monitoring (nsslapd-disk-monitoring).">Enable Disk Space Monitoring</label>

-         </div>

-         <div>

-           <label for="nsslapd-disk-monitoring-threshold" class="ds-config-diskmon-label disk-monitoring" title=

-             "The available disk space, in bytes, that will trigger the shutdown process. Default is 2mb. Once below half of the threshold then we enter the shutdown mode. (nsslapd-disk-monitoring-threshold)">

-             Disk Monitoring Threshold</label><input

-             class="ds-input disk-monitoring disk-monitoring" type="text" id="nsslapd-disk-monitoring-threshold" size="10"/>

-         </div>

-         <div>

-           <label for="nsslapd-disk-monitoring-grace-period" class="ds-config-diskmon-label disk-monitoring" title=

-             "How many minutes to wait to allow an admin to clean up disk space before shutting slapd down. The default is 60 minutes. (nsslapd-disk-monitoring-grace-period).">

-             Disk Monitoring Grace Period </label><input

-             class="ds-input disk-monitoring" type="text" id="nsslapd-disk-monitoring-grace-period" size="10"/>

-         </div>

-         <div>

-           <input type="checkbox" class="ds-config-diskmon-checkbox disk-monitoring" id="nsslapd-disk-monitoring-logging-critical"><label

-             for="nsslapd-disk-monitoring-logging-critical" class="ds-label disk-monitoring" title="When disk space gets critically low do not remove logs to free up disk space ().">Preserve Logs</label>

-         </div>

-       </div>

-    </div>

- 

-    <button class="accordion ds-accordion" id="config-accordion" type="button">&#9658 Show Advanced Settings</button>

-    <div class="ds-accordion-panel ds-indent">

-      <div class="ds-container ds-indent">

-        <div class="ds-inline">

-          <div>

-            <input type="checkbox" class="ds-config-checkbox" id="nsslapd-schemacheck" checked><label

-              for="nsslapd-schemacheck" class="ds-label" title="Enable schema checking (nsslapd-schemacheck)."> Enable Schema Checking</label>

-          </div>

-          <div>

-            <input type="checkbox" class="ds-config-checkbox" id="nsslapd-syntaxcheck" checked><label

-              for="nsslapd-syntaxcheck" class="ds-label" title="Enable attribute syntax checking (nsslapd-syntaxcheck)."> Enable Attribute syntax checking</label>

-          </div>

-          <div>

-            <input type="checkbox" class="ds-config-checkbox" id="nsslapd-syntaxlogging" checked><label

-              for="nsslapd-syntaxlogging" class="ds-label" title="Enable syntax logging (nsslapd-syntaxlogging)."> Enable Attribute Syntax Logging</label>

-          </div>

-          <div>

-            <input type="checkbox" class="ds-config-checkbox" id="nsslapd-plugin-logging" checked><label

-              for="nsslapd-plugin-logging" class="ds-label" title="Enable plugins to log access and audit events.  (nsslapd-plugin-logging)."> Enable Plugin Logging</label>

-          </div>

-          <div>

-            <input type="checkbox" class="ds-config-checkbox" id="nsslapd-plugin-binddn-tracking" checked><label

-              for="nsslapd-plugin-binddn-tracking" class="ds-label" title=

-              "Enabling this feature will write new operational attributes to the modified entry: internalModifiersname & internalCreatorsname. These new attributes contain the plugin DN, while modifiersname will be the original binding entry that triggered the update. (nsslapd-plugin-binddn-tracking)."> Enable Plugin Bind DN Tracking </label>

-          </div>

-        </div>

-        <div class="ds-divider"></div>

-        <div class="ds-divider"></div>

-        <div class="ds-divider"></div>

-        <div class="ds-divider"></div>

-        <div class="ds-inline">

-          <div>

-            <input type="checkbox" class="ds-config-checkbox" id="nsslapd-attribute-name-exceptions"><label

-              for="nsslapd-attribute-name-exceptions" class="ds-label" title="Allows non-standard characters in attribute names to be used for backwards compatibility with older servers"> Allow Attribute Naming Exceptions </label>

-          </div>

-          <div>

-            <input type="checkbox" class="ds-config-checkbox" id="nsslapd-dn-validate-strict"><label

-              for="nsslapd-dn-validate-strict" class="ds-label" title="Enables strict syntax validation for DNs, according to section 3 in RFC 4514 (nsslapd-dn-validate-strict)."> Enable Strict DN Syntax Validation</label>

-          </div>

-          <div>

-            <input type="checkbox" class="ds-config-checkbox" id="nsslapd-entryusn-global"><label

-              for="nsslapd-entryusn-global" class="ds-label" title="For USN plugin - maintain unique USNs across all back end databases (nsslapd-entryusn-global)."> Enable Unique USNs Across All Backends</label>

-          </div>

-          <div>

-            <input type="checkbox" class="ds-config-checkbox" id="nsslapd-ignore-time-skew"><label

-              for="nsslapd-ignore-time-skew" class="ds-label" title="Ignore time skew when generating CSNs"> Ignore CSN Time Skew</label>

-          </div>

-          <div>

-            <input type="checkbox" class="ds-config-checkbox" id="nsslapd-readonly"><label

-              for="nsslapd-readonly" class="ds-label" title="Make entire server read-only (nsslapd-readonly)"> Server Read-Only</label>

-          </div>

- 

-        </div>

-      </div>

-      <div>

-        <label for="nsslapd-allow-anonymous-access" class="ds-config-label" title="Allow anonymous binds to the server (nsslapd-allow-anonymous-access)."> Allow Anonymous Access</label><select

-           class="btn btn-default dropdown" id="nsslapd-allow-anonymous-access">

-             <option>on</option>

-             <option>off</option>

-             <option title="Allows anonymous search and read access to search the root DSE itself, but restricts access to all other directory entries. ">rootdse</option>

-        </select>

-      </div>

-      <div>

-        <label for="nsslapd-anonlimitsdn" class="ds-config-label" title=

-          "The DN of a template entry containing the resource limits to apply to anonymous connections (nsslapd-anonlimitsdn).">Anonymous Resource Limits DN</label><input

-          class="ds-input" type="text" id="nsslapd-anonlimitsdn" size="40"/>

-      </div>

- 

-      <hr>

-      <div class="ds-inline">

-        <form>

-          <div>

-            <label for="nsslapd-rootdn" class="ds-config-label" title="The DN of the unrestricted directory manager (nsslapd-rootdn).">Directory Manager DN</label><input

-              class="ds-input" type="text" disabled id="nsslapd-rootdn" value="cn=Directory Manager" size="40"/>

-          </div>

-          <div>

-            <label for="nsslapd-rootpw" class="ds-config-label" title="The Directory Manager password (nsslapd-rootpw).">Directory Manager Password</label><input

-              class="ds-input" type="password" id="nsslapd-rootpw" size="40"/>

-          </div>

-          <div>

-            <label for="nsslapd-rootpw-confirm" class="ds-config-label" title="Confirm directory manager password.">Confirm Password</label><input

-              class="ds-input" type="password" id="nsslapd-rootpw-confirm" size="40"/>

-          </div>

-          <div>

-            <label for="nsslapd-rootpwstoragescheme" class="ds-config-label" title="Set the Directory Manager password storage scheme (nsslapd-rootpwstoragescheme).">Password Storage Scheme</label><select

-              class="btn btn-default dropdown" id="nsslapd-rootpwstoragescheme">

-                <option>PBKDF2_SHA256</option>

-                <option>SSHA512</option>

-                <option>SSHA384</option>

-                <option>SSHA256</option>

-                <option>SSHA</option>

-                <option>MD5</option>

-                <option>SMD5</option>

-                <option>CRYPT-MD5</option>

-                <option>CRYPT-SHA512</option>

-                <option>CRYPT-SHA256</option>

-                <option>CRYPT</option>

-                <option>CLEAR</option>

-              </select>

-           </div>

-         </form>

-       </div>

-     </div>

-     <p></p>

- 

-     <div class="ds-footer">

-       <button class="btn btn-primary save-button">Save Configuration</button>

-     </div>

-   </div>

- 

- 

-   <!--

-         SASL Settings

-   -->

-   <div id="server-sasl" class="all-pages ds-margin-left" hidden>

-     <h3 class="ds-config-header">SASL Settings</h3>

-     <div class="ds-inline">

-       <div>

-         <label for="nsslapd-sasl-max-buffer-size" class="ds-config-label" title="The maximum SASL buffer size in bytes (nsslapd-sasl-max-buffer-size).">Max SASL Buffer Size</label><input

-           class="ds-input" type="text" id="nsslapd-sasl-max-buffer-size" size="40"/>

-       </div>

-       <div>

-         <label for="nsslapd-allowed-sasl-mechanisms" class="ds-config-label" title="A list of SASL mechanisms the server will only accept (nsslapd-allowed-sasl-mechanisms).">Allowed SASL Mechanisms</label><input

-           class="ds-input" type="text" id="nsslapd-allowed-sasl-mechanisms" size="40"/>

-       </div>

-       <div>

-         <input type="checkbox" class="ds-config-checkbox" id="nsslapd-sasl-mapping-fallback" checked><label

-           for="nsslapd-sasl-mapping-fallback" class="ds-label" title="Check all sasl mappings until one succeeds or they all fail (nsslapd-sasl-mapping-fallback)."> Allow SASL Mapping Fallback</label>

-       </div>

-     </div>

-     <div class="ds-footer">

-       <button class="btn btn-primary save-button">Save</button>

-     </div>

- 

-     <h4 class="ds-config-header"><br>SASL Mappings</h4>

-     <div class="ds-indent">

-       <table id="sasl-table" class="display ds-sasl-table" cellspacing="0" width="100%">

-         <thead>

-           <tr class="ds-table-header">

-             <th>Mapping Name</th>

-             <th>Regular Expression</th>

-             <th>Search Base DN</th>

-             <th>Search Filter</th>

-             <th title="Requires 'nsslapd-sasl-mapping-fallback' to “on”">Priority</th>

-             <th>Action</th>

-         </thead>

-         <tbody id="sasl-mappings">

-         </tbody>

-       </table>

-       <button class="btn btn-default" data-toggle="modal" data-target="#sasl-map-form" id="create-sasl-map-btn">Create SASL Mapping</button>

-     </div>

-   </div>

- 

- 

-   <!--

-         Password Policy Settings -> for REACT JS this should be tabbed navigation

-   -->

-   <div id="global-password-policy" class="all-pages ds-margin-left" hidden>

-     <h3 class="ds-config-header">Global Password Policy Settings</h3>

-       <div class="ds-container">

-         <div class="ds-split">

-           <div class="ds-inline">

-             <h4>General Settings</h4>

-             <hr class="ds-hr">

-             <div>

-               <label for="passwordstoragescheme" class="ds-spacing-sm" title="Set the password storage scheme (passwordstoragescheme).">Password Storage Scheme</label><select

-                 class="btn btn-default dropdown" id="passwordstoragescheme">

-                   <option>PBKDF2_SHA256</option>

-                   <option>SSHA512</option>

-                   <option>SSHA384</option>

-                   <option>SSHA256</option>

-                   <option>SSHA</option>

-                   <option>NS-MTA-MD5</option>

-                   <option>MD5</option>

-                   <option>SMD5</option>

-                   <option>CRYPT-MD5</option>

-                   <option>CRYPT-SHA512</option>

-                   <option>CRYPT-SHA256</option>

-                   <option>CRYPT</option>

-                   <option>CLEAR</option>

-                 </select>

-             </div>

-             <div>

-               <input type="checkbox" class="ds-config-checkbox" id="nsslapd-pwpolicy-local" checked><label

-                 for="nsslapd-pwpolicy-local" class="ds-label" title="Allow subtree/user defined password policies (nsslapd-pwpolicy-local)."> Allow Local Password Policies</label>

-             </div>

-             <div>

-               <input type="checkbox" class="ds-config-checkbox" id="nsslapd-pwpolicy-inherit-global" checked><label

-                 for="nsslapd-pwpolicy-inherit-global" class="ds-label" title=

-                 "If a local password policy does not defined any syntax rules then inherit the local policy syntax (nsslapd-pwpolicy-inherit-global)."> Local Policy Inherits Global Policy</label>

-             </div>

-             <div>

-               <input type="checkbox" class="ds-config-checkbox" id="nsslapd-allow-hashed-passwords"><label

-                 for="nsslapd-allow-hashed-passwords" class="ds-label" title="Allow anyone to add a prehashed password (nsslapd-allow-hashed-passwords)."> Allow Adding Pre-Hashed Passwords</label>

-             </div>

-             <div>

-               <input type="checkbox" class="ds-config-checkbox" id="passwordisglobalpolicy" checked><label

-                 for="passwordisglobalpolicy" class="ds-label" title="Allow password policy state attributes to replicate (passwordIsGlobalPolicy)."> Replicate Password Policy State Attributes</label>

-             </div>

-             <div>

-               <input type="checkbox" class="ds-config-checkbox" id="passwordtrackupdatetime" checked><label

-                 for="passwordtrackupdatetime" class="ds-label" title=

-                 "Record a separate timestamp specifically for the last time that the password for an entry was changed. If this is enabled, then it adds the pwdUpdateTime operational attribute to the user account entry (passwordTrackUpdateTime)."> Track Password Update Time</label>

-             </div>

-             <div>

-               <p></p>

-               <label for="passwordAdminDN" title="The DN for a password administrator or administrator group (passwordAdminDN)">Password Administrator</label><input

-                 class="ds-input" type="text" id="passwordAdminDN" size="40"/>

-             </div>

-           </div>

-         </div>

-         <div class="ds-divider"></div>

-         <div class="ds-divider"></div>

-         <div class="ds-split">

-           <div class="ds-inline">

-             <h4>User Password Settings</h4>

-             <hr class="ds-hr">

-             <div>

-               <input type="checkbox" class="ds-config-checkbox" id="passwordchange" checked><label

-                 for="passwordchange" class="ds-label" title="Allow user's to change their passwords (passwordChange)."> Allow Users To Change Their Passwords</label>

-             </div>

-             <div>

-               <input type="checkbox" class="ds-config-checkbox" id="passwordmustchange" checked><label

-                 for="passwordmustchange" class="ds-label" title="User must change its password after its been reset by an administrator (passwordMustChange).">User Must Change Password After Reset</label>

-             </div>

-             <div>

-               <input type="checkbox" class="ds-config-checkbox" id="passwordhistory" checked><label

-                 for="passwordhistory" class="ds-label" title="Maintain a password history (passwordHistory).">Keep Password History</label>

-             </div>

-             <div>

-               <input class="ds-history-input" type="text" id="passwordinhistory" size="2"/>Passwords In History

-               <label for="passwordminage" class="ds-minage-label" title="Indicates the number of seconds that must pass before a user can change their password. (passwordMinAge)">Allow Password Changes (in seconds)</label><input

-                class="ds-input" type="text" id="passwordminage" size="10"/>

-             </div>

-           </div>

-         </div>

-       </div>

-       <div class="ds-container">

-         <div class="ds-split">

-           <h4><br>Password Expiration Settings</h4>

-           <hr class="ds-hr">

-           <input type="checkbox" class="ds-config-checkbox" id="passwordexp" checked><label

-             for="passwordexp" class="ds-label" title="Enable a password expiration policy (passwordExp).">Enable Password Expiration</label>

-           <div class="ds-inline">

-             <div class="ds-expired-div" id="expiration-attrs">

-               <div>

-                 <label for="passwordmaxage" class="ds-expire-label" title="The server's local hostname (passwordMaxAge).">Password Expiration Time (in seconds)</label><input

-                   class="ds-input" type="text" id="passwordmaxage" size="6"/>

-               </div>

-               <div>

-                 <label for="passwordgracelimit" class="ds-expire-label" title="The server's local hostname (passwordGraceLimit).">Allowed Logins After Password Expires</label><input

-                   class="ds-input" type="text" id="passwordgracelimit" size="6"/>

-               </div>

-               <div>

-                 <label for="passwordwarning" class="ds-expire-label" title="Set the time (in seconds), before a password is about to expire, to send a warning. (passwordWarning).">Send Password Expiring Warning (in seconds)</label><input

-                   class="ds-input" type="text" id="passwordwarning" size="6"/>

-               </div>

-               <div>

-                 <input type="checkbox" class="ds-send-expiring-checkbox" id="passwordsendexpiringtime"><label

-                   for="passwordsendexpiringtime" class="ds-label" title="Always return a password expiring control when requested (passwordSendExpiringTime).">Always Send <i>Password Expiring</i> Control</label>

-               </div>

-             </div>

-           </div>

-         </div>

-         <div class="ds-divider"></div>

-         <div class="ds-divider"></div>

-         <div class="ds-split">

-           <h4><br>Account Lockout Settings</h4>

-           <hr class="ds-hr">

-           <input type="checkbox" class="ds-config-checkbox" id="passwordlockout" checked><label

-             for="passwordlockout" class="ds-label" title="Enable account lockout (passwordLockout).">Enable Account Lockout</label>

-           <div class="ds-expired-div" id="lockout-attrs">

-             <label for="passwordmaxfailure" class="ds-expire-label" title=

-               "The maximum number of failed logins before account gets locked (passwordMaxFailure).">Number of Failed Logins to Lockout Account</label><input

-               class="ds-input" type="text" id="passwordmaxfailure" size="5"/>

-             <label for="passwordresetfailurecount" class="ds-expire-label" title=

-               "The number of seconds until an accounts failure count is reset (passwordResetFailureCount).">Time Before Failure Count Reset </label><input

-               class="ds-input" type="text" id="passwordresetfailurecount" size="5"/>

-             <div>

-               <label title="Lock out the account forever (passwordUnlock)."><input

-                 class="ds-radio" type="radio" id="passwordunlock" value="passwordunlock" name="account-lockout" checked="checked"> Lockout Account Forever</label>

-               <label title="The number of seconds before account gets unlocked (passwordLockoutDuration)."><input

-                 class="ds-radio" type="radio" name="account-lockout" value="passwordlockoutduration"> Time Until Account Unlocked <input

-                 class="ds-input" type="text" id="passwordlockoutduration" size="5"/></label>

-             </div>

-           </div>

-           <p></p>

-         </div>

-       </div>

- 

-       <div class="ds-inline">

-         <h4><br>Password Syntax Settings</h4>

-         <hr class="ds-hr">

-         <input type="checkbox" class="ds-config-checkbox" id="passwordchecksyntax" checked><label

-           for="passwordchecksyntax" class="ds-label" title="Enable account lockout (passwordCheckSyntax).">Check Password Syntax</label>

-         <div class="ds-container ds-expired-div" id="syntax-attrs">

-           <div class="ds-inline">

-             <div>

-               <label for="passwordminlength" class="ds-expire-label" title=

-                 "The minimum number of characters in the password (passwordMinLength).">Password Minimum Length </label><input

-                 class="ds-input" type="text" id="passwordminlength" size="5"/>

-             </div>

-             <div>

-               <label for="passwordmindigits" class="ds-expire-label" title=

-                 "Reject passwords with fewer than this many digit characters (0-9) (passwordMinDigits).">Minimum Digit Characters </label><input

-                 class="ds-input" type="text" id="passwordmindigits" size="5"/>

-             </div>

-             <div>

-               <label for="passwordminalphas" class="ds-expire-label" title=

-                 "Reject passwords with fewer than this many alpha characters (passwordMinAlphas).">Minimum Alpha Characters </label><input

-                 class="ds-input" type="text" id="passwordminalphas" size="5"/>

-             </div>

-             <div>

-               <label for="passwordminuppers" class="ds-expire-label" title=

-                 "Reject passwords with fewer than this many uppercase characters (passwordMinUppers).">Minimum Uppercase Characters </label><input

-                 class="ds-input" type="text" id="passwordminuppers" size="5"/>

-             </div>

-             <div>

-               <label for="passwordminlowers" class="ds-expire-label" title=

-                 "Reject passwords with fewer than this many lowercase characters (passwordMinLowers).">Minimum Lowercase Characters </label><input

-                 class="ds-input" type="text" id="passwordminlowers" size="5"/>

-             </div>

-             <div>

-               <label for="passwordminspecials" class="ds-expire-label" title=

-                 "Reject passwords with fewer than this many special non-alphanumeric characters (passwordMinSpecials).">Minimum Special Characters </label><input

-                 class="ds-input" type="text" id="passwordminspecials" size="5"/>

-             </div>

-             <div>

-               <label for="passwordmin8bit" class="ds-expire-label" title=

-                 "Reject passwords with fewer than this many 8-bit or multi-byte characters (passwordMin8Bit).">Minimum 8-bit Characters </label><input

-                 class="ds-input" type="text" id="passwordmin8bit" size="5"/>

-             </div>

-             <div>

-               <label for="passwordmincategories" class="ds-expire-label" title=

-                 "The minimum number of character categories that a password must contain (categories are upper, lower, digit, special, and 8-bit) (passwordMinCategories).">Minimum Required Character Categories </label><input

-                 class="ds-input" type="text" id="passwordmincategories" size="5"/>

-             </div>

-             <div>

-               <label for="passwordmintokenlength" class="ds-expire-label" title=

-                 "The smallest attribute value used when checking if the password contains any of the user's account information (passwordMinTokenLength).">Minimum Token Length </label><input

-                 class="ds-input" type="text" id="passwordmintokenlength" size="5"/>

-             </div>

-           </div>

-           <div class="ds-divider"></div>

-           <div class="ds-divider"></div>

-           <div class="ds-inline">

-             <div>

-               <label for="passwordmaxrepeats" class="ds-expire-label" title=

-                 "The maximum number of times the same character can sequentially appear in a password (passwordMaxRepeats).">Maximum Number Of Repeated Characters </label><input

-                 class="ds-input" type="text" id="passwordmaxrepeats" size="5"/>

-             </div>

-             <div>

-               <label for="passwordmaxsequence" class="ds-expire-label" title=

-                 "The maximum number of allowed monotonic characters sequences (passwordMaxSequence).">Maximum Character Sequences </label><input

-                 class="ds-input" type="text" id="passwordmaxsequence" size="5"/>

-             </div>

-             <div>

-               <label for="passwordmaxseqsets" class="ds-expire-label" title=

-                 "The maximum number of allowed monotonic characters sequences that can appear more than once (passwordMaxSeqSets).">Maximum Character Sequence Sets </label><input

-                 class="ds-input" type="text" id="passwordmaxseqsets" size="5"/>

-             </div>

-             <div>

-               <label for="passwordmaxclasschars" class="ds-expire-label" title=

-                 "The maximum number of consecutive characters from the same character class/category (passwordMaxClassChars).">Maximum Consecutive Chars Per Char Class </label><input

-                 class="ds-input" type="text" id="passwordmaxclasschars" size="5"/>

-             </div>

-             <div>

-               <label for="passwordpalindrome" class="ds-expire-label" title=

-                 "Reject a password if it is a palindrome (passwordPalindrome).">Reject Passwords that Are Palindromes </label><input

-                 class="ds-margin-top" type="checkbox" id="passwordpalindrome"/>

-             </div>

-             <div>

-               <label for="passworddictcheck" class="ds-expire-label" title=

-                 "Check the password against the system's CrackLib dictionary (passwordDictCheck).">Check Password Contains Dictionary Word </label><input

-                 class="ds-margin-top" type="checkbox" id="passworddictcheck"/>

-             </div>

-           </div>

-         </div>

-         <div class="ds-margin-left-sm ds-margin-top">

-             <div>

-               <label for="passwordbadwords" title=

-                 "A space-separated list of words that are not allowed to be contained in the new password (passwordBadWords).">Reject Passwords That Contain These Words </label><input

-                 class="ds-input-auto" type="text" id="passwordbadwords"/>

-             </div>

-             <div class="ds-margin-top">

-               <label for="passworduserattributes" title=

-                 "A space-separated list of entry attributes to compare to the new password (passwordUserAttributes).">Entry Attributes To Compare </label><input

-                 class="ds-input-auto" type="text" id="passworduserattributes"/>

-             </div>

-         </div>

-         <div class="ds-footer">

-           <button class="btn btn-primary save-button">Save</button>

-         </div>

-       </div>

-     </div>

- 

-     <!-- -------------------------------------

-     Local Password Policies

-     ---------------------------------------- -->

-     <div id="local-password-policy" class="all-pages ds-margin-left" hidden>

-       <h3 class="ds-config-header">Local Password Policies  </h3>

-       <label for="local-pwp-suffix">Database Suffix</Label> <select

-         class="btn btn-default dropdown" id="local-pwp-suffix">

-       </select>

-       <div class="ds-page-content">

-         <table id="passwd-policy-table" class="display ds-repl-table" cellspacing="0" width="100%">

-           <thead>

-             <tr class="ds-table-header">

-               <th>Policy Target Entry</th>

-               <th>Policy Type</th>

-               <th></th>

-             </tr>

-           </thead>

-           <tbody id="local-pwpolicy-tbody">

-           </tbody>

-         </table>

-         <button class="btn btn-primary" data-toggle="modal" data-target="#local-pwp-form" id="create-local-pwp-btn">Create Local Password Policy</button>

-         <p></p>

-       </div>

-     </div>

- 

- 

-   <!--

-         Logging Settings

-   -->

- 

-   <!-- Access logging -->

-   <div id="server-access-log" class="all-pages ds-margin-left" hidden>

-     <h3 class="ds-config-header">Access Log Settings</h3>

-       <input type="checkbox" class="ds-config-checkbox" id="nsslapd-accesslog-logging-enabled"><label

-         for="nsslapd-accesslog-logging-enabled" class="ds-label" title="Enable access logging (nsslapd-accesslog-logging-enabled)."> Enable Access Logging</label>

-       <div class="ds-expired-div" id="accesslog-attrs">

-         <div class="ds-inline">

-           <div>

-             <label for="nsslapd-accesslog" class="ds-config-label" title="The access log location and name (nsslapd-accesslog).">Access Log Location</label><input

-               class="ds-input" type="text" id="nsslapd-accesslog" size="40"/>

-           </div>

-           <div>

-             <input type="checkbox" class="ds-server-checkbox" id="nsslapd-accesslog-logbuffering"><label

-               for="nsslapd-accesslog-logbuffering" class="ds-label" title="Disable access log buffering for faster troubleshooting (nsslapd-accesslog-logbuffering)."> Disable Access Log Buffering</label>

-           </div>

-         </div>

-         <p></p>

-         <h4 class="ds-sub-header">Rotation Policy</h4>

-         <hr class="ds-hr-logs">

-         <div class="ds-inline">

-           <div>

-             <label for="nsslapd-accesslog-maxlogsperdir" class="ds-config-sub-label" title="The maximum number of logs that are archived (nsslapd-accesslog-maxlogsperdir).">Maximum Number Of Logs</label><input

-               class="ds-input" type="text" id="nsslapd-accesslog-maxlogsperdir" size="40"/>

-           </div>

-           <div>

-             <label for="nsslapd-accesslog-maxlogsize" class="ds-config-sub-label" title="The maximum size of each log file in megabytes (nsslapd-accesslog-maxlogsize).">Maximum Log Size (in MB)</label><input

-               class="ds-input" type="text" id="nsslapd-accesslog-maxlogsize" size="40"/>

-           </div>

-           <div>

-             <label for="nsslapd-accesslog-logrotationtime" class="ds-config-sub-label" title="Access log rotation time settings (nsslapd-accesslog-logrotationtime).">Create New Log Every...</label><input

-               class="ds-input" type="text" id="nsslapd-accesslog-logrotationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-accesslog-logrotationtimeunit">

-                 <option>minute</option>

-                 <option>hour</option>

-                 <option>day</option>

-                 <option>week</option>

-                 <option>month</option>

-               </select> at <input class="ds-input" type="text" title="Hour" id="nsslapd-accesslog-logrotationsynchour" placeholder="0" size="1"/> : <input class="ds-input" type="text" placeholder="0"

-                 title="Minute" id="nsslapd-accesslog-logrotationsyncmin" size="1"/>

-           </div>

-         </div>

-         <p></p>

-         <h4 class="ds-sub-header">Deletion Policy</h4>

-         <hr class="ds-hr-logs">

-         <div class="ds-inline">

-           <div>

-             <label for="nsslapd-accesslog-logmaxdiskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-accesslog-logmaxdiskspace).">Total Log Archive Exceeds (in MB)</label><input

-               class="ds-input" type="text" id="nsslapd-accesslog-logmaxdiskspace" size="40"/>

-           </div>

-           <div>

-             <label for="nsslapd-accesslog-logminfreediskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-accesslog-logminfreediskspace).">Free Disk Space (in MB)</label><input

-               class="ds-input" type="text" id="nsslapd-accesslog-logminfreediskspace" size="40"/>

-           </div>

-           <div>

-             <label for="nsslapd-accesslog-logexpirationtime" class="ds-config-sub-label" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-accesslog-logexpirationtime).">Log File is Older Than... </label><input

-               class="ds-input" type="text" id="nsslapd-accesslog-logexpirationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-accesslog-logexpirationtimeunit">

-                 <option>day</option>

-                 <option>week</option>

-                 <option>month</option>

-               </select>

-           </div>

-         </div>

-         <p></p>

-         <h4 class="ds-sub-header">Access Logging Levels</h4>

-         <hr class="ds-hr-logs">

- 

- 

-         <table class="table table-striped table-bordered table-hover ds-loglevel-table" id="accesslog-level-table">

-           <thead>

-             <tr>

-               <th class="ds-table-checkbox"></th>

-               <th>Logging Level</th>

-             </tr>

-           </thead>

-           <tbody>

-             <tr>

-              <td class="ds-table-checkbox"><input class="ds-accesslog-table" id="accesslog-256" type="checkbox"></td>

-              <td>Default Logging</td>

-             </tr>

-             <tr>

-              <td class="ds-table-checkbox"><input class="ds-accesslog-table"  id="accesslog-4"type="checkbox"></td>

-              <td>Internal Operations</td>

-             </tr>

-             <tr>

-              <td class="ds-table-checkbox"><input class="ds-accesslog-table" id="accesslog-512" type="checkbox"></td>

-              <td>Entry Access and Referrals</td>

-             </tr>

-           <tbody>

-         </table>

-       </div>

-     <div class="ds-footer">

-       <button class="btn btn-primary save-button">Save</button>

-     </div>

-   </div>

- 

-   <div id="server-audit-log" class="all-pages ds-margin-left" hidden>

-     <h3 class="ds-config-header">Audit Log Settings</h3>

-     <input type="checkbox" class="ds-config-checkbox" id="nsslapd-auditlog-logging-enabled"><label

-       for="nsslapd-auditlog-logging-enabled" class="ds-label" title="Enable audit logging (nsslapd-auditlog-logging-enabled)."> Enable Audit Logging</label>

-     <div class="ds-expired-div" id="auditlog-attrs">

-       <div class="ds-inline">

-         <div>

-           <label for="nsslapd-auditlog" class="ds-config-label" title="The audit log location and name (nsslapd-auditlog).">Audit Log Location</label><input

-             class="ds-input" type="text" id="nsslapd-auditlog" size="40"/>

-         </div>

-       </div>

-       <p></p>

-       <h4 class="ds-sub-header">Rotation Policy</h4>

-       <hr class="ds-hr-logs">

-       <div class="ds-inline">

-         <div>

-           <label for="nsslapd-auditlog-maxlogsperdir" class="ds-config-sub-label" title="The maximum number of logs that are archived (nsslapd-auditlog-maxlogsperdir).">Maximum Number Of Logs</label><input

-             class="ds-input" type="text" id="nsslapd-auditlog-maxlogsperdir" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-auditlog-maxlogsize" class="ds-config-sub-label" title="The maximum size of each log file in megabytes (nsslapd-auditlog-maxlogsize).">Maximum Log Size (in MB)</label><input

-             class="ds-input" type="text" id="nsslapd-auditlog-maxlogsize" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-auditlog-logrotationtime" class="ds-config-sub-label" title="Audit log rotation time settings (nsslapd-auditlog-logrotationtime).">Create New Log Every...</label><input

-             class="ds-input" type="text" id="nsslapd-auditlog-logrotationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-auditlog-logrotationtimeunit">

-               <option>minute</option>

-               <option>hour</option>

-               <option>day</option>

-               <option>week</option>

-               <option>month</option>

-             </select> at <input class="ds-input" type="text"  title="Hour" id="nsslapd-auditlog-logrotationsynchour" placeholder="0" size="1"/> : <input class="ds-input" type="text" placeholder="0"

-               title="Minute" id="nsslapd-auditlog-logrotationsyncmin" size="1"/>

-         </div>

-       </div>

-       <p></p>

-       <h4 class="ds-sub-header">Deletion Policy</h4>

-       <hr class="ds-hr-logs">

-       <div class="ds-inline">

-         <div>

-           <label for="nsslapd-auditlog-logmaxdiskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-auditlog-logmaxdiskspace).">Total Log Archive Exceeds (in MB)</label><input

-             class="ds-input" type="text" id="nsslapd-auditlog-logmaxdiskspace" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-auditlog-logminfreediskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-auditlog-logminfreediskspace).">Free Disk Space (in MB)</label><input

-             class="ds-input" type="text" id="nsslapd-auditlog-logminfreediskspace" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-auditlog-logexpirationtime" class="ds-config-sub-label" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-auditlog-logexpirationtime).">Log File is Older Than... </label><input

-             class="ds-input" type="text" id="nsslapd-auditlog-logexpirationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-auditlog-logexpirationtimeunit">

-               <option>day</option>

-               <option>week</option>

-               <option>month</option>

-             </select>

-         </div>

-       </div>

-     </div>

-     <div class="ds-footer">

-       <button class="btn btn-primary save-button">Save</button>

-     </div>

-   </div>

- 

-     <!-- Auditfail logging -->

-   <div id="server-auditfail-log" class="all-pages ds-margin-left" hidden>

-     <h3 class="ds-config-header">Audit Failure Log Settings</h3>

-     <input type="checkbox" class="ds-config-checkbox" id="nsslapd-auditfaillog-logging-enabled"><label

-       for="nsslapd-auditfaillog-logging-enabled" class="ds-label" title="Enable audit failure logging (nsslapd-auditfaillog-logging-enabled)."> Enable Audit Failure Logging</label>

-     <div class="ds-expired-div" id="auditfaillog-attrs">

-       <label for="nsslapd-auditfaillog" class="ds-config-label" title="The audit failure log location and name (nsslapd-auditfaillog).">Audit Failure Log Location</label><input

-         class="ds-input" type="text" id="nsslapd-auditfaillog" size="40"/>

-       <p></p>

-       <h4 class="ds-sub-header">Rotation Policy</h4>

-       <hr class="ds-hr-logs">

-       <div class="ds-inline">

-         <div>

-           <label for="nsslapd-auditfaillog-maxlogsperdir" class="ds-config-sub-label" title="The maximum number of logs that are archived (nsslapd-auditfaillog-maxlogsperdir).">Maximum Number Of Logs</label><input

-             class="ds-input" type="text" id="nsslapd-auditfaillog-maxlogsperdir" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-auditfaillog-maxlogsize" class="ds-config-sub-label" title="The maximum size of each log file in megabytes (nsslapd-auditfaillog-maxlogsize).">Maximum Log Size (in MB)</label><input

-             class="ds-input" type="text" id="nsslapd-auditfaillog-maxlogsize" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-auditfaillog-logrotationtime" class="ds-config-sub-label" title="Audit failure log rotation time settings (nsslapd-auditlog-logrotationtime).">Create New Log Every...</label><input

-             class="ds-input" type="text" id="nsslapd-auditfaillog-logrotationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-auditfaillog-logrotationtimeunit">

-               <option>minute</option>

-               <option>hour</option>

-               <option>day</option>

-               <option>week</option>

-               <option>month</option>

-             </select> at <input class="ds-input" type="text"  title="Hour" id="nsslapd-auditfaillog-logrotationsynchour" placeholder="0" size="1"/> : <input class="ds-input" type="text" placeholder="0"

-               title="Minute" id="nsslapd-auditfaillog-logrotationsyncmin" size="1"/>

-         </div>

-       </div>

-       <h4 class="ds-sub-header">Deletion Policy</h4>

-       <hr class="ds-hr-logs">

-       <div class="ds-inline">

-         <div>

-           <label for="nsslapd-auditfaillog-logmaxdiskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-auditfaillog-logmaxdiskspace).">Total Log Archive Exceeds (in MB)</label><input

-             class="ds-input" type="text" id="nsslapd-auditfaillog-logmaxdiskspace" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-auditfaillog-logminfreediskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-auditfaillog-logminfreediskspace).">Free Disk Space (in MB)</label><input

-             class="ds-input" type="text" id="nsslapd-auditfaillog-logminfreediskspace" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-auditfaillog-logexpirationtime" class="ds-config-sub-label" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-auditfaillog-logexpirationtime).">Log File is Older Than... </label><input

-             class="ds-input" type="text" id="nsslapd-auditfaillog-logexpirationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-auditfaillog-logexpirationtimeunit">

-               <option>day</option>

-               <option>week</option>

-               <option>month</option>

-             </select>

-         </div>

-       </div>

-     </div>

-     <div class="ds-footer">

-       <button class="btn btn-primary save-button">Save</button>

-     </div>

-   </div>

- 

-   <!-- Error logging -->

-   <div id="server-errors-log" class="all-pages ds-margin-left" hidden>

-     <h3 class="ds-config-header">Error Log Settings</h3>

-     <input type="checkbox" class="ds-config-checkbox" id="nsslapd-errorlog-logging-enabled" checked><label

-       for="nsslapd-errorlog-logging-enabled" class="ds-label" title="Enable error logging (nsslapd-errorlog-logging-enabled)."> Enable Error Logging</label>

-     <div class="ds-expired-div" id="errorlog-attrs">

-       <label for="nsslapd-errorlog" class="ds-config-label" title="The errors log location and name (nsslapd-errorlog).">Errors Log Location</label><input

-         class="ds-input" type="text" id="nsslapd-errorlog" size="40"/>

-       <h4 class="ds-sub-header">Rotation Policy</h4>

-       <hr class="ds-hr-logs">

-       <div class="ds-inline">

-         <div>

-           <label for="nsslapd-errorlog-maxlogsperdir" class="ds-config-sub-label" title="The maximum number of logs that are archived (nsslapd-errorlog-maxlogsperdir).">Maximum Number Of Logs</label><input

-             class="ds-input" type="text" id="nsslapd-errorlog-maxlogsperdir" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-errorlog-maxlogsize" class="ds-config-sub-label" title="The maximum size of each log file in megabytes (nsslapd-errorlog-maxlogsize).">Maximum Log Size (in MB)</label><input

-             class="ds-input" type="text" id="nsslapd-errorlog-maxlogsize" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-errorlog-logrotationtime" class="ds-config-sub-label" title="Errors log rotation time settings (nsslapd-errorlog-logrotationtime).">Create New Log Every...</label><input

-             class="ds-input" type="text" id="nsslapd-errorlog-logrotationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-errorlog-logrotationtimeunit">

-               <option>minute</option>

-               <option>hour</option>

-               <option>day</option>

-               <option>week</option>

-               <option>month</option>

-             </select> at <input class="ds-input" type="text"  title="Hour" id="nsslapd-errorlog-logrotationsynchour" placeholder="0" size="1"/> : <input class="ds-input" type="text" placeholder="0"

-               title="Minute" id="nsslapd-errorlog-logrotationsyncmin" size="1"/>

-         </div>

-       </div>

- 

-       <h4 class="ds-sub-header">Deletion Policy</h4>

-       <hr class="ds-hr-logs">

-       <div class="ds-inline">

-         <div>

-           <label for="nsslapd-errorlog-logmaxdiskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log when the total of all the logs reaches this amount (nsslapd-errorlog-logmaxdiskspace).">Total Log Archive Exceeds (in MB)</label><input

-             class="ds-input" type="text" id="nsslapd-errorlog-logmaxdiskspace" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-errorlog-logminfreediskspace" class="ds-config-sub-label" title="The server deletes the oldest archived log file when available disk space is less than this amount. (nsslapd-errorlog-logminfreediskspace).">Free Disk Space (in MB)</label><input

-             class="ds-input" type="text" id="nsslapd-errorlog-logminfreediskspace" size="40"/>

-         </div>

-         <div>

-           <label for="nsslapd-errorlog-logexpirationtime" class="ds-config-sub-label" title="Server deletes an old archived log file when it is older than the specified age. (nsslapd-errorlog-logexpirationtime).">Log File is Older Than... </label><input

-             class="ds-input" type="text" id="nsslapd-errorlog-logexpirationtime" size="40"/> <select class="btn btn-default dropdown" id="nsslapd-errorlog-logexpirationtimeunit">

-               <option>day</option>

-               <option>week</option>

-               <option>month</option>

-             </select>

-         </div>

-       </div>

- 

-       <h4 class="ds-sub-header">Error Logging Levels</h4>

-       <hr class="ds-hr-logs">

-       <table class="table table-striped table-bordered table-hover ds-loglevel-table" id="errorlog-level-table">

-         <thead>

-           <tr>

-             <th class="ds-table-checkbox"></th>

-             <th>Logging Level</th>

-           </tr>

-         </thead>

-         <tbody>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-1" type="checkbox"></td>

-            <td>Trace Function Calls</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-2" type="checkbox"></td>

-            <td>Packet Handling</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-4" type="checkbox"></td>

-            <td>Heavy Trace Output</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-8" type="checkbox"></td>

-            <td>Connection Management</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-16" type="checkbox"></td>

-            <td>Packets Sent/Received</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-32" type="checkbox"></td>

-            <td>Search Filter Processing</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-64" type="checkbox"></td>

-            <td>Config File Processing</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-128" type="checkbox"></td>

-            <td>Access Control List Processing</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-2048" type="checkbox"></td>

-            <td>Log Entry Parsing</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-4096" type="checkbox"></td>

-            <td>Housekeeping</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-8192" type="checkbox"></td>

-            <td>Replication</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-32768" type="checkbox"></td>

-            <td>Entry Cache</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-65536" type="checkbox"></td>

-            <td>Plug-ins</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-262144" type="checkbox"></td>

-            <td>Access Control Summary</td>

-           </tr>

-           <tr>

-            <td class="ds-table-checkbox"><input class="ds-errorlog-table" id="errorlog-1048576" type="checkbox"></td>

-            <td>Nunc-Stans Connection Logging</td>

-           </tr>

-         </tbody>

-       </table>

-     </div>

-     <div class="ds-footer">

-       <button class="btn btn-primary save-button">Save</button>

-     </div>

-   </div>

- 

-   <!--

-         Tuning

-   -->

-   <div id="server-tuning" class="all-pages ds-margin-left" hidden>

-     <h3 class="ds-config-header">Server Tuning & Limits</h3>

-     <div class="ds-container">

-       <!-- Attribute list -->

-       <div class="ds-inline">

-         <div>

-           <label for="nsslapd-threadnumber" class="ds-config-label" title="The number of worker threads (nsslapd-threadnumber).">Number Of Worker Threads</label><input

-             class="ds-input" type="text" id="nsslapd-threadnumber" size="15"/>

-         </div>

-         <div>

-           <label for="nsslapd-maxdescriptors" class="ds-config-label" title="The maximum number of file descriptors the server will use (nsslapd-maxdescriptors).">Maximum File  Descriptors</label><input

-             class="ds-input" type="text" id="nsslapd-maxdescriptors" size="15"/>

-         </div>

-         <div>

-           <label for="nsslapd-timelimit" class="ds-config-label" title="The maximum number of seconds allocated for a search request (nsslapd-timelimit).">Search Time Limit</label><input

-             class="ds-input" type="text" id="nsslapd-timelimit" size="15"/>

-         </div>

-         <div>

-           <label for="nsslapd-sizelimit" class="ds-config-label" title="The maximum number of entries to return from a search operation (nsslapd-sizelimit).">Search Size Limit</label><input

-             class="ds-input" type="text" id="nsslapd-sizelimit" size="15"/>

-         </div>

-         <div>

-           <label for="nsslapd-pagedsizelimit" class="ds-config-label" title="The maximum number of entries to return from a paged search operation (nsslapd-pagedsizelimit).">Paged Search Size Limit</label><input

-             class="ds-input" type="text" id="nsslapd-pagedsizelimit" size="15"/>

-         </div>

-       </div>

-       <div class="ds-divider"></div>

-       <div class="ds-inline">

-         <div>

-           <label for="nsslapd-idletimeout" class="ds-config-label" title=

-             "Sets the amount of time in seconds after which an idle LDAP client connection is closed by the server (nsslapd-idletimeout).">Idle Connection Timeout</label><input

-             class="ds-input" type="text" id="nsslapd-idletimeout" size="15"/>

-         </div>

-         <div>

-           <label for="nsslapd-ioblocktimeout" class="ds-config-label" title=

-             "Sets the amount of time in milliseconds after which the connection to a stalled LDAP client is closed (nsslapd-ioblocktimeout).">I/O Block Timeout</label><input

-             class="ds-input" type="text" id="nsslapd-ioblocktimeout" size="15"/>

-         </div>

-         <div>

-           <p></p>

-           <input type="checkbox" class="ds-config-checkbox" id="nsslapd-ndn-cache-enabled" checked><label

-             for="nsslapd-ndn-cache-enabled" class="ds-label" title=

-             "Enable the normalized DN cache (nsslapd-ndn-cache-enabled)."> Enable Normalized DN Cache</label>

-         </div>

-         <div>

-           <label for="nsslapd-ndn-cache-max-size" class="ds-config-sub-label" title="The size of the normalized DN cache in bytes (nsslapd-ndn-cache-max-size).">Normalized DN Cache Size</label><input

-             class="ds-input" type="text" id="nsslapd-ndn-cache-max-size" size="15"/>

-         </div>

-       </div>

-     </div>

-     <p></p>

- 

-     <button class="accordion ds-accordion" id="tuning-config-accordion" type="button">&#9658 Show Advanced Settings </button>

-     <div class="ds-accordion-panel">

-       <div class="ds-container">

-         <div class="ds-inline">

-           <div>

-             <label for="nsslapd-outbound-ldap-io-timeout" class="ds-config-label" title=

-               "Sets the I/O wait time for all outbound LDAP connections (nsslapd-outbound-ldap-io-timeout).">Outbound IO Timeout</label><input

-               class="ds-input" type="text" id="nsslapd-outbound-ldap-io-timeout" size="15"/>

-           </div>

-           <div>

-             <label for="nsslapd-maxbersize" class="ds-config-label" title="The maximum size in bytes allowed for an incoming message (nsslapd-maxbersize).">Maximum BER Size</label><input

-               class="ds-input" type="text" id="nsslapd-maxbersize" size="15"/>

-           </div>

-           <div>

-             <label for="nsslapd-maxsasliosize" class="ds-config-label" title="The maximum allowed SASL IO packet size that the server will accept (nsslapd-maxsasliosize).">

-               Maximum SASL IO Size</label><input class="ds-input" type="text" id="nsslapd-maxsasliosize" size="15"/>

-           </div>

-           <div>

-             <label for="nsslapd-listen-backlog-size" class="ds-config-label" title=

-               "The maximum length for how long the connection queue for the socket can grow before refusing connections (nsslapd-listen-backlog-size).">Listen Backlog Size</label><input

-               class="ds-input" type="text" id="nsslapd-listen-backlog-size" size="15"/>

-           </div>

-           <div>

-             <label for="nsslapd-max-filter-nest-level" class="ds-config-label" title=

-               "Sets how deep a nested search filter is analysed (nsslapd-max-filter-nest-level).">Maximum Nested Filter Level</label><input

-               class="ds-input" type="text" id="nsslapd-max-filter-nest-level" size="15"/>

-           </div>

-         </div>

-         <div class="ds-divider"></div>

-         <div class="ds-inline ds-margin-top">

-           <div>

-             <input type="checkbox" class="ds-config-checkbox" id="nsslapd-ignore-virtual-attrs" checked><label

-               for="nsslapd-ignore-virtual-attrs" class="ds-label" title=

-               "Disable the virtual attribute lookup in a search entry (nsslapd-ignore-virtual-attrs)."> Disable Virtual Attribute Lookups</label>

-           </div>

-           <div>

-             <input type="checkbox" class="ds-config-checkbox" id="nsslapd-connection-nocanon" checked><label

-               for="nsslapd-connection-nocanon" class="ds-label" title=

-               "Disable DNS reverse entries for outgoing connections (nsslapd-connection-nocanon)."> Disable Reverse DNS Lookups</label>

-           </div>

-           <div>

-             <input type="checkbox" class="ds-config-checkbox" id="nsslapd-enable-turbo-mode" checked><label

-               for="nsslapd-enable-turbo-mode" class="ds-label" title=

-               "Sets the worker threads to continuously read a connection without passing it back to the polling mechanism. (nsslapd-enable-turbo-mode)."> Enable Connection Turbo Mode</label>

-           </div>

-         </div>

-       </div>

-     </div>

-     <div class="ds-footer">

-       <button class="btn btn-primary save-button">Save</button>

-     </div>

-   </div>

- 

- 

-   <!--

-         LDAPI & Autobind Settings

-   -->

-   <div id="server-ldapi" class="all-pages ds-margin-left" hidden>

-     <h3 class="ds-config-header">LDAPI & Autobind Settings</h3>

-     <div class="ldapi-attrs ds-inline" hidden>

-       <div>

-         <label for="nsslapd-ldapifilepath" class="ds-config-label" title="The Unix socket file (nsslapd-ldapifilepath).  The UI requires this exact path so it is a read-only setting.">LDAPI Socket File Path</label><input

-           class="ds-input" type="text" id="nsslapd-ldapifilepath" size="35" disabled/>

-       </div>

-       <div class="ds-inline">

-         <div class="autobind-attrs">

-           <div>

-             <label for="nsslapd-ldapimaprootdn" class="ds-config-label" title="Map the Unix root entry to this Directory Manager DN (nsslapd-ldapimaprootdn).  The UI requires this to be set to the current root DN so it is a read-only setting">DN to map "root" To</label><input

-               class="ds-input" type="text" id="nsslapd-ldapimaprootdn"  disabled size="35"/>

-           </div>

-           <div>

-             <p></p>

-             <input type="checkbox" class="ds-config-checkbox" id="nsslapd-ldapimaptoentries"><label

-               for="nsslapd-ldapimaptoentries" class="ds-label" title="Map regular system users to Directory Server entries (nsslapd-ldapimaptoentries).">Map System User to Database Entry</label>

-           </div>

-         </div>

-         <div class="autobind-entry-attrs" hidden>

-           <div>

-             <label for="nsslapd-ldapiuidnumbertype" class="ds-config-indent-sm-label" title=

-               "The Directory Server attribute to map system UIDs to user entries (nsslapd-ldapiuidnumbertype).">LDAPI UID Number Type</label><input

-               class="ds-input" type="text" id="nsslapd-ldapiuidnumbertype" placeholder="e.g.  uidNumber" size="35"/>

-           </div>

-           <div>

-             <label for="nsslapd-ldapigidnumbertype" class="ds-config-indent-sm-label" title=

-               "The Directory Server attribute to map system GUIDs to user entries (nsslapd-ldapigidnumbertype).">LDAPI UID Number Type</label><input

-               class="ds-input" type="text" id="nsslapd-ldapigidnumbertype" placeholder="e.g.  gidNumber" size="35"/>

-           </div>

-           <div>

-             <label for="nsslapd-ldapientrysearchbase" class="ds-config-indent-sm-label" title=

-               "The subtree to search for user entries to use for autobind. (nsslapd-ldapientrysearchbase).">LDAPI Entry Search Base</label><input

-               class="ds-input" type="text" id="nsslapd-ldapientrysearchbase" size="35"/>

-           </div>

-         </div>

-       </div>

-     </div>

-     <div class="ds-footer">

-       <button class="btn btn-primary save-button">Save</button>

-     </div>

-   </div>

- 

- 

- 

-   <!-- Modals/Popups/Wizards -->

- 

- 

-   <!-- Create SASL Mapping -->

-   <div class="modal fade" id="sasl-map-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="sasl-header" aria-hidden="true">

-     <div class="modal-dialog">

-       <div class="modal-content">

-         <div class="modal-header">

-           <button type="button" class="close" data-dismiss="modal" aria-hidden="true" aria-label="Close">

-             <span class="pficon pficon-close"></span>

-           </button>

-           <h4 class="modal-title" id="sasl-header">Create SASL Mapping</h4>

-         </div>

-         <div class="modal-body">

-           <form class="form-horizontal">

-             <div class="ds-inline">

-               <p class="ds-modal-error"></p>

-               <div>

-                 <label for="sasl-map-name" class="ds-config-label" title="SASL mapping name">

-                   SASL Mapping Name</label><input class="ds-input sasl-input" type="text" id="sasl-map-name"/>

-               </div>

-               <div>

-                 <label for="sasl-map-regex" class="ds-config-label" title="SASL mapping regular expression">

-                   SASL Mapping Regex</label><input class="ds-input sasl-input" type="text" id="sasl-map-regex"/><label

-                   for="test-sasl-regex" class="ds-left-margin">Test Regex</label><input type="checkbox"

-                   class="ds-left-margin ds-margin-top" id="test-sasl-regex">

-               </div>

-               <div id="sasl-test-div" hidden>

-                 <div class="ds-inline">

-                   <div>

-                     <label for="sasl-test-regex" class="ds-config-label" title="SASL mapping name">

-                       </label><input class="ds-input sasl-test-input" placeholder="Enter text to test" type="text" id="sasl-test-regex-string"/><button

-                       type="button" class="btn btn-default ds-trailing-btn" id="sasl-test-regex-btn">Test It</button>

-                     <hr>

-                   </div>

-                 </div>

-               </div>

-               <div>

-                 <label for="sasl-map-base" class="ds-config-label" title=

-                   "The search base or a specific entry DN to match against the constructed DN">

-                   SASL Mapping Base</label><input class="ds-input sasl-input" type="text" id="sasl-map-base"/>

-               </div>

-               <div>

-                 <label for="sasl-map-filter" class="ds-config-label" title="SASL mapping filter">

-                   SASL Mapping Filter</label><input class="ds-input sasl-input" type="text" id="sasl-map-filter"/>

-               </div>

-               <div>

-                 <label for="sasl-map-priority" class="ds-config-label" title=

-                   "SASL mapping priority.  1 is highest priority, and 100 is lowest">

-                   SASL Mapping Priority</label><input class="ds-input sasl-input" type="text" id="sasl-map-priority"/>

-               </div>

-             </div>

-           </form>

-         </div>

-         <div class="modal-footer">

-           <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>

-           <button type="button" class="btn btn-primary" id="sasl-map-save">Save</button>

-         </div>

-       </div>

-     </div>

-   </div>

- 

- 

-   <!-- Create Local Password Policy -->

-   <div class="modal fade" id="local-pwp-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="local-pwp-header" aria-hidden="true">

-     <div class="modal-dialog">

-       <div class="ds-modal-wide modal-content">

-         <div class="modal-header">

-           <button type="button" class="close" data-dismiss="modal" aria-hidden="true" aria-label="Close">

-             <span class="pficon pficon-close"></span>

-           </button>

-           <h4 class="modal-title" id="local-pwp-header">Create Local Password Policy</h4>

-         </div>

-         <div class="modal-body">

-           <form class="form-horizontal">

-             <div class="ds-inline">

-               <div>

-                 <label title="Create a subtree level password policy" for="subtree-pwp-radio"><input

-                   class="ds-radio pwp-role" type="radio" id="subtree-pwp-radio" name="pwp-role" value="subtree" checked="checked"> Subtree Password Policy</label>

-               </div>

-               <div>

-                 <label for="user-pwp-radio" title="Create a user level password policy"><input

-                   class="ds-radio pwp-role" type="radio" id="user-pwp-radio" name="pwp-role" value="user"> User Password Policy</label>

-               </div>

-               <div>

-                 <div>

-                   <label for="local-entry-dn" class="ds-config-label ds-pwp-input" title="The entry to apply a local policy to.">Entry for Password Policy</label>

-                 </div>

-                 <div class="ds-inline">

-                   <input class="ds-input ds-pwp-input" type="text" id="local-entry-dn" size="70" required />

-                 </div>

-                 <hr>

-               <div>

- 

-               </div>

-             </div>

-             <div class="ds-container">

-               <div class="ds-split">

-                 <h4>General Settings</h4>

-                 <hr class="ds-hr">

-                 <div class="ds-inline">

-                   <div>

-                     <label for="local-passwordstoragescheme" class="ds-config-label-lrg" title="Set the password storage scheme (passwordstoragescheme).">Password Storage Scheme</label><select

-                       class="btn btn-default dropdown" id="local-passwordstoragescheme">

-                         <option>PBKDF2_SHA256</option>

-                         <option>SSHA512</option>

-                         <option>SSHA384</option>

-                         <option>SSHA256</option>

-                         <option>SSHA</option>

-                         <option>NS-MTA-MD5</option>

-                         <option>MD5</option>

-                         <option>SMD5</option>

-                         <option>CRYPT-MD5</option>

-                         <option>CRYPT-SHA512</option>

-                         <option>CRYPT-SHA256</option>

-                         <option>CRYPT</option>

-                         <option>CLEAR</option>

-                       </select>

-                   </div>

-                   <div>

-                     <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordtrackupdatetime"><label

-                       for="local-passwordtrackupdatetime" class="ds-label" title=

-                       "Record a separate timestamp specifically for the last time that the password for an entry was changed. If this is enabled, then it adds the pwdUpdateTime operational attribute to the user account entry (passwordTrackUpdateTime)."> Track Password Update Time</label>

-                   </div>

-                   <div>

-                     <label for="local-passwordadmindn" title="The DN for a password administrator or administrator group (passwordAdminDN)">Password Administrator</label><input

-                       class="ds-input ds-pwp-input" type="text" id="local-passwordadmindn" size="40"/>

-                   </div>

-                 </div>

-               </div>

-               <div class="ds-divider"></div>

-               <div class="ds-split">

-                 <h4>User Password Settings</h4>

-                 <hr class="ds-hr">

-                 <div class="ds-inline">

-                   <div>

-                     <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordchange"><label

-                       for="local-passwordchange" class="ds-label" title="Allow user's to change their passwords (passwordChange)."> Allow Users To Change Their Passwords</label>

-                   </div>

-                   <div>

-                     <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordmustchange"><label

-                       for="local-passwordmustchange" class="ds-label" title="User must change its password after its been reset by an administrator (passwordMustChange).">User Must Change Password After Reset</label>

-                   </div>

-                   <div>

-                     <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordhistory"><label

-                       for="local-passwordhistory" class="ds-label" title="Maintain a password history (passwordHistory).">Keep Password History</label>

-                   </div>

-                   <div>

-                     <input class="ds-history-input ds-pwp-input" type="text" id="local-passwordinhistory" size="2"/>Passwords In History

-                     <label for="local-passwordminage" class="ds-minage-label" title="Indicates the number of seconds that must pass before a user can change their password. (passwordMinAge)">Allow Password Changes (in seconds)</label><input

-                      class="ds-input ds-pwp-input" type="text" id="local-passwordminage" size="10"/>

-                    </div>

-                 </div>

-               </div>

-             </div>

-             <div class="ds-container">

-               <div class="ds-split">

-                 <h4><br>Password Expiration Settings</h4>

-                 <hr class="ds-hr">

-                 <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordexp"><label

-                   for="local-passwordexp" class="ds-label" title="Enable a password expiration policy (passwordExp).">Enable Password Expiration</label>

-                 <div class="ds-expired-div" id="local-expiration-attrs">

-                   <label for="local-passwordmaxage" class="ds-expire-label" title="The server's local hostname (passwordMaxAge).">Password Expiration Time (in seconds)</label><input

-                     class="ds-input ds-pwp-input" type="text" id="local-passwordmaxage" size="5"/>

-                   <label for="local-passwordgracelimit" class="ds-expire-label" title="The server's local hostname (passwordGraceLimit).">Allowed Logins After Password Expires</label><input

-                     class="ds-input ds-pwp-input" type="text" id="local-passwordgracelimit" size="5"/>

-                   <label for="local-passwordwarning" class="ds-expire-label" title="Set the time (in seconds), before a password is about to expire, to send a warning. (passwordWarning).">Send Password Expiring Warning (in seconds)</label><input

-                     class="ds-input ds-pwp-input" type="text" id="local-passwordwarning" size="5"/>

-                   <input type="checkbox" class="ds-send-expiring-checkbox ds-pwp-checkbox" id="local-passwordsendexpiringtime"><label

-                     for="local-passwordsendexpiringtime" class="ds-label" title=

-                     "Always return a password expiring control when requested (passwordSendExpiringTime).">Always Send <i>Password Expiring Control</i></label>

-                 </div>

-               </div>

-               <div class="ds-divider"></div>

-               <div class="ds-split">

-                 <h4><br>Account Lockout Settings</h4>

-                 <hr class="ds-hr">

-                 <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordlockout"><label

-                   for="local-passwordlockout" class="ds-label" title="Enable account lockout (passwordLockout).">Enable Account Lockout</label>

-                 <div class="ds-expired-div" id="local-lockout-attrs">

-                   <label for="local-passwordmaxfailure" class="ds-expire-label" title=

-                     "The maximum number of failed logins before account gets locked (passwordMaxFailure).">Number of Failed Logins to Lockout Account</label><input

-                     class="ds-input ds-pwp-input" type="text" id="local-passwordmaxfailure" size="5"/>

-                   <label for="local-passwordresetfailurecount" class="ds-expire-label" title=

-                     "The number of seconds until an accounts failure count is reset (passwordResetFailureCount).">Time Before Failure Count Reset </label><input

-                     class="ds-input ds-pwp-input" type="text" id="local-passwordresetfailurecount" size="5"/>

-                   <div>

-                     <label title="Lock out the account forever (passwordUnlock)." for="local-passwordunlock" ><input

-                       class="ds-radio" type="radio" id="local-passwordunlock" value="passwordunlock" name="account-lockout" checked="checked"> Lockout Account Forever</label>

-                     <label title="The number of seconds before account gets unlocked (passwordLockoutDuration)." for="local-passwordlockoutduration"><input

-                       class="ds-radio" type="radio" name="account-lockout" value="passwordlockoutduration"> Time Until Account Unlocked <input

-                       class="ds-input ds-pwp-input" type="text" id="local-passwordlockoutduration" size="5"/></label>

-                   </div>

-                 </div>

-                 <p></p>

-               </div>

-             </div>

- 

-             <h4><br>Password Syntax Settings</h4>

-             <hr class="ds-hr">

-             <input type="checkbox" class="ds-config-checkbox ds-pwp-checkbox" id="local-passwordchecksyntax"><label

-               for="local-passwordchecksyntax" class="ds-label" title="Enable account lockout (passwordCheckSyntax).">Check Password Syntax</label>

-             <div class="ds-container ds-expired-div" id="local-syntax-attrs">

-               <div class="ds-split">

-                 <label for="local-passwordminlength" class="ds-expire-label" title=

-                   "The minimum number of characters in the password (passwordMinLength).">Password Minimum Length </label><input

-                   class="ds-pw-input ds-pwp-input" type="text" id="local-passwordminlength" size="5"/>

-                 <label for="local-passwordmindigits" class="ds-expire-label" title=

-                   "Reject passwords with fewer than this many digit characters (0-9) (passwordMinDigits).">Minimum Digit Characters </label><input

-                   class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmindigits" size="5"/>

-                 <label for="local-passwordminalphas" class="ds-expire-label" title=

-                   "Reject passwords with fewer than this many alpha characters (passwordMinAlphas).">Minimum Alpha Characters </label><input

-                   class="ds-pw-input ds-pwp-input" type="text" id="local-passwordminalphas" size="5"/>

-                 <label for="local-passwordminuppers" class="ds-expire-label" title=

-                   "Reject passwords with fewer than this many uppercase characters (passwordMinUppers).">Minimum Uppercase Characters </label><input

-                   class="ds-pw-input ds-pwp-input" type="text" id="local-passwordminuppers" size="5"/>

-                 <label for="local-passwordminlowers" class="ds-expire-label" title=

-                   "Reject passwords with fewer than this many lowercase characters (passwordMinLowers).">Minimum Lowercase Characters </label><input

-                   class="ds-pw-input ds-pwp-input" type="text" id="local-passwordminlowers" size="5"/>

-                 <label for="local-passwordminspecials" class="ds-expire-label" title=

-                   "Reject passwords with fewer than this many special non-alphanumeric characters (passwordMinSpecials).">Minimum Special Characters </label><input

-                   class="ds-pw-input ds-pwp-input" type="text" id="local-passwordminspecials" size="5"/>

-                 <label for="local-passwordmin8bit" class="ds-expire-label" title=

-                   "Reject passwords with fewer than this many 8-bit or multi-byte characters (passwordMin8Bit).">Minimum 8-bit Characters </label><input

-                   class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmin8bit" size="5"/>

-                 <label for="local-passwordmincategories" class="ds-expire-label" title=

-                   "The minimum number of character categories that a password must contain (categories are upper, lower, digit, special, and 8-bit) (passwordMinCategories).">Minimum Required Character Categories </label><input

-                   class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmincategories" size="5"/>

-                 <label for="local-passwordmintokenlength" class="ds-expire-label" title=

-                   "The smallest attribute value used when checking if the password contains any of the user's account information (passwordMinTokenLength).">Minimum Token Length </label><input

-                   class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmintokenlength" size="5"/>

-               </div>

-               <div class="ds-split">

-                 <label for="local-passwordmaxrepeats" class="ds-expire-label" title=

-                   "The maximum number of times the same character can sequentially appear in a password (passwordMaxRepeats).">Maximum Number Of Repeated Characters </label><input

-                   class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmaxrepeats"/>

-                 <label for="local-passwordmaxsequence" class="ds-expire-label" title=

-                   "The maximum number of allowed monotonic characters sequences (passwordMaxSequence).">Maximum Character Sequences </label><input

-                   class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmaxsequence"/>

-                 <label for="local-passwordmaxseqsets" class="ds-expire-label" title=

-                   "The maximum number of allowed monotonic characters sequences that can appear more than once (passwordMaxSeqSets).">Maximum Character Sequence Sets </label><input

-                   class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmaxseqsets"/>

-                 <label for="local-passwordmaxclasschars" class="ds-expire-label" title=

-                   "The maximum number of consecutive characters from the same character class/category (passwordMaxClassChars).">Maximum Consecutive Chars Per Char Class </label><input

-                   class="ds-pw-input ds-pwp-input" type="text" id="local-passwordmaxclasschars"/>

-                 <label for="local-passwordpalindrome" class="ds-expire-label" title=

-                   "Reject a password if it is a palindrome (passwordPalindrome).">Reject Passwords that Are Palindromes </label><input

-                   class="ds-margin-top ds-pwp-checkbox" type="checkbox" id="local-passwordpalindrome"/>

-                 <label for="local-passworddictcheck" class="ds-expire-label" title=

-                   "Check the password against the system's CrackLib dictionary (passwordDictCheck).">Check Password Contains Dictionary Word </label><input

-                   class="ds-margin-top ds-pwp-checkbox" type="checkbox" id="local-passworddictcheck"/>

-               </div>

-             </div>

-             <div class="ds-margin-left-sm ds-margin-top">

-                 <div>

-                   <label for="passwordbadwords" title=

-                     "A space-separated list of words that are not allowed to be contained in the new password (passwordBadWords).">Reject Passwords That Contain These Words </label><input

-                     class="ds-input-auto" type="text" id="local-passwordbadwords"/>

-                 </div>

-                 <div class="ds-margin-top">

-                   <label for="passworduserattributes" title=

-                     "A space-separated list of entry attributes to compare to the new password (passwordUserAttributes).">Entry Attributes To Compare </label><input

-                     class="ds-input-auto" type="text" id="local-passworduserattributes"/>

-                 </div>

-             </div>

-           </form>

-         </div>

-       </div>

-       <div class="modal-footer">

-         <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>

-         <button type="button" class="btn btn-primary" id="local-pwp-save">Save</button>

-       </div>

-     </div>

-   </div>

- 

- </div>

@@ -1,1808 +0,0 @@ 

- 

- var sasl_action_html =

-   '<div class="dropdown">' +

-     '<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown">' +

-       'Choose Action...' +

-       '<span class="caret"></span>' +

-     '</button>' +

-     '<ul class="dropdown-menu ds-agmt-dropdown" role="menu" aria-labelledby="dropdownMenu1">' +

-       '<li role=""><a role="menuitem" class="sasl-edit-btn" tabindex="2" href="#">Edit Mapping</a></li>' +

-       '<li role=""><a role="menuitem" class="sasl-del-btn" tabindex="1" href="#">Delete Mapping</a></li>' +

-     '</ul>' +

-   '</div>';

- 

- var local_pwp_html =

-   '<div class="dropdown" >' +

-      '<button class="btn btn-default dropdown-toggle" type="button" id="menu1" data-toggle="dropdown">Choose Action...' +

-        '<span class="caret"></span></button>' +

-      '<ul id="test-drop" class="dropdown-menu ds-agmt-dropdown" role="menu" aria-labelledby="menu1">' +

-        '<li role="policy-role"><a role="pwpolicy" tabindex="0" class="edit-local-pwp" href="#">View/Edit Policy</a></li>' +

-        '<li role="policy-role"><a role="pwpolicy" tabindex="-1" class="delete-local-pwp" href="#">Delete Policy</a></li>' +

-      '</ul>' +

-    '</div>';

- 

- var create_full_template =

-   "[general]\n" +

-   "config_version = 2\n" +

-   "defaults = 999999999\n" +

-   "full_machine_name = FQDN\n" +

-   "selinux = True\n" +

-   "strict_host_checking = True\n" +

-   "systemd = True\n" +

-   "[slapd]\n" +

-   "backup_dir = /var/lib/dirsrv/slapd-{instance_name}/bak\n" +

-   "bin_dir = /usr/bin\n" +

-   "cert_dir = /etc/dirsrv/slapd-{instance_name}\n" +

-   "config_dir = /etc/dirsrv/slapd-{instance_name}\n" +

-   "data_dir = /usr/share\n" +

-   "db_dir = /var/lib/dirsrv/slapd-{instance_name}/db\n" +

-   "user = dirsrv\n" +

-   "group = dirsrv\n" +

-   "initconfig_dir = /etc/sysconfig\n" +

-   "inst_dir = /usr/lib64/dirsrv/slapd-{instance_name}\n" +

-   "instance_name = localhost\n" +

-   "ldif_dir = /var/lib/dirsrv/slapd-{instance_name}/ldif\n" +

-   "lib_dir = /usr/lib64\n" +

-   "local_state_dir = /var\n" +

-   "lock_dir = /var/lock/dirsrv/slapd-{instance_name}\n" +

-   "log_dir = /var/log/dirsrv/slapd-{instance_name}\n" +

-   "port = PORT\n" +

-   "prefix = /usr\n" +

-   "root_dn = ROOTDN\n" +

-   "root_password = ROOTPW\n" +

-   "run_dir = /var/run/dirsrv\n" +

-   "sbin_dir = /usr/sbin\n" +

-   "schema_dir = /etc/dirsrv/slapd-{instance_name}/schema\n" +

-   "secure_port = SECURE_PORT\n" +

-   "self_sign_cert = True\n" +

-   "sysconf_dir = /etc\n" +

-   "tmp_dir = /tmp\n";

- 

- var create_inf_template =

-   "[general]\n" +

-   "config_version = 2\n" +

-   "full_machine_name = FQDN\n\n" +

-   "[slapd]\n" +

-   "user = dirsrv\n" +

-   "group = dirsrv\n" +

-   "instance_name = INST_NAME\n" +

-   "port = PORT\n" +

-   "root_dn = ROOTDN\n" +

-   "root_password = ROOTPW\n" +

-   "secure_port = SECURE_PORT\n" +

-   "self_sign_cert = SELF_SIGN\n";

- 

- 

- var sasl_table;

- var pwp_table;

- 

- // log levels

- var accesslog_levels = [4, 256, 512]

- var errorlog_levels =  [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 2048,

-                         4096, 8192, 16384, 32768, 65536, 262144, 1048576];

- 

- function load_server_config() {

-   var mark = document.getElementById("server-config-title");

-   mark.innerHTML = "Configuration for server: <b>" + server_id + "</b>";

- }

- 

- function clear_sasl_map_form () {

-   $(".ds-modal-error").hide();

-   $(".sasl-input").val("");

-   $(".sasl-input").css("border-color", "initial");

- }

- 

- function clear_local_pwp_form () {

-   $(".ds-pwp-input").val("");

-   $(".ds-pwp-checkbox").prop('checked', false);

-   $("#local-passwordstoragescheme").prop('selectedIndex',0);

-   $("#subtree-pwp-radio").attr('disabled', false);

-   $("#user-pwp-radio").attr('disabled', false);

-   $("#local-entry-dn").attr('disabled', false);

-   $("#local-pwp-header").html("<b>Create Local Password Policy</b>");

- }

- 

- function clear_inst_input() {

-   // Reset the color of the fields

-   $(".ds-inst-input").css("border-color", "initial");

- }

- 

- function clear_inst_form() {

-   $(".ds-modal-error").hide();

-   $("#create-inst-serverid").val("");

-   $("#create-inst-port").val("389");

-   $("#create-inst-secureport").val("636");

-   $("#create-inst-rootdn").val("cn=Directory Manager");

-   $("#rootdn-pw").val("");

-   $("#rootdn-pw-confirm").val("");

-   $("#backend-suffix").val("dc=example,dc=com");

-   $("#backend-name").val("userRoot");

-   $("#create-sample-entries").prop('checked', false);

-   $("#create-inst-tls").prop('checked', true);

-   $(".ds-inst-input").css("border-color", "initial");

- }

- 

- /*

-  * Validate the val and add it to the argument list for "dsconf"

-  *

-  * arg_list - array of options for dsconf

-  * valtype - value type:  "num" or "dn"

-  * val - the new value

-  * def_val - set this default is there is no new value("")

-  * edit - if we are editing a value, we accept ("") and do not ignore it

-  * attr - the dict key(its also the element ID)

-  * arg - the CLI argument (--pwdlen)

-  * msg - error message to display when things go wrong

-  *

-  * Return false on validation failure

-  */

- function add_validate_arg (arg_list, valtype, val, def_val, edit, attr, arg, msg) {

-   if ( val != "" || (edit && localpwp_values[attr] !== undefined && val != localpwp_values[attr])) {

-     if (val == "") {

-        val = def_val;

-     }

-     if ( valtype == "num" && !valid_num(val) ) {

-       popup_msg("Error", "\"" + msg + "\" value \"" + val + "\" is not a number");

-       return false;

-     } else if (valtype == "dn" && !valid_dn(val) && val != "" ) {

-       popup_msg("Error", "\"" + msg + "\" value \"" + val + "\" is not a DN (distinguished name)");

-       return false;

-     }

-     arg_list.push(arg + '=' + val);

-     return true;

-   }

-   // No change, no error

-   return true;

- }

- 

- function get_and_set_config () {

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

-   console.log("Loading server configuration.");

-   log_cmd('get_and_set_config', 'Get server configuration', cmd);

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

-     var obj = JSON.parse(data);

-     // Reset tables before populating them

-     $(".ds-accesslog-table").prop('checked', false);

-     $(".ds-errorlog-table").prop('checked', false);

-     config_values = {};

-     update_progress();

- 

-     for (var attr in obj['attrs']) {

-       var val = obj['attrs'][attr][0];

-       attr = attr.toLowerCase();

-       if( $('#' + attr).length ) {

-         // We have  an element that matches, set the html and store the original value

-         $("#" + attr).val(val);  // Always set value, then check if its something else

-         if (val == "on") {

-           $("#" + attr).prop('checked', true);

-           $("#" + attr).trigger('change');

-         } else if (val == "off") {

-           $("#" + attr).prop('checked', false);

-           $("#" + attr).trigger('change');

-         }

-         config_values[attr] = val;

- 

-         // Handle password confirm inputs

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

-           $("#nsslapd-rootpw-confirm").val(val);

-         }

-       }

- 

-       // Do the log level tables

-       if (attr == "nsslapd-accesslog-level") {

-         config_values[attr] = val;

-         var level_val = parseInt(val);

-         for ( var level in accesslog_levels ) {

-           if (level_val & accesslog_levels[level]) {

-             $("#accesslog-" + accesslog_levels[level].toString()).prop('checked', true);

-           }

-         }

-       } else if (attr == "nsslapd-errorlog-level") {

-         config_values[attr] = val;

-         var level_val = parseInt(val);

-         for ( var level in errorlog_levels ) {

-           if (level_val & errorlog_levels[level]) {

-             $("#errorlog-" + errorlog_levels[level].toString()).prop('checked', true);

-           }

-         }

-       }

-     }

-     console.log("Finished loading server configuration.");

-     config_loaded = 1;

-     check_inst_alive();

-   }).fail(function(data) {

-       popup_err("Error", "Failed to get config\n" + data.message);

-       check_inst_alive(1);

-   });

- }

- 

- function update_suffix_dropdowns () {

-     var dropdowns = ['local-pwp-suffix', 'select_repl_suffix', 'select-repl-cfg-suffix',

-                      'select-repl-agmt-suffix', 'select-repl-winsync-suffix',

-                      'monitor-repl-backend-list'];

- 

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

-     log_cmd('update_suffix_dropdowns', 'Get suffix list', cmd);

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

-       // Clear all the dropdowns first

-       for (var idx in dropdowns) {

-         $("#" + dropdowns[idx]).empty();

-       }

-       // Update dropdowns

-       var obj = JSON.parse(data);

-       for (var idx in obj['items']) {

-         for (var list in dropdowns){

-           $("#" + dropdowns[list]).append('<option value="' + obj['items'][idx] + '" selected="selected">' + obj['items'][idx] +'</option>');

-         }

-       }

-       update_progress();

-   }).fail(function(data) {

-       if (quiet === undefined) {

-         popup_err("Error", "Failed to get backend suffix list\n" + data.message);

-       }

-       check_inst_alive(1);

-   });

- }

- 

- function get_and_set_localpwp (quiet) {

-   // Now populate the table

-   console.log("Loading local password policies...");

-   var suffix = $('#local-pwp-suffix').val();

-   if (suffix == null){

-     return;

-   }

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

-   log_cmd('get_and_set_localpwp', 'Get local password policies', cmd);

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

-     var obj = JSON.parse(data);

-     update_progress();

-     // Empty table

-     pwp_table.clear().draw();

- 

-     // Populate table

-     for (var idx in obj['items']) {

-       pwp_table.row.add([

-         obj['items'][idx][0],

-         obj['items'][idx][1],

-         local_pwp_html,]

-       ).draw( false );

-     }

-     console.log("Finished loading password policies.");

-   }).fail(function(data) {

-     check_inst_alive(0);

-   });

- }

- 

- function get_and_set_sasl () {

-   // First empty the table

-   console.log("Loading SASL configuration...");

- 

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

-   log_cmd('get_and_set_sasl', 'Get SASL mappings', cmd);

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

-     var obj = JSON.parse(data);

-     update_progress();

-     sasl_table.clear().draw();

-     for (var idx in obj['items']) {

-       var map_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','sasl', 'get', obj['items'][idx] ];

-       log_cmd('get_and_set_sasl', 'Get SASL mapping', map_cmd);

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

-         var map_obj = JSON.parse(data);

-         // Update html table

-         var sasl_priority = '100';

-         if ( map_obj['attrs'].hasOwnProperty('nssaslmappriority') ){

-           sasl_priority = map_obj['attrs'].nssaslmappriority

-         }

-         sasl_table.row.add( [

-           map_obj['attrs'].cn,

-           map_obj['attrs'].nssaslmapregexstring,

-           map_obj['attrs'].nssaslmapbasedntemplate,

-           map_obj['attrs'].nssaslmapfiltertemplate,

-           sasl_priority,

-           sasl_action_html

-         ] ).draw( false );

-       });

-     }

-     console.log("Finished loading SASL configuration.");

-   }).fail(function(data) {

-       popup_err("Failed to SASL configuration", data.message);

-       check_inst_alive(1);

-   });

- }

- 

- function apply_mods(mods) {

-   let mod = mods.pop();

- 

-   if (!mod) {

-     return 0; /* all done*/

-   }

-   let cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket', 'config', 'replace'];

-   cmd.push(mod.attr + "=" + mod.val);

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

-     config_values[mod.attr] = mod.val;

-     // Continue with next mods (if any))

-     apply_mods(mods);

-   })

-   .fail(function(err) {

-     var err_obj = JSON.parse(err);

-     popup_err("Failed to update attribute: " + mod.attr, err_obj.info);

-     // Reset HTML for remaining values that have not been processed

-     $("#" + mod.attr).val(config_values[mod.attr]);

-     for (remaining in mods) {

-       $("#" + remaining.attr).val(config_values[remaining.attr]);

-     }

-     check_inst_alive(0);

-     return -1;  // Stop on error

-   });

- }

- 

- function delete_mods(mods) {

-   let mod = mods.pop();

- 

-   if (!mod) {

-     return 0; /* all done*/

-   }

-   var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket', 'config', 'delete', mod.attr];

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

-     config_values[mod.attr] = "";

-     // Continue with next mods (if any))

-     delete_mods(mods);

-   }, function(ex, data) {

-      var err_obj = JSON.parse(data);

-      popup_err("Failed to delete attribute: " + mod.attr, err_obj.info);

-      // Reset HTML for remaining values that have not been processed

-      $("#" + mod.attr).val(config_values[mod.attr]);

-      for (remaining in mods) {

-        $("#" + remaining.attr).val(config_values[remaining.attr]);

-      }

-      check_inst_alive(0);

-      return -1;  // Stop on error

-   });

- }

- 

- function save_config() {

-   // Loop over current config_values check for differences

-   let mod_list = [];

-   let del_list = [];

-   for (var attr in config_values) {

-     var mod = {};

-     if ( $("#" + attr).is(':checkbox')) {

-       // Handle check boxes

-       if ( $("#" + attr).is(":checked")) {

-         if (config_values[attr] != "on") {

-           mod['attr'] = attr;

-           mod['val'] = "on";

-           mod_list.push(mod);

-         }

-       } else {

-         // Not checked

-         if (config_values[attr] != "off") {

-           mod['attr'] = attr;

-           mod['val'] = "off";

-           mod_list.push(mod);

-         }

-       }

-     } else {

-       // Normal input

-       var val = $("#" + attr).val();

-       // But first check for rootdn-pw changes and check confirm input matches

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

-         if (val != config_values[attr] || val != $("#nsslapd-rootpw-confirm").val()) {

-             // Password change, make sure passwords match

-             if (val != $("#nsslapd-rootpw-confirm").val()){

-               popup_msg("Passwords do not match!", "The Directory Manager passwords do not match, please correct before saving again.");

-               return;

-             }

-         }

-         if (val.length < 8) {

-             popup_msg("Password is too short!", "The Directory Manager password must be at least 8 characters long.");

-             $("#nsslapd-rootpw").val(config_values[attr]);

-             $("#nsslapd-rootpw-confirm").val(config_values[attr]);

-             return;

-         }

-       }

- 

-       if (attr == "nsslapd-port") {

-           if (!valid_port(val)) {

-               popup_msg("Port number is not valid");

-               $("#nsslapd-port").val(config_values[attr]);

-           }

-       }

- 

-       if (attr.indexOf("logrotationsynchour") != -1) {

-           if (!valid_num(val) || val < 0 || val > 23) {

-               popup_msg("Invalid value", "You must use a number between 0 - 23 for: " + attr);

-               $("#" + attr).val(config_values[attr])

-               return;

-           }

-       }

-       if (attr.indexOf("logrotationsyncmin") != -1) {

-           if (!valid_num(val) || val < 0 || val > 59){

-               popup_msg("Invalid value", "You must use a number between 0 - 59 for: " + attr);

-               $("#" + attr).val(config_values[attr])

-               return;

-           }

-       }

- 

-       if (val && val != config_values[attr]) {

-         mod['attr'] = attr;

-         mod['val'] = val;

-         mod_list.push(mod);

-     } else if (val == "" && val != config_values[attr]) {

-         mod['attr'] = attr;

-         del_list.push(mod);

-       }

-     }

-   }

- 

-   // Save access log levels

-   var access_log_level = 0;

-   $(".ds-accesslog-table").each(function() {

-     var val = this.id;

-     if (this.checked){

-       val = parseInt(val.replace("accesslog-", ""));

-       access_log_level += val;

-     }

-   });

-   if (config_values["nsslapd-accesslog-level"] === undefined) {

-       config_values["nsslapd-accesslog-level"] = "256";

-   }

-   if (config_values["nsslapd-accesslog-level"] != access_log_level) {

-       mod = {}

-       mod['attr'] = "nsslapd-accesslog-level";

-       mod['val'] = access_log_level;

-       mod_list.push(mod);

-   }

- 

-   // Save error log levels

-   var error_log_level = 0;

-   $(".ds-errorlog-table").each(function() {

-     var val = this.id;

-     if (this.checked) {

-       val = parseInt(val.replace("errorlog-", ""));

-       error_log_level += val;

-     }

-   });

-   if (config_values["nsslapd-errorlog-level"] === undefined ||

-       config_values["nsslapd-errorlog-level"] == "16384")

-   {

-       config_values["nsslapd-errorlog-level"] = "0";

-   }

-   if (config_values["nsslapd-errorlog-level"] != error_log_level) {

-       mod = {}

-       mod['attr'] = "nsslapd-errorlog-level";

-       mod['val'] = error_log_level;

-       mod_list.push(mod);

-   }

- 

-   // Build dsconf commands to apply all the mods

-   if (mod_list.length || del_list.length) {

-       let err = 0;

-       if (mod_list.length) {

-         if (apply_mods(mod_list) == -1) {

-             return;

-         }

-       }

-       if (del_list.length) {

-         if (delete_mods(del_list) == -1) {

-             return;

-         }

-       }

-       popup_success("Successfully updated configuration");

-   } else {

-     // No changes to save, log msg?  popup_msg()

-   }

- }

- 

- function do_backup(server_inst, backup_name) {

-   var cmd = [DSCTL, '-j', server_inst, 'status'];

-   $("#backup-spinner").show();

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

-   done(function(status_data) {

-     var status_json = JSON.parse(status_data);

-     if (status_json.running == true) {

-       var cmd = [DSCONF, "-j", server_inst, 'backup', 'create',  backup_name];

-       log_cmd('#ds-backup-btn (click)', 'Backup server instance', cmd);

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

-       done(function(data) {

-         $("#backup-spinner").hide();

-         popup_success("Backup has been created");

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

-       }).

-       fail(function(data) {

-         $("#backup-spinner").hide();

-         popup_err("Failed to backup the server", data.message);

-       })

-     } else {

-       var cmd = [DSCTL, server_inst, 'db2bak', backup_name];

-       log_cmd('#ds-backup-btn (click)', 'Backup server instance (offline)', cmd);

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

-       done(function(data) {

-         $("#backup-spinner").hide();

-         popup_success("Backup has been created");

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

-       }).

-       fail(function(data) {

-         $("#backup-spinner").hide();

-         popup_err("Failed to backup the server", data.message);

-       });

-     }

-   }).

-   fail(function() {

-     popup_err("Failed to check the server status", data.message);

-   });

- }

- 

- /*

-  * load the server config pages

-  */

- $(document).ready( function() {

- 

-   // Set an interval event to wait for all the pages to load, then load the config

-   var init_config = setInterval(function() {

-       if (server_page_loaded == 1 && security_page_loaded == 1 && db_page_loaded == 1 &&

-           repl_page_loaded == 1 && schema_page_loaded == 1 && plugin_page_loaded == 1 &&

-           monitor_page_loaded == 1)

-       {

-         get_insts();

- 

-         /*

-          *  Stop, Start, and Restart server

-          */

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

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

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

-           var cmd = [DSCTL, server_inst, 'start'];

-           log_cmd('#start-server-btn (click)', 'Start server instance', cmd);

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

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

-             load_config(true);

-             popup_success("Started instance \"" + server_id + "\"");

-           }).fail(function(data) {

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

-             popup_err("Failed to start instance \"" + server_id,  data.message);

-           });

-         });

- 

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

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

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

-           var cmd = [DSCTL, server_inst, 'stop'];

-           log_cmd('#stop-server-btn (click)', 'Stop server instance', cmd);

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

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

-             popup_success("Stopped instance \"" + server_id + "\"");

-             check_inst_alive();

-           }).fail(function(data) {

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

-             popup_err("Error", "Failed to stop instance \"" + server_id+ "\"", data.message);

-             check_inst_alive();

-           });

-         });

- 

- 

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

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

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

-           var cmd = [DSCTL, server_inst, 'restart'];

-           log_cmd('#restart-server-btn (click)', 'Restart server instance', cmd);

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

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

-             load_config(true);

-             popup_success("Restarted instance \"" + server_id + "\"");

-           }).fail(function(data) {

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

-             popup_err("Failed to restart instance \"" + server_id + "\"", data.message);

-           });

-         });

- 

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

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

-             if (yes) {

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

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

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

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

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

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

-                 popup_success("Instance has been deleted");

-                 get_insts();

-               }).fail(function(data) {

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

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

-               });

-             }

-           });

-         });

- 

-         clearInterval(init_config);

-       }

-   }, 250);

- 

-   console.log("Loading Server Page...");

- 

-   $("#main-banner").load("banner.html");

-   check_for_389();

- 

-   $("#server-tab").css( 'color', '#228bc0');

- 

-   $("#server-content").load("servers.html", function () {

-     // Initialize all the tables first

-     sasl_table = $('#sasl-table').DataTable( {

-       "paging": true,

-       "bAutoWidth": false,

-       "dom": '<"pull-left"f><"pull-right"l>tip',

-       "lengthMenu": [ 10, 25, 50, 100],

-       "language": {

-         "emptyTable": "No SASL Mappings",

-         "search": "Search Mappings"

-       },

-       "columnDefs": [ {

-         "targets": 5,

-         "orderable": false

-       } ]

-     });

- 

-     // backup/restore table

-     var backup_table = $('#backup-table').DataTable( {

-       "paging": true,

-       "bAutoWidth": false,

-       "dom": '<"pull-left"f><"pull-right"l>tip',

-       "lengthMenu": [ 10, 25, 50, 100],

-       "language": {

-         "emptyTable": "No backups available for restore",

-         "search": "Search Backups"

-       },

-       "columnDefs": [ {

-         "targets": [3, 4],

-         "orderable": false

-       } ],

-       "columns": [

-         { "width": "120px" },

-         { "width": "80px" },

-         { "width": "30px" },

-         { "width": "40px" },

-         { "width": "30px" }

-       ],

-     });

- 

-     // Set up local passwd policy table

-     pwp_table = $('#passwd-policy-table').DataTable( {

-       "paging": true,

-       "bAutoWidth": false,

-       "dom": '<"pull-left"f><"pull-right"l>tip',

-       "lengthMenu": [ 10, 25, 50, 100],

-       "language": {

-         "emptyTable": "No local policies",

-         "search": "Search Policies"

-       },

-       "columnDefs": [ {

-         "targets": 2,

-         "orderable": false

-       } ]

-     });

- 

-     $('.disk-monitoring').hide();

-     $(".all-pages").hide();

-     $("#server-content").show();

-     $("#server-config").show();

- 

-     // To remove text border on firefox on dropdowns)

-     if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {

-       $("select").focus( function() {

-         this.style.setProperty( 'outline', 'none', 'important' );

-         this.style.setProperty( 'color', 'rgba(0,0,0,0)', 'important' );

-         this.style.setProperty( 'text-shadow', '0 0 0 #000', 'important' );

-       });

-     }

- 

-     $(".save-button").on('click', function (){

-       // This is for all pages.  Click Save -> it saves everything

-       save_all();

-     });

- 

-     // Events

-     $(".ds-nav-choice").on('click', function (){

-       $(".ds-tab-list").css( 'color', '#777');

-       var tab = $(this).attr("parent-id");

-       $("#" + tab).css( 'color', '#228bc0');

-     });

- 

-     $(".ds-tab-standalone").on('click', function (){

-       $(".ds-tab-list").css( 'color', '#777');

-       $(this).css( 'color', '#228bc0');

-     });

- 

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

-       $(".all-pages").hide();

-       $("#server-content").show();

-       $("#server-config").show();

-     });

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

-       $(".all-pages").hide();

-       $("#server-content").show();

-       $("#server-sasl").show();

-     });

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

-       $(".all-pages").hide();

-       $("#server-content").show();

-       $("#global-password-policy").show();

-     });

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

-       $(".all-pages").hide();

-       $("#server-content").show();

-       $("#local-password-policy").show();

-     });

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

-       $(".all-pages").hide();

-       $("#server-content").show();

-       $("#server-access-log").show();

-     });

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

-       $(".all-pages").hide();

-       $("#server-content").show();

-       $("#server-audit-log").show();

-     });

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

-       $(".all-pages").hide();

-       $("#server-content").show();

-       $("#server-auditfail-log").show();

-     });

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

-       $(".all-pages").hide();

-       $("#server-content").show();

-       $("#server-errors-log").show();

-     });

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

-       $(".all-pages").hide();

-       $("#server-content").show();

-       $("#server-tasks").show();

-     });

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

-       $(".all-pages").hide();

-       $("#server-content").show();

-       $("#server-tuning").show();

-     });

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

-       $(".all-pages").hide();

-       $("#server-content").show();

-       $("#server-ldapi").show();

-     });

- 

-     // Disable disk monitoring input if not in use

-     $("#nsslapd-disk-monitoring").change(function() {

-       if(this.checked) {

-         $('.disk-monitoring').show();

-       } else {

-         $('.disk-monitoring').hide();

-       }

-     });

- 

-     $('.ds-loglevel-table tr').click(function(event) {

-         if (event.target.type !== 'checkbox') {

-             $(':checkbox', this).trigger('click');

-         }

-     });

- 

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

-       clear_sasl_map_form();

-       $("#sasl-map-name").prop("readonly", false);

-     });

- 

-     $("#test-sasl-regex").change(function() {

-       if(this.checked) {

-         // Test SASL mapping

-         $("#sasl-test-div").show();

-       } else {

-         $("#sasl-test-div").hide();

-       }

-     });

- 

-     // Test SASL Mapping Regex

-     $("#sasl-test-regex-btn").on('click', function () {

-       var result = "No match!"

-       var regex = $("#sasl-map-regex").val().replace(/\\\(/g, '(').replace(/\\\)/g, ')');

-       var test_string = $("#sasl-test-regex-string").val();

-       var sasl_regex = RegExp(regex);

-       if (sasl_regex.test(test_string)){

-         popup_msg("Match", "The text matches the regular expression");

-       } else {

-         popup_msg("No Match", "The text does not match the regular expression");

-       }

-     });

- 

-     // Edit SASL mapping

-     $(document).on('click', '.sasl-edit-btn', function(e) {

-       // Load the Edit form

-       e.preventDefault();

-       clear_sasl_map_form();

-       var data = sasl_table.row( $(this).parents('tr') ).data();

-       var edit_sasl_name = data[0];

-       var edit_sasl_regex = data[1];

-       var edit_sasl_base = data[2];

-       var edit_sasl_filter = data[3];

-       var edit_sasl_priority = data[4];

- 

-       $("#sasl-header").html("Edit SASL Mapping");

-       $("#sasl-map-name").val(edit_sasl_name);

-       $("#sasl-map-name").prop("readonly", true);

-       $("#sasl-map-regex").val(edit_sasl_regex);

-       $("#sasl-map-base").val(edit_sasl_base);

-       $("#sasl-map-filter").val(edit_sasl_filter);

-       $("#sasl-map-priority").val(edit_sasl_priority);

-       $("#sasl-map-form").modal("toggle");

-     });

- 

-     // Verify SASL Mapping regex - open modal and ask for "login" to test regex mapping

-     $(document).on('click', '.sasl-verify-btn', function(e) {

-         // TODO - get this working

-         e.preventDefault();

-         var data = sasl_table.row( $(this).parents('tr') ).data();

-         var verify_sasl_name = data[0];

-     });

- 

-     // Delete SASL Mapping

-     $(document).on('click', '.sasl-del-btn', function(e) {

-         e.preventDefault();

-         var data = sasl_table.row( $(this).parents('tr') ).data();

-         var del_sasl_name = data[0];

-         var sasl_row = $(this); // Store element for callback

-         popup_confirm("Are you sure you want to delete sasl mapping: <b>" + del_sasl_name + "</b>", "Confirmation", function (yes) {

-         if (yes) {

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

-           log_cmd('.sasl-del-btn (click)', 'Delete SASL mapping', cmd);

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

-             sasl_table.row( sasl_row.parents('tr') ).remove().draw( false );

-             popup_success("Removed SASL mapping <b>" + del_sasl_name + "</b>");

-           }).fail(function(data) {

-             popup_err("Failed To Delete SASL Mapping: <b>" + del_sasl_name + "</b>", data.message);

-           });

-         }

-       });

-     });

- 

-     // Load password policy and update form based on settings

-     // TODO

- 

-     // Global password policy form control

-     $("#passwordhistory").change(function() {

-       if(this.checked) {

-         $('#passwordinhistory').attr('disabled', false);

-       } else {

-         $('#passwordinhistory').attr('disabled', true);

-       }

-     });

-     if ( $("#passwordhistory").is(":checked") ) {

-       $('#passwordinhistory *').attr('disabled', false);

-     } else {

-       $('#passwordinhistory *').attr('disabled', true);

-     }

- 

-     $("#passwordexp").change(function() {

-       if(this.checked) {

-         $('#expiration-attrs *').attr('disabled', false);

-       } else {

-         $('#expiration-attrs *').attr('disabled', true);

-       }

-     });

-     if ( $("#passwordexp").is(":checked") ) {

-       $('#expiration-attrs *').attr('disabled', false);

-     } else {

-       $('#expiration-attrs *').attr('disabled', true);

-     }

- 

-     $("#passwordchecksyntax").change(function() {

-       if(this.checked) {

-         $('#syntax-attrs *').attr('disabled', false);

-       } else {

-         $('#syntax-attrs *').attr('disabled', true);

-       }

-     });

-     if ( $("#passwordchecksyntax").is(":checked") ) {

-       $('#syntax-attrs *').attr('disabled', false);

-     } else {

-       $('#syntax-attrs *').attr('disabled', true);

-     }

- 

-     $("#passwordlockout").change(function() {

-       if(this.checked) {

-         $('#lockout-attrs *').attr('disabled', false);

-       } else {

-         $('#lockout-attrs *').attr('disabled', true);

-       }

-     });

-     if ( $("#passwordlockout").is(":checked") ) {

-       $('#lockout-attrs *').attr('disabled', false);

-     } else {

-       $('#lockout-attrs *').attr('disabled', true);

-     }

- 

-     /*

-      * local password policy form control

-      */

-     $("#local-passwordhistory").change(function() {

-       if(this.checked) {

-         $('#local-passwordinhistory').attr('disabled', false);

-       } else {

-         $('#local-passwordinhistory').attr('disabled', true);

-       }

-     });

-     if ( $("#local-passwordhistory").is(":checked") ) {

-       $('#local-passwordhistory *').attr('disabled', false);

-     } else {

-       $('#local-passwordhistorys *').attr('disabled', true);

-     }

- 

-     $("#local-passwordexp").change(function() {

-       if(this.checked) {

-         $('#local-expiration-attrs *').attr('disabled', false);

-       } else {

-         $('#local-expiration-attrs *').attr('disabled', true);

-       }

-     });

-     if ( $("#local-passwordexp").is(":checked") ) {

-       $('#local-expiration-attrs *').attr('disabled', false);

-     } else {

-       $('#local-expiration-attrs *').attr('disabled', true);

-     }

- 

-     $("#local-passwordchecksyntax").change(function() {

-       if(this.checked) {

-         $('#local-syntax-attrs *').attr('disabled', false);

-       } else {

-         $('#local-syntax-attrs *').attr('disabled', true);

-       }

-     });

-     if ( $("#local-passwordchecksyntax").is(":checked") ) {

-       $('#local-syntax-attrs *').attr('disabled', false);

-     } else {

-       $('#local-syntax-attrs *').attr('disabled', true);

-     }

- 

-     $("#local-passwordlockout").change(function() {

-       if(this.checked) {

-         $('#local-lockout-attrs *').attr('disabled', false);

-       } else {

-         $('#local-lockout-attrs *').attr('disabled', true);

-       }

-     });

-     if ( $("#local-passwordlockout").is(":checked") ) {

-       $('local-lockout-attrs *').attr('disabled', false);

-     } else {

-       $('#local-lockout-attrs *').attr('disabled', true);

-     }

- 

-     /*

-      * Logging form control

-      */

-     $("#nsslapd-accesslog-logging-enabled").change(function() {

-       if(this.checked) {

-         $('#accesslog-attrs *').attr('disabled', false);

-       } else {

-         $('#accesslog-attrs *').attr('disabled', true);

-       }

-     });

- 

-     $("#nsslapd-errorlog-logging-enabled").change(function() {

-       if(this.checked) {

-         $('#errorlog-attrs *').attr('disabled', false);

-       } else {

-         $('#errorlog-attrs *').attr('disabled', true);

-       }

-     });

- 

-     $("#nsslapd-auditlog-logging-enabled").change(function() {

-       if(this.checked) {

-         $('#auditlog-attrs *').attr('disabled', false);

-       } else {

-         $('#auditlog-attrs *').attr('disabled', true);

-       }

-     });

- 

-     $("#nsslapd-auditfaillog-logging-enabled").change(function() {

-       if(this.checked) {

-         $('#auditfaillog-attrs *').attr('disabled', false);

-       } else {

-         $('#auditfaillog-attrs *').attr('disabled', true);

-       }

-     });

- 

-     $("#nsslapd-ndn-cache-enabled").change(function() {

-       if(this.checked) {

-         $('#nsslapd-ndn-cache-max-size').attr('disabled', false);

-       } else {

-         $('#nsslapd-ndn-cache-max-size').attr('disabled', true);

-       }

-     });

- 

-     // LDAPI form control

-     $("#nsslapd-ldapimaptoentries").change(function() {

-       if (this.checked){

-         $(".autobind-entry-attrs").show();

-       } else {

-         $(".autobind-entry-attrs").hide();

-       }

-     });

- 

-     /*

-      *  Modal Forms

-      */

- 

-     /*

-      * Local password policy

-      */

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

-       clear_local_pwp_form();

-     });

- 

-     $("#local-pwp-save").on("click", function() {

-       /*

-        * We are either saving a new policy or editing an existing one

-        * If we are editing and we remove a setting, we have to set it

-        * to the default value - so it makes things a little more tedious

-        * for editing a local policy

-        */

- 

-       // Is this the create or edit form?

-       var edit = false;

-       if ( $("#local-pwp-header").text().startsWith('Edit') ) {

-         edit = true;

-       }

- 

-       /*

-        * Get all the current values from the form.

-        */

-       var policy_name = $("#local-entry-dn").val();

-       if (policy_name == "" || !valid_dn(policy_name)) {

-         popup_msg("Error", "You must enter a valid DN for the local password policy");

-         return;

-       }

-       var pwp_track = "off";

-       if ( $("#local-passwordtrackupdatetime").is(":checked") ) {

-         pwp_track = "on";

-       }

-       var pwp_passwordchange = "off";

-       if ($("#local-passwordchange").is(":checked") ){

-         pwp_passwordchange = "on";

-       }

-       var pwp_passwordmustchange = "off";

-       if ($("#local-passwordmustchange").is(":checked") ){

-         pwp_passwordmustchange = "on";

-       }

-       var pwp_history = "off";

-       if ($("#local-passwordhistory").is(":checked") ){

-         pwp_history = "on";

-       }

-       var pwp_exp = "off";

-       if ($("#local-passwordexp").is(":checked") ){

-         pwp_exp = "on";

-       }

-       var pwp_sendexp = "off";

-       if ($("#local-passwordsendexpiringtime").is(":checked") ){

-         pwp_sendexp = "on";

-       }

-       var pwp_lockout = "off";

-       if ($("#local-passwordlockout").is(":checked") ){

-         pwp_lockout = "on";

-       }

-       var pwp_unlock = "off";

-       if ($("#local-passwordunlock").is(":checked") ){

-         pwp_unlock = "on";

-       }

-       var pwp_checksyntax = "off";

-       if ($("#local-passwordchecksyntax").is(":checked") ){

-         pwp_checksyntax = "on";

-       }

-       var pwp_palindrome = "on";

-       if ( $("#local-passwordpalindrome").is(":checked") ){

-         pwp_palindrome = "off";

-       }

-       var pwp_dictcheck = "off";

-       if ( $("#local-passworddictcheck").is(":checked") ){

-          pwp_dictcheck = "on";

-       }

- 

-       var pwp_admin = $("#local-passwordadmindn").val();

-       var pwp_inhistory = $("#local-passwordinhistory").val();

-       var pwp_minage = $("#local-passwordminage").val();

-       var pwp_maxage = $("#local-passwordmaxage").val();

-       var pwp_gracelimit = $("#local-passwordgracelimit").val();

-       var pwp_warning = $("#local-passwordwarning").val();

-       var pwp_maxfailure = $("#local-passwordmaxfailure").val();

-       var pwp_failcount = $("#local-passwordresetfailurecount").val();

-       var pwp_lockoutdur = $("#local-passwordlockoutduration").val();

-       var pwp_minlen = $("#local-passwordminlength").val();

-       var pwp_mindigits = $("#local-passwordmindigits").val();

-       var pwp_minalphas = $("#local-passwordminalphas").val();

-       var pwp_minuppers = $("#local-passwordminuppers").val();

-       var pwp_minlowers = $("#local-passwordminlowers").val();

-       var pwp_minspecials = $("#local-passwordminspecials").val();

-       var pwp_min8bits = $("#local-passwordmin8bit").val();

-       var pwp_maxrepeats = $("#local-passwordmaxrepeats").val();

-       var pwp_mincat = $("#local-passwordmincategories").val();

-       var pwp_mintoken = $("#local-passwordmintokenlength").val();

-       var pwp_badwords = $("#local-passwordbadwords").val();

-       var pwp_userattrs = $("#local-passworduserattributes").val();

-       var pwp_maxseq = $("#local-passwordmaxsequence").val();

-       var pwp_maxseqset = $("#local-passwordmaxseqsets").val();

-       var pwp_maxclass = $("#local-passwordmaxclasschars").val();

-       var pwp_scheme = $("#local-passwordstoragescheme").val();

- 

-       var pwp_type = "User Policy";

-       if ( $("#subtree-pwp-radio").is(":checked")) {

-         pwp_type = "Subtree Policy";

-       }

- 

-       /*

-        * Go through all the settings and create an arg list, but if editing

-        * the policy and the value is now "", we need to set it to the default

-        * value.

-        */

-       arg_list = [];

- 

-       // Do the on/off settings first

-       arg_list.push('--pwdtrack=' + pwp_track);

-       arg_list.push('--pwdchange=' + pwp_passwordchange);

-       arg_list.push('--pwdmustchange=' + pwp_passwordmustchange);

-       arg_list.push('--pwdhistory=' + pwp_history);

-       arg_list.push('--pwdexpire=' + pwp_exp);

-       arg_list.push('--pwdlockout=' + pwp_lockout);

-       arg_list.push('--pwdunlock=' + pwp_unlock);

-       arg_list.push('--pwdchecksyntax=' + pwp_checksyntax);

-       arg_list.push('--pwdpalindrome=' + pwp_palindrome);

-       arg_list.push('--pwddictcheck=' + pwp_dictcheck);

-       arg_list.push('--pwdsendexpiring=' + pwp_sendexp);

-       // Do the rest

-       if ( !add_validate_arg (arg_list, "num", pwp_inhistory, "0", edit, 'passwordinhistory', '--pwdhistorycount', 'Passwords in history') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_minage, "0", edit, 'passwordminage', '--pwdminage', 'Allowed Password Changes') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_maxage, "0", edit, 'passwordmaxage', '--pwdmaxage', 'Password Expiration Time') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_gracelimit, "0", edit, 'passwordgracelimit', '--pwdgracelimit', 'Allowed Logins') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_inhistory, "0", edit, 'passwordwarning', '--pwdwarning', 'Password Warning') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_maxfailure, "0", edit, 'passwordmaxfailure', '--pwdmaxfailures', 'Number of Failed Logins') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_failcount, "0", edit, 'passwordresetfailurecount', '--pwdresetfailcount', 'Failure Count Reset') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_lockoutdur, "0", edit, 'passwordlockoutduration', '--pwdlockoutduration', 'Time Until Account Unlock') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_minlen, "6", edit, 'passwordminlength', '--pwdminlen', 'Password Minimum Length') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_mindigits, "0", edit, 'passwordmindigits', '--pwdmindigits', 'Minimum Digits') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_minalphas, "0", edit, 'passwordminalphas', '--pwdminalphas', 'Minimum Alphas') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_minuppers, "0", edit, 'passwordminuppers', '--pwdminuppers', 'Minimum Uppercase Characters') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_minlowers, "0", edit, 'passwordminlowers', '--pwdminlowers', 'Minimum Lowercase Characters') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_minspecials, "0", edit, 'passwordminspecials', '--pwdminspecials', 'Minimum Special Characters') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_min8bits, "0", edit, 'passwordmin8bits', '--pwdmin8bits', 'Minimum Alphas') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_maxrepeats, "0", edit, 'passwordmaxrepeats', '--pwdmaxrepeats', 'Maximum Repeats') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_mincat, "3", edit, 'passwordmincategories', '--pwdmincatagories', 'Minimum Catagories') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_mintoken, "3", edit, 'passwordmintokenlength', '--pwdmintokenlen', 'Minimum Alphas') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_maxseq, "0", edit, 'passwordmaxsequence', '--pwdmaxseq', 'Maximum Sequence') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_maxseqset, "0", edit, 'passwordmaxseqsets', '--pwdmaxseqsets', 'Maximum Sequence Sets') ) { return; }

-       if ( !add_validate_arg (arg_list, "num", pwp_maxclass, "0", edit, 'passwordmaxclasschars', '--pwdmaxclasschars', 'Maximum Character Classes') ) { return; }

-       if ( !add_validate_arg (arg_list, "dn", pwp_admin, "", edit, 'passwordadmindn', '--pwdadmin', 'Password Administrator') ) { return; }

-       if (pwp_badwords != "" || (edit && localpwp_values['passwordbadwords'] !== undefined && pwp_badwords != localpwp_values['passwordbadwords'])) {

-         arg_list.push('--pwdbadwords=' + pwp_badwords);

-       }

-       if (pwp_userattrs != "" || (edit && localpwp_values['passworduserattributes'] !== undefined && pwp_userattrs != localpwp_values['passworduserattributes'])) {

-         arg_list.push('--pwduserattrs=' + pwp_userattrs);

-       }

-       if (pwp_scheme != "" || (edit && localpwp_values['passwordstoragescheme'] !== undefined && pwp_scheme != localpwp_values['passwordstoragescheme'])) {

-         arg_list.push('--pwdscheme=' + pwp_scheme);

-       }

- 

-       /*

-        * Update/Add Password policy to DS

-        */

-       if ( edit ) {

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

-         cmd = cmd.concat(arg_list);

-         log_cmd('local-pwp-save', 'Set local password policy', cmd);

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

-           popup_success('Successfully edited local password policy');

-           $("#local-pwp-form").modal('toggle')

-         }).fail(function(data) {

-             popup_err("Failed to edit local password policy", data.message);

-         });

-       } else {

-         // Create new local policy

-         var action = "addsubtree";

-         if (pwp_type == "User Policy") {

-           action = "adduser";

-         }

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

-         cmd = cmd.concat(arg_list);

-         log_cmd('local-pwp-save', 'Add local password policy', cmd);

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

-           pwp_table.row.add( [

-               policy_name,

-               pwp_type,

-               local_pwp_html

-           ] ).draw( false );

-           popup_success('Successfully created local password policy');

-           $("#local-pwp-form").modal('toggle');

-         }).fail(function(data) {

-             popup_err("Failed to create local password policy", data.message);

-         });

-       }

-     });

- 

-     // Delete local password policy

-     $(document).on('click', '.delete-local-pwp', function(e) {

-       e.preventDefault();

-       // Update HTML table

-       var data = pwp_table.row( $(this).parents('tr') ).data();

-       var del_pwp_name = data[0];

-       var pwp_row = $(this);

-       popup_confirm("Are you sure you want to delete local password policy: <b>" + del_pwp_name + "</b>", "Confirmation", function (yes) {

-         if (yes) {

-           // Delete pwp from DS

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

-           log_cmd('.delete-local-pwp (click)', 'Remove local password policy', cmd);

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

-             // Update html table

-             pwp_table.row( pwp_row.parents('tr') ).remove().draw( false );

-             popup_success('Successfully deleted local password policy');

-           }).fail(function(data) {

-               popup_err("Failed to delete local password policy", data.message);

-           });

-         }

-       });

-     });

- 

-     // SASL Mappings Form

-     $("#sasl-map-save").on("click", function() {

-       var sasl_map_name = $("#sasl-map-name").val();

-       var sasl_regex =  $("#sasl-map-regex").val();

-       var sasl_base =  $("#sasl-map-base").val();

-       var sasl_filter = $("#sasl-map-filter").val();

-       var sasl_priority = $("#sasl-map-priority").val();

- 

-       // Validate values

-       if (sasl_map_name == '') {

-         report_err($("#sasl-map-name"), 'You must provide a mapping name');

-         return;

-       }

-       if (sasl_map_name == '') {

-         report_err($("#sasl-map-regex"), 'You must provide an regex');

-         return;

-       }

-       if (sasl_regex == '') {

-         report_err($("#sasl-map-base"), 'You must provide a base DN template');

-         return;

-       }

-       if (sasl_filter == '') {

-         report_err($("#sasl-map-filter"), 'You must provide an filter template');

-         return;

-       }

-       if (sasl_priority == '') {

-         sasl_priority = '100'

-       } else if (valid_num(sasl_priority)) {

-         var priority = Number(sasl_priority);

-         if (priority < 1 || priority > 100) {

-           report_err($("#sasl-map-priority"), 'You must provide a number between 1 and 100');

-           return;

-         }

-       } else {

-         report_err($("#sasl-map-priority"), 'You must provide a number between 1 and 100');

-         return;

-       }

- 

-       // Build command line args

-       var sasl_name_cmd = "--cn=" + sasl_map_name ;

-       var sasl_regex_cmd = "--nsSaslMapRegexString=" + sasl_regex;

-       var sasl_base_cmd = "--nsSaslMapBaseDNTemplate=" + sasl_base;

-       var sasl_filter_cmd = "--nsSaslMapFilterTemplate=" + sasl_filter;

-       var sasl_priority_cmd = "--nsSaslMapPriority=" + sasl_priority;

- 

-       if ( $("#sasl-header").html().includes("Create") ) {

-         // Create new mapping and update table

-         var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','sasl', 'create',

-                    sasl_name_cmd, sasl_regex_cmd, sasl_base_cmd, sasl_filter_cmd, sasl_priority_cmd];

-         log_cmd('#sasl-map-save (click)', 'Add SASL mapping', cmd);

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

-           // Update html table

-           sasl_table.row.add( [

-             sasl_map_name,

-             sasl_regex,

-             sasl_base,

-             sasl_filter,

-             sasl_priority,

-             sasl_action_html

-           ] ).draw( false );

-           popup_success("Successfully added new SASL mapping");

-           $("#sasl-map-form").modal('toggle');

-         }).fail(function(data) {

-           popup_err("Failed To Add SASL Mapping: " + sasl_map_name, data.message);

-           $("#sasl-map-form").modal("toggle");

-         });

-       } else {

-         // Editing mapping.  First delete the old mapping

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

-         log_cmd('#sasl-map-save (click)', 'Delete SASL mapping', cmd);

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

-           // Remove row from old

-           sasl_table.rows( function ( idx, data, node ) {

-             return data[0] == sasl_map_name;

-           }).remove().draw();

- 

-           // Then add new mapping and update table

-           var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id + '.socket','sasl', 'create',

-                      sasl_name_cmd, sasl_regex_cmd, sasl_base_cmd, sasl_filter_cmd, sasl_priority_cmd];

-           log_cmd('#sasl-map-save (click)', 'Add SASL mapping', cmd);

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

-             // Update html table

-             sasl_table.row.add( [

-               sasl_map_name,

-               sasl_regex,

-               sasl_base,

-               sasl_filter,

-               sasl_priority,

-               sasl_action_html

-             ] ).draw( false );

-             popup_success("Successfully added new SASL mapping");

-             $("#sasl-map-form").modal('toggle');

-           }).fail(function(data) {

-             popup_err("Failure Adding SASL Mapping",

-                       "Failed To Add SASL Mapping: <b>" + sasl_map_name + "</b>: \n" + data.message);

-             $("#sasl-map-form").modal("toggle");

-           });

-         }).fail(function(data) {

-           popup_err("Failure Deleting Old SASL Mapping",

-                     "Failed To Delete SASL Mapping: <b>" + sasl_map_name + "</b>: \n" + data.message);

-           $("#sasl-map-form").modal("toggle");

-         });

-       }

-     });

- 

-     /* Backup server */

-     $("#ds-backup-btn").on('click', function () {

-       var backup_name = $("#backup-name").val();

-       if (backup_name == ""){

-         popup_msg("Error", "Backup must have a name");

-         return;

-       }

-       if (backup_name.indexOf(' ') >= 0) {

-         popup_msg("Error", "Backup name can not contain any spaces");

-         return;

-       }

-       if (backup_name.indexOf('/') >= 0) {

-         popup_msg("Error", "Backup name can not contain a forward slash. " +

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

-         return;

-       }

- 

-       // First check if backup name is already used

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

-       log_cmd('#restore-server-btn (click)', 'Restore server instance', check_cmd);

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

-         var obj = JSON.parse(data);

-         var found_backup = false;

-         for (var i = 0; i < obj.items.length; i++) {

-           if (obj.items[i][0] == backup_name) {

-             found_backup = true;

-             break;

-           }

-         }

-         if (found_backup) {

-           popup_confirm("A backup already exists with this name, replace it?", "Confirmation", function (yes) {

-             if (yes) {

-               do_backup(server_inst, backup_name);

-             } else {

-               return;

-             }

-           });

-         } else {

-           do_backup(server_inst, backup_name);

-         }

-       });

-     });

- 

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

-       $("#backup-name").val("");

-     });

- 

-     /* Restore.  load restore table with current backups */

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

-       var cmd = [DSCTL, '-j', server_inst, 'backups'];

-       log_cmd('#restore-server-btn (click)', 'Restore server instance', cmd);

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

-         var backup_btn = "<button class=\"btn btn-default restore-btn\" type=\"button\">Restore</button>";

-         var del_btn =  "<button title=\"Delete backup directory\" class=\"btn btn-default ds-del-backup-btn\" type=\"button\"><span class='glyphicon glyphicon-trash'></span></button>";

-         var obj = JSON.parse(data);

-         backup_table.clear().draw( false );

-         for (var i = 0; i < obj.items.length; i++) {

-           var backup_name = obj.items[i][0];

-           var backup_date = obj.items[i][1];

-           var backup_size = obj.items[i][2];

-           backup_table.row.add([backup_name, backup_date, backup_size, backup_btn, del_btn]).draw( false );

-         }

-       }).fail(function(data) {

-         popup_err("Failed to get list of backups", data.message);

-       });

-     });

- 

-     /* Restore server */

-     $(document).on('click', '.restore-btn', function(e) {

-       e.preventDefault();

-       var data = backup_table.row( $(this).parents('tr') ).data();

-       var restore_name = data[0];

-       popup_confirm("Are you sure you want to restore this backup:  <b>" + restore_name + "<b>", "Confirmation", function (yes) {

-         if (yes) {

-           var cmd = [DSCTL, '-j', server_inst, 'status'];

-           $("#restore-spinner").show();

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

-           done(function(status_data) {

-             var status_json = JSON.parse(status_data);

-             if (status_json.running == true) {

-               var cmd = [DSCONF, server_inst, 'backup', 'restore',  restore_name];

-               log_cmd('.restore-btn (click)', 'Restore server instance(online)', cmd);

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

-               done(function(data) {

-                 $("#restore-spinner").hide();

-                 popup_success("The backup has been restored");

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

-               }).

-               fail(function(data) {

-                 $("#restore-spinner").hide();

-                 popup_err("Failed to restore from the backup", data.message);

-               });

-             } else {

-               var cmd = [DSCTL, server_inst, 'bak2db', restore_name];

-               log_cmd('.restore-btn (click)', 'Restore server instance(offline)', cmd);

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

-               done(function(data) {

-                 $("#restore-spinner").hide();

-                 popup_success("The backup has been restored");

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

-               }).

-               fail(function(data) {

-                 $("#restore-spinner").hide();

-                 popup_err("Failed to restore from the backup", data.message);

-               });

-             }

-           }).

-           fail(function() {

-             popup_err("Failed to check the server status", data.message);

-           });

-         }

-       });

-     });

- 

-     /* Delete backup directory */

-     $(document).on('click', '.ds-del-backup-btn', function(e) {

-       e.preventDefault();

-       var data = backup_table.row( $(this).parents('tr') ).data();

-       var restore_name = data[0];

-       var backup_row = $(this);

-       popup_confirm("Are you sure you want to delete this backup: <b>" + restore_name + "</b>", "Confirmation", function (yes) {

-         if (yes) {

-           var cmd = [DSCTL, server_inst, 'backups', '--delete', restore_name];

-           $("#restore-spinner").show();

-           log_cmd('.ds-del-backup-btn (click)', 'Delete backup', cmd);

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

-             $("#restore-spinner").hide();

-             backup_table.row( backup_row.parents('tr') ).remove().draw( false );

-             popup_success("The backup has been deleted");

-           }).fail(function(data) {

-             $("#restore-spinner").hide();

-             popup_err("Failed to delete the backup", data.message);

-           });

-         }

-       });

-     });

- 

-     /* reload schema */

-     $("#schema-reload-btn").on("click", function () {

-       var schema_dir = $("#reload-dir").val();

-       if (schema_dir != ""){

-         var cmd = [DSCONF, server_inst, 'schema', 'reload', '--schemadir', schema_dir, '--wait'];

-       } else {

-         var cmd = [DSCONF, server_inst, 'schema', 'reload', '--wait'];

-       }

-       $("#reload-spinner").show();

-       log_cmd('#schema-reload-btn (click)', 'Reload schema files', cmd);

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

-         popup_success("Successfully reloaded schema");  // TODO use timed interval success msg (waiting for another PR top be merged before we can add it)

-         $("#schema-reload-form").modal('toggle');

-         $("#reload-spinner").hide();

-       }).fail(function(data) {

-         popup_err("Failed to reload schema files", data.message);

-         $("#reload-spinner").hide();

-       });

-     });

- 

-     // Create instance form

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

-       clear_inst_form();

-       set_ports();

-     });

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

-       clear_inst_form();

-     });

- 

-     // Create Instance

-     $("#create-inst-save").on("click", function() {

-       $(".ds-modal-error").hide();

-       $(".ds-inst-input").css("border-color", "initial");

- 

-       /*

-        * Validate settings and update the INF settings

-        */

-       var setup_inf = create_inf_template;

- 

-       // Server ID

-       var new_server_id = $("#create-inst-serverid").val();

-       if (new_server_id == ""){

-         report_err($("#create-inst-serverid"), 'You must provide an Instance name');

-         $("#create-inst-serverid").css("border-color", "red");

-         return;

-       } else {

-         new_server_id = new_server_id.replace(/^slapd-/i, "");  // strip "slapd-"

-         if (new_server_id.length > 128) {

-             report_err($("#create-inst-serverid"), 'Instance name is too long, it must not exceed 128 characters');

-             $("#create-inst-serverid").css("border-color", "red");

-             return;

-         }

-         if (new_server_id.match(/^[#%:A-Za-z0-9_\-]+$/g)) {

-             setup_inf = setup_inf.replace('INST_NAME', new_server_id);

-         } else {

-             report_err($("#create-inst-serverid"), 'Instance name can only contain letters, numbers, and:  # % : - _');

-             $("#create-inst-serverid").css("border-color", "red");

-             return;

-         }

-       }

- 

-       // Port

-       var server_port = $("#create-inst-port").val();

-       if (server_port == ""){

-         report_err($("#create-inst-port"), 'You must provide a port number');

-         $("#create-inst-port").css("border-color", "red");

-         return;

-     } else if (!valid_port(server_port)) {

-         report_err($("#create-inst-port"), 'Port must be a number between 1 and 65534!');

-         $("#create-inst-port").css("border-color", "red");

-         return;

-       } else {

-         setup_inf = setup_inf.replace('PORT', server_port);

-       }

- 

-       // Secure Port

-       var secure_port = $("#create-inst-secureport").val();

-       if (secure_port == ""){

-         report_err($("#create-inst-secureport"), 'You must provide a secure port number');

-         $("#create-inst-secureport").css("border-color", "red");

-         return;

-     } else if (!valid_port(secure_port)) {

-         report_err($("#create-inst-secureport"), 'Secure port must be a number!');

-         $("#create-inst-secureport").css("border-color", "red");

-         return;

-       } else {

-         setup_inf = setup_inf.replace('SECURE_PORT', secure_port);

-       }

- 

-       // Root DN

-       var server_rootdn = $("#create-inst-rootdn").val();

-       if (server_rootdn == ""){

-         report_err($("#create-inst-rootdn"), 'You must provide a Directory Manager DN');

-         $("#create-inst-rootdn").css("border-color", "red");

-         return;

-       } else {

-         setup_inf = setup_inf.replace('ROOTDN', server_rootdn);

-       }

- 

-       // Setup Self-Signed Certs

-       if ( $("#create-inst-tls").is(":checked") ){

-         setup_inf = setup_inf.replace('SELF_SIGN', 'True');

-       } else {

-         setup_inf = setup_inf.replace('SELF_SIGN', 'False');

-       }

- 

-       // Root DN password

-       var root_pw = $("#rootdn-pw").val();

-       var root_pw_confirm = $("#rootdn-pw-confirm").val();

-       if (root_pw != root_pw_confirm) {

-         report_err($("#rootdn-pw"), 'Directory Manager passwords do not match!');

-         $("#rootdn-pw-confirm").css("border-color", "red");

-         return;

-       } else if (root_pw == ""){

-         report_err($("#rootdn-pw"), 'Directory Manager password can not be empty!');

-         $("#rootdn-pw-confirm").css("border-color", "red");

-         return;

-       } else if (root_pw.length < 8) {

-         report_err($("#rootdn-pw"), 'Directory Manager password must have at least 8 characters');

-         $("#rootdn-pw-confirm").css("border-color", "red");

-         return;

-       } else {

-         setup_inf = setup_inf.replace('ROOTPW', root_pw);

-       }

- 

-       // Backend/Suffix

-       var backend_name = $("#backend-name").val();

-       var backend_suffix = $("#backend-suffix").val();

-       if ( (backend_name != "" && backend_suffix == "") || (backend_name == "" && backend_suffix != "") ) {

-         if (backend_name == ""){

-           report_err($("#backend-name"), 'If you specify a backend suffix, you must also specify a backend name');

-           $("#backend-name").css("border-color", "red");

-           return;

-         } else {

-           report_err($("#backend-suffix"), 'If you specify a backend name, you must also specify a backend suffix');

-           $("#backend-suffix").css("border-color", "red");

-           return;

-         }

-       }

-       if (backend_name != ""){

-         // We definitely have a backend name and suffix, next validate the suffix is a DN

-         if (valid_dn(backend_suffix)) {

-           // It's valid, add it

-           setup_inf += "\n[backend-" + backend_name + "]\nsuffix = " + backend_suffix + "\n";

-         } else {

-           // Not a valid DN

-           report_err($("#backend-suffix"), 'Invalid DN for Backend Suffix');

-           return;

-         }

-         if ( $("#create-sample-entries").is(":checked") ) {

-           setup_inf += '\nsample_entries = yes\n';

-         } else if ( $("#create-suffix-entry").is(":checked") ) {

-           setup_inf += '\ncreate_suffix_entry = yes\n';

-         }

-       }

- 

-       /*

-        * Here are steps we take to create the instance

-        *

-        * [1] Get FQDN Name for nsslapd-localhost setting in setup file

-        * [2] Create a file for the inf setup parameters

-        * [3] Set strict permissions on that file

-        * [4] Populate the new setup file with settings (including cleartext password)

-        * [5] Create the instance

-        * [6] Remove setup file

-        */

-       cockpit.spawn(["hostname", "--fqdn"], { superuser: true, "err": "message" }).fail(function(ex, data) {

-         // Failed to get FQDN

-         popup_err("Failed to get hostname!", data);

-       }).done(function (data){

-         /*

-          * We have FQDN, so set the hostname in inf file, and create the setup file

-          */

-         setup_inf = setup_inf.replace('FQDN', data);

-         var setup_file = "/tmp/389-setup-" + (new Date).getTime() + ".inf";

-         var rm_cmd = ['rm', setup_file];

-         var create_file_cmd = ['touch', setup_file];

-         cockpit.spawn(create_file_cmd, { superuser: true, "err": "message" }).fail(function(ex, data) {

-           // Failed to create setup file

-           popup_err("Failed to create installation file!", data);

-         }).done(function (){

-           /*

-            * We have our new setup file, now set permissions on that setup file before we add sensitive data

-            */

-           var chmod_cmd = ['chmod', '600', setup_file];

-           cockpit.spawn(chmod_cmd, { superuser: true, "err": "message" }).fail(function(ex, data) {

-             // Failed to set permissions on setup file

-             cockpit.spawn(rm_cmd, { superuser: true });  // Remove Inf file with clear text password

-             $("#create-inst-spinner").hide();

-             popup_err("Failed to set permission on setup file " + setup_file + ": ", data);

-           }).done(function (){

-             /*

-              * Success we have our setup file and it has the correct permissions.

-              * Now populate the setup file...

-              */

-             let cmd = ["/bin/sh", "-c", '/usr/bin/echo -e "' + setup_inf + '" >> ' + setup_file];

-             cockpit.spawn(cmd, { superuser: true, "err": "message" }).fail(function(ex, data) {

-               // Failed to populate setup file

-               popup_err("Failed to populate installation file!", data);

-             }).done(function (){

-               /*

-                * Next, create the instance...

-                */

-               cmd = [DSCREATE, 'from-file', setup_file];

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

-                 // Failed to create the new instance!

-                 cockpit.spawn(rm_cmd, { superuser: true });  // Remove Inf file with clear text password

-                 $("#create-inst-spinner").hide();

-                 popup_err("Failed to create instance!", data);

-               }).done(function (){

-                 // Success!!!  Now cleanup everything up...

-                 cockpit.spawn(rm_cmd, { superuser: true });  // Remove Inf file with clear text password

-                 $("#create-inst-spinner").hide();

-                 $("#server-list-menu").attr('disabled', false);

-                 $("#no-instances").hide();

-                 get_insts();  // Refresh server list

-                 popup_success("Successfully created instance:  <b>slapd-" + new_server_id + "</b>");

-                 $("#create-inst-form").modal('toggle');

-               });

-             });

-             $("#create-inst-spinner").show();

-           });

-         });

-       }).fail(function(data) {

-         console.log("failed: " + data.message);

-       });

-     });

- 

-     // Accordion opening/closings

-     $(".ds-accordion-panel").css('display','none');

- 

-     $("#config-accordion").on("click", function() {

-       this.classList.toggle("active");

-       var panel = this.nextElementSibling;

-       if (panel.style.display === "block") {

-         var show = "&#9658 Show Advanced Settings ";

-         $(this).html(show);

-         panel.style.display = "none";

-         $(this).blur();

-       } else {

-         var hide = "&#9660 Hide Advanced Settings ";

-         $(this).html(hide);

-         panel.style.display = "block";

-         $(this).blur();

-       }

-     });

- 

-     $("#tuning-config-accordion").on("click", function() {

-       this.classList.toggle("active");

-       var panel = this.nextElementSibling;

-       if (panel.style.display === "block") {

-         var show = "&#9658 Show Advanced Settings ";

-         $(this).html(show);

-         panel.style.display = "none";

-         $(this).blur();

-       } else {

-         var hide = "&#9660 Hide Advanced Settings ";

-         $(this).html(hide);

-         panel.style.display = "block";

-         $(this).blur();

-       }

-     });

- 

-     $('#local-pwp-suffix').on('change', function () {

-       // Reload the table for the new selected suffix

-       get_and_set_localpwp();

-     });

- 

-     // Edit Local Password Policy

-     $(document).on('click', '.edit-local-pwp', function(e) {

-       e.preventDefault();

-       clear_local_pwp_form();

- 

-       var data = pwp_table.row( $(this).parents('tr') ).data();

-       var policy_name = data[0];

- 

-       // lookup the entry, and get the current settings

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

-       log_cmd('.edit-local-pwp (click)', 'Get local password policy', cmd);

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

-         localpwp_values = {};  // Clear it out

-         var obj = JSON.parse(data);

-         for (var attr in obj['attrs']) {

-           var val = obj['attrs'][attr];

-           $("#local-" + attr).val(val);

-           if (val == "on") {

-             $("#local-" + attr).prop('checked', true);

-             $("#local-" + attr).trigger('change');

-           } else if (val == "off") {

-             $("#local-" + attr).prop('checked', false);

-             $("#local-" + attr).trigger('change');

-           }

-           localpwp_values[attr] = val;

-         }

-         if ( obj['pwp_type'] == "User") {

-           $("#user-pwp-radio").prop("checked", true );

-         } else {

-           $("#subtree-pwp-radio").prop("checked", true );

-         }

- 

-         // Set the form header and fields

-         $("#local-pwp-header").html("<b>Edit Local Password Policy</b>");

-         $("#local-entry-dn").val(policy_name);

-         // Disable radio buttons

-         $("#subtree-pwp-radio").attr('disabled', true);

-         $("#user-pwp-radio").attr('disabled', true);

-         $("#local-entry-dn").attr('disabled', true);

- 

-         // Open form

-         $("#local-pwp-form").modal('toggle');

- 

-       }).fail(function(data) {

-           popup_err("Failed to get local password policy", data.message);

-       });

-     });

- 

-     // Mark this page as loaded

-     server_page_loaded = 1;

-   }); // servers.html loaded

- }); // Document ready

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

          "fonts",

          "images",

          "index.html",

-         "servers.html",

-         "servers.js",

          "static",

          "manifest.json"

      ]

@@ -179,7 +179,7 @@ 

          # ensure all the keys are lowercase

          str_attrs = dict((k.lower(), v) for k, v in list(str_attrs.items()))

  

-         response = json.dumps({"type": "entry", "dn": ensure_str(self._dn), "attrs": str_attrs})

+         response = json.dumps({"type": "entry", "dn": ensure_str(self._dn), "attrs": str_attrs}, indent=4)

  

          return response

  
@@ -219,6 +219,17 @@ 

          # How can we be sure this returns the primary one?

          return ensure_str(self.get_attr_val(self._rdn_attribute))

  

+     def get_basedn(self):

+         """Get the suffix this entry belongs to

+         """

+         from lib389.backend import Backends

+         backends = Backends(self._instance).list()

+         for backend in backends:

+             suffix = backend.get_suffix()

+             if self._dn.endswith(suffix):

+                 return suffix

+         return ""

+ 

      def present(self, attr, value=None):

          """Assert that some attr, or some attr / value exist on the entry.

  

file modified
+3 -1
@@ -951,4 +951,6 @@ 

          self._must_attributes = ['cn']

          self._create_objectclasses = ['top', 'extensibleObject']

          self._protected = True

-         self._dn = "cn=config,cn=ldbm database,cn=plugins,cn=config"

+         # Have to set cn=bdb, but when we can choose between bdb and lmdb we'll

+         # have some hoops to jump through.

+         self._dn = "cn=bdb,cn=config,cn=ldbm database,cn=plugins,cn=config"

@@ -168,7 +168,7 @@ 

      ol = mc.list()

      if len(ol) == 0:

          if args and args.json:

-             print(json.dumps({"type": "list", "items": []}))

+             print(json.dumps({"type": "list", "items": []}, indent=4))

          else:

              log.info("No objects to display")

      elif len(ol) > 0:
@@ -182,7 +182,7 @@ 

              else:

                  print(o_str)

          if args and args.json:

-             print(json.dumps(json_result))

+             print(json.dumps(json_result, indent=4))

  

  

  # Display these entries better!
@@ -214,7 +214,7 @@ 

          else:

              print(mc.display_attr(attr).rstrip())

      if args.json:

-         print(json.dumps({"type": "entry", "dn": mc._dn, "attrs": vals}))

+         print(json.dumps({"type": "entry", "dn": mc._dn, "attrs": vals}, indent=4))

  

  

  def _generic_get_dn(inst, basedn, log, manager_class, dn, args=None):
@@ -247,6 +247,7 @@ 

              if "=" in myattr:

                  [attr, val] = myattr.split("=", 1)

                  mc.replace(attr, val)

+                 print("MARK val: " + val)

                  print("Successfully replaced \"{}\"".format(attr))

              else:

                  raise ValueError("You must specify a value to replace the attribute ({})".format(myattr))

@@ -144,7 +144,7 @@ 

  

      be_list.sort()

      if args.json:

-         print(json.dumps({"type": "list", "items": be_list}))

+         print(json.dumps({"type": "list", "items": be_list}, indent=4))

      else:

          if len(be_list) > 0:

              for be in be_list:
@@ -331,13 +331,13 @@ 

      if len(subsuffixes) > 0:

          subsuffixes.sort()

          if args.json:

-             print(json.dumps({"type": "list", "items": subsuffixes}))

+             print(json.dumps({"type": "list", "items": subsuffixes}, indent=4))

          else:

              for sub in subsuffixes:

                  print(sub)

      else:

          if args.json:

-             print(json.dumps({"type": "list", "items": []}))

+             print(json.dumps({"type": "list", "items": []}, indent=4))

          else:

              print("No sub-suffixes under this backend")

  
@@ -432,7 +432,7 @@ 

      # No suffixes, return empty list

      if len(nodes) == 0:

          if args.json:

-             log.info(json.dumps(nodes))

+             log.info(json.dumps(nodes, indent=4))

          else:

              log.info("There are no suffixes defined")

      else:
@@ -442,7 +442,7 @@ 

  

          # Done

          if args.json:

-             log.info(json.dumps(nodes))

+             log.info(json.dumps(nodes, indent=4))

          else:

              print_suffix_tree(nodes, 1, log)

  
@@ -580,7 +580,7 @@ 

              else:

                  print(index.display())

      if args.json:

-         print(json.dumps({"type": "list", "items": results}))

+         print(json.dumps({"type": "list", "items": results}, indent=4))

  

  

  def backend_list_index(inst, basedn, log, args):
@@ -600,7 +600,7 @@ 

                  print(index.display())

  

      if args.json:

-         print(json.dumps({"type": "list", "items": results}))

+         print(json.dumps({"type": "list", "items": results}, indent=4))

  

  

  def backend_del_index(inst, basedn, log, args):
@@ -643,7 +643,7 @@ 

              else:

                  for result in results:

                      json_results.append(json.loads(result.get_all_attrs_json()))

-             print(json.dumps({"type": "list", "items": json_results}))

+             print(json.dumps({"type": "list", "items": json_results}, indent=4))

  

          else:

              if len(results) == 0:
@@ -694,7 +694,7 @@ 

                      print()

  

      if args.json:

-         print(json.dumps({"type": "list", "items": results}))

+         print(json.dumps({"type": "list", "items": results}, indent=4))

  

  

  def backend_get_vlv(inst, basedn, log, args):
@@ -727,7 +727,7 @@ 

                      print()

  

              if args.json:

-                 print(json.dumps({"type": "list", "items": results}))

+                 print(json.dumps({"type": "list", "items": results}, indent=4))

  

  

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

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

          if args.avail_controls:

              ctrls = chain_cfg.get_controls()

              if args.json:

-                 print(json.dumps({"type": "list", "items": ctrls}))

+                 print(json.dumps({"type": "list", "items": ctrls}, indent=4))

              else:

                  print("Available Components:")

                  for ctrl in ctrls:
@@ -77,7 +77,7 @@ 

          if args.avail_comps:

              comps = chain_cfg.get_comps()

              if args.json:

-                 print(json.dumps({"type": "list", "items": comps}))

+                 print(json.dumps({"type": "list", "items": comps}, indent=4))

              else:

                  print("Available Controls:")

                  for comp in comps:

@@ -18,7 +18,7 @@ 

          results = []

          for conflict in conflicts:

              results.append(json.loads(conflict.get_all_attrs_json()))

-         log.info(json.dumps({'type': 'list', 'items': results}))

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

      else:

          if len(conflicts) > 0:

              for conflict in conflicts:
@@ -35,7 +35,7 @@ 

          results = []

          results.append(json.loads(conflict.get_all_attrs_json()))

          results.append(json.loads(valid_entry.get_all_attrs_json()))

-         log.info(json.dumps({'type': 'list', 'items': results}))

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

      else:

          log.info("Conflict Entry:\n")

          log.info(conflict.display(conflict_attrs))
@@ -64,7 +64,7 @@ 

          results = []

          for glue in glues:

              results.append(json.loads(glue.get_all_attrs_json()))

-         log.info(json.dumps({'type': 'list', 'items': results}))

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

      else:

          if len(glues) > 0:

              for glue in glues:

@@ -96,7 +96,7 @@ 

              log.info("Percentage Used: " + percent + "%\n")

  

      if args.json:

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

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

  

  

  def create_parser(subparsers):

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

      plugins = mc.list()

      if len(plugins) == 0:

          if args and args.json:

-             print(json.dumps({"type": "list", "items": []}))

+             print(json.dumps({"type": "list", "items": []}, indent=4))

          else:

              plugin_log.info("No objects to display")

      elif len(plugins) > 0:
@@ -67,7 +67,7 @@ 

              else:

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

          if args and args.json:

-             print(json.dumps(json_result))

+             print(json.dumps(json_result, indent=4))

  

  

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

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

  

  import json

  import ldap

+ from lib389.backend import Backends

  from lib389.utils import ensure_str

  from lib389.pwpolicy import PwPolicyEntries, PwPolicyManager

  from lib389.idm.account import Account
@@ -38,21 +39,30 @@ 

      attr_list = list(pwp_manager.arg_to_attr.values())

      if "global" in policy_type.lower():

          targetdn = 'cn=config'

-         attr_list.extend(['passwordIsGlobalPolicy', 'nsslapd-pwpolicy_local'])

+         policydn = targetdn

+         basedn = targetdn

+         attr_list.extend(['passwordisglobalpolicy', 'nsslapd-pwpolicy_local'])

          all_attrs = inst.config.get_attrs_vals_utf8(attr_list)

          attrs = {k: v for k, v in all_attrs.items() if len(v) > 0}

      else:

          policy = pwp_manager.get_pwpolicy_entry(targetdn)

-         targetdn = policy.dn

+         basedn = policy.get_basedn()

+         policydn = policy.dn

          all_attrs = policy.get_attrs_vals_utf8(attr_list)

          attrs = {k: v for k, v in all_attrs.items() if len(v) > 0}

      if use_json:

-         print(json.dumps({"type": "entry", "pwp_type": policy_type, "dn": ensure_str(targetdn), "attrs": attrs}))

+         print(json.dumps({

+             "dn": ensure_str(policydn),

+             "targetdn": targetdn,

+             "type": "entry",

+             "pwp_type": policy_type,

+             "basedn": basedn,

+             "attrs": attrs}, indent=4))

      else:

          if "global" in policy_type.lower():

              response = "Global Password Policy: cn=config\n------------------------------------\n"

          else:

-             response = "Local {} Policy: {}\n------------------------------------\n".format(policy_type, targetdn)

+             response = "Local {} Policy for \"{}\": {}\n------------------------------------\n".format(policy_type, targetdn, policydn)

          for key, value in list(attrs.items()):

              if len(value) == 0:

                  value = ""
@@ -64,33 +74,55 @@ 

  

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

      log = log.getChild('list_policies')

-     targetdn = args.DN[0]

+     

+     if args.DN is None:

+         # list all the password policies for all the backends

+         targetdns = []

+         backends = Backends(inst).list()

+         for backend in backends:

+             targetdns.append(backend.get_suffix())

+     else:

+         targetdns = [args.DN]

  

      if args.json:

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

      else:

          result = ""

  

-     # Verify target dn exists before getting started

-     user_entry = Account(inst, args.DN[0])

-     if not user_entry.exists():

-         raise ValueError('The target entry dn does not exist')

- 

-     # User pwpolicy entry is under the container that is under the parent,

-     # so we need to go one level up

-     pwp_entries = PwPolicyEntries(inst, targetdn)

-     for pwp_entry in pwp_entries.list():

-         dn_comps = ldap.dn.explode_dn(pwp_entry.get_attr_val_utf8_l('cn'))

-         dn_comps.pop(0)

-         entrydn = ",".join(dn_comps)

-         policy_type = _get_policy_type(inst, entrydn)

-         if args.json:

-             result['items'].append([entrydn, policy_type])

-         else:

-             result += "%s (%s)\n" % (entrydn, policy_type.lower())

+     for targetdn in targetdns:

+         # Verify target dn exists before getting started

+         user_entry = Account(inst, targetdn)

+         if not user_entry.exists():

+             raise ValueError('The target entry dn does not exist')

+     

+         # User pwpolicy entry is under the container that is under the parent,

+         # so we need to go one level up

+         pwp_entries = PwPolicyEntries(inst, targetdn)

+         pwp_manager = PwPolicyManager(inst)

+         attr_list = list(pwp_manager.arg_to_attr.values())

+     

+         for pwp_entry in pwp_entries.list():

+             dn_comps = ldap.dn.explode_dn(pwp_entry.get_attr_val_utf8_l('cn'))

+             dn_comps.pop(0)

+             entrydn = ",".join(dn_comps)

+             policy_type = _get_policy_type(inst, entrydn)

+             all_attrs = pwp_entry.get_attrs_vals_utf8(attr_list)

+             attrs = {k: v for k, v in all_attrs.items() if len(v) > 0}

+             if args.json:

+                 result['items'].append(

+                     {

+                         "dn": pwp_entry.dn,

+                         "targetdn": entrydn,

+                         "pwp_type": policy_type,

+                         "basedn": pwp_entry.get_basedn(),

+                         "attrs": attrs

+                     }

+                 )

+             else:

+                 result += "%s (%s)\n" % (entrydn, policy_type.lower())

  

      if args.json:

-         print(json.dumps(result))

+         print(json.dumps(result, indent=4))

      else:

          print(result)

  
@@ -173,7 +205,7 @@ 

      # List all the local policies

      list_parser = local_subcommands.add_parser('list', help='List all the local password policies')

      list_parser.set_defaults(func=list_policies)

-     list_parser.add_argument('DN', nargs=1, help='Suffix to search for local password policies')

+     list_parser.add_argument('DN', nargs='?', help='Suffix to search for local password policies')

      # Get a local policy

      get_parser = local_subcommands.add_parser('get', help='Get local password policy entry')

      get_parser.set_defaults(func=get_local_policy)
@@ -216,11 +248,12 @@ 

      set_parser.add_argument('--pwdmaxseq', help="The maximum number of allowed monotonic character sequences in a password")

      set_parser.add_argument('--pwdmaxseqsets', help="The maximum number of allowed monotonic character sequences that can be duplicated in a password")

      set_parser.add_argument('--pwdmaxclasschars', help="The maximum number of sequential characters from the same character class that is allowed in a password")

-     set_parser.add_argument('--pwdmincatagories', help="The minimum number of syntax catagory checks")

+     set_parser.add_argument('--pwdmincatagories', help="The minimum number of syntax category checks")

      set_parser.add_argument('--pwdmintokenlen', help="Sets the smallest attribute value length that is used for trivial/user words checking.  This also impacts \"--pwduserattrs\"")

      set_parser.add_argument('--pwdbadwords', help="A space-separated list of words that can not be in a password")

      set_parser.add_argument('--pwduserattrs', help="A space-separated list of attributes whose values can not appear in the password (See \"--pwdmintokenlen\")")

-     set_parser.add_argument('--pwddictcheck', help="Set to \"on\" to enfore CrackLib dictionary checking")

+     set_parser.add_argument('--pwpinheritglobal', help="Set to \"on\" to allow local policies to inherit the global policy")

+     set_parser.add_argument('--pwddictcheck', help="Set to \"on\" to enforce CrackLib dictionary checking")

      set_parser.add_argument('--pwddictpath', help="Filesystem path to specific/custom CrackLib dictionary files")

      # delete local password policy

      del_parser = local_subcommands.add_parser('remove', help='Remove a local password policy')

@@ -113,7 +113,7 @@ 

      ruv_dict = ruv.format_ruv()

      ruvs = ruv_dict['ruvs']

      if args and args.json:

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

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

      else:

          add_gap = False

          for ruv in ruvs:
@@ -275,7 +275,7 @@ 

          suffixes.append(replica.get_suffix())

  

      if args.json:

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

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

      else:

          if len(suffixes) == 0:

              log.info("There are no replicated suffixes")
@@ -289,7 +289,7 @@ 

      replica = replicas.get(args.suffix)

      status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd)

      if args.json:

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

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

      else:

          for agmt in status:

              log.info(agmt)
@@ -300,7 +300,7 @@ 

      replica = replicas.get(args.suffix)

      status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd, winsync=True)

      if args.json:

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

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

      else:

          for agmt in status:

              log.info(agmt)
@@ -459,7 +459,7 @@ 

              report_items.append(report_item)

  

      if args.json:

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

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

  

  

  def create_cl(inst, basedn, log, args):
@@ -612,7 +612,7 @@ 

          else:

              log.info(agmt.display())

      if args.json:

-         log.info(json.dumps(result))

+         log.info(json.dumps(result, indent=4))

  

  

  def add_agmt(inst, basedn, log, args):
@@ -712,7 +712,7 @@ 

      elif error:

          status = "Agreement initialization failed: " + error

      if args.json:

-         log.info(json.dumps(status))

+         log.info(json.dumps(status, indent=4))

      else:

          log.info(status)

  
@@ -787,7 +787,7 @@ 

          else:

              log.info(agmt.display())

      if args.json:

-         log.info(json.dumps(result))

+         log.info(json.dumps(result, indent=4))

  

  

  def add_winsync_agmt(inst, basedn, log, args):
@@ -907,7 +907,7 @@ 

      elif error:

          status = "Agreement initialization failed."

      if args.json:

-         log.info(json.dumps(status))

+         log.info(json.dumps(status, indent=4))

      else:

          log.info(status)

  
@@ -945,7 +945,7 @@ 

      clean_task.create(properties=properties)

      rdn = clean_task.rdn

      if args.json:

-         log.info(json.dumps(rdn))

+         log.info(json.dumps(rdn, indent=4))

      else:

          log.info('Created task ' + rdn)

  
@@ -970,7 +970,7 @@ 

          else:

              log.info(task.display())

      if args.json:

-         log.info(json.dumps(result))

+         log.info(json.dumps(result, indent=4))

      else:

          if not tasks_found:

              log.info("No CleanAllRUV tasks found")
@@ -1005,7 +1005,7 @@ 

          else:

              log.info(task.display())

      if args.json:

-         log.info(json.dumps(result))

+         log.info(json.dumps(result, indent=4))

      else:

          if not tasks_found:

              log.info("No CleanAllRUV abort tasks found")

@@ -1,11 +1,12 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

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

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

  # All rights reserved.

  #

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

  # See LICENSE for details.

  # --- END COPYRIGHT BLOCK ---

  

+ import json

  from lib389.saslmap import SaslMapping, SaslMappings

  from lib389.utils import ensure_str

  from lib389.cli_base import (
@@ -25,7 +26,21 @@ 

  

  

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

-     _generic_list(inst, basedn, log.getChild('sasl_map_list'), MANY, args)

+     if args.details:

+         # List SASL mappings with details

+         mappings = SaslMappings(inst).list()

+         result = {"type": "list", "items": []}

+         for sasl_map in mappings:

+             if args.json:

+                 entry = sasl_map.get_all_attrs_json()

+                 # Append decoded json object, because we are going to dump it later

+                 result['items'].append(json.loads(entry))

+             else:

+                 log.info(sasl_map.display())

+         if args.json:

+             log.info(json.dumps(result, indent=4))

+     else:

+         _generic_list(inst, basedn, log.getChild('sasl_map_list'), MANY, args)

  

  

  def sasl_map_get(inst, basedn, log, args):
@@ -57,15 +72,29 @@ 

      if warn and args.json is False:

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

      _generic_delete(inst, basedn, log.getChild('sasl_map_delete'), SINGULAR, dn, args)

+     

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

+     """Get a list of the supported sasl mechanisms"""

+     mechs = inst.rootdse.supported_sasl()

+     if args.json:

+         result = {'type': 'list', 'items': mechs}

+         log.info(json.dumps(result, indent=4, ))

+     else:

+         for mech in mechs:

+             log.info(mech)

  

  

  def create_parser(subparsers):

-     sasl_parser = subparsers.add_parser('sasl', help='Query and manipulate sasl mappings')

+     sasl_parser = subparsers.add_parser('sasl', help='Query and manipulate SASL mappings')

  

      subcommands = sasl_parser.add_subparsers(help='sasl')

  

-     list_mappings_parser = subcommands.add_parser('list', help='List avaliable SASL mappings')

+     list_mappings_parser = subcommands.add_parser('list', help='List available SASL mappings')

      list_mappings_parser.set_defaults(func=sasl_map_list)

+     list_mappings_parser.add_argument('--details', action='store_true', default=False,

+         help="Get each SASL Mapping in detail.")

+     get_mech_parser= subcommands.add_parser('get-mechs', help='List available SASL mechanisms')

+     get_mech_parser.set_defaults(func=sasl_get_supported)

  

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

      get_parser.set_defaults(func=sasl_map_get)

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

          print(dump_json({'type': 'schema',

                           'objectclasses': objectclass_elems,

                           'attributetypes': attributetype_elems,

-                          'matchingrules': matchingrule_elems}))

+                          'matchingrules': matchingrule_elems}, indent=4))

      else:

          separator_line = "".join(["-" for _ in range(50)])

          print("Objectclasses:\n", separator_line)
@@ -54,7 +54,7 @@ 

      log = log.getChild('list_attributetypes')

      schema = Schema(inst)

      if args is not None and args.json:

-         print(dump_json(schema.get_attributetypes(json=True)))

+         print(dump_json(schema.get_attributetypes(json=True), indent=4))

      else:

          for attributetype in schema.get_attributetypes():

              print(attributetype)
@@ -64,7 +64,7 @@ 

      log = log.getChild('list_objectclasses')

      schema = Schema(inst)

      if args is not None and args.json:

-         print(dump_json(schema.get_objectclasses(json=True)))

+         print(dump_json(schema.get_objectclasses(json=True), indent=4))

      else:

          for oc in schema.get_objectclasses():

              print(oc)
@@ -74,7 +74,7 @@ 

      log = log.getChild('list_matchingrules')

      schema = Schema(inst)

      if args is not None and args.json:

-         print(dump_json(schema.get_matchingrules(json=True)))

+         print(dump_json(schema.get_matchingrules(json=True), indent=4))

      else:

          for mr in schema.get_matchingrules():

              print(mr)
@@ -86,7 +86,7 @@ 

      # Need the query type

      attr = _get_arg(args.name, msg="Enter attribute to query")

      if args.json:

-         print(dump_json(schema.query_attributetype(attr, json=args.json)))

+         print(dump_json(schema.query_attributetype(attr, json=args.json), indent=4))

      else:

          attributetype, must, may = schema.query_attributetype(attr, json=args.json)

          print(attributetype)
@@ -107,7 +107,7 @@ 

      oc = _get_arg(args.name, msg="Enter objectclass to query")

      result = schema.query_objectclass(oc, json=args.json)

      if args.json:

-         print(dump_json(result))

+         print(dump_json(result, indent=4))

      else:

          print(result)

  
@@ -119,7 +119,7 @@ 

      attr = _get_arg(args.name, msg="Enter attribute to query")

      result = schema.query_matchingrule(attr, json=args.json)

      if args.json:

-         print(dump_json(result))

+         print(dump_json(result, indent=4))

      else:

          print(result)

  
@@ -215,7 +215,7 @@ 

      schema = Schema(inst)

      result = schema.get_attr_syntaxes(json=args.json)

      if args.json:

-         print(dump_json(result))

+         print(dump_json(result, indent=4))

      else:

          for id, name in result.items():

              print("%s (%s)", name, id)

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

              val = ""

          result[props.attr.lower()] = val

      if args.json:

-         print(json.dumps({'type': 'list', 'items': result}))

+         print(json.dumps({'type': 'list', 'items': result}, indent=4))

      else:

          print('\n'.join([f'{attr}: {value or ""}' for attr, value in result.items()]))

  
@@ -196,7 +196,7 @@ 

          lst = enc.ciphers

  

      if args.json:

-         print(json.dumps({'type': 'list', 'items': lst}))

+         print(json.dumps({'type': 'list', 'items': lst}, indent=4))

      else:

          if lst == []:

              log.getChild('security').warn('List of ciphers is empty')
@@ -283,7 +283,7 @@ 

              log.info('Expires: {}'.format(cert[3]))

              log.info('Trust Flags: {}\n'.format(cert[4]))

      if args.json:

-         log.info(json.dumps(cert_list))

+         log.info(json.dumps(cert_list, indent=4))

  

  

  def cacert_list(inst, basedn, log, args):
@@ -313,7 +313,7 @@ 

              log.info('Expires: {}'.format(cert[3]))

              log.info('Trust Flags: {}\n'.format(cert[4]))

      if args.json:

-         log.info(json.dumps(cert_list))

+         log.info(json.dumps(cert_list, indent=4))

  

  

  def cert_get(inst, basedn, log, args):
@@ -332,7 +332,7 @@ 

                                  'expires': details[3],

                                  'flags': details[4],

                              }

-                 }

+                 }, indent=4

              )

          )

      else:

@@ -96,7 +96,7 @@ 

          if not args.json:

              log.info("No issues found.")

          else:

-             log.info(json.dumps(report))

+             log.info(json.dumps(report, indent=4))

      else:

          plural = ""

          if count > 1:
@@ -109,7 +109,7 @@ 

                  idx += 1

              log.info('\n\n===== End Of Report ({} Issue{} found) ====='.format(count, plural))

          else:

-             log.info(json.dumps(report))

+             log.info(json.dumps(report, indent=4))

  

      disconnect_instance(inst)

  

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

  

  def instance_status(inst, log, args):

      if args.json:

-         print(json.dumps({"type": "result", "running": inst.status()}))

+         print(json.dumps({"type": "result", "running": inst.status()}, indent=4))

          return

  

      if inst.status() is True:

@@ -15,7 +15,7 @@ 

      dse_ldif = DSEldif(inst)

      states = dse_ldif.readNsState(suffix=args.suffix, flip=args.flip)

      if args.json:

-         log.info(json.dumps(states))

+         log.info(json.dumps(states, indent=4))

      else:

          for state in states:

              log.info("Replica DN:           " + state['dn'])

file modified
+39 -38
@@ -27,44 +27,45 @@ 

          self.arg_to_attr = {

              'pwdlocal': 'nsslapd-pwpolicy-local',

              'pwdscheme': 'passwordstoragescheme',

-             'pwdchange': 'passwordChange',

-             'pwdmustchange': 'passwordMustChange',

-             'pwdhistory': 'passwordHistory',

-             'pwdhistorycount': 'passwordInHistory',

-             'pwdadmin': 'passwordAdminDN',

-             'pwdtrack': 'passwordTrackUpdateTime',

-             'pwdwarning': 'passwordWarning',

-             'pwdisglobal': 'passwordIsGlobalPolicy',

-             'pwdexpire': 'passwordExp',

-             'pwdmaxage': 'passwordMaxAge',

-             'pwdminage': 'passwordMinAge',

-             'pwdgracelimit': 'passwordGraceLimit',

-             'pwdsendexpiring': 'passwordSendExpiringTime',

-             'pwdlockout': 'passwordLockout',

-             'pwdunlock': 'passwordUnlock',

-             'pwdlockoutduration': 'passwordLockoutDuration',

-             'pwdmaxfailures': 'passwordMaxFailure',

-             'pwdresetfailcount': 'passwordResetFailureCount',

-             'pwdchecksyntax': 'passwordCheckSyntax',

-             'pwdminlen': 'passwordMinLength',

-             'pwdmindigits': 'passwordMinDigits',

-             'pwdminalphas': 'passwordMinAlphas',

-             'pwdminuppers': 'passwordMinUppers',

-             'pwdminlowers': 'passwordMinLowers',

-             'pwdminspecials': 'passwordMinSpecials',

-             'pwdmin8bits': 'passwordMin8bit',

-             'pwdmaxrepeats': 'passwordMaxRepeats',

-             'pwdpalindrome': 'passwordPalindrome',

-             'pwdmaxseq': 'passwordMaxSequence',

-             'pwdmaxseqsets': 'passwordMaxSeqSets',

-             'pwdmaxclasschars': 'passwordMaxClassChars',

-             'pwdmincatagories': 'passwordMinCategories',

-             'pwdmintokenlen': 'passwordMinTokenLength',

-             'pwdbadwords': 'passwordBadWords',

-             'pwduserattrs': 'passwordUserAttributes',

-             'pwddictcheck': 'passwordDictCheck',

-             'pwddictpath': 'passwordDictPath',

-             'pwdallowhash': 'nsslapd-allow-hashed-passwords'

+             'pwdchange': 'passwordchange',

+             'pwdmustchange': 'passwordmustchange',

+             'pwdhistory': 'passwordhistory',

+             'pwdhistorycount': 'passwordinhistory',

+             'pwdadmin': 'passwordadmindn',

+             'pwdtrack': 'passwordtrackupdatetime',

+             'pwdwarning': 'passwordwarning',

+             'pwdisglobal': 'passwordisglobalpolicy',

+             'pwdexpire': 'passwordexp',

+             'pwdmaxage': 'passwordmaxage',

+             'pwdminage': 'passwordminage',

+             'pwdgracelimit': 'passwordgracelimit',

+             'pwdsendexpiring': 'passwordsendexpiringtime',

+             'pwdlockout': 'passwordlockout',

+             'pwdunlock': 'passwordunlock',

+             'pwdlockoutduration': 'passwordlockoutduration',

+             'pwdmaxfailures': 'passwordmaxfailure',

+             'pwdresetfailcount': 'passwordresetfailurecount',

+             'pwdchecksyntax': 'passwordchecksyntax',

+             'pwdminlen': 'passwordminlength',

+             'pwdmindigits': 'passwordmindigits',

+             'pwdminalphas': 'passwordminalphas',

+             'pwdminuppers': 'passwordminuppers',

+             'pwdminlowers': 'passwordminlowers',

+             'pwdminspecials': 'passwordminspecials',

+             'pwdmin8bits': 'passwordmin8bit',

+             'pwdmaxrepeats': 'passwordmaxrepeats',

+             'pwdpalindrome': 'passwordpalindrome',

+             'pwdmaxseq': 'passwordmaxsequence',

+             'pwdmaxseqsets': 'passwordmaxseqsets',

+             'pwdmaxclasschars': 'passwordmaxclasschars',

+             'pwdmincatagories': 'passwordmincategories',

+             'pwdmintokenlen': 'passwordmintokenlength',

+             'pwdbadwords': 'passwordbadwords',

+             'pwduserattrs': 'passworduserattributes',

+             'pwddictcheck': 'passworddictcheck',

+             'pwddictpath': 'passworddictpath',

+             'pwdallowhash': 'nsslapd-allow-hashed-passwords',

+             'pwpinheritglobal': 'nsslapd-pwpolicy-inherit-global'

          }

  

      def is_subtree_policy(self, dn):

Description:

Ported the server tab to reactJS. Also made other changes:

         - Moved Password policy to the database tab tree.
         - Moved the Security Tab to the Server Tab tree.
         - Fixed all the typeAhead errors
         - Removed unused CSS classes
         - Changed Server Tab to use a TreeView

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

I can not build the server...

Module not found: Error: Can't resolve './server.jsx'

rebased onto 771f52de74da4206e7774cfec73549c2ce80ca6f

4 years ago

I can not build the server...
Module not found: Error: Can't resolve './server.jsx'

Sorry, missed the "git add", fixed now...

Fields for LDAP Port and LDAPS Port accept any character. Can we make it num only?

In Server Settings - Advanced - If I click on Server Read-Only, it doesn't make Save button active.

In Server Settings - Disk Monitoring - I think the fields Disk Monitoring Threshold, Disk Monitoring Grace Period and Server Logs are aligned in an odd way. Maybe, we can put it more to the left (so it's the same level with Enable Disk Space Monitoring? I understand why it is done like this but it really looks off to me...
Attaching a screenshot to the issue.

In Tuning & Limits advanced settings - Enable Normalized DN Cache box has a text field beside it. If we zoom the page just a bit (or user will have a smaller screen to begin with - for me it was broken to begin with), then the field goes to the next line and it looks broken.
Attaching a screenshot to the issue.

In Logging rotation policies, can you please add num limits for Hour and Minute?

rebased onto bb8f26f4f8e7a7d95df0892e987a54472736a730

4 years ago

Fields for LDAP Port and LDAPS Port accept any character. Can we make it num only?

Yup, I missed that.

In Server Settings - Advanced - If I click on Server Read-Only, it doesn't make Save button active.
In Server Settings - Disk Monitoring - I think the fields Disk Monitoring Threshold, Disk Monitoring Grace Period and Server Logs are aligned in an odd way. Maybe, we can put it more to the left (so it's the same level with Enable Disk Space Monitoring? I understand why it is done like this but it really looks off to me...
Attaching a screenshot to the issue.
In Tuning & Limits advanced settings - Enable Normalized DN Cache box has a text field beside it. If we zoom the page just a bit (or user will have a smaller screen to begin with - for me it was broken to begin with), then the field goes to the next line and it looks broken.
Attaching a screenshot to the issue.
In Logging rotation policies, can you please add num limits for Hour and Minute?

These are already number inputs with min and max limits

Changes applied, please review...

I know that you just moved the code but could you please check the Security tab too?

Server Secure Port can be a number also.

And a few checkboxes on the bottom are off in Security Configuration tab (attaching a screenshot)

Require Secure Connections
Verify Certificate Subject Hostname
Allow Weak Ciphers

I am not sure if it was like this before your PR...

The rest looks good as far as I can see.

I know that you just moved the code but could you please check the Security tab too?
Server Secure Port can be a number also.
And a few checkboxes on the bottom are off in Security Configuration tab (attaching a screenshot)
Require Secure Connections
Verify Certificate Subject Hostname
Allow Weak Ciphers

I'll look into this. Did you also check the password policy stuff that got moved to the database tab? Just checking :-)

I'll look into this. Did you also check the password policy stuff that got moved to the database tab? Just checking :-)

Yeah-yeah, it looks good! Interesting solution with 3 tabs for local pwpolicy managment.

I'll look into this. Did you also check the password policy stuff that got moved to the database tab? Just checking :-)

Yeah-yeah, it looks good! Interesting solution with 3 tabs for local pwpolicy management.

I kind of stole the idea from you and what you did with the replication monitor :-) It's tough when a modal is too small for what you need, but I think it works well enough.

rebased onto b206e5997e04fb92e6f601ec4e4e6385b5f94d84

4 years ago

All changes made, and added nsTLSAllowClientRenegotiation to the security page. Please review...

rebased onto c6198c5

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

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 57
+10 -198
file changed
src/cockpit/389-console/src/css/ds.css
+70 -19
file changed
src/cockpit/389-console/src/database.jsx
+621 -26
file changed
src/cockpit/389-console/src/ds.js
+6 -7
file changed
src/cockpit/389-console/src/index.es6
+4 -44
file changed
src/cockpit/389-console/src/index.html
+45 -23
file changed
src/cockpit/389-console/src/lib/database/databaseConfig.jsx
+190 -0
file changed
src/cockpit/389-console/src/lib/database/databaseTables.jsx
+1345
file added
src/cockpit/389-console/src/lib/database/globalPwp.jsx
+2 -8
file changed
src/cockpit/389-console/src/lib/database/indexes.jsx
+2375
file added
src/cockpit/389-console/src/lib/database/localPwp.jsx
+692
file added
src/cockpit/389-console/src/lib/database/pwpolicy.jsx
+4 -4
file changed
src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
+12 -30
file changed
src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
+10 -22
file changed
src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
+8 -14
file changed
src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
+3 -6
file changed
src/cockpit/389-console/src/lib/plugins/dna.jsx
+5 -14
file changed
src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
+7 -16
file changed
src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
+18 -44
file changed
src/cockpit/389-console/src/lib/plugins/memberOf.jsx
+7 -10
file changed
src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
+5 -8
file changed
src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
+3 -9
file changed
src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
+8 -8
file changed
src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
+2 -4
file changed
src/cockpit/389-console/src/lib/security/ciphers.jsx
+763
file added
src/cockpit/389-console/src/lib/server/accessLog.jsx
+623
file added
src/cockpit/389-console/src/lib/server/auditLog.jsx
+621
file added
src/cockpit/389-console/src/lib/server/auditfailLog.jsx
+956
file added
src/cockpit/389-console/src/lib/server/errorLog.jsx
+331
file added
src/cockpit/389-console/src/lib/server/ldapi.jsx
+790
file added
src/cockpit/389-console/src/lib/server/sasl.jsx
+224
file added
src/cockpit/389-console/src/lib/server/serverModals.jsx
+247
file added
src/cockpit/389-console/src/lib/server/serverTables.jsx
+1348
file added
src/cockpit/389-console/src/lib/server/settings.jsx
+556
file added
src/cockpit/389-console/src/lib/server/tuning.jsx
+1 -1
file changed
src/cockpit/389-console/src/replication.jsx
+81 -60
file changed
src/cockpit/389-console/src/security.jsx
+345
file added
src/cockpit/389-console/src/server.jsx
-1269
file removed
src/cockpit/389-console/src/servers.html
-1808
file removed
src/cockpit/389-console/src/servers.js
+0 -2
file changed
src/cockpit/389-console/webpack.config.js
+12 -1
file changed
src/lib389/lib389/_mapped_object.py
+3 -1
file changed
src/lib389/lib389/backend.py
+4 -3
file changed
src/lib389/lib389/cli_base/__init__.py
+10 -10
file changed
src/lib389/lib389/cli_conf/backend.py
+2 -2
file changed
src/lib389/lib389/cli_conf/chaining.py
+3 -3
file changed
src/lib389/lib389/cli_conf/conflicts.py
+1 -1
file changed
src/lib389/lib389/cli_conf/monitor.py
+2 -2
file changed
src/lib389/lib389/cli_conf/plugin.py
+59 -26
file changed
src/lib389/lib389/cli_conf/pwpolicy.py
+12 -12
file changed
src/lib389/lib389/cli_conf/replication.py
+33 -4
file changed
src/lib389/lib389/cli_conf/saslmappings.py
+8 -8
file changed
src/lib389/lib389/cli_conf/schema.py
+5 -5
file changed
src/lib389/lib389/cli_conf/security.py
+2 -2
file changed
src/lib389/lib389/cli_ctl/health.py
+1 -1
file changed
src/lib389/lib389/cli_ctl/instance.py
+1 -1
file changed
src/lib389/lib389/cli_ctl/nsstate.py
+39 -38
file changed
src/lib389/lib389/pwpolicy.py