[389-ds-base] branch 389-ds-base-1.4.0 updated: Ticket 50215 - UI - implement Database Tab in reachJS
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.0 by this push:
new 6e95c65 Ticket 50215 - UI - implement Database Tab in reachJS
6e95c65 is described below
commit 6e95c6599f692ee30640bdc23bb6039c3c951e2f
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Mon Feb 11 09:58:24 2019 -0500
Ticket 50215 - UI - implement Database Tab in reachJS
Description: Implement database tab in ReactJS.
https://pagure.io/389-ds-base/issue/50215
Reviewed by: spichugi & firstyear (Thanks!!)
---
src/cockpit/389-console/.eslintrc.json | 3 +-
src/cockpit/389-console/src/backend.html | 1007 ---------------
src/cockpit/389-console/src/backend.js | 956 --------------
src/cockpit/389-console/src/css/ds.css | 200 ++-
src/cockpit/389-console/src/database.jsx | 1223 ++++++++++++++++++
src/cockpit/389-console/src/ds.js | 6 +-
src/cockpit/389-console/src/index.es6 | 9 +
src/cockpit/389-console/src/index.html | 12 +-
.../src/lib/database/attrEncryption.jsx | 184 +++
.../389-console/src/lib/database/backups.jsx | 979 +++++++++++++++
.../389-console/src/lib/database/chaining.jsx | 1311 ++++++++++++++++++++
.../src/lib/database/databaseConfig.jsx | 398 ++++++
.../389-console/src/lib/database/databaseModal.jsx | 502 ++++++++
.../src/lib/database/databaseTables.jsx | 1018 +++++++++++++++
.../389-console/src/lib/database/indexes.jsx | 949 ++++++++++++++
.../389-console/src/lib/database/referrals.jsx | 414 +++++++
.../389-console/src/lib/database/suffix.jsx | 906 ++++++++++++++
.../389-console/src/lib/database/suffixConfig.jsx | 89 ++
.../389-console/src/lib/database/vlvIndexes.jsx | 951 ++++++++++++++
.../lib/{plugins/pluginTable.jsx => dsTable.jsx} | 302 +++--
src/cockpit/389-console/src/lib/notifications.jsx | 68 +-
.../389-console/src/lib/plugins/pluginModal.jsx | 4 +-
.../389-console/src/lib/plugins/pluginTable.jsx | 243 +---
src/cockpit/389-console/src/monitor.html | 10 +-
src/cockpit/389-console/src/plugins.jsx | 2 +-
src/cockpit/389-console/src/servers.html | 2 +-
src/cockpit/389-console/src/servers.js | 2 +-
src/cockpit/389-console/webpack.config.js | 2 -
src/lib389/lib389/__init__.py | 46 +-
src/lib389/lib389/backend.py | 18 +-
src/lib389/lib389/chaining.py | 64 +-
src/lib389/lib389/cli_conf/backend.py | 226 +++-
src/lib389/lib389/cli_conf/chaining.py | 76 +-
src/lib389/lib389/cli_ctl/dbtasks.py | 22 +-
src/lib389/lib389/index.py | 15 +-
src/lib389/lib389/instance/remove.py | 1 +
src/lib389/lib389/rootdse.py | 3 +
src/lib389/lib389/tasks.py | 23 +-
38 files changed, 9756 insertions(+), 2490 deletions(-)
diff --git a/src/cockpit/389-console/.eslintrc.json b/src/cockpit/389-console/.eslintrc.json
index ee0a235..11a82c8 100644
--- a/src/cockpit/389-console/.eslintrc.json
+++ b/src/cockpit/389-console/.eslintrc.json
@@ -44,8 +44,7 @@
"eqeqeq": "off",
"import/no-webpack-loader-syntax": "off",
"object-property-newline": "off",
- "react/jsx-no-bind": "off",
- "max-len": ["error", { "code": 100 }]
+ "react/jsx-no-bind": "off"
},
"globals": {
"require": false,
diff --git a/src/cockpit/389-console/src/backend.html b/src/cockpit/389-console/src/backend.html
deleted file mode 100644
index c209f96..0000000
--- a/src/cockpit/389-console/src/backend.html
+++ /dev/null
@@ -1,1007 +0,0 @@
-
-
- <div id="db-global-page" class="all-pages" hidden>
- <h3 class="ds-config-header">Global Database Settings</h3>
- <div class="ds-container">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-lookthrough-limit" class="ds-config-label" title=
- "The maximum number of entries that the Directory Server will check when examining candidate entries in response to a search request (nsslapd-lookthrough-limit).">
- Database Look Though Limit</label><input class="ds-input" type="text" id="nsslapd-lookthrough-limit" size="15"/>
- </div>
- <div>
- <label for="nsslapd-idlistscanlimit" class="ds-config-label" title=
- "The number of entry IDs that are searched during a search operation (nsslapd-idlistscanlimit).">
- ID List Scan Limit</label><input class="ds-input" type="text" id="nsslapd-idlistscanlimit" size="15"/>
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-inline">
- <div>
- <label for="nsslapd-pagedlookthroughlimit" class="ds-config-label" title=
- "The maximum number of entries that the Directory Server will check when examining candidate entries for a search which uses the simple paged results control (nsslapd-pagedlookthroughlimit).">
- Paged Search Look Through Limit</label><input class="ds-input" type="text" id="nsslapd-pagedlookthroughlimit" size="15"/>
- </div>
- <div>
- <label for="nsslapd-pagedidlistscanlimit" class="ds-config-label" title=
- "The number of entry IDs that are searched, specifically, for a search operation using the simple paged results control (nsslapd-pagedidlistscanlimit).">
- Paged Search ID List Scan Limit</label><input class="ds-input" type="text" id="nsslapd-pagedidlistscanlimit" size="15"/>
- </div>
- <div>
- <label for="nsslapd-rangelookthroughlimit" class="ds-config-label" title=
- "The maximum number of entries that the Directory Server will check when examining candidate entries in response to a range search request (nsslapd-rangelookthroughlimit).">
- Range Search Look Through Limit</label><input class="ds-input" type="text" id="nsslapd-rangelookthroughlimit" size="15"/>
- </div>
- </div>
- </div>
- <hr>
- <h4 class="ds-sub-header">Database Cache Settings</h4>
- <div class="ds-roles">
- <label title="Set Database/Entry to be set manually"><input
- class="ds-radio cache-role" type="radio" id="manual-cache" name="cache-role" value="manual-cache" checked="checked"> Manual Cache Tuning</label>
- <label title="Set Database/Entry to be set automatically"><input
- class="ds-radio cache-role" type="radio" id="auto-cache" name="cache-role" value="auto-cache"> Automatic Cache Tuning</label>
- </div>
- <div id="manual-cache-form" class="ds-margin-left" hidden>
- <label for="nsslapd-dbcachesize" class="ds-config-label-xlrg" title=
- "Specifies the database index cache size in bytes (nsslapd-dbcachesize).">
- Database Cache Size (bytes)</label><input class="ds-input" type="text" id="nsslapd-dbcachesize" size="15"/>
- </div>
- <div id="auto-cache-form" class="ds-inline ds-margin-left" hidden>
- <div>
- <label for="nsslapd-cache-autosize" class="ds-config-label-xlrg" title=
- "Enable database and entry cache auto-tuning using a percentage of the systems current resources (nsslapd-cache-autosize).">
- System Memory Percentage</label><input class="ds-input" type="text" id="nsslapd-cache-autosize" size="3"/> %
- </div>
- <div>
- <label for="nsslapd-cache-autosize-split" class="ds-config-label-xlrg" title=
- "Sets the percentage of memory that is used for the database cache. The remaining percentage is used for the entry cache (nsslapd-cache-autosize-split).">
- DB Cache Percentage</label><input class="ds-input" type="text" id="nsslapd-cache-autosize-split" size="3"/> %
- </div>
- </div>
-
- <hr>
- <h4 class="ds-sub-header">Import Cache Settings</h4>
- <div class="ds-roles">
- <label title="Set import cache size manually"><input
- class="ds-radio import-cache-role" type="radio" id="manual-import-cache" name="import-cache-role" value="manual-import-cache" checked="checked"> Manual Import Cache Tuning</label>
- <label title="Set Database/Entry to be set automatically"><input
- class="ds-radio import-cache-role" type="radio" id="auto-import-cache" name="import-cache-role" value="auto-import-cache"> Automatic Import Cache Tuning</label>
- </div>
- <div id="manual-import-cache-form" class="ds-margin-left" hidden>
- <label for="nsslapd-import-cachesize" class="ds-config-label-xlrg" title=
- "The size of the database cache used in the bulk import process. (nsslapd-import-cachesize).">
- Import Cache Size (bytes)</label><input class="ds-input" type="text" id="nsslapd-import-cachesize" size="15"/>
- </div>
- <div id="auto-import-cache-form" class="ds-margin-left" hidden>
- <label for="nsslapd-import-cache-autosize" class="ds-config-label-xlrg" title=
- "Enter '-1' to use 50% of available memory, '0' to disable autotuning, or enter the percentage of available memory to use. Value range -1 through 100, default is '-1' (nsslapd-import-cache-autosize).">
- Import Cache Autosize</label><input class="ds-input" type="text" id="nsslapd-import-cache-autosize" size="15"/>
- </div>
-
- <button class="accordion ds-accordion" id="db-accordion" type="button">► Show Advanced Settings</button>
- <div class="ds-accordion-panel">
- <div class="ds-container">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-db-logdirectory" class="ds-config-label ds-cache-label" title=
- "Database Transaction Log Location (nsslapd-db-logdirectory).">
- Transaction Logs Directory</label><input class="ds-input" type="text" id="nsslapd-db-logdirectory" size="15"/>
- </div>
- <div>
- <label for="nsslapd-db-home-directory" class="ds-config-label ds-cache-label" title=
- "Location for database memory mapped files. You must specify a subdirectory of a tempfs type filesystem (nsslapd-db-home-directory).">
- Database Home Directory</label><input class="ds-input" type="text" id="nsslapd-db-home-directory" size="15"/>
- </div>
- <div>
- <label for="nsslapd-db-locks" class="ds-config-label ds-cache-label" title=
- "The number of database locks (nsslapd-db-locks).">
- Database Locks</label><input class="ds-input" type="text" id="nsslapd-db-locks" size="15"/>
- </div>
- <div>
- <label for="nsslapd-db-checkpoint-interval" class="ds-config-label ds-cache-label" title=
- "Amount of time in seconds after which the Directory Server sends a checkpoint entry to the database transaction log (nsslapd-db-checkpoint-interval).">
- Database Checkpoint Interval</label><input class="ds-input" type="text" id="nsslapd-db-checkpoint-interval" size="15"/>
- </div>
- <div>
- <label for="nsslapd-db-compactdb-interval" class="ds-config-label ds-cache-label" title=
- "The interval in seconds when the database is compacted (nsslapd-db-compactdb-interval).">
- Database Compact Interval</label><input class="ds-input" type="text" id="nsslapd-db-compactdb-interval" size="15"/>
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-divider"></div>
- <div class="ds-inline">
- <div class="ds-first">
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-db-verbose"><label
- for="nsslapd-db-verbose" class="ds-label" title="Enable database verbose logging (nsslapd-db-verbose)."> Enable Database Verbose Logging</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-db-debug"><label
- for="nsslapd-db-debug" class="ds-label" title="Enables database debug logging (nsslapd-db-debug)."> Enable Database Debug Logging</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-db-durable-transactions" checked><label
- for="nsslapd-db-durable-transactions" class="ds-label" title=
- "sets whether database transaction log entries are immediately written to the disk. (nsslapd-db-durable-transactions)."> Enable Durable transactions</label>
- </div>
- </div>
- </div>
- <p></p>
- </div>
- <div class="ds-footer">
- <button class="btn btn-primary save-button">Save</button>
- </div>
- </div>
-
- <div class="all-pages" id="db-chaining-settings-page" hidden>
- <!-- Database Chaining -->
- <h3 class="ds-config-header">Database Link Settings</h3>
- <div class="ds-container">
- <div class="ds-chaining-split">
- <form>
- <label class="ds-config-label" for="chaining-oid-list" title=
- "A list of LDAP control OIDs to be forwarded through chaining"><b>Forwarded LDAP Controls</b></label>
- <select id="chaining-oid-list" class="ds-chaining-list" name="nstransmittedcontrols" size="10">
- <option value="1.5.6.5.5.6.88.4553.344">1.5.6.5.5.6.88.4553.344</option>
- </select>
- </form>
- <div class="clearfix ds-container">
- <div class="ds-panel-left">
- <button type="button" data-toggle="modal" data-target="#chaining-oid-form" id="chaining-oid-button" class="ds-button-left">Add</button>
- </div>
- <div class="ds-panel-right">
- <button type="button" id="delete-chaining-oid-button" class="ds-button-right">Delete</button>
- </div>
- </div>
- </div>
- <div class="ds-chaining-divider"></div>
- <div class="ds-chaining-split">
- <form>
- <label class="ds-config-label" for="chaining-comp-list" title=
- "A list of components to go through chaining"><b>Components to Chain</b></label>
- <select id="chaining-comp-list" class="ds-chaining-list" name="nsactivechainingcomponents" size="10">
- <option value="1.5.6.5.5.6.88.4553.344">cn=roles,cn=components,cn=config</option>
- </select>
- </form>
- <div class="clearfix ds-container">
- <div class="ds-panel-left">
- <button type="button" data-toggle="modal" data-target="#chaining-comp-form" id="chaining-comp-button" class="ds-button-left">Add</button>
- </div>
- <div class="ds-panel-right">
- <button type="button" id="delete-chaining-comp-button" class="ds-button-right">Delete</button>
- </div>
- </div>
- </div>
- </div>
-
- <h4 class="ds-sub-header"><br>Default Database Link Creation Settings</h4>
- <hr>
- <div class="ds-container">
- <div class="ds-split">
- <label for="default-chaining-size-limit" class="ds-config-label" title=
- "The size limit of entries returned over a database link (nsslapd-sizelimit).">
- Size Limit</label><input class="ds-input" type="text" id="default-chaining-size-limit" size="15"/>
- <label for="default-chaining-time-limit" class="ds-config-label" title=
- "The time limit of an operation over a database link (nsslapd-timelimit).">
- Time Limit</label><input class="ds-input" type="text" id="default-chaining-time-limit" size="15"/>
- <label for="default-nsbindconnectionslimit" class="ds-config-label" title=
- "The maximum number of TCP connections the database link establishes with the remote server. (nsbindconnectionslimit).">
- Max TCP Connections</label><input class="ds-input" type="text" id="default-nsbindconnectionslimit" size="15"/>
- <label for="default-nsoperationconnectionslimit" class="ds-config-label" title=
- "The maximum number of connections allowed over the database link. (nsoperationconnectionslimit).">
- Max LDAP Connections</label><input class="ds-input" type="text" id="default-nsoperationconnectionslimit" size="15"/>
- <label for="default-nsconcurrentbindlimit" class="ds-config-label" title=
- "The maximum number of concurrent bind operations per TCP connection. (nsconcurrentbindlimit).">
- Max Binds Per Connection</label><input class="ds-input" type="text" id="default-nsconcurrentbindlimit" size="15"/>
- <label for="default-nsbindtimeout" class="ds-config-label" title=
- "The amount of time before the bind attempt times out. (nsbindtimeout).">
- Bind Timeout</label><input class="ds-input" type="text" id="default-nsbindtimeout" size="15"/>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-split ds-inline">
- <div>
- <label for="default-nsbindretrylimit" class="ds-config-label" title=
- "The number of times the database link tries to bind with the remote server after a connection failure. (nsbindretrylimit).">
- Bind Retry Limit</label><input class="ds-input" type="text" id="default-nsbindretrylimit" size="15"/>
- </div>
- <div>
- <label for="default-nsconcurrentoperationslimit" class="ds-config-label" title=
- "The maximum number of operations per connections. (nsconcurrentoperationslimit).">
- Max Operations Per Connection</label><input class="ds-input" type="text" id="default-nsconcurrentoperationslimit" size="15"/>
- </div>
- <div>
- <label for="default-nsconnectionlife" class="ds-config-label" title=
- "The life of a database link connection to the remote server. 0 is unlimited (nsconnectionlife).">
- Connection Lifetime (in seconds)</label><input class="ds-input" type="text" id="default-nsconnectionlife" size="15"/>
- </div>
- <div>
- <label for="default-nsabandonedsearchcheckinterval" class="ds-config-label" title=
- "The number of seconds that pass before the server checks for abandoned operations. (nsabandonedsearchcheckinterval).">
- Abandoned Op Check Interval</label><input class="ds-input" type="text" id="default-nsabandonedsearchcheckinterval" size="15"/>
- </div>
- <div>
- <label for="default-nshoplimit" class="ds-config-label" title=
- "The maximum number of times a request can be forwarded from one database link to another. (nshoplimit).">
- Database Link Hop Limit</label><input class="ds-input" type="text" id="default-nshoplimit" size="15"/>
- </div>
- <div>
- <p></p>
- <input type="checkbox" class="ds-config-checkbox" id="default-nschecklocalaci"><label
- for="default-nschecklocalaci" class="ds-label" title=
- "Sets whether ACIs are evaluated on the database link as well as the remote data server (nschecklocalaci)."> Check Local ACIs</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="default-nsreferralonscopedsearch"><label
- for="default-nsreferralonscopedsearch" class="ds-label" title=
- "Sets whether referrals are returned by scoped searches (meaning 'one-level' or 'subtree' scoped searches). (nsreferralonscopedsearch)."> Send Referral On Scoped Search</label>
- </div>
- </div>
- </div>
- <div class="ds-footer">
- <button class="btn btn-primary save-button">Save</button>
- </div>
- </div>
-
-
- <!-- Suffix Configuration Page -->
- <div id="db-page" class="all-pages ds-container" hidden>
-
- <div>
- <div id="db-tree" class="jstree-open ds-tree">
- <ul>
- <li type="suffix" id="suffix-dc=example,dc=com" data-jstree='{"icon":"glyphicon glyphicon-tree-conifer"}'>dc=example,dc=com
- <ul>
- <li type="subsuffix" id="suffix-ou=people-dc=example,dc=com" data-jstree='{"icon":"glyphicon glyphicon-leaf"}'>ou=people,dc=example,dc=com</li>
- </ul>
- </li>
- <li type="suffix" id="suffix-o=ipaca.com" data-jstree='{"icon":"glyphicon glyphicon-tree-conifer"}'>o=ipaca.com
- <ul>
- <li type="chained-suffix" id="dblink-o=ipaca.com" data-jstree='{"icon":"glyphicon glyphicon-link"}'>MyChainingDBLink</li>
- </ul>
- </li>
- </ul>
-
- </div>
- <div>
- <button class="btn btn-default" data-toggle="modal" data-target="#add-suffix-form" id="create-ref-btn" type="button"><span class="glyphicon glyphicon-plus"></span> Create New Suffix</button>
- </div>
- </div>
-
- <!-- Database Link Page -->
- <div id="chaining-page" class="ds-tree-content all-pages" hidden>
- <h3 class="ds-config-header" id="chaining-header">Database Chaining Configuration</h3>
- <div class="ds-container">
- <div class="ds-inline">
- <form>
- <div>
- <label for="nsfarmserverurl" class="ds-config-label" title=
- "The URL for the remote server. Add additional failure servers URLs separated by a space. (nsfarmserverurl)">
- Remote Server URL(s)</label><input class="ds-input" type="text" id="nsfarmserverurl"/>
- </div>
- <div>
- <label for="nsmultiplexorbinddn" class="ds-config-label" title="Bind DN used to authenticate against the remote server (nsmultiplexorbinddn).">Remote Server Bind DN</label><input
- class="ds-input" type="text" autocomplete="username" placeholder="Bind DN" id="nsmultiplexorbinddn">
- </div>
- <div>
- <label for="nsmultiplexorcredentials" class="ds-config-label" title="Replication Bind DN (nsDS5ReplicaCredentials).">Bind DN Credentials</label><input
- class="ds-input" type="password" autocomplete="new-password" placeholder="Enter password" id="nsmultiplexorcredentials">
- </div>
- <div>
- <label for="nsmultiplexorcredentials-confirm" class="ds-config-label" title="Confirm password">Confirm Password</label><input
- class="ds-input" type="password" autocomplete="new-password" placeholder="Confirm password" id="nsmultiplexorcredentials-confirm">
- </div>
- <div>
- <label for="dblink-conn" class="ds-config-label" title="The connection protocol for the remote server.">Connection Protocol</label><select
- class="btn btn-default dropdown ds-dblink-dropdown" id="dblink-conn">
- <option>LDAP</option>
- <option>LDAPS</option>
- <option>Start TLS</option>
- </select>
- </div>
- <div>
- <label for="nsbindmechanism" class="ds-config-label" title="The bind method for contacting the remote server (nsbindmechanism).">Bind Method</label><select
- class="btn btn-default dropdown ds-dblink-dropdown" id="nsbindmechanism">
- <option>Simple</option>
- <option>SASL/DIGEST-MD5</option>
- <option>SASL/GSSAPI</option>
- </select>
- </div>
- </form>
- </div>
- </div>
- <p></p>
-
- <button class="accordion ds-accordion" id="chaining-adv-accordion" type="button">► Show Advanced Database Link Settings </button>
- <div class="ds-accordion-panel">
- <div class="ds-accordian-div">
- <div class="ds-container">
- <div class="ds-inline">
- <div>
- <label for="chaining-size-limit" class="ds-config-label" title=
- "The size limit of entries returned over a database link (nsslapd-sizelimit).">
- Size Limit</label><input class="ds-input" type="text" id="chaining-size-limit" size="15"/>
- </div>
- <div>
- <label for="chaining-time-limit" class="ds-config-label" title=
- "The time limit of an operation over a database link (nsslapd-timelimit).">
- Time Limit</label><input class="ds-input" type="text" id="chaining-time-limit" size="15"/>
- </div>
- <div>
- <label for="nsbindconnectionslimit" class="ds-config-label" title=
- "The maximum number of TCP connections the database link establishes with the remote server. (nsbindconnectionslimit).">
- Max TCP Connections</label><input class="ds-input" type="text" id="nsbindconnectionslimit" size="15"/>
- </div>
- <div>
- <label for="nsoperationconnectionslimit" class="ds-config-label" title=
- "The maximum number of connections allowed over the database link. (nsoperationconnectionslimit).">
- Max LDAP Connections</label><input class="ds-input" type="text" id="nsoperationconnectionslimit" size="15"/>
- </div>
- <div>
- <label for="nsconcurrentbindlimit" class="ds-config-label" title=
- "The maximum number of concurrent bind operations per TCP connection. (nsconcurrentbindlimit).">
- Max Binds Per Connection</label><input class="ds-input" type="text" id="nsconcurrentbindlimit" size="15"/>
- </div>
- <div>
- <label for="nsbindtimeout" class="ds-config-label" title=
- "The amount of time before the bind attempt times out. (nsbindtimeout).">
- Bind Timeout</label><input class="ds-input" type="text" id="nsbindtimeout" size="15"/>
- </div>
- </div>
- <div class="ds-divider"></div>
- <div class="ds-inline">
- <div>
- <label for="nsbindretrylimit" class="ds-config-label" title=
- "The number of times the database link tries to bind with the remote server after a connection failure. (nsbindretrylimit).">
- Bind Retry Limit</label><input class="ds-input" type="text" id="nsbindretrylimit" size="15"/>
- </div>
- <div>
- <label for="nsconcurrentoperationslimit" class="ds-config-label" title=
- "The maximum number of operations per connections. (nsconcurrentoperationslimit).">
- Max Operations Per Connection</label><input class="ds-input" type="text" id="nsconcurrentoperationslimit" size="15"/>
- </div>
- <div>
- <label for="nsconnectionlife" class="ds-config-label" title=
- "The life of a database link connection to the remote server. 0 is unlimited (nsconnectionlife).">
- Connection Lifetime (in seconds)</label><input class="ds-input" type="text" id="nsconnectionlife" size="15"/>
- </div>
- <div>
- <label for="nsabandonedsearchcheckinterval" class="ds-config-label" title=
- "The number of seconds that pass before the server checks for abandoned operations. (nsabandonedsearchcheckinterval).">
- Abandoned Op Check Interval</label><input class="ds-input" type="text" id="nsabandonedsearchcheckinterval" size="15"/>
- </div>
- <div>
- <label for="nshoplimit" class="ds-config-label" title=
- "The maximum number of times a request can be forwarded from one database link to another. (nshoplimit).">
- Database Link Hop Limit</label><input class="ds-input" type="text" id="nshoplimit" size="15"/>
- </div>
- </div>
- </div>
- <div>
- <p></p>
- <input type="checkbox" class="ds-config-checkbox" id="nschecklocalaci"><label
- for="nschecklocalaci" class="ds-label" title=
- "Sets whether ACIs are evaluated on the database link as well as the remote data server (nschecklocalaci)."> Check Local ACIs</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsreferralonscopedsearch"><label
- for="nsreferralonscopedsearch" class="ds-label" title=
- "Sets whether referrals are returned by scoped searches (meaning 'one-level' or 'subtree' scoped searches). (nsreferralonscopedsearch)."> Send Referral On Scoped Search</label>
- </div>
- </div>
- </div>
- </div>
-
- <!-- Suffix Page -->
- <div class="ds-tree-content all-pages" id="suffix-page">
- <h3 class="ds-config-header" id="suffix-header">Suffix Configuration</h3>
- <div class="ds-container">
- <div class="ds-inline">
- <div>
- <label for="nsslapd-cachememsize" class="ds-config-label-lrg" title=
- "The size for the available memory space for the entry cache (nsslapd-cachememsize).">
- Entry Cache Size (bytes)</label><input class="ds-input" type="text" id="nsslapd-cachememsize" size="20"/>
- </div>
- <div>
- <label for="nsslapd-cachesize" class="ds-config-label-lrg" title=
- "The number of entries to keep in the entry cache, use'-1' for unlimited (nsslapd-cachesize).">
- Entry Cache Max Entries</label><input class="ds-input" type="text" id="nsslapd-cachesize" size="20"/>
- </div>
- <div>
- <label for="nsslapd-cachememsize" class="ds-config-label-lrg" title=
- "the available memory space for the DN cache. The DN cache is similar to the entry cache for a database, only its table stores only the entry ID and the entry DN (nsslapd-dncachememsize).">
- DN Cache Size (bytes)</label><input class="ds-input" type="text" id="nsslapd-dncachememsize" size="20"/>
- </div>
-
- </div>
- <div class="ds-divider"></div>
- <div class="ds-inline">
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-readonly-suffix"><label
- for="nsslapd-readonly-suffix" class="ds-label" title="Put database in Read-Only mode (nsslapd-readonly)."> Database Read-Only Mode</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="nsslapd-require-index" checked><label
- for="nsslapd-require-index" class="ds-label" title="Block unindexed searches on this suffix (nsslapd-require-index)."> Block Unindexed Searches</label>
- </div>
- </div>
- </div>
-
- <div>
- <hr>
- <table id="referral-table" class="display ds-referral-table" cellspacing="0" width="100%">
- <thead>
- <tr class="ds-table-header">
- <th>Referrals</th>
- <th></th>
- </tr>
- </thead>
- <tbody id="referral-tbody">
- <tr>
- <td>ldaps://localhost.localdomain.lab.testing.domain.com:636</td>
- <td class='ds-center'><button class="btn btn-default del-ref-btn" type="button"><span class="glyphicon glyphicon-trash"></span> Delete</button></td>
- </tr>
- <tr>
- <td>ldaps://example.fedora.org:636</td>
- <td class='ds-center'><button class="btn btn-default del-ref-btn" type="button"><span class="glyphicon glyphicon-trash"></span> Delete</button></td>
- </tr>
- </tbody>
- </table>
- <button class="btn btn-default" data-toggle="modal" data-target="#create-ref-form" id="create-ref-btn" type="button">Create Referral</button>
- <p></p>
- </div>
-
-
- <button class="accordion ds-accordion" id="db-system-index-accordion" type="button">► Show System Indexes </button>
- <div class="ds-accordion-panel">
- <h4 class="ds-accordion-header">System Indexes</h4>
- <div class="ds-indent">
- <table id="system-index-table" class="display ds-table" cellspacing="0" width="100%">
- <thead>
- <tr class="ds-table-header">
- <th>Attribute Name</th>
- <th title="Presence indexing">Pres</th>
- <th title="Equality indexing">Eq</th>
- <th title="Substring indexing">Sub</th>
- <th title="Approximate indexing">Approx</th>
- <th>Matching Rules</th>
- </tr>
- </thead>
- <tbody id="sysindex-tbody">
- <tr>
- <td>objectclass</td>
- <td><input type="checkbox" disabled="disabled" class="" id="objectclass-pres" checked></td>
- <td><input type="checkbox" disabled="disabled" class="" id="objectclass-eq" checked></td>
- <td><input type="checkbox" disabled="disabled" class="" id="objectclass-sub" checked></td>
- <td><input type="checkbox" disabled="disabled" class="" id="objectclass-approx"></td>
- <td></td>
- </tr>
- <tr>
- <td>cn</td>
- <td><input type="checkbox" disabled="disabled" class="" id="cn-pres"></td>
- <td><input type="checkbox" disabled="disabled" class="" id="cn-eq" checked></td>
- <td><input type="checkbox" disabled="disabled" class="" id="cn-sub"></td>
- <td><input type="checkbox" disabled="disabled" class="" id="cn-approx"></td>
- <td></td>
- </tr>
- </tbody>
- </table>
- </div>
- </div>
-
- <div>
- <button class="accordion ds-accordion" id="db-index-accordion" type="button">► Show Database Indexes </button>
- <div class="ds-accordion-panel">
- <h4 class="ds-accordion-header">Database Indexes</h4>
- <div class="ds-indent">
- <table id="index-table" class="display ds-repl-table" cellspacing="0" width="100%">
- <thead>
- <tr class="ds-table-header">
- <th>Attribute Name</th>
- <th title="Presence indexing">Pres</th>
- <th title="Equality indexing">Eq</th>
- <th title="Substring indexing">Sub</th>
- <th title="Approximate indexing">Approx</th>
- <th>Matching Rules</th>
- <th>Action</th>
- </tr>
- </thead>
- <tbody id="index-tbody">
- <tr>
- <td>uidNumber</td>
- <td><input type="checkbox" id="uidnumber-pres" checked></td>
- <td><input type="checkbox" id="uidnumber-eq" checked></td>
- <td><input type="checkbox" id="uidnumber-sub" checked></td>
- <td><input type="checkbox" id="uidnumber-approx"></td>
- <td></td>
- <td class="">
- <div class="dropdown">
- <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">
- Choose Action...
- <span class="caret"></span>
- </button>
- <ul class="dropdown-menu" role="menu">
- <li><a class="db-index-save-btn">Save Index</a></li>
- <li><a class="db-index-save-reindex-btn">Save & Reindex</a></li>
- <li><a class="db-index-reindex-btn">Reindex Attribute</a></li>
- <li><a class="db-index-delete-btn">Delete Index</a></li>
- </ul>
- </div>
- </td>
- </tr>
- <tr>
- <td>manager</td>
- <td><input type="checkbox" id="manager-pres"></td>
- <td><input type="checkbox" id="manager-eq" checked></td>
- <td><input type="checkbox" id="manager-sub"></td>
- <td><input type="checkbox" id="manager-approx"></td>
- <td></td>
- <td class="">
- <div class="dropdown">
- <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">
- Choose Action...
- <span class="caret"></span>
- </button>
- <ul class="dropdown-menu" role="menu">
- <li><a class="db-index-save-btn">Save Index</a></li>
- <li><a class="db-index-save-reindex-btn">Save & Reindex</a></li>
- <li><a class="db-index-reindex-btn">Reindex Attribute</a></li>
- <li><a class="db-index-delete-btn">Delete Index</a></li>
- </ul>
- </div>
- </td>
- </tr>
- </tbody>
- </table>
- <button class="btn btn-primary" type="button" data-toggle="modal" data-target="#add-index-form" id="add-index-button">Add Index</button>
- </div>
- </div>
- </div>
-
- <div>
- <button class="accordion ds-accordion" id="suffix-attrencrypt-accordion" type="button">► Show Encrypted Attributes </button>
- <div class="ds-accordion-panel">
- <h4 class="ds-accordion-header">Attribute Encryption</h4>
- <div class="ds-indent">
- <table id="attr-encrypt-table" class="display ds-repl-table" cellspacing="0" width="100%">
- <thead>
- <tr class="ds-table-header">
- <th>Attribute Name</th>
- <th>Encryption Algorithm</th>
- <th>Action</th>
- </tr>
- </thead>
- <tbody id="attr-encrypt-tbody">
- <tr>
- <td class="" id="attr-encrypt-ssn-">ssn</td>
- <td>AES</td>
- <td><button class="btn btn-default attr-encrypt-delete-btn" type="button">Remove Attribute</button></td>
- </tr>
- </tbody>
- </table>
- <button class="btn btn-primary" type="button" data-toggle="modal" data-target="#add-encrypted-attr-form" id="add-encrypted-attr-button">Add Attribute</button>
- </div>
- </div>
- </div>
- </div>
-
- <div class="ds-footer">
- <button class="btn btn-primary save-button">Save</button>
- </div>
- </div>
-
-
-
- <!-- Modals/Popups/Wizards -->
-
- <div class="modal fade" id="chaining-oid-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="chaining-label" aria-hidden="true">
- <div class="modal-dialog ds-modal">
- <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="chaining-label">Forwarded Chaining Controls</h4>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <label class="ds-config-label" for="avail-chaining-oid-list" title=
- "A list of LDAP control OIDs to be forwarded through chaining">Available LDAP Controls</label>
- <div>
- <select id="avail-chaining-oid-list" class="ds-chaining-form-list" name="availcontrols" size="10" multiple>
- <option value="1.5.6.5.5.6.88.4553.344">1.5.6.5.5.6.88.4553.344</option>
- <option value="1.5.6.5.5.6.88.4553.345">1.5.6.5.5.6.88.4553.345</option>
- <option value="1.5.6.5.5.6.88.4553.346">1.5.6.5.5.6.88.4553.346</option>
- <option value="1.5.6.5.5.6.88.4553.347">1.5.6.5.5.6.88.4553.347</option>
- <option value="1.5.6.5.5.6.88.4553.348">1.5.6.5.5.6.88.4553.348</option>
- <option value="1.5.6.5.5.6.88.4553.349">1.5.6.5.5.6.88.4553.349</option>
- <option value="1.5.6.5.5.6.88.4553.340">1.5.6.5.5.6.88.4553.340</option>
- <option value="1.5.6.5.5.6.88.4553.351">1.5.6.5.5.6.88.4553.351</option>
- </select>
- </div>
- </form>
- </div>
- <div class="modal-footer" ds-modal-footer>
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="chaining-oid-save" data-dismiss="modal">Add Controls</button>
- </div>
- </div>
- </div>
- </div>
-
- <div class="modal fade" id="chaining-comp-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="chaining-comp-label" aria-hidden="true">
- <div class="modal-dialog ds-modal">
- <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="chaining-comp-label">Chaining Components</h4>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <label class="ds-config-label" for="chaining-comp-list" title=
- "A list of components to work with chaining">Available LDAP Controls</label>
- <div>
- <select id="avail-chaining-comp-list" class="ds-chaining-form-list" name="availcomps" size="10" multiple>
- <option value="cn=roles,cn=compnments,cn=config">cn=roles,cn=components,cn=config</option>
- <option value="cn=Password Policy,cn=components,cn=config">cn=Password Policy,cn=components,cn=config</option>
- </select>
- </div>
- </form>
- </div>
- <div class="modal-footer ds-modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="chaining-comp-save" data-dismiss="modal">Add Components</button>
- </div>
- </div>
- </div>
- </div>
-
-
- <div class="modal fade" id="add-index-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="add-index-label" aria-hidden="true">
- <div class="modal-dialog ds-modal">
- <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="add-index-label">Add Database Index</h4>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <div class="ds-inline">
- <div>
- <label class="ds-config-label" for="ds-index-form-list" title=
- "Select an attribute to index">Select An Attribute</label>
- </div>
- <div>
- <select
- id="index-list-select" class="ds-index-form-list" name="availcomps" size="10">
- <option value="alias">alias</option>
- <option value="cn">cn</option>
- <option value="uid">uid</option>
- <option value="sn">sn</option>
- <option value="givenname">givenname</option>
- <option value="description">description</option>
- <option value="mail">mail</option>
- <option value="memberof">memberof</option>
- </select>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="add-index-type-eq" checked><label
- for="add-index-type-eq" class="ds-label" title="Use equality indexing (nsIndexType)."> Equality Indexing</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="add-index-type-pres" checked><label
- for="add-index-type-pres" class="ds-label" title="Use presence indexing (nsIndexTypeg)."> Presence Indexing</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="add-index-type-sub" checked><label
- for="add-index-type-sub" class="ds-label" title="Use substring indexing (nsIndexType)."> Substring Indexing</label>
- </div>
- <div>
- <input type="checkbox" class="ds-config-checkbox" id="add-index-type-approx"><label
- for="add-index-type-approx" class="ds-label" title="Use approximate indexing (nsIndexType)."> Approximate Indexing</label>
- </div>
- <div>
- <p></p>
- <label for="add-index-matchingrules" class="" title=
- "Matching rule(s) (nsMatchingRule).">
- Matching Rules</label>
- </div>
- <div>
- <input type="text" placeholder="None" id="add-index-matchingrules" size="40"/>
- </div>
- <div>
- <hr>
- <input type="checkbox" class="ds-config-checkbox" id="reindex-on-add" checked><label
- for="reindex-on-add" class="ds-label" title="Index attribute after creating it."> Index Attribute After Saving</label>
- </div>
- </div>
- </form>
- </div>
- <div class="modal-footer ds-modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="add-index-save" data-dismiss="modal">Add Index</button>
- </div>
- </div>
- </div>
- </div>
-
- <!-- Attribute encryption Form -->
- <div class="modal fade" id="add-encrypted-attr-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="attr-enc-label" aria-hidden="true">
- <div class="modal-dialog ds-modal">
- <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="attr-enc-label">Add Encrypted Attribute</h4>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <div class="ds-inline">
- <label class="ds-config-label" for="attr-encrypt-list" title=
- "Select an attribute to be encrypted">Select An Attribute</label>
- </div>
- <div>
- <select
- id="attr-encrypt-list" class="ds-index-form-list" name="availattrs" size="10">
- <option value="alias">alias</option>
- <option value="cn">cn</option>
- </select>
- </div>
- <div>
- <label for="nsencryptionalgorithm" class="ds-config-label" title="Agreement name (nsEncryptionAlgorithm).">Encryption Algorithm</label><select
- class="btn btn-default dropdown ds-agmt-wiz-dropdown ds-form-input" id="nsencryptionalgorithm">
- <option>AES</option>
- <option>3DES</option>
- </select>
- </div>
- </form>
- </div>
- <div class="modal-footer ds-modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="add-encrypted-attr-save" data-dismiss="modal">Add Attribute</button>
- </div>
- </div>
- </div>
- </div>
-
- <!-- Add Suffix Form -->
- <div class="modal fade" id="add-suffix-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="suffix-label" aria-hidden="true">
- <div class="modal-dialog ds-modal">
- <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="suffix-label">Create New Suffix</h4>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <div class="ds-inline">
- <div>
- <label for="add-suffix-dn" class="ds-config-label" title=
- "Database Suffix DN (nsslapd-suffix)">
- Suffix DN</label><input class="ds-input" type="text" id="add-suffix-dn" size="40"/>
- </div>
- <div>
- <label for="add-suffix-backend" class="ds-config-label" title=
- "Database backend name (nsslapd-backend)">
- Backend Name</label><input class="ds-input" type="text" id="add-suffix-backend" size="40"/>
- </div>
- <div>
- <p></p>
- <input type="checkbox" class="ds-config-checkbox" id="add-suffix-create-dn" checked><label
- for="add-suffix-create-dn" class="ds-label" title="Create the top suffix root entry"> Create Suffix Entry</label>
- </div>
- </div>
- </form>
- </div>
- <div class="modal-footer ds-modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="add-suffix-save" data-dismiss="modal">Create Suffix</button>
- </div>
- </div>
- </div>
- </div>
-
-
- <div class="modal fade" id="add-subsuffix-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="subsuffix-label" aria-hidden="true">
- <div class="modal-dialog ds-modal">
- <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="subsuffix-label">Create New Sub-Suffix</h4>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <div class="ds-inline">
- <div>
- <label class="ds-config-labelZ" id="parent-suffix"></label>
- <p></p>
- </div>
- <div>
- <label for="add-subsuffix-dn" class="ds-config-label" title=
- "Database Suffix DN (nsslapd-suffix)">
- Sub-Suffix DN</label><input class="ds-input" type="text" id="add-subsuffix-dn" size="40"/>
- </div>
- <div>
- <label for="add-subsuffix-backend" class="ds-config-label" title=
- "Database backend name (nsslapd-backend)">
- Backend Name</label><input class="ds-input" type="text" id="add-subsuffix-backend" size="40"/>
- </div>
- <div>
- <p></p>
- <input type="checkbox" class="ds-config-checkbox" id="add-subsuffix-create-dn" checked><label
- for="add-subsuffix-create-dn" class="ds-config-label" title="Create the suffix root entry"> Create Sub-Suffix Root Entry</label>
- </div>
- </div>
- </form>
- </div>
- <div class="modal-footer ds-modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="add-subsuffix-save" data-dismiss="modal">Create Sub-Suffix</button>
- </div>
- </div>
- </div>
- </div>
-
- <div class="modal fade" id="import-ldif-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="import-label" 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="import-label">Initialize Database</h4>
- </div>
- <div class="modal-body">
- <div class="ds-inline">
- <div>
- <label for="root-suffix-import" class="ds-config-label-lrg" title=
- "Suffix where to import">
- Root Suffix</label><input class="ds-input" type="text" id="root-suffix-import" size="40" disabled/>
- </div>
- <div>
- <label for="ldif-file-import" class="ds-config-label-lrg" title=
- "LDIF filename without an extension. LDIF files are written to the server's LDIF directory (nsslapd-ldifdir)">
- LDIF Filename</label><input class="ds-input" type="text" id="ldif-file-import" size="40"/>
- </div>
- <div>
- <label for="exclude-suffix-import" class="ds-config-label-lrg" title=
- "Exclude Suffix (Optional)">
- Exclude Suffix (Optional)</label><input class="ds-input" type="text" id="exclude-suffix-import" size="40"/>
- </div>
- <div>
- <label for="include-suffix-import" class="ds-config-label-lrg" title=
- "Include Suffix (Optional)">
- Include Suffix (Optional)</label><input class="ds-input" type="text" id="include-suffix-import" size="40"/>
- </div>
- </div>
- <div id="import-ldif-spinner" class="ds-center" hidden>
- <p></p>
- <p><span class="spinner spinner-xs spinner-inline"></span> Running LDIF import task...</p>
- </div>
- </div>
- <div class="modal-footer ds-modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="import-ldif-save">Import LDIF File</button>
- </div>
- </div>
- </div>
- </div>
-
- <div class="modal fade" id="export-ldif-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="export-label" 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="export-label">Export Database</h4>
- </div>
- <div class="modal-body">
- <div class="ds-inline">
- <div>
- <label for="root-suffix-export" class="ds-config-label-lrg" title=
- "Suffix where to export">
- Root Suffix</label><input class="ds-input" type="text" id="root-suffix-export" size="40" disabled/>
- </div>
- <div>
- <label for="ldif-file-export" class="ds-config-label-lrg" title=
- "LDIF filename without an extension. LDIF files are written to the server's LDIF directory (nsslapd-ldifdir)">
- LDIF Filename (Optional)</label><input class="ds-input" type="text" id="ldif-file-export" size="40"/>
- </div>
- <div>
- <label for="exclude-suffix-export" class="ds-config-label-lrg" title=
- "Exclude Suffix (Optional)">
- Exclude Suffix (Optional)</label><input class="ds-input" type="text" id="exclude-suffix-export" size="40"/>
- </div>
- <div>
- <label for="include-suffix-export" class="ds-config-label-lrg" title=
- "Include Suffix (Optional)">
- Include Suffix (Optional)</label><input class="ds-input" type="text" id="include-suffix-export" size="40"/>
- </div>
- </div>
- <div id="export-ldif-spinner" class="ds-center" hidden>
- <p></p>
- <p><span class="spinner spinner-xs spinner-inline"></span> Running LDIF export task...</p>
- </div>
- </div>
- <div class="modal-footer ds-modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="export-ldif-save">Export Database</button>
- </div>
- </div>
- </div>
- </div>
-
- <!-- Create Referral Form, -->
- <div class="modal fade" id="create-ref-form" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="ref-label" aria-hidden="true">
- <div class="modal-dialog ds-modal">
- <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="ref-label">Create Referral</h4>
- </div>
- <div class="modal-body">
- <form class="form-horizontal">
- <div class="ds-inline">
- <div>
- <label for="ref-protocol" class="ds-label-sm">Connection Protocol</label><select
- class="btn btn-default dropdown ds-ref-dropdown" id="ref-protocol">
- <option selected>ldap://</option>
- <option>ldaps://</option>
- </select>
- </div>
- <div>
- <label for="ref-hostname" class="ds-label-sm">Host Name</label><input class="ds-ref-input" type="text" id="ref-hostname"/>
- </div>
- <div>
- <label for="ref-port" class="ds-label-sm">Port Number</label><input class="ds-ref-input" type="text" id="ref-port"/>
- </div>
- <div>
- <label for="ref-suffix" class="ds-label-sm">Suffix (optional)</label><input class="ds-ref-input" type="text" id="ref-suffix"/>
- </div>
- <div>
- <label for="ref-attrs" class="ds-label-sm">Attributes (optional)</label><input class="ds-ref-input" type="text" id="ref-attrs"/>
- </div>
- <div>
- <label for="ref-scope" class="ds-label-sm">Scope (optional)</label><select
- class="btn btn-default dropdown ds-ref-dropdown" id="ref-scope">
- <option selected></option>
- <option>sub</option>
- <option>one</option>
- <option>base</option>
- </select>
- </div>
- <div>
- <label for="ref-filter" class="ds-label-sm"><b>Filter</b> (optional)</label><input class="ds-ref-input" type="text" id="ref-filter"/>
- </div>
- <p></p>
- <hr class="ds-hr">
- <p></p>
- <div class="ds-inline">
- <button type="button" id="preview-ref-btn" class="btn btn-default ds-ref-button">Preview Referral</button><input
- class="ds-ref-input" type="text" id="ref-preview-field" readOnly/>
- </div>
- </div>
- </form>
- </div>
- <div class="modal-footer ds-modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" id="create-ref-save">Create referral</button>
- </div>
- </div>
- </div>
- </div>
-
-</div>
-
diff --git a/src/cockpit/389-console/src/backend.js b/src/cockpit/389-console/src/backend.js
deleted file mode 100644
index f396b89..0000000
--- a/src/cockpit/389-console/src/backend.js
+++ /dev/null
@@ -1,956 +0,0 @@
-var prev_tree_node = null;
-var ref_del_html = "<button class=\"btn btn-default del-ref-btn\" type=\"button\"><span class='glyphicon glyphicon-trash'></span> Delete</button>";
-var attr_encrypt_del_html = "<button class=\"btn btn-default attr-encrypt-delete-btn\" type=\"button\">Remove Attribute</button></td>";
-var index_btn_html =
- '<div class="dropdown"> ' +
- '<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">' +
- 'Choose Action...' +
- '<span class="caret"></span>' +
- '</button>' +
- '<ul class="dropdown-menu" role="menu">' +
- '<li><a class="db-index-save-btn">Save Index</a></li>' +
- '<li><a class="db-index-save-reindex-btn">Save & Reindex</a></li>' +
- '<li><a class="db-index-reindex-btn">Reindex Attribute</a></li>' +
- '<li><a class="db-index-delete-btn">Delete Index</a></li>' +
- '</ul>' +
- '</div>';
-
-
-var chaining_attr_map = {
- 'nsbindconnectionslimit': '--conn-bind-limit',
- 'nsoperationconnectionslimit': '--conn-op-limit',
- 'nsabandonedsearchcheckinterval': '--abandon-check-interval',
- 'nsconcurrentbindlimit': '--bind-limit',
- 'nsconcurrentoperationslimit': '--op-limit',
- 'nsproxiedauthorization': '--proxied-auth',
- 'nsconnectionlife': '--conn-lifetime',
- 'nsbindtimeout': '--bind-timeout',
- 'nsreferralonscopedsearch': '--return-ref',
- 'nschecklocalaci': '--check-aci',
- 'nsbindretrylimit': '--bind-attempts',
- 'nsslapd-sizelimit': '--size-limit',
- 'nsslapd-timelimit': '--time-limit',
- 'nshoplimit': '--hop-limit',
- 'nsmaxresponsedelay': '--response-delay',
- 'nsmaxtestresponsedelay': '--test-response-delay',
- 'nsusestarttls':'--use-starttls' ,
- 'nsfarmserverurl': '--server-url',
- 'nsbindmechanism': '--bind-mech',
- 'nsmultiplexorbinddn': '--bind-dn',
- 'nsmultiplexorcredentials': '--bind-pw'
-};
-
-function customMenu (node) {
- var dblink_items = {
- "delete_link": {
- "label": "Delete DB Link",
- "icon": "glyphicon glyphicon-trash",
- "action": function (data) {
- popup_confirm("Are you sure you want to delete this Database Link?", "Confirmation", function (yes) {
- if (yes) {
-
- // TODO Delete db link
- }
- });
- }
- }
- };
-
- var suffix_items = {
- 'import': {
- "label": "Initialize Suffix",
- "icon": "glyphicon glyphicon-circle-arrow-right",
- "action": function (data) {
- var suffix_id = $(node).attr('id');
- var parent_suffix = suffix_id.substring(suffix_id.indexOf('-')+1);
- $("#root-suffix-import").val(parent_suffix);
- $("#import-ldif-file").val("");
- $("#import-ldif-form").modal('toggle');
- }
- },
- 'export': {
- "label": "Export Suffix",
- "icon": "glyphicon glyphicon-circle-arrow-left",
- "action": function (data) {
- var suffix_id = $(node).attr('id');
- var parent_suffix = suffix_id.substring(suffix_id.indexOf('-')+1);
- $("#root-suffix-export").val(parent_suffix);
- $("#export-ldif-file").val("");
- $("#export-ldif-form").modal('toggle');
- }
- },
- 'reindex': {
- "label": "Reindex Suffix",
- "icon": "glyphicon glyphicon-wrench",
- "action": function (data) {
- popup_confirm("This will impact DB performance during the indexing. Are you sure you want to reindex all attributes?", "Confirmation", function (yes) {
- if (yes) {
- // TODO Reindex suffix
- }
- });
- }
- },
- "create_db_link": {
- "label": "Create Database Link",
- "icon": "glyphicon glyphicon-link",
- "action": function (data) {
- var suffix_id = $(node).attr('id');
- var parent_suffix = suffix_id.substring(suffix_id.indexOf('-')+1);
- //clear_chaining_form(); //TODO
- $("#create-db-link-form").modal('toggle');
- }
- },
- "create_sub_suffix": {
- "label": "Create Sub-Suffix",
- "icon": "glyphicon glyphicon-triangle-bottom",
- "action": function (data) {
- var suffix_id = $(node).attr('id');
- var parent_suffix = suffix_id.substring(suffix_id.indexOf('-')+1);
- $("#parent-suffix").html('<b>Parent Suffix:</b> ' + parent_suffix);
- $("#add-subsuffix-dn").val(' ,' + parent_suffix);
- $("#add-subsuffix-form").modal('toggle');
- }
- },
- 'delete_suffix': {
- "label": "Delete Suffix",
- "icon": "glyphicon glyphicon-remove",
- "action": function (data) {
- popup_confirm("Are you sure you want to delete suffix?", "Confirmation", function (yes) {
- if (yes) {
- // TODO
- }
- });
- }
- }
- };
-
- if ( $(node).attr('id').startsWith('suffix') ){
- return suffix_items;
- } else {
- // chaining
- return dblink_items;
- }
-};
-
-function get_encoded_ref () {
- var ref_encoded = $("#ref-protocol").val() + $("#ref-hostname").val();
- var ref_port = $("#ref-port").val();
- var ref_suffix = $("#ref-suffix").val();
- var ref_attrs = $("#ref-attrs").val();
- var ref_filter = $("#ref-filter").val();
- var ref_scope = $("#ref-scope").val();
-
- $("#preview-ref-btn").blur();
-
- if (ref_port != ""){
- ref_encoded += ":" + ref_port;
- }
-
- if (ref_suffix == "" && (ref_attrs != "" || ref_filter != "" || ref_scope != "")) {
- popup_msg("Attention!", "Missing suffix - you can not set the attributes, scope, or filter without a suffix.");
- return;
- }
- if (ref_suffix != "" || ref_attrs != "" || ref_filter != "" || ref_scope != "") {
- ref_encoded += "/" + encodeURIComponent(ref_suffix);
- if ( ref_attrs != "" ) {
- ref_encoded += "?" + encodeURIComponent(ref_attrs);
- } else if ( ref_filter != "" || ref_scope != "" ) {
- ref_encoded += "?";
- }
- if ( ref_scope != "" ) {
- ref_encoded += "?" + encodeURIComponent(ref_scope);
- } else if ( ref_filter != "" ) {
- ref_encoded += "?";
- }
- if ( ref_filter != "") {
- ref_encoded += "?" + encodeURIComponent(ref_filter);
- }
- }
- return ref_encoded;
-}
-
-function load_jstree() {
- $('#db-tree').jstree({
- "plugins": [ "contextmenu", "wholerow", "sort" ],
- "contextmenu": {
- "items" : customMenu
- },
- "core": {
- "check_callback": true
- }
- });
-
- $('#db-tree').jstree('select_node', 'ul > li:first');
-
-
- $('#db-tree').on("changed.jstree", function (e, data) {
- var node_type = data.selected[0];
- var suffix = data.instance.get_node(data.selected[0]).text.replace(/(\r\n|\n|\r)/gm,"");
-
- if (node_type.startsWith("dblink")) {
- var parent_suffix = node_type.substring(node_type.indexOf('-')+1);
- $(".all-pages").hide();
- $("#database-content").show();
- $("#db-page").show();
- $("#chaining-header").html("Database Chaining Configuration <font size=\"2\">(<b>" + parent_suffix + "</b>)</font>");
- $("#chaining-page").show();
- } else {
- // suffix
- $(".all-pages").hide();
- $("#database-content").show();
- $("#db-page").show();
- $("#suffix-header").html("Suffix Configuration <font size=\"2\">(<b>" + suffix + "</b>)</font>");
- $("#suffix-page").show();
- }
- });
-};
-
-function clear_ref_form () {
- $("#ref-scope").prop('selectedIndex',0);
- $("#ref-protocol").prop('selectedIndex',0);
- $("#ref-suffix").val("");
- $("#ref-filter").val("");
- $("#ref-hostname").val("");
- $("#ref-port").val("");
- $("#ref-attrs").val("");
- $("#ref-preview-field").val("");
-}
-
-function clear_index_form () {
- $("#index-list-select").prop('selectedIndex',0);
- $("#add-index-type-eq").prop('checked', false);
- $("#add-index-type-pres").prop('checked', false);
- $("#add-index-type-sub").prop('checked', false);
- $("#add-index-type-approx").prop('checked', false);
- $("#add-index-matchingrules").val("");
-}
-
-function clear_attr_encrypt_form() {
- $("#attr-encrypt-list").prop('selectedIndex',0);
- $("#nsencryptionalgorithm").prop('selectedIndex',0);
-}
-
-$(document).ready( function() {
- $("#database-content").load("backend.html", function () {
- load_jstree();
-
- $(".ds-suffix-panel").toggle("active");
- $(".ds-suffix-panel").css('display','none');
-
- $("#create-ref-btn").on("click", function () {
- clear_ref_form();
- });
-
- $("#db-chaining-btn").on("click", function() {
- $(".all-pages").hide();
- $("#database-content").show();
- $("#db-chaining-settings-page").show();
- });
- $("#db-global-btn").on("click", function() {
- $(".all-pages").hide();
- $("#database-content").show();
- $("#db-global-page").show();
- });
- $("#db-suffix-btn").on("click", function() {
- $(".all-pages").hide();
- $("#database-content").show();
- $("#db-page").show();
- $("#suffix-page").show();
- });
-
- $("#chaining-adv-accordion").on("click", function() {
- this.classList.toggle("active");
- var panel = this.nextElementSibling;
- if (panel.style.display === "block") {
- var show = "► Show Advanced Database Link Settings ";
- $(this).html(show);
- panel.style.display = "none";
- $(this).blur();
- } else {
- var hide = "▼ Hide Advanced Database Link Settings";
- $(this).html(hide);
- panel.style.display = "block";
- $(this).blur();
- }
- });
-
-
- $("#db-system-index-accordion").on("click", function() {
- this.classList.toggle("active");
- var panel = this.nextElementSibling;
- if (panel.style.display === "block") {
- var show = "► Show System Indexes ";
- $(this).html(show);
- panel.style.display = "none";
- $(this).blur();
- } else {
- var hide = "▼ Hide System Indexes ";
- $(this).html(hide);
- panel.style.display = "block";
- $(this).blur();
- }
- });
-
- $("#db-index-accordion").on("click", function() {
- this.classList.toggle("active");
- var panel = this.nextElementSibling;
- if (panel.style.display === "block") {
- var show = "► Show Database Indexes ";
- $(this).html(show);
- panel.style.display = "none";
- $(this).blur();
- } else {
- var hide = "▼ Hide Database Indexes ";
- $(this).html(hide);
- panel.style.display = "block";
- $(this).blur();
- }
- });
-
- $("#suffix-attrencrypt-accordion").on("click", function() {
- this.classList.toggle("active");
- var panel = this.nextElementSibling;
- if (panel.style.display === "block") {
- var show = "► Show Encrypted Attributes ";
- $(this).html(show);
- panel.style.display = "none";
- $(this).blur();
- } else {
- var hide = "▼ Hide Encrypted Attributes ";
- $(this).html(hide);
- panel.style.display = "block";
- $(this).blur();
- }
- });
-
-
- /*
- We need logic to see if autocaching (import and db) is being used, and disable fields,
- and set radio buttons, et
- */
-
- var ref_table = $('#referral-table').DataTable( {
- "paging": false,
- "searching": false,
- "bInfo" : false,
- "bAutoWidth": false,
- "dom": '<"pull-left"f><"pull-right"l>tip',
- "language": {
- "emptyTable": "No Referrals"
- },
- "columnDefs": [
- {
- "targets": 1,
- "orderable": false,
- },
- {
- "targets": 1,
- "className": "ds-center"
- }
- ]
- });
-
-
- $('#system-index-table').DataTable( {
- "paging": true,
- "bAutoWidth": false,
- "dom": '<"pull-left"f><"pull-right"l>tip',
- "lengthMenu": [ 10, 25, 50, 100],
- "language": {
- "search": "Search",
- "emptyTable": "No System Indexes"
- },
- "columns": [
- { "width": "10%" },
- { "width": "20px" },
- { "width": "20px" },
- { "width": "20px" },
- { "width": "20px" },
- { "width": "20%" }
- ],
- });
- var index_table = $('#index-table').DataTable( {
- "paging": true,
- "bAutoWidth": false,
- "dom": '<"pull-left"f><"pull-right"l>tip',
- "lengthMenu": [ 10, 25, 50, 100],
- "language": {
- "search": "Search",
- "emptyTable": "No Indexes"
- },
- "columns": [
- { "width": "10%" },
- { "width": "15px" },
- { "width": "15px" },
- { "width": "15px" },
- { "width": "15px" },
- { "width": "20%" },
- { "width": "76px" }
- ],
- "columnDefs": [ {
- "targets": 6,
- "orderable": false
- } ]
- });
-
-
-
-
- var attr_encrypt_table = $('#attr-encrypt-table').DataTable( {
- "paging": true,
- "bAutoWidth": false,
- "searching": true,
- "dom": '<"pull-left"f><"pull-right"l>tip',
- "lengthMenu": [ 10, 25, 50, 100],
- "language": {
- "search": "Search",
- "emptyTable": "No Encrypted Attributes"
- },
- "columnDefs": [ {
- "targets": 2,
- "orderable": false
- } ]
- });
-
- // Accordion opening/closings
- $(".ds-accordion-panel").css('display','none');
-
- var suffix_acc = document.getElementsByClassName("suffix-accordion");
- for (var i = 0; i < suffix_acc.length; i++) {
- suffix_acc[i].onclick = function() {
- this.classList.toggle("active");
- var panel = this.nextElementSibling;
- if (panel.style.display === "block") {
- panel.style.display = "none";
- } else {
- panel.style.display = "block";
- }
- }
- }
-
- $("#db-accordion").on("click", function() {
- this.classList.toggle("active");
- var panel = this.nextElementSibling;
- if (panel.style.display === "block") {
- var show = "► Show Advanced Settings";
- $(this).html(show);
- panel.style.display = "none";
- $(this).blur();
- } else {
- var hide = "▼ Hide Advanced Settings ";
- $(this).html(hide);
- panel.style.display = "block";
- $(this).blur();
- }
- });
-
- var db_acc = document.getElementsByClassName("db-accordion");
- for (var i = 0; i < db_acc.length; i++) {
- db_acc[i].onclick = function() {
- this.classList.toggle("active");
- var panel = this.nextElementSibling;
- if (panel.style.display === "block") {
- panel.style.display = "none";
- } else {
- panel.style.display = "block";
- }
- }
- }
-
- var cache_acc = document.getElementsByClassName("cache-accordion");
- for (var i = 0; i < cache_acc.length; i++) {
- cache_acc[i].onclick = function() {
- this.classList.toggle("active");
- var panel = this.nextElementSibling;
- if (panel.style.display === "block") {
- panel.style.display = "none";
- } else {
- panel.style.display = "block";
- }
- }
- }
-
-
- $(".index-type").attr('readonly', true);
-
- if ( $("#manual-cache").is(":checked") ){
- $("#auto-cache-form").hide();
- $("#manual-cache-form").show();
- $("#nsslapd-dncachememsize").prop('disabled', false);
- $("#nsslapd-dncachememsize").val('');
- $("#nsslapd-cachememsize").prop('disabled', false);
- $("#nsslapd-cachememsize").val('');
- $("#nsslapd-cachesize").prop('disabled', false);
- $("#nsslapd-cachesize").val('');
- } else {
- $("#manual-cache-form").hide();
- $("#auto-cache-form").show();
- $("#nsslapd-dncachememsize").prop('disabled', true);
- $("#nsslapd-dncachememsize").val('AUTOTUNED');
- $("#nsslapd-cachememsize").prop('disabled', true);
- $("#nsslapd-cachememsize").val('AUTOTUNED');
- $("#nsslapd-cachesize").prop('disabled', true);
- $("#nsslapd-cachesize").val('AUTOTUNED');
- }
-
- if ( $("#manual-import-cache").is(":checked") ){
- $("#auto-import-cache-form").hide();
- $("#manual-import-cache-form").show();
- } else {
- $("#manual-import-cache-form").hide();
- $("#auto-import-cache-form").show();
- }
-
- $(".cache-role").on("change", function() {
- var cache_role = $("input[name=cache-role]:checked").val();
- if (cache_role == "manual-cache") {
- $("#auto-cache-form").hide();
- $("#manual-cache-form").show();
- } else {
- // auto cache
- $("#manual-cache-form").hide();
- $("#auto-cache-form").show();
- }
- });
-
- $(document).on('click', '.del-ref-btn', function(e) {
- e.preventDefault();
- var data = ref_table.row( $(this).parents('tr') ).data();
- var del_ref_name = data[0];
- var ref_row = $(this); // Store element for callback
- popup_confirm("Are you sure you want to delete referral: <b>" + del_ref_name + "</b>", "Confirmation", function (yes) {
- if (yes) {
- // TODO Delete mapping from DS
-
- // Update html table
- ref_table.row( ref_row.parents('tr') ).remove().draw( false );
- }
- });
- });
-
- $("#backend-config-save-btn").on("click", function () {
- var role = $("input[name=cache-role]:checked").val();
- if (role == "manual-cache") {
- // TODO - need to get cache values and fields (overwrite AUTOTUNED)
- $("#nsslapd-dncachememsize").prop('disabled', false);
- $("#nsslapd-dncachememsize").val('')
- $("#nsslapd-cachememsize").prop('disabled', false);
- $("#nsslapd-cachememsize").val('');
- $("#nsslapd-cachesize").prop('disabled', false);
- $("#nsslapd-cachesize").val('');
- } else {
- // auto cache
- $("#nsslapd-dncachememsize").prop('disabled', true);
- $("#nsslapd-dncachememsize").val('AUTOTUNED');
- $("#nsslapd-cachememsize").prop('disabled', true);
- $("#nsslapd-cachememsize").val('AUTOTUNED');
- $("#nsslapd-cachesize").prop('disabled', true);
- $("#nsslapd-cachesize").val('AUTOTUNED');
- }
- });
-
- $(".import-cache-role").on("change", function() {
- var role = $("input[name=import-cache-role]:checked").val();
- if (role == "manual-import-cache") {
- $("#auto-import-cache-form").hide();
- $("#manual-import-cache-form").show();
- } else {
- // auto cache
- $("#manual-import-cache-form").hide();
- $("#auto-import-cache-form").show();
- }
- });
-
- // Based on the db-link connection type change the agmt-auth options
- $("#dblink-conn").change(function() {
- var ldap_opts = {"Simple": "Simple",
- "SASL/DIGEST-MD5": "SASL/DIGEST-MD5",
- "SASL/GSSAPI": "SASL/GSSAPI"};
- var ldaps_opts = {"Simple": "Simple",
- "SSL Client Authentication": "SSL Client Authentication",
- "SASL/DIGEST-MD5": "SASL/DIGEST-MD5"};
- var $auth = $("#nsbindmechanism");
- $auth.empty();
- var conn = $('#dblink-conn').val();
- if (conn == "LDAP"){
- $.each(ldap_opts, function(key, value) {
- $auth.append($("<option></option>").attr("value", value).text(key));
- });
- } else {
- // TLS options
- $.each(ldaps_opts, function(key, value) {
- $auth.append($("<option></option>").attr("value", value).text(key));
- });
- }
- $("#nsmultiplexorbinddn").prop('disabled', false);
- $("#nsmultiplexorcredentials").prop('disabled', false);
- $("#nsmultiplexorcredentials-confirm").prop('disabled', false);
- });
-
- // Check for auth changes and disable/enable bind DN & password for db-links
- $("#nsbindmechanism").change(function() {
- var authtype = $('#nsbindmechanism').val();
- if (authtype == "SSL Client Authentication") {
- $("#nsmultiplexorbinddn").prop('disabled', true);
- $("#nsmultiplexorcredentials").prop('disabled', true);
- $("#nsmultiplexorcredentials-confirm").prop('disabled', true);
- } else {
- $("#nsmultiplexorbinddn").prop('disabled', false);
- $("#nsmultiplexorcredentials").prop('disabled', false);
- $("#nsmultiplexorcredentials-confirm").prop('disabled', false);
- }
- });
-
- //
- // Modal Forms
- //
-
- // Chaining OIDS
- $("#chaining-oid-button").on("click", function() {
- $(this).blur();
- });
- $("#chaining-oid-save").on("click", function() {
- // Update oids
- var chaining_oids = $("#avail-chaining-oid-list").val();
- for (var i = 0; chaining_oids && i < chaining_oids.length; i++) {
- $('#chaining-oid-list').append($('<option/>', {
- value: chaining_oids[i],
- text : chaining_oids[i]
- }));
- $("#avail-chaining-oid-list option[value='" + chaining_oids[i] + "']").remove();
- }
- sort_list( $("#chaining-oid-list") );
- $("#chaining-oid-form").modal('toggle');
- });
- $("#delete-chaining-oid-button").on("click", function() {
- var oids = $("#chaining-oid-list").find('option:selected');
- if (oids && oids != '' && oids.length > 0) {
- for (var i = 0; i < oids.length; i++) {
- if ( $('#avail-chaining-comp-list option[value="' + oids[i].text + '"]').val() === undefined) {
- $('#avail-chaining-oid-list').append($("<option/>").val(oids[i].text).text(oids[i].text));
- }
- }
- }
- $("#chaining-oid-list").find('option:selected').remove();
- sort_list( $('#avail-chaining-oid-list') );
- });
-
- // Chaining Comps
- $("#delete-chaining-comp-button").on("click", function() {
- var comps = $("#chaining-comp-list").find('option:selected');
- if (comps && comps != '' && comps.length > 0) {
- for (var i = 0; i < comps.length; i++) {
- if ( $('#avail-chaining-comp-list option[value="' + comps[i].text + '"]').val() === undefined) {
- $('#avail-chaining-comp-list').append($("<option/>").val(comps[i].text).text(comps[i].text));
- }
- }
- }
- $("#chaining-comp-list").find('option:selected').remove();
- sort_list($('#avail-chaining-comp-list') );
- });
- $("#chaining-comp-save").on("click", function() {
- // Update comps
- var chaining_comps = $("#avail-chaining-comp-list").val();
- for (var i = 0; chaining_comps && i < chaining_comps.length; i++) {
- $('#chaining-comp-list').append($('<option/>', {
- value: chaining_comps[i],
- text : chaining_comps[i]
- }));
- $("#avail-chaining-comp-list option[value='" + chaining_comps[i] + "']").remove();
- }
- sort_list( $("#chaining-comp-list") );
- $("#chaining-comp-form").css('display', 'none');
- });
-
- // Create DB Link
- $("#create-chain-close").on("click", function() {
- $("#create-db-link-form").css('display', 'none');
- });
- $("#chaining-cancel").on("click", function() {
- $("#create-db-link-form").css('display', 'none');
- });
- $("#chaining-save").on("click", function() {
- // Create DB link, if LDAPS is selected replace remotefarmUrl "ldap://" with "ldaps://", and visa versa to remove ldaps://
- var chaining_name = $("#chaining-name").val();
- var parent_suffix = $("#db-tree").jstree().get_selected(true)[0];
- var suffix = $("#db-tree").jstree().get_selected(true)[0].text.replace(/(\r\n|\n|\r)/gm,"");
-
- // TODO - create db link in DS
-
- $('#db-tree').jstree().create_node(parent_suffix,
- { "id" : "dblink-" + suffix, "text" : chaining_name, "icon" : "glyphicon glyphicon-link" },
- "last");
- $("#create-db-link-form").css('display', 'none');
- });
-
- // Add Index
- $("#add-index-button").on("click", function() {
- clear_index_form();
- })
-
- $("#add-index-save").on("click", function() {
- var attr_name = $("#index-list-select").val();
- var add_attr_type_eq = '<input type="checkbox" id="' + attr_name + '-eq">';
- var add_attr_type_pres = '<input type="checkbox" id="' + attr_name + '-pres">';
- var add_attr_type_sub = '<input type="checkbox" id="' + attr_name + '-sub">';
- var add_attr_type_approx = '<input type="checkbox" id="' + attr_name + '-approx">';
- var add_index_matchingrules = $("#add-index-matchingrules").val();
-
- if ( $("#add-index-type-eq").is(":checked") ){
- add_attr_type_eq = '<input type="checkbox" id="' + attr_name + '-eq" checked>';
- }
- if ( $("#add-index-type-pres").is(":checked") ){
- add_attr_type_pres = '<input type="checkbox" id="' + attr_name + '-pres" checked>';
- }
- if ( $("#add-index-type-sub").is(":checked") ){
- add_attr_type_sub = '<input type="checkbox" id="' + attr_name + '-sub" checked>';
- }
- if ( $("#add-index-type-approx").is(":checked") ){
- add_attr_type_approx = '<input type="checkbox" id="' + attr_name + '-approx" checked>';
- }
-
- // TODO - add index to DS
-
- // Update table on success
- index_table.row.add( [
- attr_name,
- add_attr_type_eq,
- add_attr_type_pres,
- add_attr_type_sub,
- add_attr_type_approx,
- add_index_matchingrules,
- index_btn_html
- ] ).draw( false );
-
- $("#add-index-form").css('display', 'none');
- clear_index_form();
- // Do the actual save in DS
- // Update html
- });
-
-
- // Add encrypted attribute
- $("#add-encrypted-attr-button").on("click", function() {
- clear_attr_encrypt_form();
- })
- $("#add-encrypted-attr-save").on("click", function() {
-
- // Do the actual save in DS
- // Update html
-
- var encrypt_attr_name = $("#attr-encrypt-list").val();
- var attr_encrypt_algo = $("#nsencryptionalgorithm").val();
- // TODO - add encrypted attr to DS
-
- // Update table on success
- attr_encrypt_table.row.add( [
- encrypt_attr_name,
- attr_encrypt_algo,
- attr_encrypt_del_html
- ] ).draw( false );
-
- $("#add-encrypted-attr-form").modal('toggle');
-
- });
-
- // Create Suffix
- $("#add-suffix-save").on("click", function() {
- var suffix = $("#add-suffix-dn").val();
- var backend = $("#add-suffix-backend").val();
-
- // TODO - create suffix in DS
-
- $('#db-tree').jstree().create_node('db-root',
- { "id" : "suffix-" + suffix, "text" : suffix, "icon" : "glyphicon glyphicon-tree-conifer" },
- "last");
- $("#add-suffix-form").css('display', 'none');
- });
-
- // Create Sub Suffix
- $("#add-subsuffix-close").on("click", function() {
- $("#add-subsuffix-form").css('display', 'none');
- });
- $("#add-subsuffix-cancel").on("click", function() {
- $("#add-subsuffix-form").css('display', 'none');
- });
- $("#add-subsuffix-save").on("click", function() {
- var suffix = $("#add-subsuffix-dn").val();
- var backend = $("#add-subsuffix-backend").val();
- var parent_suffix = $("#db-tree").jstree().get_selected(true)[0];
-
- // TODO - create suffix in DS
-
- $('#db-tree').jstree().create_node(parent_suffix,
- { "id" : "subsuffix-" + suffix, "text" : suffix, "icon" : "glyphicon glyphicon-leaf" },
- "last");
- $("#add-subsuffix-form").css('display', 'none');
- });
-
- // Init Suffix (import)
- $("#import-ldif-save").on("click", function() {
- var root_suffix_import = $("#root-suffix-import").val();
- var ldif_file_import = $("#ldif-file-import").val();
- var exclude_suffix_import = $("#exclude-suffix-import").val();
- var include_suffix_import = $("#include-suffix-import").val();
- var cmd = [DSCONF, server_inst, 'backend', 'import', root_suffix_import];
- // Process and validate parameters
- if (ldif_file_import == ""){
- popup_msg("Error", "LDIF file should be specified");
- return;
- } else if (ldif_file_import.indexOf(' ') >= 0) {
- popup_msg("Error", "LDIF file can not contain any spaces");
- return;
- } else if (ldif_file_import.indexOf('/') === -1 ) {
- popup_msg("Error", "LDIF file can not contain a forward slash. " +
- "LDIF files are written to the server's LDIF directory (nsslapd-ldifdir)");
- return;
- } else {
- cmd.push(ldif_file_import);
- }
- if (include_suffix_import != "") {
- cmd.push.apply(cmd, ["-s", include_suffix_import]);
- }
- if (exclude_suffix_import != "") {
- cmd.push.apply(cmd, ["-x", exclude_suffix_import]);
- }
- $("#import-ldif-spinner").show();
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).
- done(function(data) {
- $("#import-ldif-spinner").hide();
- popup_success("LDIF has been imported");
- $("#import-ldif-form").modal('toggle');
- }).
- fail(function(data) {
- $("#import-ldif-spinner").hide();
- popup_err("Error", "Failed to import LDIF\n" + data.message);
- })
- });
-
- // Export Suffix (import)
- $("#export-ldif-close").on("click", function() {
- $("#export-ldif-form").css('display', 'none');
- });
- $("#export-ldif-cancel").on("click", function() {
- $("#export-ldif-form").css('display', 'none');
- });
- $("#export-ldif-save").on("click", function() {
- var root_suffix_export = $("#root-suffix-export").val();
- var ldif_file_export = $("#ldif-file-export").val();
- var exclude_suffix_export = $("#exclude-suffix-export").val();
- var include_suffix_export = $("#include-suffix-export").val();
- var cmd = [DSCONF, server_inst, 'backend', 'export', root_suffix_export];
- // Process and validate parameters
- if (ldif_file_export.indexOf(' ') >= 0) {
- popup_msg("Error", "LDIF file can not contain any spaces");
- return;
- } else if (ldif_file_import.indexOf('/') === -1 ) {
- popup_msg("Error", "LDIF file can not contain a forward slash. " +
- "LDIF files are written to the server's LDIF directory (nsslapd-ldifdir)");
- return;
- } else if (ldif_file_export != ""){
- cmd.push.apply(cmd, ["-l", ldif_file_export]);
- }
- if (include_suffix_export != "") {
- cmd.push.apply(cmd, ["-s", include_suffix_export]);
- }
- if (exclude_suffix_export != "") {
- cmd.push.apply(cmd, ["-x", exclude_suffix_export]);
- }
- $("#export-ldif-spinner").show();
- cockpit.spawn(cmd, { superuser: true, "err": "message", "environ": [ENV]}).
- done(function(data) {
- $("#export-ldif-spinner").hide();
- popup_success("LDIF has been exported");
- $("#export-ldif-form").modal('toggle');
- }).
- fail(function(data) {
- $("#export-ldif-spinner").hide();
- popup_err("Error", "Failed to export LDIF\n" + data.message);
- })
- });
-
- $("#create-ref-save").on("click", function() {
- var ref = get_encoded_ref();
- // Do the actual save in DS
- // Update html
- var tr_row = ref_table.row.add( [
- ref,
- ref_del_html
- ] );
- $( tr_row ).addClass('ds-nowrap-td');
- $( tr_row ).find("td:nth-child(1))").addClass('ds-center');
- tr_row.draw(false);
- $("#create-ref-form").modal('toggle');
- });
-
- $("#preview-ref-btn").on('click', function() {
- $("#ref-preview-field").val(get_encoded_ref());
- });
-
- $(document).on('click', '.delete-referral-btn', function(e) {
- e.preventDefault();
- var data = ref_table.row( $(this).parents('tr') ).data();
- var del_ref_name = data[0];
- var ref_row = $(this);
- popup_confirm("Are you sure you want to delete referral: " + del_ref_name, "Confirmation", function (yes) {
- if (yes) {
- // TODO Delete ref
- ref_table.row( ref_row.parents('tr') ).remove().draw( false );
- }
- });
- });
-
-
- // suffix index actions
- $(document).on('click', '.db-index-save-btn', function(e) {
- e.preventDefault();
- var data = index_table.row( $(this).parents('tr') ).data();
- var save_index_name = data[0];
- // Do save!
- });
-
- $(document).on('click', '.db-index-reindex-btn', function(e) {
- e.preventDefault();
- var data = index_table.row( $(this).parents('tr') ).data();
- var reindex_name = data[0];
- popup_confirm("Are you sure you want to reindex attribute: <b>" + reindex_name + "</b>", "Confirmation", function (yes) {
- if (yes) {
- // TODO reindex attr
-
- }
- });
- });
-
- $(document).on('click', '.db-index-delete-btn', function(e) {
- e.preventDefault();
- var data = index_table.row( $(this).parents('tr') ).data();
- var del_index_name = data[0];
- var index_row = $(this);
-
- popup_confirm("Are you sure you want to delete index: <b>" + del_index_name + "</b>", "Confirmation", function (yes) {
- if (yes) {
- // TODO Delete index
- index_table.row( index_row.parents('tr') ).remove().draw( false );
- }
- });
- });
-
- // Attribute encryption
- $(document).on('click', '.attr-encrypt-delete-btn', function(e) {
- e.preventDefault();
- var data = attr_encrypt_table.row( $(this).parents('tr') ).data();
- var attr_name = data[0];
- var eattr_row = $(this);
- popup_confirm("Are you sure you want to delete encrypted attribute: <b>" + attr_name + "</b>", "Confirmation", function (yes) {
- if (yes) {
- // TODO Delete ref
- attr_encrypt_table.row( eattr_row.parents('tr') ).remove().draw( false );
- }
- });
- });
-
- // Page is loaded, mark it as so...
- db_page_loaded = 1;
- });
-});
-
diff --git a/src/cockpit/389-console/src/css/ds.css b/src/cockpit/389-console/src/css/ds.css
index 8e9ba21..6af009c 100644
--- a/src/cockpit/389-console/src/css/ds.css
+++ b/src/cockpit/389-console/src/css/ds.css
@@ -41,6 +41,7 @@
line-height: 0;
}
+/* Main nav page index.html */
.ds-content {
padding: 0;
padding-top: 115px;
@@ -283,8 +284,9 @@ td {
min-width: 300px;
min-height: 400px;
max-height: 400px;
- margin: 23px 10px 2px 0px;
+ margin: 25px 10px 2px 0px;
overflow: auto;
+ padding-top: 5px;
}
.ds-monitor-tree {
@@ -327,6 +329,20 @@ td {
padding-left: 5px !important;
}
+.ds-input-bad {
+ border-color: red;
+}
+
+.ds-input-auto-bad {
+ width: 100%;
+ border-color: red;
+ padding-left: 5px;
+}
+
+.ds-input-right {
+ text-align: right;
+}
+
.ds-pw-input {
margin-top: 5px;
padding-right: 5px;
@@ -382,11 +398,6 @@ td {
padding-left: 5px !important;
}
-.ds-ref-input {
- width: 300px !important;
- padding-left: 5px;
-}
-
.ds-history-input {
margin-top: !important;
margin-right: 5px;
@@ -401,6 +412,10 @@ td {
width: 35px;
}
+.ds-divider-lrg {
+ width: 50px;
+}
+
.ds-divider-med {
width: 15px;
}
@@ -448,10 +463,6 @@ td {
margin-bottom: 5px;
}
-.ds-radio {
- margin: 5px !important;
-}
-
.ds-repl-managers-list {
width: 420px;
height: 200px;
@@ -495,11 +506,6 @@ td {
overflow: auto;
}
-.ds-ref-dropdown {
- width: 300px !important;
- text-align: left;
-}
-
.ds-agmt-wiz-dropdown {
width: 175px !important;
text-align: left;
@@ -515,13 +521,8 @@ td {
}
.ds-dblink-dropdown {
- padding: 0px !important;
- padding-right: 5px !important;
- line-height: 0 !important;
width: 140px !important;
text-align: left;
- outline: 0 !important;
- margin-top: 5px;
}
.ds-dblink-dropdown a {
@@ -576,6 +577,15 @@ td {
text-align: center;
}
+.ds-db-table {
+ background-color: white !important;
+ padding: 0px;
+ border: 1px solid #909090;
+ clear: both;
+ word-wrap: break-word !important;
+ text-align: center;
+}
+
.ds-table {
background-color: white !important;
padding: 0px;
@@ -671,14 +681,6 @@ td {
vertical-align: middle;
}
-.modal-content {
- position: absolute;
- max-height: 750px;
- max-width: 550px;
- min-width: 400px;
- overflow: auto;
-}
-
.ds-log-panel {
padding: 0 18px;
max-height: 0 !important;
@@ -1016,13 +1018,15 @@ td {
}
.ds-chaining-form-list {
- width: 355px;
- height: 400px;
+ width: 100%;
+}
+
+.ds-vlv-sort-list {
+ width: 100%;
}
.ds-index-form-list {
- width: 363px;
- height: 500px;
+ width: 100%;
}
.ds-oc-form-list {
@@ -1144,10 +1148,6 @@ option {
border-top: 1px solid #999 !important;
}
-.ds-no-horizontal-scrollbar {
- overflow-x: hidden;
-}
-
.ds-modal-footer {
background-color: #f5f5f5 !important;
padding: 10px;
@@ -1170,11 +1170,11 @@ option {
}
.ds-config-header {
- margin-bottom: 25px;
+ margin-bottom: 20px;
}
.ds-sub-header {
- margin-top: 25px;
+ margin-top: 35px !important;
margin-bottom: 10px;
}
@@ -1219,10 +1219,26 @@ option {
}
.ds-accordion-panel {
- padding-left: 25px;
margin-top: 10px;
}
+.ds-margin-top {
+ margin-top: 10px;
+}
+
+.ds-margin-top-lg {
+ margin-top: 20px !important;
+}
+
+.ds-margin-top-xlg {
+ margin-top: 35px !important;
+}
+
+.ds-modal-spinner {
+ margin-top: 10px;
+ text-align: center;
+}
+
.ds-accordion-header {
margin-bottom: 15px;
margin-top: 0px;
@@ -1278,8 +1294,9 @@ option {
}
.ds-tree-content {
- margin: 33px;
- margin-top: 20px;
+ margin-left: 25px;
+ margin-top: 25px;
+ width: 100%;
}
.ds-select {
@@ -1361,6 +1378,10 @@ option {
margin-top: 10px;
}
+.ds-save-btn {
+ margin-top: 20px !important;
+}
+
.ds-spacing-sm {
margin-right: 10px;
}
@@ -1438,12 +1459,11 @@ option {
.ds-input-auto {
width: 100%;
margin-right: 10px;
+ padding-left: 5px;
}
.content-view-pf-pagination > div > span:last-child {
- margin-left: 8px;
position: relative;
- top: -8px;
}
tbody:nth-child(even) {
@@ -1459,6 +1479,10 @@ tbody:nth-child(even) {
margin-left: 5px;
}
+.ds-raise-button {
+ margin-top: -5px !important;
+}
+
.ds-plugin-spinner {
margin-top: 20px;
margin-bottom: 10px;
@@ -1508,3 +1532,93 @@ tbody:nth-child(even) {
.rbt-input-multi {
height: auto !important;
}
+
+.list-group {
+ margin-bottom: 1px !important;
+}
+
+.ds-tab-table {
+ margin-top: 25px !important;
+}
+
+.ds-nav-med {
+ font-size: 12px !important;
+}
+
+.ds-table-pagination {
+ background-color: transparent !important;
+ border: 1px solid #909090;
+}
+
+.ds-vlv-label {
+ width: 50px;
+}
+
+.list-view-pf-view {
+ margin-top: 10px;
+}
+
+.ds-fadein {
+ visibility: visible;
+ opacity: 1;
+ transition: opacity 0.5s linear;
+}
+
+.ds-fadeout {
+ visibility: hidden;
+ opacity: 0;
+ transition: visibility 0s 0s, opacity 1s linear;
+}
+
+.ds-col-append {
+ /* Move this item to the left to offset PF margins */
+ margin-left: -35px;
+ padding-right: 0px;
+ word-wrap: break-word;
+}
+
+.ds-word-wrap {
+ word-wrap: break-word;
+ padding-top: 3px;
+}
+
+.ds-suffix-header {
+ font-size: 16px;
+ margin-bottom: 15px;
+}
+
+.ds-no-padding () {
+ padding: 0 !imporant;
+}
+
+.alert {
+ max-width: 600px;
+}
+
+.ds-confirm {
+ margin-left: 5%;
+ margin-top: 5%;
+}
+
+.treeview .list-group-item {
+ /* remove focus border */
+ outline: none;
+}
+
+.treeitem-row {
+ display: block;
+ outline: none;
+}
+
+.treeview .list-group-item.node-selected > .treeitem-row {
+ background: #0088ce !important;
+ border-color: #0088ce !important;
+ color: #fff !important;
+ display: block;
+}
+
+.treeview-hover .treeitem-row:hover {
+ background-color: #def3ff;
+ border-color: #bee1f4;
+ display: block;
+}
diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx
new file mode 100644
index 0000000..ccc2c19
--- /dev/null
+++ b/src/cockpit/389-console/src/database.jsx
@@ -0,0 +1,1223 @@
+import cockpit from "cockpit";
+import React from "react";
+import { NotificationController } from "./lib/notifications.jsx";
+import { log_cmd } from "./lib/tools.jsx";
+import {
+ ChainingConfig,
+ ChainingDatabaseConfig
+} from "./lib/database/chaining.jsx";
+import { GlobalDatabaseConfig } from "./lib/database/databaseConfig.jsx";
+import { Suffix } from "./lib/database/suffix.jsx";
+import { Backups } from "./lib/database/backups.jsx";
+import {
+ Modal,
+ Icon,
+ Form,
+ Button,
+ noop,
+ TreeView,
+ Spinner
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import "./css/ds.css";
+
+const DB_CONFIG = "dbconfig";
+const CHAINING_CONFIG = "chaining-config";
+const BACKUP_CONFIG = "backups";
+const treeViewContainerStyles = {
+ width: '295px',
+};
+
+export class Database extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ notifications: [],
+ errObj: {},
+ nodes: [],
+ node_name: "",
+ node_text: "",
+ dbtype: "",
+ showSuffixModal: false,
+ createSuffix: "",
+ createBeName: "",
+ createRootNode: false,
+ // DB config
+ globalDBConfig: {},
+ configUpdated: 0,
+ // Chaining Config
+ chainingConfig: {},
+ chainingUpdated: 0,
+ // Chaining Link
+ chainingLoading: false,
+ // Suffix
+ suffixLoading: false,
+ attributes: [],
+ // Loaded suffix configurations
+ suffix: {},
+ // Other
+ LDIFRows: [],
+ BackupRows: [],
+ suffixList: [],
+ loaded: false,
+ };
+
+ // General
+ this.selectNode = this.selectNode.bind(this);
+ this.removeNotification = this.removeNotification.bind(this);
+ this.addNotification = this.addNotification.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.loadGlobalConfig = this.loadGlobalConfig.bind(this);
+ this.loadLDIFs = this.loadLDIFs.bind(this);
+ this.loadBackups = this.loadBackups.bind(this);
+ this.loadSuffixList = this.loadSuffixList.bind(this);
+
+ // Suffix
+ this.showSuffixModal = this.showSuffixModal.bind(this);
+ this.closeSuffixModal = this.closeSuffixModal.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.createSuffix = this.createSuffix.bind(this);
+ this.loadSuffix = this.loadSuffix.bind(this);
+ this.loadSuffixConfig = this.loadSuffixConfig.bind(this);
+ this.loadIndexes = this.loadIndexes.bind(this);
+ this.loadVLV = this.loadVLV.bind(this);
+ this.loadAttrEncrypt = this.loadAttrEncrypt.bind(this);
+ this.loadReferrals = this.loadReferrals.bind(this);
+ this.getAutoTuning = this.getAutoTuning.bind(this);
+ this.loadAttrs = this.loadAttrs.bind(this);
+
+ // ChainingConfig
+ this.loadChainingLink = this.loadChainingLink.bind(this);
+ this.loadChainingConfig = this.loadChainingConfig.bind(this);
+ this.loadAvailableControls = this.loadAvailableControls.bind(this);
+ this.loadDefaultConfig = this.loadDefaultConfig.bind(this);
+
+ // Other
+ this.loadSuffixTree = this.loadSuffixTree.bind(this);
+ }
+
+ componentWillMount () {
+ this.loadGlobalConfig();
+ this.loadChainingConfig();
+ this.loadLDIFs();
+ this.loadBackups();
+ this.loadSuffixList();
+ }
+
+ componentDidMount() {
+ this.loadSuffixTree(false);
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.serverId !== prevProps.serverId) {
+ this.loadSuffixTree(false);
+ }
+ }
+
+ loadSuffixList () {
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "suffix", "list", "--suffix"
+ ];
+ log_cmd("loadSuffixList", "Get a list of all the suffixes", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const suffixList = JSON.parse(content);
+ this.setState(() => (
+ {suffixList: suffixList.items}
+ ));
+ });
+ }
+
+ loadGlobalConfig () {
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "config", "get"
+ ];
+ log_cmd("loadGlobalConfig", "Load the database global configuration", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ const attrs = config.attrs;
+ let db_cache_auto = false;
+ let import_cache_auto = false;
+ let dbhome = "";
+
+ if ('nsslapd-db-home-directory' in attrs) {
+ dbhome = attrs['nsslapd-db-home-directory'];
+ }
+ if (attrs['nsslapd-cache-autosize'] != "0") {
+ db_cache_auto = true;
+ }
+ if (attrs['nsslapd-import-cache-autosize'] != "0") {
+ import_cache_auto = true;
+ }
+
+ this.setState(() => (
+ {
+ globalDBConfig:
+ {
+ loading: false,
+ db_cache_auto: db_cache_auto,
+ import_cache_auto: import_cache_auto,
+ looklimit: attrs['nsslapd-lookthroughlimit'],
+ idscanlimit: attrs['nsslapd-idlistscanlimit'],
+ pagelooklimit: attrs['nsslapd-pagedlookthroughlimit'],
+ pagescanlimit: attrs['nsslapd-pagedidlistscanlimit'],
+ rangelooklimit: attrs['nsslapd-rangelookthroughlimit'],
+ autosize: attrs['nsslapd-cache-autosize'],
+ autosizesplit: attrs['nsslapd-cache-autosize-split'],
+ dbcachesize: attrs['nsslapd-dbcachesize'],
+ txnlogdir: attrs['nsslapd-db-logdirectory'],
+ dbhomedir: dbhome,
+ dblocks: attrs['nsslapd-db-locks'],
+ chxpoint: attrs['nsslapd-db-checkpoint-interval'],
+ compactinterval: attrs['nsslapd-db-compactdb-interval'],
+ importcacheauto: attrs['nsslapd-import-cache-autosize'],
+ importcachesize: attrs['nsslapd-import-cachesize'],
+ },
+ loaded: true,
+ configUpdated: 1
+ }), this.setState({configUpdated: 0}));
+ })
+ .fail(err => {
+ this.props.addNotification(
+ "error",
+ `Error loading database configuration - ${err}`
+ );
+ });
+ }
+
+ loadAvailableControls () {
+ // Get the available control oids from rootdse
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "chaining", "config-get", "--avail-controls"
+ ];
+ log_cmd("loadChainingConfig", "Get available controls", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ const availableOids = config.items.filter((el) => !this.state.chainingConfig.oidList.includes(el));
+ this.setState((prevState) => (
+ {
+ chainingConfig: {
+ ...this.state.chainingConfig,
+ availableOids: availableOids,
+ },
+ chainingUpdated: 1
+ }
+ ), this.setState({chainingUpdated: 0})
+ );
+ });
+ }
+
+ loadDefaultConfig() {
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "chaining", "config-get-def"
+ ];
+ log_cmd("loadChainingConfig", "Load chaining default configuration", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ const attr = config.attrs;
+ let checkAci = false;
+ let proxy = false;
+ let refOnScope = false;
+ let useStartTLS = false;
+
+ if (attr['nschecklocalaci'] == "on") {
+ checkAci = true;
+ }
+ if (attr['nsproxiedauthorization'] == "on") {
+ proxy = true;
+ }
+ if (attr['nsreferralonscopedsearch'] == "on") {
+ refOnScope = true;
+ }
+ if (attr['nsusestarttls'] == "on") {
+ useStartTLS = true;
+ }
+ this.setState(() => (
+ {
+ chainingConfig: {
+ ...this.state.chainingConfig,
+ defSearchCheck: attr['nsabandonedsearchcheckinterval'],
+ defBindConnLimit: attr['nsbindconnectionslimit'],
+ defBindTimeout: attr['nsbindtimeout'],
+ defBindRetryLimit: attr['nsbindretrylimit'],
+ defConcurLimit: attr['nsconcurrentbindlimit'],
+ defConcurOpLimit: attr['nsconcurrentoperationslimit'],
+ defConnLife: attr['nsconnectionlife'],
+ defHopLimit: attr['nshoplimit'],
+ defDelay: attr['nsmaxresponsedelay'],
+ defTestDelay: attr['nsmaxtestresponsedelay'],
+ defOpConnLimit: attr['nsoperationconnectionslimit'],
+ defSizeLimit: attr['nsslapd-sizelimit'],
+ defTimeLimit: attr['nsslapd-timelimit'],
+ defProxy: proxy,
+ defRefOnScoped: refOnScope,
+ defCheckAci: checkAci,
+ defUseStartTLS: useStartTLS,
+ }
+ }
+ ), this.loadAvailableControls());
+ })
+ .fail(err => {
+ this.props.addNotification(
+ "error",
+ `Error loading default chaining configuration - ${err}`
+ );
+ this.setState({
+ loading: false
+ });
+ });
+ }
+
+ loadChainingConfig() {
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "chaining", "config-get"
+ ];
+ log_cmd("loadChainingConfig", "Load chaining OIDs and Controls", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let availableComps = config.attrs.nspossiblechainingcomponents;
+ let compList = [];
+ if ('nsactivechainingcomponents' in config.attrs) {
+ availableComps = config.attrs.nspossiblechainingcomponents.filter((el) => !config.attrs.nsactivechainingcomponents.includes(el));
+ compList = config.attrs.nsactivechainingcomponents;
+ }
+ this.setState(() => (
+ {
+ chainingConfig: {
+ ...this.state.chainingConfig,
+ oidList: config.attrs.nstransmittedcontrols,
+ compList: compList,
+ availableComps: availableComps
+ }
+ }
+ ), this.loadDefaultConfig());
+ });
+ }
+
+ loadSuffixTree(fullReset) {
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "get-tree",
+ ];
+ log_cmd("getTree", "Start building the suffix tree", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let treeData = JSON.parse(content);
+ let basicData = [
+ {
+ text: "Global Database Configuration",
+ selectable: true,
+ selected: true,
+ icon: "pficon-settings",
+ id: "dbconfig",
+
+ },
+ {
+ text: "Chaining Configuration",
+ icon: "glyphicon glyphicon-link",
+ selectable: true,
+ id: "chaining-config",
+ },
+ {
+ text: "Backups & LDIFS",
+ icon: "glyphicon glyphicon-duplicate",
+ selectable: true,
+ id: "backups",
+ },
+ {
+ "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;
+
+ this.setState(() => ({
+ nodes: basicData,
+ node_name: current_node,
+
+ }), this.update_tree_nodes);
+ });
+ }
+
+ loadChainingLink(suffix) {
+ this.setState({
+ chainingLoading: true,
+ });
+
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "chaining", "link-get", suffix
+ ];
+ log_cmd("loadChainingLink", "Load chaining link configuration", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ const attrs = config.attrs;
+ let bindmech = "Simple";
+ let usestarttls = false;
+ let refOnScope = false;
+ let proxiedAuth = false;
+ let checkLocalAci = false;
+
+ // Handler checkboxes, need to convert "on" to true
+ if (config.attrs.nsreferralonscopedsearch[0] == "on") {
+ refOnScope = true;
+ }
+ if (config.attrs.nsproxiedauthorization[0] == "on") {
+ proxiedAuth = true;
+ }
+ if (config.attrs.nschecklocalaci[0] == "on") {
+ checkLocalAci = true;
+ }
+ if (config.attrs.nsusestarttls[0] == "on") {
+ usestarttls = true;
+ }
+ if (config.attrs.nsbindmechanism !== undefined) {
+ bindmech = config.attrs.nsbindmechanism[0];
+ }
+
+ this.setState({
+ [suffix]: {
+ nsfarmserverurl: attrs.nsfarmserverurl[0],
+ nsmultiplexorbinddn: attrs.nsmultiplexorbinddn[0],
+ nsmultiplexorcredentials: attrs.nsmultiplexorcredentials[0],
+ nsmultiplexorcredentials_confirm: attrs.nsmultiplexorcredentials[0],
+ sizelimit: attrs['nsslapd-sizelimit'][0],
+ timelimit: attrs['nsslapd-timelimit'][0],
+ bindconnlimit: attrs.nsbindconnectionslimit[0],
+ opconnlimit: attrs.nsoperationconnectionslimit[0],
+ concurrbindlimit: attrs.nsconcurrentbindlimit[0],
+ bindtimeout: attrs.nsbindtimeout[0],
+ bindretrylimit: attrs.nsbindretrylimit[0],
+ concurroplimit: attrs.nsconcurrentoperationslimit[0],
+ connlifetime: attrs.nsconnectionlife[0],
+ searchcheckinterval: attrs.nsabandonedsearchcheckinterval[0],
+ hoplimit: attrs.nshoplimit[0],
+ nsbindmechanism: bindmech,
+ nsusestarttls: usestarttls,
+ nsreferralonscopedsearch: refOnScope,
+ nsproxiedauthorization: proxiedAuth,
+ nschecklocalaci: checkLocalAci,
+ },
+ chainingLoading: false,
+ });
+ })
+ .fail(err => {
+ this.addNotification(
+ "error",
+ `Error getting chaining link configuration - ${err}`
+ );
+ });
+ }
+
+ selectNode(selectedNode) {
+ if (selectedNode.id == "dbconfig" ||
+ selectedNode.id == "chaining-config" ||
+ selectedNode.id == "backups") {
+ // Nothing special to do, these configurations have 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 whatever it is...
+ if (selectedNode.type == "dblink") {
+ // Chained suffix
+ this.loadChainingLink(selectedNode.id);
+ } else {
+ // Suffix/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);
+ }
+ }
+
+ showSuffixModal () {
+ this.setState({
+ showSuffixModal: true,
+ errObj: {},
+ });
+ }
+
+ handleChange(e) {
+ const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj
+ });
+ }
+
+ closeSuffixModal() {
+ this.setState({
+ showSuffixModal: false
+ });
+ }
+
+ createSuffix() {
+ // validate
+ let errors = false;
+ let missingArgs = {
+ createSuffix: false,
+ createBeName: false
+ };
+
+ if (this.state.createSuffix == "") {
+ this.addNotification(
+ "warning",
+ `Missing the suffix DN`
+ );
+ missingArgs.createSuffix = true;
+ errors = true;
+ }
+ if (this.state.createBeName == "") {
+ this.addNotification(
+ "warning",
+ `Missing the suffix backend name`
+ );
+ missingArgs.createBeName = true;
+ errors = true;
+ }
+ if (errors) {
+ this.setState({
+ errObj: missingArgs
+ });
+ return;
+ }
+
+ // Create a new suffix
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "create", "--be-name", this.state.createBeName, '--suffix', this.state.createSuffix,
+ ];
+ if (this.state.createSampleEntries == true) {
+ cmd.push('--create-entries');
+ }
+
+ log_cmd("createSuffix", "Create a new backend", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.closeSuffixModal();
+ this.addNotification(
+ "success",
+ `Successfully create new suffix`
+ );
+ // Refresh tree
+ this.loadSuffixTree(false);
+ })
+ .fail(err => {
+ this.addNotification(
+ "error",
+ `Error creating suffix - ${err}`
+ );
+ this.closeSuffixModal();
+ });
+ }
+
+ getAutoTuning(suffix) {
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "config", "get"
+ ];
+ log_cmd("getAutoTuning", "Check cache auto tuning", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ if ('nsslapd-cache-autosize' in config.attrs &&
+ config.attrs['nsslapd-cache-autosize'] != "0") {
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ autoTuning: true,
+ },
+ });
+ }
+ });
+ }
+
+ loadSuffixConfig(suffix) {
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "suffix", "get", suffix
+ ];
+ log_cmd("loadSuffixConfig", "Load suffix config", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let refs = [];
+ let readonly = false;
+ let requireindex = false;
+ if ('nsslapd-referral' in config.attrs) {
+ refs = config.attrs['nsslapd-referral'];
+ }
+ if ('nsslapd-readonly' in config.attrs) {
+ if (config.attrs['nsslapd-readonly'] == "on") {
+ readonly = true;
+ }
+ }
+ if ('nsslapd-require-index' in config.attrs) {
+ if (config.attrs['nsslapd-require-index'] == "on") {
+ requireindex = true;
+ }
+ }
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ refRows: refs,
+ cachememsize: config.attrs['nsslapd-cachememsize'][0],
+ cachesize: config.attrs['nsslapd-cachesize'][0],
+ dncachememsize: config.attrs['nsslapd-dncachememsize'][0],
+ readOnly: readonly,
+ requireIndex: requireindex,
+ }
+ }, this.getAutoTuning(suffix));
+ });
+ }
+
+ loadVLV(suffix) {
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "vlv-index", "list", suffix
+ ];
+ log_cmd("loadVLV", "Load VLV indexes", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ vlvItems: config.items,
+ }
+ });
+ });
+ }
+
+ loadAttrEncrypt(suffix) {
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "attr-encrypt", "--list", "--just-names", suffix
+ ];
+ log_cmd("loadAttrEncrypt", "Load encrypted attrs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let rows = [];
+ for (let row of config.items) {
+ rows.push({"name": row});
+ }
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ encAttrsRows: rows
+ }
+ });
+ });
+ }
+
+ loadIndexes(suffix) {
+ // Load the suffix configurations. Start with indexes which is the
+ // most time consuming (and which controls the spinner), and spawn new
+ // commands for the other areas.
+ const index_cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "index", "list", suffix
+ ];
+ log_cmd("loadIndexes", "Load backend indexes", index_cmd);
+ cockpit
+ .spawn(index_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ // Now do the Indexes
+ const config = JSON.parse(content);
+ let rows = [];
+ let systemRows = [];
+ for (let item of config.items) {
+ let index = item.attrs;
+ let types = [];
+ let mrs = [];
+ if (index.nsindextype.length > 1) {
+ types = index.nsindextype.join(', ');
+ } else {
+ types = index.nsindextype[0];
+ }
+ if ('nsmatchingrule' in index) {
+ if (index.nsmatchingrule.length > 1) {
+ mrs = index.nsmatchingrule.join(', ');
+ } else {
+ mrs = index.nsmatchingrule[0];
+ }
+ }
+ if (index.nssystemindex[0] == 'true') {
+ systemRows.push({'name': index.cn, 'types': [types], 'matchingrules': [mrs]});
+ } else {
+ rows.push({'name': index.cn, 'types': [types], 'matchingrules': [mrs]});
+ }
+ }
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ indexRows: rows,
+ systemIndexRows: systemRows
+ }
+ });
+ })
+ .fail(err => {
+ this.props.addNotification(
+ "error",
+ `Error loading indexes for ${suffix} - ${err}`
+ );
+ });
+ }
+
+ loadReferrals(suffix) {
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "suffix", "get", suffix
+ ];
+ log_cmd("loadReferrals", "get referrals", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let refs = [];
+ if ('nsslapd-referral' in config.attrs) {
+ refs = config.attrs['nsslapd-referral'];
+ }
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ refRows: refs,
+ }
+ });
+ });
+ }
+
+ loadSuffix(suffix) {
+ // Load everything, we must nest cockpit promise so we can proper set
+ // the loading is finished
+ this.setState({
+ activeKey: 1,
+ suffixLoading: true
+ }, this.loadAttrs());
+
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "suffix", "get", suffix
+ ];
+ log_cmd("loadSuffixConfig", "Load suffix config", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let refs = [];
+ let readonly = false;
+ let requireindex = false;
+ if ('nsslapd-referral' in config.attrs) {
+ refs = config.attrs['nsslapd-referral'];
+ }
+ if ('nsslapd-readonly' in config.attrs) {
+ if (config.attrs['nsslapd-readonly'] == "on") {
+ readonly = true;
+ }
+ }
+ if ('nsslapd-require-index' in config.attrs) {
+ if (config.attrs['nsslapd-require-index'] == "on") {
+ requireindex = true;
+ }
+ }
+ this.setState({
+ [suffix]: {
+ refRows: refs,
+ cachememsize: config.attrs['nsslapd-cachememsize'][0],
+ cachesize: config.attrs['nsslapd-cachesize'][0],
+ dncachememsize: config.attrs['nsslapd-dncachememsize'][0],
+ readOnly: readonly,
+ requireIndex: requireindex,
+ }
+ }, this.getAutoTuning(suffix));
+
+ // Now load VLV indexes
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "vlv-index", "list", suffix
+ ];
+ log_cmd("loadVLV", "Load VLV indexes", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ vlvItems: config.items,
+ }
+ });
+
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "attr-encrypt", "--list", "--just-names", suffix
+ ];
+ log_cmd("loadAttrEncrypt", "Load encrypted attrs", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let rows = [];
+ for (let row of config.items) {
+ rows.push({"name": row});
+ }
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ encAttrsRows: rows
+ }
+ });
+ const index_cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "index", "list", suffix
+ ];
+ log_cmd("loadIndexes", "Load backend indexes", index_cmd);
+ cockpit
+ .spawn(index_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ // Now do the Indexes
+ const config = JSON.parse(content);
+ let rows = [];
+ let systemRows = [];
+ for (let item of config.items) {
+ let index = item.attrs;
+ let types = [];
+ let mrs = [];
+ if (index.nsindextype.length > 1) {
+ types = index.nsindextype.join(', ');
+ } else {
+ types = index.nsindextype[0];
+ }
+ if ('nsmatchingrule' in index) {
+ if (index.nsmatchingrule.length > 1) {
+ mrs = index.nsmatchingrule.join(', ');
+ } else {
+ mrs = index.nsmatchingrule[0];
+ }
+ }
+ if (index.nssystemindex[0] == 'true') {
+ systemRows.push({'name': index.cn, 'types': [types], 'matchingrules': [mrs]});
+ } else {
+ rows.push({'name': index.cn, 'types': [types], 'matchingrules': [mrs]});
+ }
+ }
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ indexRows: rows,
+ systemIndexRows: systemRows,
+ },
+ suffixLoading: false
+ });
+ })
+ .fail(err => {
+ this.props.addNotification(
+ "error",
+ `Error loading indexes for ${suffix} - ${err}`
+ );
+ this.setState({
+ suffixLoading: false
+ });
+ });
+ });
+ });
+ });
+ }
+
+ loadLDIFs() {
+ const cmd = [
+ "dsctl", "-j", this.props.serverId, "ldifs"
+ ];
+ log_cmd("loadLDIFs", "Load LDIF Files", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let rows = [];
+ for (let row of config.items) {
+ rows.push({'name': row[0], 'date': [row[1]], 'size': [row[2]], 'suffix': [row[3]]});
+ }
+ this.setState({
+ LDIFRows: rows,
+ });
+ });
+ }
+
+ loadBackups() {
+ const cmd = [
+ "dsctl", "-j", this.props.serverId, "backups"
+ ];
+ log_cmd("loadLDIFs", "Load Backups", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let rows = [];
+ for (let row of config.items) {
+ rows.push({'name': row[0], 'date': [row[1]], 'size': [row[2]]});
+ }
+ this.setState({
+ BackupRows: rows
+ }, this.loadLDIFs());
+ });
+ }
+
+ loadAttrs() {
+ // Now get the schema that various tabs use
+ const attr_cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema", "attributetypes", "list"
+ ];
+ log_cmd("Suffixes", "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);
+ }
+ this.setState({
+ attributes: attrs,
+ });
+ })
+ .fail(err => {
+ this.props.addNotification(
+ "error",
+ `Failed to get attributes - ${err}`
+ );
+ });
+ }
+
+ render() {
+ const { nodes } = this.state;
+ let db_element = "";
+ if (this.state.loaded) {
+ if (this.state.node_name == DB_CONFIG || this.state.node_name == "") {
+ db_element =
+ <GlobalDatabaseConfig
+ serverId={this.props.serverId}
+ addNotification={this.addNotification}
+ reload={this.loadGlobalConfig}
+ data={this.state.globalDBConfig}
+ key={this.state.configUpdated}
+ />;
+ } else if (this.state.node_name == CHAINING_CONFIG) {
+ db_element =
+ <ChainingDatabaseConfig
+ serverId={this.props.serverId}
+ addNotification={this.addNotification}
+ reload={this.loadChainingConfig}
+ data={this.state.chainingConfig}
+ key={this.state.chainingUpdated}
+ />;
+ } else if (this.state.node_name == BACKUP_CONFIG) {
+ db_element =
+ <Backups
+ serverId={this.props.serverId}
+ addNotification={this.addNotification}
+ backups={this.state.BackupRows}
+ suffixes={this.state.suffixList}
+ ldifs={this.state.LDIFRows}
+ reload={this.loadBackups}
+ />;
+ } else if (this.state.node_name != "") {
+ // We have a suffix, or database link
+ if (this.state.dbtype == "suffix" || this.state.dbtype == "subsuffix") {
+ if (this.state.suffixLoading) {
+ db_element =
+ <div className="ds-loading-spinner ds-center">
+ <p />
+ <h4>Loading suffix configuration for <b>{this.state.node_text} ...</b></h4>
+ <Spinner loading size="md" />
+ </div>;
+ } else {
+ db_element =
+ <Suffix
+ serverId={this.props.serverId}
+ suffix={this.state.node_text}
+ bename={this.state.bename}
+ loadSuffixTree={this.loadSuffixTree}
+ reload={this.loadSuffix}
+ reloadRefs={this.loadReferrals}
+ reloadIndexes={this.loadIndexes}
+ reloadVLV={this.loadVLV}
+ reloadAttrEnc={this.loadAttrEncrypt}
+ addNotification={this.addNotification}
+ reloadLDIFs={this.loadLDIFs}
+ LDIFRows={this.state.LDIFRows}
+ dbtype={this.state.dbtype}
+ data={this.state[this.state.node_text]}
+ attrs={this.state.attributes}
+ key={this.state.node_text}
+ />;
+ }
+ } else {
+ // Chaining
+ if (this.state.chainingLoading) {
+ db_element =
+ <div className="ds-loading-spinner ds-center">
+ <p />
+ <h4>Loading chaining configuration for <b>{this.state.node_text} ...</b></h4>
+ <Spinner loading size="md" />
+ </div>;
+ } else {
+ db_element =
+ <ChainingConfig
+ serverId={this.props.serverId}
+ suffix={this.state.node_text}
+ bename={this.state.bename}
+ loadSuffixTree={this.loadSuffixTree}
+ addNotification={this.addNotification}
+ data={this.state[this.state.node_text]}
+ reload={this.loadChainingLink}
+ />;
+ }
+ }
+ }
+ }
+
+ return (
+ <div className="container-fluid">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ <div className="ds-container">
+ <div>
+ <div className="ds-tree">
+ <div className="tree-view-container" id="db-tree"
+ style={treeViewContainerStyles}>
+ <TreeView
+ nodes={nodes}
+ highlightOnHover
+ highlightOnSelect
+ selectNode={this.selectNode}
+ />
+ </div>
+ </div>
+ <div>
+ <button className="btn btn-primary save-button"
+ onClick={this.showSuffixModal}>Create Suffix</button>
+ </div>
+ </div>
+ <div className="ds-tree-content">
+ {db_element}
+ </div>
+ </div>
+ <CreateSuffixModal
+ showModal={this.state.showSuffixModal}
+ closeHandler={this.closeSuffixModal}
+ handleChange={this.handleChange}
+ saveHandler={this.createSuffix}
+ error={this.state.errObj}
+ />
+ </div>
+ );
+ }
+}
+
+class CreateSuffixModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ error
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Create New Suffix
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <div className="ds-inline">
+ <div>
+ <label htmlFor="createSuffix" className="ds-config-label" title="Database Suffix DN (nsslapd-suffix)">
+ Suffix DN</label><input onChange={handleChange} className={error.createSuffix ? "ds-input-bad" : "ds-input"} type="text" id="createSuffix" size="40" />
+ </div>
+ <div>
+ <label htmlFor="createBeName" className="ds-config-label" title="Database backend name (nsslapd-backend)">
+ Backend Name</label><input onChange={handleChange} className={error.createBeName ? "ds-input-bad" : "ds-input"} type="text" id="createBeName" size="40" />
+ </div>
+ <div>
+ <p />
+ <input type="checkbox" className="ds-config-checkbox" id="createSampleEntries" onChange={handleChange} /><label
+ htmlFor="createSampleEntries" className="ds-label" title="Create the datbase with sample entries"> Create Sample Entries</label>
+ </div>
+ </div>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Create Suffix
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+// Property types and defaults
+
+Database.propTypes = {
+ serverId: PropTypes.string
+};
+
+Database.defaultProps = {
+ serverId: ""
+};
+
+CreateSuffixModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ error: PropTypes.object,
+};
+
+CreateSuffixModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ error: {},
+};
diff --git a/src/cockpit/389-console/src/ds.js b/src/cockpit/389-console/src/ds.js
index c735a86..d309845 100644
--- a/src/cockpit/389-console/src/ds.js
+++ b/src/cockpit/389-console/src/ds.js
@@ -9,7 +9,7 @@ var dn_regex = new RegExp( "^([A-Za-z]+=.*)" );
*/
var server_page_loaded = 0;
var security_page_loaded = 0;
-var db_page_loaded = 0;
+var db_page_loaded = 1;
var repl_page_loaded = 0;
var plugin_page_loaded = 1;
var schema_page_loaded = 0;
@@ -433,4 +433,8 @@ $(window.document).ready(function() {
$(".all-pages").hide();
$("#plugin-content").show();
});
+ $("#database-tab").on("click", function() {
+ $(".all-pages").hide();
+ $("#database-content").show();
+ });
});
diff --git a/src/cockpit/389-console/src/index.es6 b/src/cockpit/389-console/src/index.es6
index bc74f51..7dfd433 100644
--- a/src/cockpit/389-console/src/index.es6
+++ b/src/cockpit/389-console/src/index.es6
@@ -1,6 +1,7 @@
import React from "react";
import ReactDOM from "react-dom";
import { Plugins } from "./plugins.jsx";
+import { Database } from "./database.jsx";
var serverIdElem;
@@ -13,10 +14,18 @@ function renderReactDOM(clear) {
.getElementById("select-server")
.value.replace("slapd-", "");
}
+
+ // Plugins Tab
ReactDOM.render(
<Plugins serverId={serverIdElem} />,
document.getElementById("plugins")
);
+
+ // Database tab
+ ReactDOM.render(
+ <Database serverId={serverIdElem} />,
+ document.getElementById("database")
+ );
}
// We have to create the wrappers because this is no simple way
diff --git a/src/cockpit/389-console/src/index.html b/src/cockpit/389-console/src/index.html
index b23a722..5015717 100644
--- a/src/cockpit/389-console/src/index.html
+++ b/src/cockpit/389-console/src/index.html
@@ -22,7 +22,6 @@
<script src="schema.js"></script>
<script src="servers.js"></script>
<script src="security.js"></script>
- <script src="backend.js"></script>
<script src="replication.js"></script>
<script src="monitor.js"></script>
<link href="static/bootstrap.min.css" rel="stylesheet">
@@ -101,16 +100,10 @@
</li>
<!-- Database navtab -->
- <li class="dropdown ds-nav-tab">
- <a href="#0" class="dropdown-toggle ds-tab-list" data-toggle="dropdown" id="database-tab">
+ <li class="ds-nav-tab">
+ <a href="#0" class="ds-tab-list ds-tab-standalone" id="database-tab">
Database
- <b class="caret"></b>
</a>
- <ul class="dropdown-menu dropup ds-nav-item">
- <li><a href="#0" class="ds-nav-choice" id="db-chaining-btn" parent-id="database-tab">Chaining Settings</a></li>
- <li><a href="#0" class="ds-nav-choice" id="db-global-btn" parent-id="database-tab">Global Database Settings</a></li>
- <li><a href="#0" class="ds-nav-choice" id="db-suffix-btn" parent-id="database-tab">Database Management</a></li>
- </ul>
</li>
<!-- Replication navtab -->
@@ -527,6 +520,7 @@
</div>
<div id="database-content" class="all-pages" hidden>
+ <div id="database"></div>
</div>
<div id="replication-content" class="all-pages" hidden>
diff --git a/src/cockpit/389-console/src/lib/database/attrEncryption.jsx b/src/cockpit/389-console/src/lib/database/attrEncryption.jsx
new file mode 100644
index 0000000..6965d72
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/attrEncryption.jsx
@@ -0,0 +1,184 @@
+import cockpit from "cockpit";
+import React from "react";
+import { ConfirmPopup } from "../notifications.jsx";
+import { EncryptedAttrTable } from "./databaseTables.jsx";
+import {
+ Row,
+ Col,
+ Button,
+ noop
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import { Typeahead } from "react-bootstrap-typeahead";
+import { log_cmd } from "../tools.jsx";
+import "../../css/ds.css";
+
+export class AttrEncryption extends React.Component {
+ constructor (props) {
+ super(props);
+
+ this.state = {
+ showConfirmAttrDelete: false,
+ typeahead: "",
+ addAttr: "",
+ delAttr: "",
+ };
+
+ // Delete referral and confirmation
+ this.showConfirmAttrDelete = this.showConfirmAttrDelete.bind(this);
+ this.closeConfirmAttrDelete = this.closeConfirmAttrDelete.bind(this);
+ this.handleTypeaheadChange = this.handleTypeaheadChange.bind(this);
+ this.addEncryptedAttr = this.addEncryptedAttr.bind(this);
+ this.delEncryptedAttr = this.delEncryptedAttr.bind(this);
+ }
+
+ showConfirmAttrDelete (item) {
+ this.setState({
+ showConfirmAttrDelete: true,
+ delAttr: item.name
+ });
+ }
+
+ closeConfirmAttrDelete() {
+ this.setState({
+ showConfirmAttrDelete: false
+ });
+ }
+
+ handleTypeaheadChange (value) {
+ this.setState({
+ addAttr: value
+ });
+ }
+
+ addEncryptedAttr () {
+ // reset typeahead input field
+ this.typeahead.getInstance().clear();
+ if (this.state.addAttr == "") {
+ return;
+ }
+
+ // Add the new encrypted attr
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "attr-encrypt", "--add-attr=" + this.state.addAttr, this.props.suffix
+ ];
+ log_cmd("addEncryptedAttr", "Delete suffix referral", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ `Successfully added encrypted attribute`
+ );
+ })
+ .fail(err => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Failed to delete encrypted attribute - ${err}`
+ );
+ });
+ }
+
+ delEncryptedAttr() {
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "attr-encrypt", "--del-attr=" + this.state.delAttr, this.props.suffix
+ ];
+ log_cmd("delEncryptedAttr", "Delete encrypted attribute", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ `Encrypted attribute successfully deleted`
+ );
+ })
+ .fail(err => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Failure deleting encrypted attribute - ${err}`
+ );
+ });
+ }
+
+ render() {
+ const delAttr = <b>{this.state.delAttr}</b>;
+
+ // Update the available list of attrs for the Typeahead
+ let fullList = [];
+ let attrs = [];
+ for (let attrProp of this.props.rows) {
+ fullList.push(attrProp.name);
+ }
+ for (let attr of this.props.attrs) {
+ if (fullList.indexOf(attr) == -1) {
+ attrs.push(attr);
+ }
+ }
+
+ return (
+ <div>
+ <EncryptedAttrTable
+ rows={this.props.rows}
+ loadModalHandler={this.showConfirmAttrDelete}
+ />
+ <p />
+ <Row>
+ <Col sm={6}>
+ <Typeahead
+ id="attrEncrypt"
+ onChange={value => {
+ this.handleTypeaheadChange(value);
+ }}
+ maxResults={1000}
+ options={attrs}
+ placeholder="Type attribute name to be encrypted"
+ ref={(typeahead) => { this.typeahead = typeahead }}
+ />
+ </Col>
+ <Col sm={3} bsClass="ds-no-padding">
+ <Button
+ bsStyle="primary"
+ onClick={this.addEncryptedAttr}
+ >
+ Add Attribute
+ </Button>
+ </Col>
+ </Row>
+ <ConfirmPopup
+ showModal={this.state.showConfirmAttrDelete}
+ closeHandler={this.closeConfirmAttrDelete}
+ actionFunc={this.delEncryptedAttr}
+ actionParam={this.state.attrName}
+ msg="Are you sure you want to remove this encrypted attribute?"
+ msgContent={delAttr}
+ />
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+AttrEncryption.propTypes = {
+ rows: PropTypes.array,
+ suffix: PropTypes.string,
+ serverId: PropTypes.string,
+ addNotification: PropTypes.func,
+ attrs: PropTypes.array,
+ reload: PropTypes.func,
+};
+
+AttrEncryption.defaultProps = {
+ rows: [],
+ suffix: "",
+ serverId: "",
+ addNotification: noop,
+ attrs: [],
+ reload: noop,
+};
diff --git a/src/cockpit/389-console/src/lib/database/backups.jsx b/src/cockpit/389-console/src/lib/database/backups.jsx
new file mode 100644
index 0000000..646fb95
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/backups.jsx
@@ -0,0 +1,979 @@
+import cockpit from "cockpit";
+import React from "react";
+import { ConfirmPopup } from "../notifications.jsx";
+import { LDIFManageTable, BackupTable } from "./databaseTables.jsx";
+import {
+ Nav,
+ NavItem,
+ TabContent,
+ TabPane,
+ TabContainer,
+ Col,
+ Button,
+ Spinner,
+ Modal,
+ Icon,
+ ControlLabel,
+ FormControl,
+ Form,
+ Row,
+ noop
+} from "patternfly-react";
+import { log_cmd } from "../tools.jsx";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+export class Backups extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ activeKey: 1,
+ showConfirmBackupDelete: false,
+ showConfirmBackup: false,
+ showConfirmRestore: false,
+ showRestoreSpinningModal: false,
+ showDelBackupSpinningModal: false,
+ showBackupModal: false,
+ backupSpinning: false,
+ backupName: "",
+ deleteBackup: "",
+ // LDIF
+ showConfirmLDIFDelete: false,
+ showConfirmLDIFImport: false,
+ showLDIFSpinningModal: false,
+ showLDIFDeleteSpinningModal: false,
+ showExportModal: false,
+ exportSpinner: false,
+ ldifName: "",
+ ldifSuffix: "",
+ errObj: {}
+ };
+
+ this.handleNavSelect = this.handleNavSelect.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+
+ // Backups
+ this.doBackup = this.doBackup.bind(this);
+ this.deleteBackup = this.deleteBackup.bind(this);
+ this.restoreBackup = this.restoreBackup.bind(this);
+ this.showConfirmRestore = this.showConfirmRestore.bind(this);
+ this.closeConfirmRestore = this.closeConfirmRestore.bind(this);
+ this.showConfirmBackup = this.showConfirmBackup.bind(this);
+ this.closeConfirmBackup = this.closeConfirmBackup.bind(this);
+ this.showConfirmBackupDelete = this.showConfirmBackupDelete.bind(this);
+ this.closeConfirmBackupDelete = this.closeConfirmBackupDelete.bind(this);
+ this.showBackupModal = this.showBackupModal.bind(this);
+ this.closeBackupModal = this.closeBackupModal.bind(this);
+ this.showRestoreSpinningModal = this.showRestoreSpinningModal.bind(this);
+ this.closeRestoreSpinningModal = this.closeRestoreSpinningModal.bind(this);
+ this.showDelBackupSpinningModal = this.showDelBackupSpinningModal.bind(this);
+ this.closeDelBackupSpinningModal = this.closeDelBackupSpinningModal.bind(this);
+ // LDIFS
+ this.importLDIF = this.importLDIF.bind(this);
+ this.deleteLDIF = this.deleteLDIF.bind(this);
+ this.showConfirmLDIFImport = this.showConfirmLDIFImport.bind(this);
+ this.closeConfirmLDIFImport = this.closeConfirmLDIFImport.bind(this);
+ this.showConfirmLDIFDelete = this.showConfirmLDIFDelete.bind(this);
+ this.closeConfirmLDIFDelete = this.closeConfirmLDIFDelete.bind(this);
+ this.showLDIFSpinningModal = this.showLDIFSpinningModal.bind(this);
+ this.showLDIFDeleteSpinningModal = this.showLDIFDeleteSpinningModal.bind(this);
+ this.doExport = this.doExport.bind(this);
+ this.showExportModal = this.showExportModal.bind(this);
+ this.closeExportModal = this.closeExportModal.bind(this);
+ }
+
+ showExportModal () {
+ this.setState({
+ showExportModal: true,
+ exportSpinner: false,
+ ldifSuffix: this.props.suffixes[0]
+ });
+ }
+
+ closeExportModal () {
+ this.setState({
+ showExportModal: false
+ });
+ }
+
+ showLDIFSpinningModal () {
+ this.setState({
+ showLDIFSpinningModal: true
+ });
+ }
+
+ closeLDIFSpinningModal () {
+ this.setState({
+ showLDIFSpinningModal: false
+ });
+ }
+
+ showLDIFDeleteSpinningModal () {
+ this.setState({
+ showLDIFDeleteSpinningModal: true
+ });
+ }
+
+ closeLDIFDeleteSpinningModal () {
+ this.setState({
+ showLDIFDeleteSpinningModal: false
+ });
+ }
+
+ showDelBackupSpinningModal () {
+ this.setState({
+ showDelBackupSpinningModal: true
+ });
+ }
+
+ closeDelBackupSpinningModal () {
+ this.setState({
+ showDelBackupSpinningModal: false
+ });
+ }
+
+ showRestoreSpinningModal () {
+ this.setState({
+ showRestoreSpinningModal: true
+ });
+ }
+
+ closeRestoreSpinningModal () {
+ this.setState({
+ showRestoreSpinningModal: false
+ });
+ }
+
+ showBackupModal () {
+ this.setState({
+ showBackupModal: true,
+ backupSpinning: false
+ });
+ }
+
+ closeBackupModal () {
+ this.setState({
+ showBackupModal: false,
+ });
+ }
+
+ showConfirmLDIFImport (item) {
+ this.setState({
+ showConfirmLDIFImport: true,
+ ldifName: item.name,
+ ldifSuffix: item.suffix[0]
+ });
+ }
+
+ closeConfirmLDIFImport () {
+ // call importLDIF
+ this.setState({
+ showConfirmLDIFImport: false,
+ });
+ }
+
+ showConfirmLDIFDelete (item) {
+ // call deleteLDIF
+ this.setState({
+ showConfirmLDIFDelete: true,
+ ldifName: item.name
+ });
+ }
+
+ closeConfirmLDIFDelete () {
+ // call importLDIF
+ this.setState({
+ showConfirmLDIFDelete: false,
+ });
+ }
+
+ showConfirmBackup (item) {
+ // call deleteLDIF
+ this.setState({
+ showConfirmBackup: true,
+ backupName: item.name,
+ });
+ }
+
+ closeConfirmBackup () {
+ // call importLDIF
+ this.setState({
+ showConfirmBackup: false,
+ });
+ }
+
+ showConfirmRestore (item) {
+ this.setState({
+ showConfirmRestore: true,
+ backupName: item.name,
+ });
+ }
+
+ closeConfirmRestore () {
+ // call importLDIF
+ this.setState({
+ showConfirmRestore: false,
+ });
+ }
+
+ showConfirmBackupDelete (item) {
+ // calls deleteBackup
+ this.setState({
+ showConfirmBackupDelete: true,
+ backupName: item.name,
+ });
+ }
+
+ closeConfirmBackupDelete () {
+ // call importLDIF
+ this.setState({
+ showConfirmBackupDelete: false,
+ });
+ }
+
+ importLDIF() {
+ this.showLDIFSpinningModal();
+
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "import", this.state.ldifSuffix, this.state.ldifName, "--encrypted"
+ ];
+ log_cmd("importLDIF", "Importing LDIF", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.closeLDIFSpinningModal();
+ this.props.addNotification(
+ "success",
+ `LDIF was successfully imported`
+ );
+ })
+ .fail(err => {
+ this.closeLDIFSpinningModal();
+ this.props.addNotification(
+ "error",
+ `Failure importing LDIF - ${err}`
+ );
+ });
+ }
+
+ deleteLDIF (e) {
+ this.showLDIFDeleteSpinningModal();
+
+ const cmd = [
+ "dsctl", this.props.serverId, "ldifs", "--delete", this.state.ldifName
+ ];
+ log_cmd("deleteLDIF", "Deleting LDIF", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload();
+ this.closeLDIFDeleteSpinningModal();
+ this.props.addNotification(
+ "success",
+ `LDIF file was successfully deleted`
+ );
+ })
+ .fail(err => {
+ this.props.reload();
+ this.closeLDIFDeleteSpinningModal();
+ this.props.addNotification(
+ "error",
+ `Failure deleting LDIF file - ${err}`
+ );
+ });
+ }
+
+ doBackup () {
+ this.setState({
+ backupSpinning: true
+ });
+
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backup", "create", this.state.backupName
+ ];
+ log_cmd("doBackup", "Add backup task", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload();
+ this.closeBackupModal();
+ this.props.addNotification(
+ "success",
+ `Server has been backed up`
+ );
+ })
+ .fail(err => {
+ this.props.reload();
+ this.closeBackupModal();
+ this.props.addNotification(
+ "error",
+ `Failure backing up server - ${err}`
+ );
+ });
+ }
+
+ restoreBackup () {
+ this.showRestoreSpinningModal();
+
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backup", "restore", this.state.backupName
+ ];
+ log_cmd("restoreBackup", "Restoring server", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.closeRestoreSpinningModal();
+ this.props.addNotification(
+ "success",
+ `Server has been restored`
+ );
+ })
+ .fail(err => {
+ this.closeRestoreSpinningModal();
+ this.props.addNotification(
+ "error",
+ `Failure restoring up server - ${err}`
+ );
+ });
+ }
+
+ deleteBackup (e) {
+ // Show confirmation
+ this.showDelBackupSpinningModal();
+
+ const cmd = [
+ "dsctl", this.props.serverId, "backups", "--delete", this.state.backupName
+ ];
+ log_cmd("deleteBackup", "Deleting backup", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload();
+ this.closeDelBackupSpinningModal();
+ this.props.addNotification(
+ "success",
+ `Backup was successfully deleted`
+ );
+ })
+ .fail(err => {
+ this.props.reload();
+ this.closeDelBackupSpinningModal();
+ this.props.addNotification(
+ "error",
+ `Failure deleting backup - ${err}`
+ );
+ });
+ }
+
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ handleChange(e) {
+ const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj
+ });
+ }
+
+ doExport() {
+ let missingArgs = {ldifLocation: false};
+ if (this.state.ldifLocation == "") {
+ this.props.addNotification(
+ "warning",
+ `LDIF name is empty`
+ );
+ missingArgs.ldifLocation = true;
+ this.setState({
+ errObj: missingArgs
+ });
+ return;
+ }
+
+ // Do import
+ let export_cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "export", this.state.ldifSuffix, "--encrypted", "--ldif=" + this.state.ldifName
+ ];
+
+ this.setState({
+ exportSpinner: true,
+ });
+
+ log_cmd("doExport", "Do online export", export_cmd);
+ cockpit
+ .spawn(export_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload();
+ this.closeExportModal();
+ this.props.addNotification(
+ "success",
+ `Database export complete`
+ );
+ this.setState({
+ showExportModal: false,
+ });
+ })
+ .fail(err => {
+ this.props.reload();
+ this.closeExportModal();
+ this.props.addNotification(
+ "error",
+ `Error exporting database - ${err}`
+ );
+ this.setState({
+ showExportModal: false,
+ });
+ });
+ }
+
+ render() {
+ return (
+ <div>
+ <TabContainer id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <div>
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem eventKey={1}>
+ <div dangerouslySetInnerHTML={{__html: 'Backups'}} />
+ </NavItem>
+ <NavItem eventKey={2}>
+ <div dangerouslySetInnerHTML={{__html: 'LDIFs'}} />
+ </NavItem>
+ </Nav>
+ <TabContent>
+ <TabPane eventKey={1}>
+ <div className="ds-margin-top-xlg">
+ <BackupTable
+ rows={this.props.backups}
+ confirmRestore={this.showConfirmRestore}
+ confirmDelete={this.showConfirmBackupDelete}
+ />
+ </div>
+ <p />
+ <Button
+ bsStyle="primary"
+ onClick={this.showBackupModal}
+ >
+ Create Backup
+ </Button>
+ </TabPane>
+
+ <TabPane eventKey={2}>
+ <div className="ds-margin-top-xlg">
+ <LDIFManageTable
+ rows={this.props.ldifs}
+ confirmImport={this.showConfirmLDIFImport}
+ confirmDelete={this.showConfirmLDIFDelete}
+ />
+ </div>
+ <p />
+ <Button
+ bsStyle="primary"
+ onClick={this.showExportModal}
+ >
+ Create LDIF Export
+ </Button>
+ </TabPane>
+ </TabContent>
+ </div>
+ </TabContainer>
+
+ <ExportModal
+ showModal={this.state.showExportModal}
+ closeHandler={this.closeExportModal}
+ handleChange={this.handleChange}
+ saveHandler={this.doExport}
+ spinning={this.state.exportSpinner}
+ error={this.state.errObj}
+ suffixes={this.props.suffixes}
+ />
+ <BackupModal
+ showModal={this.state.showBackupModal}
+ closeHandler={this.closeBackupModal}
+ handleChange={this.handleChange}
+ saveHandler={this.doBackup}
+ spinning={this.state.backupSpinning}
+ error={this.state.errObj}
+ />
+ <RestoreModal
+ showModal={this.state.showRestoreSpinningModal}
+ closeHandler={this.closeRestoreSpinningModal}
+ msg={this.state.backupName}
+ />
+ <DeleteBackupModal
+ showModal={this.state.showDelBackupSpinningModal}
+ closeHandler={this.closeDelBackupSpinningModal}
+ msg={this.state.backupName}
+ />
+ <ImportingModal
+ showModal={this.state.showLDIFSpinningModal}
+ closeHandler={this.closeLDIFSpinningModal}
+ msg={this.state.ldifName}
+ />
+ <DeletingLDIFModal
+ showModal={this.state.showLDIFDeleteSpinningModal}
+ closeHandler={this.closeLDIFDeleteSpinningModal}
+ msg={this.state.ldifName}
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmLDIFDelete}
+ closeHandler={this.closeConfirmLDIFDelete}
+ actionFunc={this.deleteLDIF}
+ actionParam={this.state.ldifName}
+ msg="Are you sure you want to delete this LDIF?"
+ msgContent={this.state.ldifName}
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmLDIFImport}
+ closeHandler={this.closeConfirmLDIFImport}
+ actionFunc={this.importLDIF}
+ actionParam={this.state.ldifName}
+ msg="Are you sure you want to import this LDIF?"
+ msgContent={this.state.ldifName}
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmRestore}
+ closeHandler={this.closeConfirmRestore}
+ actionFunc={this.restoreBackup}
+ actionParam={this.state.backupName}
+ msg="Are you sure you want to restore this backup?"
+ msgContent={this.state.backupName}
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmBackupDelete}
+ closeHandler={this.closeConfirmBackupDelete}
+ actionFunc={this.deleteBackup}
+ actionParam={this.state.backupName}
+ msg="Are you sure you want to delete this backup?"
+ msgContent={this.state.backupName}
+ />
+
+ </div>
+ );
+ }
+}
+
+class ExportModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ suffixes,
+ spinning,
+ error
+ } = this.props;
+ let spinner = "";
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-modal-spinner">
+ <Spinner loading inline size="lg" />Exporting database... <font size="1">(You can safely close this window)</font>
+ </div>
+ </Row>;
+ }
+ let suffixList = suffixes.map((suffix) =>
+ <option key={suffix} value={suffix}>{suffix}</option>
+ );
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Export Database To LDIF File
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <Row>
+ <Col sm={3}>
+ <ControlLabel>Select Suffix</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <select id="ldifSuffix" onChange={handleChange}>
+ {suffixList}
+ </select>
+ </Col>
+ </Row>
+ <p />
+ <Row title="Name of exported LDIF file">
+ <Col sm={3}>
+ <ControlLabel>LDIF File Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ id="ldifName"
+ className={error.ldifName ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ <p />
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Create LDIF
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+export class BackupModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ spinning,
+ error
+ } = this.props;
+ let spinner = "";
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-modal-spinner">
+ <Spinner loading inline size="lg" />Backing up databases... <font size="1">(You can safely close this window)</font>
+ </div>
+ </Row>;
+ }
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Backup The Server
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <Row title="LDIF file to import">
+ <Col sm={3}>
+ <ControlLabel>Backup Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ id="backupName"
+ className={error.backupName ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ <p />
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Do Backup
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class RestoreModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ msg
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Restoring Server From Backup
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <div className="ds-modal-spinner">
+ <Spinner loading inline size="lg" /> Restoring backup <b>{msg}</b> ...
+ <p />
+ <p><font size="1"> (You can safely close this window)</font></p>
+ </div>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Close
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class DeleteBackupModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ msg
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Delete Backup
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <div className="ds-modal-spinner">
+ <Spinner loading inline size="lg" /> Deleting backup <b>{msg}</b> ...
+ <p />
+ <p><font size="1"> (You can safely close this window)</font></p>
+ </div>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Close
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class ImportingModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ msg
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Import LDIF File
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <div className="ds-modal-spinner">
+ <Spinner loading inline size="lg" /> Importing LDIF <b>{msg}</b> ...
+ <p />
+ <p><font size="1"> (You can safely close this window)</font></p>
+ </div>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Close
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class DeletingLDIFModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ msg
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Delete LDIF File
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <div className="ds-modal-spinner">
+ <Spinner loading inline size="lg" /> Deleting LDIF file <b>{msg}</b> ...
+ <p />
+ <p><font size="1"> (You can safely close this window)</font></p>
+ </div>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Close
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+// Properties and defaults
+
+ExportModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ error: PropTypes.object,
+ suffixes: PropTypes.array
+};
+
+BackupModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ error: PropTypes.object,
+};
+
+DeletingLDIFModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ msg: PropTypes.string
+};
+
+RestoreModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ msg: PropTypes.string
+};
+
+DeleteBackupModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ msg: PropTypes.string
+};
+
+ImportingModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ msg: PropTypes.string
+};
+
+Backups.propTypes = {
+ backups: PropTypes.array,
+ ldifs: PropTypes.array,
+ reload: PropTypes.func
+};
+
+Backups.defaultProps = {
+ backups: [],
+ ldifs: [],
+ reload: noop
+};
diff --git a/src/cockpit/389-console/src/lib/database/chaining.jsx b/src/cockpit/389-console/src/lib/database/chaining.jsx
new file mode 100644
index 0000000..cf507b3
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/chaining.jsx
@@ -0,0 +1,1311 @@
+import cockpit from "cockpit";
+import React from "react";
+import { ConfirmPopup } from "../notifications.jsx";
+import CustomCollapse from "../customCollapse.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ Modal,
+ Icon,
+ Button,
+ Form,
+ Row,
+ Col,
+ ControlLabel,
+ Checkbox,
+ FormControl,
+ noop
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+//
+// This component is the global chaining & default configuration
+//
+export class ChainingDatabaseConfig extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ oids: this.props.data.oids,
+ oidList: this.props.data.oidList,
+ availableOids: this.props.data.availableOids,
+ comps: this.props.data.comps,
+ compList: this.props.data.compList,
+ availableComps: this.props.data.availableComps,
+ selectedOids: this.props.data.selectedOids,
+ selectedComps: this.props.data.selectedComps,
+ removeOids: this.props.data.removeOids,
+ removeComps: this.props.data.removeComps,
+ showConfirmCompDelete: false,
+ showConfirmOidDelete: false,
+ showOidModal: false,
+ showCompsModal: false,
+ // Chaining config settings
+ defSearchCheck: this.props.data.defSearchCheck,
+ defBindConnLimit: this.props.data.defBindConnLimit,
+ defBindTimeout: this.props.data.defBindTimeout,
+ defBindRetryLimit: this.props.data.defBindRetryLimit,
+ defConcurLimit: this.props.data.defConcurLimit,
+ defConcurOpLimit: this.props.data.defConcurOpLimit,
+ defConnLife: this.props.data.defConnLife,
+ defHopLimit: this.props.data.defHopLimit,
+ defDelay: this.props.data.defDelay,
+ defTestDelay: this.props.data.defTestDelay,
+ defOpConnLimit: this.props.data.defOpConnLimit,
+ defSizeLimit: this.props.data.defSizeLimit,
+ defTimeLimit: this.props.data.defTimeLimit,
+ defProxy: this.props.data.defProxy,
+ defRefOnScoped: this.props.data.defRefOnScoped,
+ defCheckAci: this.props.data.defCheckAci,
+ defUseStartTLS: this.props.data.defUseStartTLS,
+ // Original values used for saving config
+ _defSearchCheck: this.props.data.defSearchCheck,
+ _defBindConnLimit: this.props.data.defBindConnLimit,
+ _defBindTimeout: this.props.data.defBindTimeout,
+ _defBindRetryLimit: this.props.data.defBindRetryLimit,
+ _defConcurLimit: this.props.data.defConcurLimit,
+ _defConcurOpLimit: this.props.data.defConcurOpLimit,
+ _defConnLife: this.props.data.defConnLife,
+ _defHopLimit: this.props.data.defHopLimit,
+ _defDelay: this.props.data.defDelay,
+ _defTestDelay: this.props.data.defTestDelay,
+ _defOpConnLimit: this.props.data.defOpConnLimit,
+ _defSizeLimit: this.props.data.defSizeLimit,
+ _defTimeLimit: this.props.data.defTimeLimit,
+ _defProxy: this.props.data.defProxy,
+ _defRefOnScoped: this.props.data.defRefOnScoped,
+ _defCheckAci: this.props.data.defCheckAci,
+ _defUseStartTLS: this.props.data.defUseStartTLS,
+ };
+
+ this.handleChange = this.handleChange.bind(this);
+ this.save_chaining_config = this.save_chaining_config.bind(this);
+ // Chaining Control OIDs
+ this.showOidModal = this.showOidModal.bind(this);
+ this.closeOidModal = this.closeOidModal.bind(this);
+ this.handleOidChange = this.handleOidChange.bind(this);
+ this.saveOids = this.saveOids.bind(this);
+ this.deleteOids = this.deleteOids.bind(this);
+ this.handleSelectOids = this.handleSelectOids.bind(this);
+ // Chaining comps
+ this.showCompsModal = this.showCompsModal.bind(this);
+ this.closeCompsModal = this.closeCompsModal.bind(this);
+ this.handleCompsChange = this.handleCompsChange.bind(this);
+ this.saveComps = this.saveComps.bind(this);
+ this.deleteComps = this.deleteComps.bind(this);
+ this.handleSelectComps = this.handleSelectComps.bind(this);
+ this.showConfirmDelete = this.showConfirmDelete.bind(this);
+ this.closeConfirmOidDelete = this.closeConfirmOidDelete.bind(this);
+ this.closeConfirmCompDelete = this.closeConfirmCompDelete.bind(this);
+ }
+
+ handleChange(e) {
+ // Generic
+ const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ this.setState({
+ [e.target.id]: value
+ });
+ }
+
+ save_chaining_config () {
+ // Build up the command list
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'chaining', 'config-set-def'
+ ];
+ let val = "";
+ if (this.state._defUseStartTLS != this.state.defUseStartTLS) {
+ val = "off";
+ if (this.state.defUseStartTLS) {
+ val = "on";
+ }
+ cmd.push("--use-starttls=" + val);
+ }
+ if (this.state._defCheckAci != this.state.defCheckAci) {
+ val = "off";
+ if (this.state.defCheckAci) {
+ val = "on";
+ }
+ cmd.push("--check-aci=" + val);
+ }
+ if (this.state._defRefOnScoped != this.state.defRefOnScoped) {
+ val = "off";
+ if (this.state.defRefOnScoped) {
+ val = "on";
+ }
+ cmd.push("--return-ref=" + val);
+ }
+ if (this.state._defProxy != this.state.defProxy) {
+ val = "off";
+ if (this.state.defProxy) {
+ val = "on";
+ }
+ cmd.push("--proxied-auth=" + val);
+ }
+ if (this.state._defTestDelay != this.state.defTestDelay) {
+ cmd.push("--test-response-delay=" + this.state.defTestDelay);
+ }
+ if (this.state._defOpConnLimit != this.state.defOpConnLimit) {
+ cmd.push("--conn-op-limit=" + this.state.defOpConnLimit);
+ }
+ if (this.state._defSizeLimit != this.state.defSizeLimit) {
+ cmd.push("--size-limit=" + this.state.defSizeLimit);
+ }
+ if (this.state._defTimeLimit != this.state.defTimeLimit) {
+ cmd.push("--time-limit=" + this.state.defTimeLimit);
+ }
+ if (this.state._defSearchCheck != this.state.defSearchCheck) {
+ cmd.push("--abandon-check-interval=" + this.state.defSearchCheck);
+ }
+ if (this.state._defBindTimeout != this.state.defBindTimeout) {
+ cmd.push("--bind-timeout=" + this.state.defBindTimeout);
+ }
+ if (this.state._defBindRetryLimit != this.state.defBindRetryLimit) {
+ cmd.push("--bind-attempts=" + this.state.defBindRetryLimit);
+ }
+ if (this.state._defConcurLimit != this.state.defConcurLimit) {
+ cmd.push("--bind-limit=" + this.state.defConcurLimit);
+ }
+ if (this.state._defConcurOpLimit != this.state.defConcurOpLimit) {
+ cmd.push("--op-limit=" + this.state.defConcurOpLimit);
+ }
+ if (this.state._defConnLife != this.state.defConnLife) {
+ cmd.push("--conn-lifetime=" + this.state.defConnLife);
+ }
+ if (this.state._defHopLimit != this.state.defHopLimit) {
+ cmd.push("--hop-limit=" + this.state.defHopLimit);
+ }
+ if (this.state._defDelay != this.state.defDelay) {
+ cmd.push("--response-delay=" + this.state.defDelay);
+ }
+ if (this.state._defBindConnLimit != this.state.defBindConnLimit) {
+ cmd.push("--conn-bind-limit=" + this.state.defBindConnLimit);
+ }
+
+ // If we have chaining mods, then apply them...
+ if (cmd.length > 5) {
+ log_cmd("save_chaining_config", "Applying default chaining config change", cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ // Continue with the next mod
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ `Successfully updated chaining configuration`
+ );
+ })
+ .fail(err => {
+ this.props.reload();
+ this.props.addNotification(
+ "error",
+ `Error updating chaining configuration - ${err}`
+ );
+ });
+ }
+ }
+
+ //
+ // Chaining OID modal functions
+ //
+ showOidModal () {
+ this.setState({
+ showOidModal: true
+ });
+ }
+
+ closeOidModal() {
+ this.setState({
+ showOidModal: false
+ });
+ }
+
+ handleOidChange(e) {
+ const options = e.target.options;
+ let values = [];
+ for (let option of options) {
+ if (option.selected) {
+ values.push(option.value);
+ }
+ }
+ this.setState({
+ selectedOids: values
+ });
+ }
+
+ saveOids () {
+ // Save chaining control oids
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "chaining", "config-set"
+ ];
+ for (let oid of this.state.selectedOids) {
+ if (!this.state.oidList.includes(oid)) {
+ cmd.push('--add-control=' + oid);
+ }
+ }
+ this.closeOidModal();
+ log_cmd("saveOids", "Save new chaining OID controls", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ `Successfully updated chaining controls`
+ );
+ })
+ .fail(err => {
+ this.props.reload();
+ this.props.addNotification(
+ "error",
+ `Error updating chaining controls - ${err}`
+ );
+ });
+ }
+
+ handleSelectOids (e) {
+ const options = e.target.options;
+ let values = [];
+ for (let option of options) {
+ if (option.selected) {
+ values.push(option.value);
+ }
+ }
+ this.setState({
+ removeOids: values
+ });
+ }
+
+ deleteOids(props) {
+ // Remove chaining controls
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "chaining", "config-set"
+ ];
+ for (let oid of this.state.removeOids) {
+ cmd.push('--del-control=' + oid);
+ }
+ this.state.removeOids = [];
+
+ log_cmd("deleteOids", "Delete chaining controls", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ `Successfully removed chaining controls`
+ );
+ })
+ .fail(err => {
+ this.props.reload();
+ this.props.addNotification(
+ "error",
+ `Error removing chaining controls - ${err}`
+ );
+ });
+ }
+
+ //
+ // Chaining Component modal functions
+ //
+ showCompsModal () {
+ this.setState({
+ showCompsModal: true
+ });
+ }
+
+ closeCompsModal() {
+ this.setState({
+ showCompsModal: false
+ });
+ }
+
+ handleCompsChange(e) {
+ const options = e.target.options;
+ let values = [];
+ for (let option of options) {
+ if (option.selected) {
+ values.push(option.value);
+ }
+ }
+ this.setState({
+ selectedComps: values
+ });
+ }
+
+ handleSelectComps (e) {
+ const options = e.target.options;
+ let values = [];
+ for (let option of options) {
+ if (option.selected) {
+ values.push(option.value);
+ }
+ }
+ this.setState({
+ removeComps: values
+ });
+ }
+
+ saveComps () {
+ // Save chaining control oids
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "chaining", "config-set"
+ ];
+ for (let comp of this.state.selectedComps) {
+ if (!this.state.compList.includes(comp)) {
+ cmd.push('--add-comp=' + comp);
+ }
+ }
+ this.closeCompsModal();
+ log_cmd("saveComps", "Save new chaining components", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ `Successfully updated chaining components`
+ );
+ })
+ .fail(err => {
+ this.props.reload();
+ this.props.addNotification(
+ "error",
+ `Error updating chaining components - ${err}`
+ );
+ });
+ }
+
+ deleteComps(props) {
+ // Remove chaining comps
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "chaining", "config-set"
+ ];
+ for (let comp of this.state.removeComps) {
+ cmd.push('--del-comp=' + comp);
+ }
+ this.state.removeComps = [];
+
+ log_cmd("deleteComps", "Delete chaining components", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ `Successfully removed chaining components`
+ );
+ })
+ .fail(err => {
+ this.props.reload();
+ this.props.addNotification(
+ "error",
+ `Error removing chaining components - ${err}`
+ );
+ });
+ }
+
+ //
+ // Confirm deletion functions
+ //
+ showConfirmDelete(item) {
+ if (item == "oid") {
+ if (this.state.removeOids.length) {
+ this.setState({
+ showConfirmOidDelete: true
+ });
+ }
+ } else if (item == "comp") {
+ if (this.state.removeComps.length) {
+ this.setState({
+ showConfirmCompDelete: true
+ });
+ }
+ }
+ }
+
+ closeConfirmOidDelete() {
+ this.setState({
+ showConfirmOidDelete: false
+ });
+ }
+
+ closeConfirmCompDelete() {
+ this.setState({
+ showConfirmCompDelete: false
+ });
+ }
+
+ render() {
+ // Get OIDs and comps
+ this.state.oids = this.state.oidList.map((oid) =>
+ <option key={oid} value={oid}>{oid}</option>
+ );
+ this.state.comps = this.state.compList.map((comp) =>
+ <option key={comp} value={comp}>{comp}</option>
+ );
+
+ return (
+ <div className="container-fluid" id="db-global-page">
+ <h3 className="ds-config-header">Database Chaining Settings</h3>
+ <hr />
+ <div className="ds-container">
+ <div className="ds-chaining-split">
+ <form>
+ <label className="ds-config-label" htmlFor="chaining-oid-list" title="A list of LDAP control OIDs to be forwarded through chaining"><b>Forwarded LDAP Controls</b></label>
+ <select id="chaining-oid-list" onChange={this.handleSelectOids} className="ds-chaining-list" name="nstransmittedcontrols" size="10" multiple>
+ {this.state.oids}
+ </select>
+ </form>
+ <div className="clearfix ds-container">
+ <div className="ds-panel-left">
+ <button type="button" onClick={this.showOidModal} className="ds-button-left">Add</button>
+ </div>
+ <div className="ds-panel-right">
+ <button type="button" onClick={e => this.showConfirmDelete("oid")} className="ds-button-right">Delete</button>
+ </div>
+ </div>
+ </div>
+ <div className="ds-chaining-divider" />
+ <div className="ds-chaining-split">
+ <form>
+ <label className="ds-config-label" htmlFor="chaining-comp-list" title="A list of components to go through chaining"><b>Components to Chain</b></label>
+ <select id="chaining-comp-list" onChange={this.handleSelectComps} className="ds-chaining-list" name="nsactivechainingcomponents" size="10" multiple>
+ {this.state.comps}}
+ </select>
+ </form>
+ <div className="clearfix ds-container">
+ <div className="ds-panel-left">
+ <button type="button" onClick={this.showCompsModal} className="ds-button-left">Add</button>
+ </div>
+ <div className="ds-panel-right">
+ <button type="button" onClick={e => this.showConfirmDelete("comp")} className="ds-button-right">Delete</button>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <h4 className="ds-sub-header"><br />Default Database Link Creation Settings</h4>
+ <hr />
+ <div className="ds-container">
+ <div className="ds-split">
+ <label htmlFor="defSizeLimit" className="ds-config-label" title="The size limit of entries returned over a database link (nsslapd-sizelimit).">
+ Size Limit</label><input className="ds-input" type="text" id="defSizeLimit" onChange={this.handleChange} value={this.state.defSizeLimit} size="15" />
+ <label htmlFor="defTimeLimit" className="ds-config-label" title="The time limit of an operation over a database link (nsslapd-timelimit).">
+ Time Limit</label><input className="ds-input" type="text" id="defTimeLimit" onChange={this.handleChange} value={this.state.defTimeLimit} size="15" />
+ <label htmlFor="defBindConnLimit" className="ds-config-label" title="The maximum number of TCP connections the database link establishes with the remote server. (nsbindconnectionslimit).">
+ Max TCP Connections</label><input className="ds-input" id="defBindConnLimit" type="text" onChange={this.handleChange} value={this.state.defBindConnLimit} size="15" />
+ <label htmlFor="defOpConnLimit" className="ds-config-label" title="The maximum number of connections allowed over the database link. (nsoperationconnectionslimit).">
+ Max LDAP Connections</label><input className="ds-input" id="defOpConnLimit" type="text" onChange={this.handleChange} value={this.state.defOpConnLimit} size="15" />
+ <label htmlFor="defConcurLimit" className="ds-config-label" title="The maximum number of concurrent bind operations per TCP connection. (nsconcurrentbindlimit).">
+ Max Binds Per Connection</label><input className="ds-input" id="defConcurLimit" type="text" onChange={this.handleChange} value={this.state.defConcurLimit} size="15" />
+ <label htmlFor="defBindTimeout" className="ds-config-label" title="The amount of time before the bind attempt times out. (nsbindtimeout).">
+ Bind Timeout</label><input className="ds-input" id="defBindTimeout" type="text" onChange={this.handleChange} value={this.state.defBindTimeout} size="15" />
+ <label htmlFor="defBindRetryLimit" className="ds-config-label" title="The number of times the database link tries to bind with the remote server after a connection failure. (nsbindretrylimit).">
+ Bind Retry Limit</label><input className="ds-input" id="defBindRetryLimit" type="text" onChange={this.handleChange} value={this.state.defBindRetryLimit} size="15" />
+ </div>
+ <div className="ds-divider" />
+ <div className="ds-split ds-inline">
+ <div>
+ <label htmlFor="defConcurOpLimit" className="ds-config-label" title="The maximum number of operations per connections. (nsconcurrentoperationslimit).">
+ Max Operations Per Connection</label><input className="ds-input" id="defConcurOpLimit" type="text" onChange={this.handleChange} value={this.state.defConcurOpLimit} size="15" />
+ </div>
+ <div>
+ <label htmlFor="defConnLife" className="ds-config-label" title="The life of a database link connection to the remote server. 0 is unlimited (nsconnectionlife).">
+ Connection Lifetime (in seconds)</label><input className="ds-input" id="defConnLife" type="text" onChange={this.handleChange} value={this.state.defConnLife} size="15" />
+ </div>
+ <div>
+ <label htmlFor="defSearchCheck" className="ds-config-label" title="The number of seconds that pass before the server checks for abandoned operations. (nsabandonedsearchcheckinterval).">
+ Abandoned Op Check Interval</label><input className="ds-input" id="defSearchCheck" type="text" onChange={this.handleChange} value={this.state.defSearchCheck} size="15" />
+ </div>
+ <div>
+ <label htmlFor="defHopLimit" className="ds-config-label" title="The maximum number of times a request can be forwarded from one database link to another. (nshoplimit).">
+ Database Link Hop Limit</label><input className="ds-input" type="text" onChange={this.handleChange} value={this.state.defHopLimit} id="defHopLimit" size="15" />
+ </div>
+ <div>
+ <p />
+ <input type="checkbox" onChange={this.handleChange} checked={this.state.defCheckAci} className="ds-config-checkbox" id="defCheckAci" /><label
+ htmlFor="defCheckAci" className="ds-label" title="Sets whether ACIs are evaluated on the database link as well as the remote data server (nschecklocalaci)."> Check Local ACIs</label>
+ </div>
+ <div>
+ <input type="checkbox" onChange={this.handleChange} checked={this.state.defRefOnScoped} className="ds-config-checkbox" id="defRefOnScoped" /><label
+ htmlFor="defRefOnScoped" className="ds-label" title="Sets whether referrals are returned by scoped searches (meaning 'one-level' or 'subtree' scoped searches). (nsreferralonscopedsearch)."> Send Referral On Scoped Search</label>
+ </div>
+ <div>
+ <input type="checkbox" onChange={this.handleChange} checked={this.state.defProxy} className="ds-config-checkbox" id="defProxy" /><label
+ htmlFor="defProxy" className="ds-label" title="Sets whether proxied authentication is allowed (nsproxiedauthorization)."> Allow Proxied Authentication</label>
+ </div>
+ <div>
+ <input type="checkbox" onChange={this.handleChange} checked={this.state.defUseStartTLS} className="ds-config-checkbox" id="defUseStartTLS" /><label
+ htmlFor="defUseStartTLS" className="ds-label" title="Sets whether to use Start TLS to initiate a secure, encrypted connection over an insecure port. (nsusestarttls)."> Use StartTLS</label>
+ </div>
+ </div>
+ </div>
+ <div className="ds-save-btn">
+ <button className="btn btn-primary save-button" onClick={this.save_chaining_config}>Save Default Settings</button>
+ </div>
+
+ <ChainControlsModal
+ showModal={this.state.showOidModal}
+ closeHandler={this.closeOidModal}
+ handleChange={this.handleOidChange}
+ saveHandler={this.saveOids}
+ oidList={this.state.availableOids}
+ />
+ <ChainCompsModal
+ showModal={this.state.showCompsModal}
+ closeHandler={this.closeCompsModal}
+ handleChange={this.handleCompsChange}
+ saveHandler={this.saveComps}
+ compList={this.state.availableComps}
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmOidDelete}
+ closeHandler={this.closeConfirmOidDelete}
+ actionFunc={this.deleteOids}
+ msg="Are you sure you want to delete these OID's?"
+ msgContent={this.state.removeOids}
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmCompDelete}
+ closeHandler={this.closeConfirmCompDelete}
+ actionFunc={this.deleteComps}
+ msg="Are you sure you want to delete these components?"
+ msgContent={this.state.removeComps}
+ />
+ </div>
+ );
+ }
+}
+
+//
+// This is the component for the actual database link under a suffix
+//
+export class ChainingConfig extends React.Component {
+ constructor(props) {
+ super(props);
+
+ if (this.props.data !== undefined) {
+ this.state = {
+ errObj: {},
+ showDeleteConfirm: false,
+ linkPwdMatch: true,
+ // Settings
+ nsfarmserverurl: this.props.data.nsfarmserverurl,
+ nsmultiplexorbinddn: this.props.data.nsmultiplexorbinddn,
+ nsmultiplexorcredentials: this.props.data.nsmultiplexorcredentials,
+ nsmultiplexorcredentials_confirm: this.props.data.nsmultiplexorcredentials,
+ sizelimit: this.props.data.sizelimit,
+ timelimit: this.props.data.timelimit,
+ bindconnlimit: this.props.data.bindconnlimit,
+ opconnlimit: this.props.data.opconnlimit,
+ concurrbindlimit: this.props.data.concurrbindlimit,
+ bindtimeout: this.props.data.bindtimeout,
+ bindretrylimit: this.props.data.bindretrylimit,
+ concurroplimit: this.props.data.concurroplimit,
+ connlifetime: this.props.data.connlifetime,
+ searchcheckinterval: this.props.data.searchcheckinterval,
+ hoplimit: this.props.data.hoplimit,
+ nsbindmechanism: this.props.data.nsbindmechanism,
+ nsusestarttls: this.props.data.nsusestarttls,
+ nsreferralonscopedsearch: this.props.data.nsreferralonscopedsearch,
+ nsproxiedauthorization: this.props.data.nsproxiedauthorization,
+ nschecklocalaci: this.props.data.nschecklocalaci,
+ // Original settings
+ _nsfarmserverurl: this.props.data.nsfarmserverurl,
+ _nsmultiplexorbinddn: this.props.data.nsmultiplexorbinddn,
+ _nsmultiplexorcredentials: this.props.data.nsmultiplexorcredentials,
+ _nsmultiplexorcredentials_confirm: this.props.data.nsmultiplexorcredentials,
+ _sizelimit: this.props.data.sizelimit,
+ _timelimit: this.props.data.timelimit,
+ _bindconnlimit: this.props.data.bindconnlimit,
+ _opconnlimit: this.props.data.opconnlimit,
+ _concurrbindlimit: this.props.data.concurrbindlimit,
+ _bindtimeout: this.props.data.bindtimeout,
+ _bindretrylimit: this.props.data.bindretrylimit,
+ _concurroplimit: this.props.data.concurroplimit,
+ _connlifetime: this.props.data.connlifetime,
+ _searchcheckinterval: this.props.data.searchcheckinterval,
+ _hoplimit: this.props.data.hoplimit,
+ _nsbindmechanism: this.props.data.nsbindmechanism,
+ _nsusestarttls: this.props.data.nsusestarttls,
+ _nsreferralonscopedsearch: this.props.data.nsreferralonscopedsearch,
+ _nsproxiedauthorization: this.props.data.nsproxiedauthorization,
+ _nschecklocalaci: this.props.data.nschecklocalaci,
+ };
+ } else {
+ this.state = {
+ errObj: {},
+ showDeleteConfirm: false,
+ linkPwdMatch: true,
+ };
+ }
+ this.handleChange = this.handleChange.bind(this);
+ this.saveLink = this.saveLink.bind(this);
+ this.deleteLink = this.deleteLink.bind(this);
+ this.showDeleteConfirm = this.showDeleteConfirm.bind(this);
+ this.closeDeleteConfirm = this.closeDeleteConfirm.bind(this);
+ }
+
+ showDeleteConfirm () {
+ this.setState({
+ showDeleteConfirm: true
+ });
+ }
+
+ closeDeleteConfirm () {
+ this.setState({
+ showDeleteConfirm: false
+ });
+ }
+
+ checkPasswords() {
+ let pwdMatch = false;
+ if (this.state.nsmultiplexorcredentials == this.state.nsmultiplexorcredentials_confirm) {
+ pwdMatch = true;
+ }
+ this.setState({
+ linkPwdMatch: pwdMatch
+ });
+ }
+
+ handleChange (e) {
+ const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj
+ }, this.checkPasswords);
+ }
+
+ saveLink() {
+ let missingArgs = {};
+ let errors = false;
+
+ if (this.state.nsfarmserverurl == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing Remote Server LDAP URL`
+ );
+ missingArgs.nsfarmserverurl = true;
+ errors = true;
+ }
+ if (this.state.nsmultiplexorbinddn == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing Remote Bind DN`
+ );
+ missingArgs.nsmultiplexorbinddn = true;
+ errors = true;
+ }
+ if (this.state.nsmultiplexorcredentials == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing Remote Bind DN Password`
+ );
+ missingArgs.nsmultiplexorcredentials = true;
+ errors = true;
+ }
+ if (this.state.nsmultiplexorcredentials_confirm == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing Remote Bind DN Password Confirmation`
+ );
+ missingArgs.nsmultiplexorcredentials_confirm = true;
+ errors = true;
+ }
+ if (this.state.nsmultiplexorcredentials != this.state.nsmultiplexorcredentials_confirm) {
+ this.props.addNotification(
+ "warning",
+ `Remote Bind DN Password Do Not Match`
+ );
+ missingArgs.nsmultiplexorcredentials = true;
+ missingArgs.nsmultiplexorcredentials_confirm = true;
+ errors = true;
+ }
+ if (errors) {
+ this.setState({
+ errObj: missingArgs
+ });
+ return;
+ }
+
+ // Buld up the command of all the hcnge we have to do
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "chaining", "link-set", this.props.suffix
+ ];
+
+ if (this.state.nsfarmserverurl != this.state._nsfarmserverurl) {
+ cmd.push('--server-url=' + this.state.nsfarmserverurl);
+ }
+ if (this.state.nsmultiplexorbinddn != this.state._nsmultiplexorbinddn) {
+ cmd.push('--bind-dn=' + this.state.nsmultiplexorbinddn);
+ }
+ if (this.state.nsmultiplexorcredentials != this.state._nsmultiplexorcredentials) {
+ cmd.push('--bind-pw=' + this.state.nsmultiplexorcredentials);
+ }
+ if (this.state.timelimit != this.state._timelimit) {
+ cmd.push('--time-limit=' + this.state.timelimit);
+ }
+ if (this.state.sizelimit != this.state._sizelimit) {
+ cmd.push('--size-limit=' + this.state.sizelimit);
+ }
+ if (this.state.bindconnlimit != this.state._bindconnlimit) {
+ cmd.push('--conn-bind-limit=' + this.state.bindconnlimit);
+ }
+ if (this.state.opconnlimit != this.state._opconnlimit) {
+ cmd.push('--conn-op-limit=' + this.state.opconnlimit);
+ }
+ if (this.state.concurrbindlimit != this.state._concurrbindlimit) {
+ cmd.push('--bind-limit=' + this.state.concurrbindlimit);
+ }
+ if (this.state.bindtimeout != this.state._bindtimeout) {
+ cmd.push('--bind-timeout=' + this.state.bindtimeout);
+ }
+ if (this.state.bindretrylimit != this.state._bindretrylimit) {
+ cmd.push('--bind-attempts=' + this.state.bindretrylimit);
+ }
+ if (this.state.concurroplimit != this.state._concurroplimit) {
+ cmd.push('--op-limit=' + this.state.concurroplimit);
+ }
+ if (this.state.connlifetime != this.state._connlifetime) {
+ cmd.push('--conn-lifetime=' + this.state.connlifetime);
+ }
+ if (this.state.searchcheckinterval != this.state._searchcheckinterval) {
+ cmd.push('--abandon-check-interval=' + this.state.searchcheckinterval);
+ }
+ if (this.state.hoplimit != this.state._hoplimit) {
+ cmd.push('--hop-limit=' + this.state.hoplimit);
+ }
+ if (this.state.nsbindmechanism != this.state._nsbindmechanism) {
+ cmd.push('--bind-mech=' + this.state.nsbindmechanism);
+ }
+
+ if (this.state.nsusestarttls != this.state._nsusestarttls) {
+ if (this.state.nsusestarttls) {
+ cmd.push('--use-starttls=on');
+ } else {
+ cmd.push('--use-starttls=off');
+ }
+ }
+ if (this.state.nsreferralonscopedsearch != this.state._nsreferralonscopedsearch) {
+ if (this.state.nsreferralonscopedsearch) {
+ cmd.push('--return-ref=on');
+ } else {
+ cmd.push('--return-ref=off');
+ }
+ }
+ if (this.state.nsproxiedauthorization != this.state._nsproxiedauthorization) {
+ if (this.state.nsproxiedauthorization) {
+ cmd.push('--proxied-auth=on');
+ } else {
+ cmd.push('--proxied-auth=off');
+ }
+ }
+ if (this.state.nschecklocalaci != this.state._nschecklocalaci) {
+ if (this.state.nschecklocalaci) {
+ cmd.push('--check-aci=on');
+ } else {
+ cmd.push('--check-aci=off');
+ }
+ }
+
+ if (cmd.length > 6) {
+ // Something changed, perform the update
+ log_cmd("saveLink", "Save chaining link config", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ `Successfully Updated Link Configuration`
+ );
+ })
+ .fail(err => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Failed to update link configuration - ${err}`
+ );
+ });
+ }
+ }
+
+ deleteLink() {
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "chaining", "link-delete", this.props.suffix
+ ];
+ log_cmd("deleteLink", "Delete database chaining link", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.loadSuffixTree(true);
+ this.props.addNotification(
+ "success",
+ `Successfully Deleted Database Link`
+ );
+ })
+ .fail(err => {
+ this.props.loadSuffixTree(true);
+ this.props.addNotification(
+ "error",
+ `Failed to delete database link - ${err}`
+ );
+ });
+ }
+
+ render () {
+ const error = this.state.errObj;
+ let useStartTLSCheckBox;
+ let checkLocalAci;
+ let referralOnScope;
+ let proxiedAuth;
+
+ // Check local aci's checkbox
+ if (this.state.nschecklocalaci) {
+ checkLocalAci =
+ <Checkbox id="nschecklocalaci" onChange={this.handleChange} key={this.state.nschecklocalaci} defaultChecked
+ title="Sets whether ACIs are evaluated on the database link as well as the remote data server (nschecklocalaci).">
+ Check Local ACIs
+ </Checkbox>;
+ } else {
+ checkLocalAci =
+ <Checkbox id="nschecklocalaci" onChange={this.handleChange} key={this.state.nschecklocalaci}
+ title="Sets whether ACIs are evaluated on the database link as well as the remote data server (nschecklocalaci).">
+ Check Local ACIs
+ </Checkbox>;
+ }
+ // Referral on scoped search checkbox
+ if (this.state.nsreferralonscopedsearch) {
+ referralOnScope =
+ <Checkbox id="nsreferralonscopedsearch" onChange={this.handleChange} defaultChecked
+ title="Sets whether referrals are returned by scoped searches (meaning 'one-level' or 'subtree' scoped searches). (nsreferralonscopedsearch).">
+ Send Referral On Scoped Search
+ </Checkbox>;
+ } else {
+ referralOnScope =
+ <Checkbox id="nsreferralonscopedsearch" onChange={this.handleChange}
+ title="Sets whether referrals are returned by scoped searches (meaning 'one-level' or 'subtree' scoped searches). (nsreferralonscopedsearch).">
+ Send Referral On Scoped Search
+ </Checkbox>;
+ }
+ // Allow proxied auth checkbox
+ if (this.state.nsproxiedauthorization) {
+ proxiedAuth =
+ <Checkbox id="nsproxiedauthorization" onChange={this.handleChange} defaultChecked
+ title="Allow proxied authentication to the remote server. (nsproxiedauthorization).">
+ Allow Proxied Authentication
+ </Checkbox>;
+ } else {
+ proxiedAuth =
+ <Checkbox id="nsproxiedauthorization" onChange={this.handleChange} defaultChecked
+ title="Allow proxied authentication to the remote server. (nsproxiedauthorization).">
+ Allow Proxied Authentication
+ </Checkbox>;
+ }
+ // use startTLS checkbox
+ if (this.state.nsusestarttls) {
+ useStartTLSCheckBox =
+ <Checkbox id="nsusestarttls" onChange={this.handleChange} title="Use StartTLS for connection to remote server. (nsusestarttls)"
+ defaultChecked
+ >
+ Use StartTLS for remote connection
+ </Checkbox>;
+ } else {
+ useStartTLSCheckBox =
+ <Checkbox id="nsusestarttls" onChange={this.handleChange} title="Use StartTLS for connection to remote server. (nsusestarttls)">
+ Use StartTLS for remote connection
+ </Checkbox>;
+ }
+
+ return (
+ <div className="container-fluid">
+ <Row>
+ <Col sm={8} className="ds-word-wrap">
+ <ControlLabel className="ds-suffix-header"><Icon type="fa" name="link" /> {this.props.suffix} (<i>{this.props.bename}</i>)</ControlLabel>
+ </Col>
+ <Col sm={2}>
+ <Button
+ bsStyle="primary"
+ onClick={this.showDeleteConfirm}
+ >
+ Delete Link
+ </Button>
+ </Col>
+ </Row>
+ <h4>Database Link Configuration</h4>
+ <hr />
+ <Form horizontal autoComplete="off">
+ <Row title="The LDAP URL for the remote server. Add additional failure server URLs by separating them with a space. (nsfarmserverurl)">
+ <Col sm={3}>
+ <ControlLabel>Remote Server LDAP URL(s)</ControlLabel>
+ </Col>
+ <Col sm={7}>
+ <FormControl
+ type="text"
+ id="nsfarmserverurl"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.nsfarmserverurl}
+ />
+ </Col>
+ </Row>
+ <p />
+ <Row title="The distinguished name (DN) of the entry to authenticate to the remote server. (nsmultiplexorbinddn)">
+ <Col sm={3}>
+ <ControlLabel>Remote Server Bind DN</ControlLabel>
+ </Col>
+ <Col sm={7}>
+ <FormControl
+ type="text"
+ id="nsmultiplexorbinddn"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.nsmultiplexorbinddn}
+ />
+ </Col>
+ </Row>
+ <p />
+ <Row title="The password for the authenticating entry. (nsmultiplexorcredentials)">
+ <Col sm={3}>
+ <ControlLabel>Bind DN Password</ControlLabel>
+ </Col>
+ <Col sm={7}>
+ <FormControl
+ type="password"
+ id="nsmultiplexorcredentials"
+ className={(error.nsmultiplexorcredentials || !this.state.linkPwdMatch) ? "ds-input-auto-bad" : "ds-input-auto"}
+ onChange={this.handleChange}
+ defaultValue={this.state.nsmultiplexorcredentials}
+ />
+ </Col>
+ </Row>
+ <p />
+ <Row title="Confirm the password for the authenticating entry. (nsmultiplexorcredentials)">
+ <Col sm={3}>
+ <ControlLabel>Confirm Password</ControlLabel>
+ </Col>
+ <Col sm={7}>
+ <FormControl
+ type="password"
+ id="nsmultiplexorcredentials_confirm"
+ className={(error.nsmultiplexorcredentials_confirm || !this.state.linkPwdMatch) ? "ds-input-auto-bad" : "ds-input-auto"}
+ onChange={this.handleChange}
+ defaultValue={this.state.nsmultiplexorcredentials_confirm}
+ />
+ </Col>
+ </Row>
+ <p />
+ <Row title="THe authentication mechanism. Simple (user name and password), SASL/DIGEST-MD5, or SASL>GSSAPI. (nsbindmechanism)">
+ <Col sm={3}>
+ <ControlLabel>Bind Mechanism</ControlLabel>
+ </Col>
+ <Col sm={7}>
+ <select value={this.state.nsbindmechanism}
+ className="btn btn-default dropdown ds-dblink-dropdown"
+ onChange={this.handleChange}
+ id="nsbindmechanism"
+ >
+ <option>Simple</option>
+ <option>SASL/DIGEST-MD5</option>
+ <option>SASL/GSSAPI</option>
+ </select>
+ </Col>
+ </Row>
+ <p />
+ <Row>
+ <Col sm={9}>
+ {useStartTLSCheckBox}
+ </Col>
+ </Row>
+ </Form>
+ <p />
+
+ <CustomCollapse>
+ <div className="ds-accordion-panel">
+ <div className="ds-accordian-div">
+ <div className="ds-container">
+ <div className="ds-inline">
+ <div>
+ <label htmlFor="sizelimit" className="ds-config-label" title="The size limit of entries returned over a database link (nsslapd-sizelimit).">
+ Size Limit</label><input onChange={this.handleChange} defaultValue={this.state.sizelimit} className="ds-input" type="text" id="sizelimit" size="15" />
+ </div>
+ <div>
+ <label htmlFor="timelimit" className="ds-config-label" title="The time limit of an operation over a database link (nsslapd-timelimit).">
+ Time Limit</label><input onChange={this.handleChange} defaultValue={this.state.timelimit} className="ds-input" type="text" id="timelimit" size="15" />
+ </div>
+ <div>
+ <label htmlFor="bindconnlimit" className="ds-config-label" title="The maximum number of TCP connections the database link establishes with the remote server. (nsbindconnectionslimit).">
+ Max TCP Connections</label><input onChange={this.handleChange} defaultValue={this.state.bindconnlimit} className="ds-input" type="text" id="bindconnlimit" size="15" />
+ </div>
+ <div>
+ <label htmlFor="opconnlimit" className="ds-config-label" title="The maximum number of connections allowed over the database link. (nsoperationconnectionslimit).">
+ Max LDAP Connections</label><input onChange={this.handleChange} defaultValue={this.state.opconnlimit} className="ds-input" type="text" id="opconnlimit" size="15" />
+ </div>
+ <div>
+ <label htmlFor="concurrbindlimit" className="ds-config-label" title="The maximum number of concurrent bind operations per TCP connection. (nsconcurrentbindlimit).">
+ Max Binds Per Connection</label><input onChange={this.handleChange} defaultValue={this.state.concurrbindlimit} className="ds-input" type="text" id="concurrbindlimit" size="15" />
+ </div>
+ <div>
+ <label htmlFor="bindtimeout" className="ds-config-label" title="The amount of time before the bind attempt times out. (nsbindtimeout).">
+ Bind Timeout</label><input onChange={this.handleChange} defaultValue={this.state.bindtimeout} className="ds-input" type="text" id="bindtimeout" size="15" />
+ </div>
+ </div>
+ <div className="ds-divider" />
+ <div className="ds-inline">
+ <div>
+ <label htmlFor="bindretrylimit" className="ds-config-label" title="The number of times the database link tries to bind with the remote server after a connection failure. (nsbindretrylimit).">
+ Bind Retry Limit</label><input onChange={this.handleChange} defaultValue={this.state.bindretrylimit} className="ds-input" type="text" id="bindretrylimit" size="15" />
+ </div>
+ <div>
+ <label htmlFor="concurroplimit" className="ds-config-label" title="The maximum number of operations per connections. (nsconcurrentoperationslimit).">
+ Max Operations Per Connection</label><input onChange={this.handleChange} defaultValue={this.state.concurroplimit} className="ds-input" type="text" id="concurroplimit" size="15" />
+ </div>
+ <div>
+ <label htmlFor="connlifetime" className="ds-config-label" title="The life of a database link connection to the remote server. 0 is unlimited (nsconnectionlife).">
+ Connection Lifetime (in seconds)</label><input onChange={this.handleChange} defaultValue={this.state.connlifetime} className="ds-input" type="text" id="connlifetime" size="15" />
+ </div>
+ <div>
+ <label htmlFor="searchcheckinterval" className="ds-config-label" title="The number of seconds that pass before the server checks for abandoned operations. (nsabandonedsearchcheckinterval).">
+ Abandoned Op Check Interval</label><input onChange={this.handleChange} defaultValue={this.state.searchcheckinterval} className="ds-input" type="text" id="searchcheckinterval" size="15" />
+ </div>
+ <div>
+ <label htmlFor="hoplimit" className="ds-config-label" title="The maximum number of times a request can be forwarded from one database link to another. (nshoplimit).">
+ Database Link Hop Limit</label><input onChange={this.handleChange} defaultValue={this.state.hoplimit} className="ds-input" type="text" id="hoplimit" size="15" />
+ </div>
+ </div>
+ </div>
+ <p />
+ <div>
+ {proxiedAuth}
+ </div>
+ <div>
+ {checkLocalAci}
+ </div>
+ <div>
+ {referralOnScope}
+ </div>
+ </div>
+ </div>
+ <hr />
+ </CustomCollapse>
+ <div className="ds-save-btn">
+ <button onClick={this.saveLink} className="btn btn-primary">Save Configuration</button>
+ </div>
+ <ConfirmPopup
+ showModal={this.state.showDeleteConfirm}
+ closeHandler={this.closeDeleteConfirm}
+ actionFunc={this.deleteLink}
+ actionParam={this.props.suffix}
+ msg="Are you really sure you want to delete this database link?"
+ msgContent={this.props.suffix}
+ />
+ </div>
+ );
+ }
+}
+
+//
+// Chaining modals
+//
+
+export class ChainControlsModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ oidList
+ } = this.props;
+
+ const oids = oidList.map((oid) =>
+ <option key={oid} value={oid}>{oid}</option>
+ );
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Chaining LDAP Controls
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <label className="ds-config-label" htmlFor="avail-chaining-oid-list" title="A list of LDAP control OIDs to be forwarded through chaining">Available LDAP Controls</label>
+ <div>
+ <select id="avail-chaining-oid-list" onChange={handleChange} className="ds-chaining-form-list" size="10" multiple>
+ {oids}
+ </select>
+ </div>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer className="ds-modal-footerZZZ">
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Add & Save New Controls
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+export class ChainCompsModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ compList
+ } = this.props;
+
+ const comps = compList.map((comp) =>
+ <option key={comp} value={comp}>{comp}</option>
+ );
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Chaining Components
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <label className="ds-config-label" htmlFor="avail-chaining-comp-list" title="A list of LDAP control OIDs to be forwarded through chaining">Available Components</label>
+ <div>
+ <select id="avail-chaining-comp-list" onChange={handleChange} className="ds-chaining-form-list" size="10" multiple>
+ {comps}
+ </select>
+ </div>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer className="ds-modal-footer">
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Add & Save New Components
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+// Property types and defaults
+
+ChainCompsModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ compList: PropTypes.array,
+};
+
+ChainCompsModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ compList: [],
+};
+
+ChainControlsModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ oidList: PropTypes.array,
+};
+
+ChainControlsModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ oidList: [],
+};
+
+ChainingDatabaseConfig.propTypes = {
+ serverId: PropTypes.string,
+ addNotification: PropTypes.func,
+ reload: PropTypes.func,
+ data: PropTypes.object,
+};
+
+ChainingDatabaseConfig.defaultProps = {
+ serverId: "",
+ addNotification: noop,
+ reload: noop,
+ data: {},
+};
+
+ChainingConfig.propTypes = {
+ serverId: PropTypes.string,
+ suffix: PropTypes.string,
+ bename: PropTypes.string,
+ loadSuffixTree: PropTypes.func,
+ addNotification: PropTypes.func,
+ data: PropTypes.object,
+ reload: PropTypes.func,
+};
+
+ChainingConfig.defaultProps = {
+ serverId: "",
+ suffix: "",
+ bename: "",
+ loadSuffixTree: noop,
+ addNotification: noop,
+ data: {},
+ reload: noop,
+};
diff --git a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
new file mode 100644
index 0000000..a34316d
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
@@ -0,0 +1,398 @@
+import cockpit from "cockpit";
+import React from "react";
+import CustomCollapse from "../customCollapse.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ Spinner,
+ noop
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+export class GlobalDatabaseConfig extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ db_cache_auto: this.props.data.db_cache_auto,
+ import_cache_auto: this.props.data.import_cache_auto,
+ looklimit: this.props.data.looklimit,
+ idscanlimit: this.props.data.idscanlimit,
+ pagelooklimit: this.props.data.pagelooklimit,
+ pagescanlimit: this.props.data.pagescanlimit,
+ rangelooklimit: this.props.data.rangelooklimit,
+ autosize: this.props.data.autosize,
+ autosizesplit: this.props.data.autosizesplit,
+ dbcachesize: this.props.data.dbcachesize,
+ txnlogdir: this.props.data.txnlogdir,
+ dbhomedir: this.props.data.dbhomedir,
+ dblocks: this.props.data.dblocks,
+ chxpoint: this.props.data.chxpoint,
+ compactinterval: this.props.data.compactinterval,
+ importcachesize: this.props.data.importcachesize,
+ importcacheauto: this.props.data.importcacheauto,
+ // These variables store the original value (used for saving config)
+ _looklimit: this.props.data.looklimit,
+ _idscanlimit: this.props.data.idscanlimit,
+ _pagelooklimit: this.props.data.pagelooklimit,
+ _pagescanlimit: this.props.data.pagescanlimit,
+ _rangelooklimit: this.props.data.rangelooklimit,
+ _autosize: this.props.data.autosize,
+ _autosizesplit: this.props.data.autosizesplit,
+ _dbcachesize: this.props.data.dbcachesize,
+ _txnlogdir: this.props.data.txnlogdir,
+ _dbhomedir: this.props.data.dbhomedir,
+ _dblocks: this.props.data.dblocks,
+ _chxpoint: this.props.data.chxpoint,
+ _compactinterval: this.props.data.compactinterval,
+ _importcachesize: this.props.data.importcachesize,
+ _importcacheauto: this.props.data.importcacheauto,
+ _db_cache_auto: this.props.data.db_cache_auto,
+ _import_cache_auto: this.props.data.import_cache_auto,
+ };
+ this.handleChange = this.handleChange.bind(this);
+ this.select_auto_cache = this.select_auto_cache.bind(this);
+ this.select_auto_import_cache = this.select_auto_import_cache.bind(this);
+ this.save_db_config = this.save_db_config.bind(this);
+ }
+
+ select_auto_cache (e) {
+ this.setState({
+ db_cache_auto: !this.state.db_cache_auto
+ }, this.handleChange(e));
+ }
+
+ select_auto_import_cache (e) {
+ this.setState({
+ import_cache_auto: !this.state.import_cache_auto
+ }, this.handleChange(e));
+ }
+
+ handleChange(e) {
+ // Generic
+ const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ this.setState({
+ [e.target.id]: value
+ });
+ }
+
+ save_db_config() {
+ // Build up the command list
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'backend', 'config', 'set'
+ ];
+ let requireRestart = false;
+
+ if (this.state._looklimit != this.state.looklimit) {
+ cmd.push("--lookthroughlimit=" + this.state.looklimit);
+ }
+ if (this.state._idscanlimit != this.state.idscanlimit) {
+ cmd.push("--idlistscanlimit=" + this.state.idscanlimit);
+ }
+ if (this.state._pagelooklimit != this.state.pagelooklimit) {
+ cmd.push("--pagedlookthroughlimit=" + this.state.pagelooklimit);
+ }
+ if (this.state._pagescanlimit != this.state.pagescanlimit) {
+ cmd.push("--pagedidlistscanlimit=" + this.state.pagescanlimit);
+ }
+ if (this.state._rangelooklimit != this.state.rangelooklimit) {
+ cmd.push("--rangelookthroughlimit=" + this.state.rangelooklimit);
+ }
+ if (this.state.db_cache_auto) {
+ // Auto cache is selected
+ if (this.state._db_cache_auto != this.state.db_cache_auto) {
+ // We just enabled auto cache,
+ if (this.state.autosize == "0") {
+ cmd.push("--cache-autosize=10");
+ } else {
+ cmd.push("--cache-autosize=" + this.state.autosize);
+ }
+ requireRestart = true;
+ } else if (this.state._autosize != this.state.autosize) {
+ // Update auto cache settings if it changed
+ cmd.push("--cache-autosize=" + this.state.autosize);
+ requireRestart = true;
+ }
+ } else {
+ // No auto cache, check if we need to reset the value
+ if (this.state._db_cache_auto != this.state.db_cache_auto) {
+ // We just disabled auto cache
+ cmd.push("--cache-autosize=0");
+ requireRestart = true;
+ }
+ }
+ if (this.state._autosizesplit != this.state.autosizesplit) {
+ cmd.push("--cache-autosize-split=" + this.state.autosizesplit);
+ requireRestart = true;
+ }
+ if (this.state._dbcachesize != this.state.dbcachesize) {
+ cmd.push("--dbcachesize=" + this.state.dbcachesize);
+ requireRestart = true;
+ }
+ if (this.state._txnlogdir != this.state.txnlogdir) {
+ cmd.push("--logdirectory=" + this.state.txnlogdir);
+ requireRestart = true;
+ }
+ if (this.state._dbhomedir != this.state.dbhomedir) {
+ cmd.push("--db-home-directory=" + this.state.dbhomedir);
+ requireRestart = true;
+ }
+ if (this.state._dblocks != this.state.dblocks) {
+ cmd.push("--locks=" + this.state.dblocks);
+ requireRestart = true;
+ }
+ if (this.state._chxpoint != this.state.chxpoint) {
+ cmd.push("--checkpoint-interval=" + this.state.chxpoint);
+ requireRestart = true;
+ }
+ if (this.state._compactinterval != this.state.compactinterval) {
+ cmd.push("--compactdb-interval=" + this.state.compactinterval);
+ requireRestart = true;
+ }
+ if (this.state.import_cache_auto) {
+ // Auto cache is selected
+ if (this.state._import_cache_auto != this.state.import_cache_auto) {
+ // We just enabled auto cache,
+ if (this.state.importcachesize == "0") {
+ cmd.push("--import-cache-autosize=-1");
+ } else {
+ cmd.push("--import-cache-autosize=" + this.state.importcachesize);
+ }
+ } else if (this.state._importcachesize != this.state.importcachesize) {
+ // Update auto cache settings if it changed
+ cmd.push("--import-cache-autosize=" + this.state.importcachesize);
+ }
+ } else {
+ // Auto cache is not selected, check if we need to reset the value
+ if (this.state._import_cache_auto != this.state.import_cache_auto) {
+ // We just disabled auto cache
+ cmd.push("--import-cache-autosize=0");
+ }
+ }
+ if (this.state._importcachesize != this.state.importcachesize) {
+ cmd.push("--import-cachesize=" + this.state.importcachesize);
+ }
+ if (cmd.length > 6) {
+ log_cmd("save_db_config", "Applying config change", cmd);
+ let msg = "Successfully updated database configuration";
+ if (requireRestart) {
+ msg = ("Successfully updated database configuration. These " +
+ "changes require the server to be restarted to take effect");
+ }
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ // Continue with the next mod
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ msg
+ );
+ })
+ .fail(err => {
+ this.props.reload();
+ this.props.addNotification(
+ "error",
+ `Error updating configuration - ${err}`
+ );
+ });
+ }
+ }
+
+ render() {
+ let db_cache_form;
+ let import_cache_form;
+ let db_auto_checked = false;
+ let import_auto_checked = false;
+
+ if (this.state.db_cache_auto) {
+ db_cache_form = <div id="auto-cache-form" className="ds-margin-left">
+ <div>
+ <label htmlFor="autosize" className="ds-config-label-xlrg"
+ title="Enable database and entry cache auto-tuning using a percentage of the systems current resources (nsslapd-cache-autosize).">
+ System Memory Percentage</label><input className="ds-input" type="text"
+ id="autosizee" size="10" onChange={this.handleChange}
+ value={this.state.autosize} />
+ </div>
+ <div>
+ <label htmlFor="autosizesplit" className="ds-config-label-xlrg"
+ title="Sets the percentage of memory that is used for the database cache. The remaining percentage is used for the entry cache (nsslapd-cache-autosize-split).">
+ DB Cache Percentage</label><input className="ds-input" type="text"
+ id="autosizesplit" size="10" onChange={this.handleChange}
+ value={this.state.autosizesplit} />
+ </div>
+ </div>;
+ db_auto_checked = true;
+ } else {
+ db_cache_form = <div id="manual-cache-form" className="ds-margin-left">
+ <label htmlFor="dbcachesize" className="ds-config-label-xlrg"
+ title="Specifies the database index cache size in bytes (nsslapd-dbcachesize).">
+ Database Cache Size (bytes)</label><input className="ds-input" type="text"
+ id="dbcachesize" size="10" onChange={this.handleChange} value={this.state.dbcachesize} />
+ </div>;
+ db_auto_checked = false;
+ }
+
+ if (this.state.import_cache_auto) {
+ import_cache_form = <div id="auto-import-cache-form" className="ds-margin-left">
+ <label htmlFor="importcacheauto" className="ds-config-label-xlrg"
+ title="Enter '-1' to use 50% of available memory, '0' to disable autotuning, or enter the percentage of available memory to use. Value range -1 through 100, default is '-1' (nsslapd-import-cache-autosize).">
+ Import Cache Autosize</label><input className="ds-input" type="text"
+ id="importcacheauto" size="10"
+ onChange={this.handleChange} value={this.state.importcacheauto} />
+ </div>;
+ import_auto_checked = true;
+ } else {
+ import_cache_form = <div id="manual-import-cache-form" className="ds-margin-left">
+ <label htmlFor="importcachesize" className="ds-config-label-xlrg"
+ title="The size of the database cache used in the bulk import process. (nsslapd-import-cachesize).">
+ Import Cache Size (bytes)</label><input className="ds-input" type="text" id="importcachesize"
+ size="10" onChange={this.handleChange} value={this.state.importcachesize} />
+ </div>;
+ import_auto_checked = false;
+ }
+
+ let spinner = "";
+ if (this.state.loading) {
+ spinner =
+ <div className="ds-loading-spinner ds-center">
+ <p />
+ <h4>Loading global database configuration ...</h4>
+ <Spinner loading size="md" />
+ </div>;
+ }
+
+ return (
+ <div className="container-fluid" id="db-global-page">
+ {spinner}
+ <div className={this.state.loading ? 'ds-fadeout' : 'ds-fadein'}>
+ <h3 className="ds-config-header">Global Database Configuration</h3>
+ <hr />
+ <div className="ds-container">
+ <div className="ds-inline">
+ <div>
+ <label htmlFor="nsslapd-lookthrough-limit" className="ds-config-label"
+ title="The maximum number of entries that the Directory Server will check when examining candidate entries in response to a search request (nsslapd-lookthrough-limit).">
+ Database Look Though Limit</label><input id="looklimit" value={this.state.looklimit}
+ onChange={this.handleChange} className="ds-input" type="text" size="15" />
+ </div>
+ <div>
+ <label htmlFor="nsslapd-idlistscanlimit" className="ds-config-label"
+ title="The number of entry IDs that are searched during a search operation (nsslapd-idlistscanlimit).">
+ ID List Scan Limit</label><input id="idscanlimit" value={this.state.idscanlimit}
+ onChange={this.handleChange} className="ds-input" type="text"size="15" />
+ </div>
+ </div>
+ <div className="ds-divider" />
+ <div className="ds-inline">
+ <div>
+ <label htmlFor="nsslapd-pagedlookthroughlimit" className="ds-config-label"
+ title="The maximum number of entries that the Directory Server will check when examining candidate entries for a search which uses the simple paged results control (nsslapd-pagedlookthroughlimit).">
+ Paged Search Look Through Limit</label><input id="pagelooklimit" value={this.state.pagelooklimit}
+ onChange={this.handleChange} className="ds-input" type="text" size="15" />
+ </div>
+ <div>
+ <label htmlFor="nsslapd-pagedidlistscanlimit" className="ds-config-label"
+ title="The number of entry IDs that are searched, specifically, for a search operation using the simple paged results control (nsslapd-pagedidlistscanlimit).">
+ Paged Search ID List Scan Limit</label><input id="pagescanlimit"
+ value={this.state.pagescanlimit} onChange={this.handleChange}
+ className="ds-input" type="text" size="15" />
+ </div>
+ <div>
+ <label htmlFor="nsslapd-rangelookthroughlimit" className="ds-config-label"
+ title="The maximum number of entries that the Directory Server will check when examining candidate entries in response to a range search request (nsslapd-rangelookthroughlimit).">
+ Range Search Look Through Limit</label><input id="rangelooklimit"
+ value={this.state.rangelooklimit} onChange={this.handleChange}
+ className="ds-input" type="text" size="15" />
+ </div>
+ </div>
+ </div>
+ <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>
+ </div>
+ <CustomCollapse>
+ <div className="ds-accordion-panel">
+ <div className="ds-container">
+ <div className="ds-inline">
+ <div>
+ <label htmlFor="nsslapd-db-logdirectory" className="ds-config-label"
+ title="Database Transaction Log Location (nsslapd-db-logdirectory).">
+ Transaction Logs Directory</label><input id="txnlogdir" value={this.state.txnlogdir}
+ onChange={this.handleChange} className="ds-input" type="text" size="25" />
+ </div>
+ <div>
+ <label htmlFor="nsslapd-db-home-directory" className="ds-config-label"
+ title="Location for database memory mapped files. You must specify a subdirectory of a tempfs type filesystem (nsslapd-db-home-directory).">
+ Database Home Directory</label><input id="dbhomedir" value={this.state.dbhomedir}
+ onChange={this.handleChange} className="ds-input" type="text" size="25" />
+ </div>
+ <div>
+ <label htmlFor="nsslapd-db-locks" className="ds-config-label"
+ title="The number of database locks (nsslapd-db-locks).">
+ Database Locks</label><input id="dblocks" value={this.state.dblocks}
+ onChange={this.handleChange} className="ds-input" type="text" size="25" />
+ </div>
+ <div>
+ <label htmlFor="nsslapd-db-checkpoint-interval" className="ds-config-label"
+ title="Amount of time in seconds after which the Directory Server sends a checkpoint entry to the database transaction log (nsslapd-db-checkpoint-interval).">
+ Database Checkpoint Interval</label><input id="chxpoint" value={this.state.chxpoint}
+ onChange={this.handleChange} className="ds-input" type="text" size="25" />
+ </div>
+ <div>
+ <label htmlFor="nsslapd-db-compactdb-interval" className="ds-config-label"
+ title="The interval in seconds when the database is compacted (nsslapd-db-compactdb-interval).">
+ Database Compact Interval</label><input id="compactinterval" value={this.state.compactinterval}
+ onChange={this.handleChange}className="ds-input" type="text" size="25" />
+ </div>
+ </div>
+ </div>
+ <p />
+ </div>
+ </CustomCollapse>
+ <hr />
+ <div className="ds-save-btn">
+ <button className="btn btn-primary save-button"
+ onClick={this.save_db_config}>Save Configuration</button>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+GlobalDatabaseConfig.propTypes = {
+ serverId: PropTypes.string,
+ addNotification: PropTypes.func,
+ data: PropTypes.object,
+ reload: PropTypes.func,
+};
+
+GlobalDatabaseConfig.defaultProps = {
+ serverId: "",
+ addNotification: noop,
+ data: {},
+ reload: noop,
+};
diff --git a/src/cockpit/389-console/src/lib/database/databaseModal.jsx b/src/cockpit/389-console/src/lib/database/databaseModal.jsx
new file mode 100644
index 0000000..01db9bb
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/databaseModal.jsx
@@ -0,0 +1,502 @@
+import React from "react";
+import {
+ Modal,
+ Row,
+ Col,
+ ControlLabel,
+ Icon,
+ Button,
+ Form,
+ FormControl,
+ Spinner,
+ noop
+} from "patternfly-react";
+import { LDIFTable } from "./databaseTables.jsx";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+class CreateLinkModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ suffix,
+ pwdMatch,
+ error,
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Create Database Link
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <div>
+ <label htmlFor="createLinkSuffix" className="ds-config-label" title="The RDN of the link suffix">
+ Link Sub-Suffix</label><input className={error.createLinkSuffix ? "ds-input-bad ds-input-right" : "ds-input ds-input-right"} onChange={handleChange} type="text" id="createLinkSuffix" size="12" /><b><font color="blue"> ,{suffix}</font></b>
+ </div>
+ <div>
+ <label htmlFor="createLinkName" className="ds-config-label" title="A name for the backend chaining database link">
+ Link Database Name</label><input onChange={handleChange} className={error.createLinkName ? "ds-input-bad" : "ds-input"} type="text" id="createLinkName" size="45" />
+ </div>
+ <div>
+ <label htmlFor="createNsfarmserverurl" className="ds-config-label" title="The LDAP URL for the remote server. Add additional failover LDAP URLs separated by a space. (nsfarmserverurl)">
+ Remote Server URL(s)</label><input className={error.createNsfarmserverurl ? "ds-input-bad" : "ds-input"} onChange={handleChange} type="text" id="createNsfarmserverurl" size="45" />
+ </div>
+ <div>
+ <input type="checkbox" onChange={handleChange} className="ds-left-indent ds-config-checkbox" id="createUseStartTLS" /><label
+ htmlFor="createUseStartTLS" className="ds-label" title="Use StartTLS for the remote server LDAP URL"> Use StartTLS</label>
+ </div>
+ <div>
+ <label htmlFor="createNsmultiplexorbinddn" className="ds-config-label" title="Bind DN used to authenticate against the remote server (nsmultiplexorbinddn).">Remote Server Bind DN</label><input
+ className={error.createNsmultiplexorbinddn ? "ds-input-bad" : "ds-input"} type="text" onChange={handleChange} placeholder="Bind DN" id="createNsmultiplexorbinddn" size="45" />
+ </div>
+ <div>
+ <label htmlFor="createNsmultiplexorcredentials" className="ds-config-label" title="Replication Bind DN (nsDS5ReplicaCredentials).">Bind DN Credentials</label><input
+ className={error.createNsmultiplexorcredentials ? "ds-input-bad" : "ds-input"} type="password" onChange={handleChange} placeholder="Enter password" id="createNsmultiplexorcredentials" size="45" />
+ </div>
+ <div>
+ <label htmlFor="createNsmultiplexorcredentialsConfirm" className="ds-config-label" title="Confirm password">Confirm Password</label><input
+ className={(error.createNsmultiplexorcredentialsConfirm || !pwdMatch) ? "ds-input-bad" : "ds-input"} type="password" onChange={handleChange} placeholder="Confirm password" id="createNsmultiplexorcredentialsConfirm" size="45" />
+ </div>
+ <div>
+ <label htmlFor="createNsbindmechanism" className="ds-config-label" title="The bind method for contacting the remote server (nsbindmechanism).">Bind Method</label><select
+ className="btn btn-default dropdown ds-dblink-dropdown" onChange={handleChange} id="createNsbindmechanism">
+ <option>Simple</option>
+ <option>SASL/DIGEST-MD5</option>
+ <option>SASL/GSSAPI</option>
+ </select>
+ </div>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Create Database Link
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class CreateSubSuffixModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ suffix,
+ error
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div>
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Create Sub Suffix
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <Row title="Database Suffix DN (nsslapd-suffix)">
+ <Col sm={3}>
+ <ControlLabel>Sub-Suffix DN</ControlLabel>
+ </Col>
+ <Col sm={3}>
+ <FormControl
+ type="text"
+ id="subSuffixValue"
+ className={error.subSuffixValue ? "ds-input-bad ds-input-right" : "ds-input-right"}
+ onChange={handleChange}
+ />
+ </Col>
+ <Col sm={6} className="ds-col-append">
+ <ControlLabel><b><font color="blue">,{suffix}</font></b></ControlLabel>
+ </Col>
+ </Row>
+ <p />
+ <Row title="Database backend name (nsslapd-backend)">
+ <Col sm={3}>
+ <ControlLabel>Backend Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ id="subSuffixBeName"
+ className={error.subSuffixBeName ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Create Sub-Suffix
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class ExportModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ spinning,
+ error
+ } = this.props;
+ let spinner = "";
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-modal-spinner">
+ <Spinner loading inline size="lg" />Exporting database... <font size="1">(You can safely close this window)</font>
+ </div>
+ </Row>;
+ }
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Export Database To LDIF File
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <Row title="Name of exported LDIF file">
+ <Col sm={3}>
+ <ControlLabel>LDIF File Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ id="ldifLocation"
+ className={error.ldifLocation ? "ds-input-bad" : ""}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ <p />
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Export Database
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class ImportModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ spinning,
+ rows,
+ suffix
+ } = this.props;
+
+ let suffixRows = [];
+ let spinner = "";
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-modal-spinner">
+ <p />
+ <Spinner loading inline size="lg" />Importing LDIF file... <font size="1">(You can safely close this window)</font>
+ </div>
+ </Row>;
+ }
+ for (let idx in rows) {
+ if (rows[idx].suffix == suffix) {
+ suffixRows.push(rows[idx]);
+ }
+ }
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Initialize Database via LDIF File
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <p />
+ <LDIFTable
+ rows={suffixRows}
+ confirmImport={this.props.showConfirmImport}
+ />
+ <hr />
+ <Form horizontal autoComplete="off">
+ <Row title="The full path to the LDIF file. The server must have permissions to read it">
+ <Col sm={4}>
+ <ControlLabel>Or, enter LDIF location</ControlLabel>
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ type="text"
+ id="ldifLocation"
+ onChange={handleChange}
+ />
+ </Col>
+ <Col sm={2}>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Import
+ </Button>
+ </Col>
+ </Row>
+ <p />
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Close
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class ReindexModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ msg
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Index Attribute
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <div className="ds-modal-spinner">
+ <Spinner loading inline size="lg" /> Indexing <b>{msg}</b> ...
+ <p><font size="1">(You can safely close this window)</font></p>
+ </div>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Close
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+// Property types and defaults
+
+CreateLinkModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ suffix: PropTypes.string,
+ pwdMatch: PropTypes.bool,
+ error: PropTypes.object,
+};
+
+CreateLinkModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ suffix: "",
+ pwdMatch: false,
+ error: {},
+};
+
+CreateSubSuffixModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ suffix: PropTypes.string,
+ error: PropTypes.object,
+};
+
+CreateSubSuffixModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ suffix: "",
+ error: {},
+};
+
+ExportModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ error: PropTypes.object,
+ spinning: PropTypes.bool
+};
+
+ExportModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ error: {},
+ spinning: false
+};
+
+ImportModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ showConfirmImport: PropTypes.func,
+ rows: PropTypes.array,
+ suffix: PropTypes.string
+};
+
+ImportModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ spinning: false,
+ showConfirmImport: noop,
+ rows: [],
+ suffix: ""
+};
+
+ReindexModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ msg: PropTypes.string
+};
+
+ReindexModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ msg: ""
+};
+
+export {
+ ReindexModal,
+ ImportModal,
+ ExportModal,
+ CreateSubSuffixModal,
+ CreateLinkModal,
+};
diff --git a/src/cockpit/389-console/src/lib/database/databaseTables.jsx b/src/cockpit/389-console/src/lib/database/databaseTables.jsx
new file mode 100644
index 0000000..adf535e
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/databaseTables.jsx
@@ -0,0 +1,1018 @@
+import React from "react";
+import {
+ Button,
+ DropdownButton,
+ MenuItem,
+ actionHeaderCellFormatter,
+ sortableHeaderCellFormatter,
+ tableCellFormatter,
+ noop
+} from "patternfly-react";
+import { DSTable, DSShortTable } from "../dsTable.jsx";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+class ReferralTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ rowKey: "name",
+ columns: [
+ {
+ property: "name",
+ header: {
+ label: "Referral",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ label: "Actions",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.name[0]}>
+ <Button
+ onClick={() => {
+ this.props.loadModalHandler(rowData);
+ }}
+ >
+ Delete Referral
+ </Button>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ this.getColumns = this.getColumns.bind(this);
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Referrals",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ let refTable;
+ if (this.props.rows.length == 0) {
+ refTable = <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{msg: "No referrals"}]}
+ />;
+ } else {
+ refTable = <DSShortTable
+ getColumns={this.getColumns}
+ rowKey={this.state.rowKey}
+ rows={this.props.rows}
+ disableLoadingSpinner
+ />;
+ }
+ return (
+ <div>
+ {refTable}
+ </div>
+ );
+ }
+}
+
+class IndexTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ searchField: "Indexes",
+ fieldsToSearch: ["name"],
+ rowKey: "name",
+ columns: [
+ {
+ property: "name",
+ header: {
+ label: "Attribute",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "types",
+ header: {
+ label: "Index Types",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "matchingrules",
+ header: {
+ label: "Matching Rules",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ],
+ };
+
+ if (this.props.editable) {
+ this.state.columns.push(
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.name[0]}>
+ <DropdownButton id={rowData.name[0]}
+ bsStyle="default" title="Actions">
+ <MenuItem eventKey="1" onClick={() => {
+ this.props.editIndex(rowData);
+ }}
+ >
+ Edit Index
+ </MenuItem>
+ <MenuItem eventKey="2" onClick={() => {
+ this.props.reindexIndex(rowData);
+ }}
+ >
+ Reindex Index
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem eventKey="3" onClick={() => {
+ this.props.deleteIndex(rowData);
+ }}
+ >
+ Delete Index
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ );
+ }
+ this.getColumns = this.getColumns.bind(this);
+ } // Constructor
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ toolBarSearchField={this.state.searchField}
+ rowKey={this.state.rowKey}
+ rows={this.props.rows}
+ disableLoadingSpinner
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />
+ </div>
+
+ );
+ }
+}
+
+class EncryptedAttrTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ rowKey: "name",
+ columns: [
+ {
+ property: "name",
+ header: {
+ label: "Encrypted Attribute",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ label: "Actions",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.name[0]}>
+ <Button
+ onClick={() => {
+ this.props.loadModalHandler(rowData);
+ }}
+ >
+ Delete Attribute
+ </Button>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ this.getColumns = this.getColumns.bind(this);
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Encrypted Attributes",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ let attrTable;
+ if (this.props.rows.length == 0) {
+ attrTable =
+ <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{msg: "No encrypted attributes"}]}
+ />;
+ } else {
+ attrTable =
+ <DSShortTable
+ getColumns={this.getColumns}
+ rowKey={this.state.rowKey}
+ rows={this.props.rows}
+ disableLoadingSpinner
+ />;
+ }
+ return (
+ <div>
+ {attrTable}
+ </div>
+ );
+ }
+}
+
+class LDIFTable extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ rowKey: "name",
+ columns: [
+ {
+ property: "name",
+ header: {
+ label: "LDIF File",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "date",
+ header: {
+ label: "Creation Date",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "size",
+ header: {
+ label: "Size",
+ 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.name[0]}>
+ <Button
+ bsStyle="primary"
+ onClick={() => {
+ this.props.confirmImport(rowData);
+ }}
+ >
+ Import
+ </Button>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+
+ this.getColumns = this.getColumns.bind(this);
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "LDIF Files",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ let LDIFTable;
+ if (this.props.rows.length == 0) {
+ LDIFTable = <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{msg: "No LDIF files"}]}
+ />;
+ } else {
+ LDIFTable =
+ <DSTable
+ noSearchBar
+ getColumns={this.getColumns}
+ rowKey={this.state.rowKey}
+ rows={this.props.rows}
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />;
+ }
+ return (
+ <div>
+ {LDIFTable}
+ </div>
+ );
+ }
+}
+
+class LDIFManageTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ rowKey: "name",
+ columns: [
+ {
+ property: "name",
+ header: {
+ label: "LDIF File",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "suffix",
+ header: {
+ label: "Suffix",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "date",
+ header: {
+ label: "Creation Date",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "size",
+ header: {
+ label: "Size",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.name[0]}>
+ <DropdownButton id={rowData.name[0]}
+ bsStyle="primary" title="Actions">
+ <MenuItem eventKey="1" onClick={() => {
+ this.props.confirmImport(rowData);
+ }}
+ >
+ Import LDIF File
+ </MenuItem>
+
+ <MenuItem divider />
+ <MenuItem eventKey="3" onClick={() => {
+ this.props.confirmDelete(rowData);
+ }}
+ >
+ Delete LDIF File
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ],
+ };
+
+ this.getColumns = this.getColumns.bind(this);
+ } // Constructor
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "LDIF Files",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ render() {
+ let LDIFTable;
+ if (this.props.rows.length == 0) {
+ LDIFTable = <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{msg: "No LDIF files"}]}
+ />;
+ } else {
+ LDIFTable =
+ <DSTable
+ noSearchBar
+ getColumns={this.getColumns}
+ rowKey={this.state.rowKey}
+ rows={this.props.rows}
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />;
+ }
+ return (
+ <div>
+ {LDIFTable}
+ </div>
+ );
+ }
+}
+
+class BackupTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ rowKey: "name",
+ columns: [
+ {
+ property: "name",
+ header: {
+ label: "Backup",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "date",
+ header: {
+ label: "Creation Date",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "size",
+ header: {
+ label: "Size",
+ 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.name[0]}>
+ <DropdownButton id={rowData.name[0]}
+ bsStyle="primary" title="Actions">
+ <MenuItem eventKey="1" onClick={() => {
+ this.props.confirmRestore(rowData);
+ }}
+ >
+ Restore Backup
+ </MenuItem>
+
+ <MenuItem divider />
+ <MenuItem eventKey="3" onClick={() => {
+ this.props.confirmDelete(rowData);
+ }}
+ >
+ Delete Backup
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ],
+ };
+
+ this.getColumns = this.getColumns.bind(this);
+ } // Constructor
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Backup",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ render() {
+ let backupTable;
+ if (this.props.rows.length == 0) {
+ backupTable = <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{msg: "No Backups"}]}
+ />;
+ } else {
+ backupTable =
+ <DSTable
+ noSearchBar
+ getColumns={this.getColumns}
+ rowKey={this.state.rowKey}
+ rows={this.props.rows}
+ toolBarPagination={[6, 12, 24, 48, 96]}
+ toolBarPaginationPerPage={6}
+ />;
+ }
+ return (
+ <div>
+ {backupTable}
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+BackupTable.propTypes = {
+ rows: PropTypes.array,
+ confirmRestore: PropTypes.func,
+ confirmDelete: PropTypes.func
+};
+
+BackupTable.defaultProps = {
+ rows: [],
+ confirmRestore: noop,
+ confirmDelete: noop
+};
+
+LDIFTable.propTypes = {
+ rows: PropTypes.array,
+ confirmImport: PropTypes.func,
+};
+
+LDIFTable.defaultProps = {
+ rows: [],
+ confirmImport: noop
+};
+
+LDIFManageTable.propTypes = {
+ rows: PropTypes.array,
+ confirmImport: PropTypes.func,
+ confirmDelete: PropTypes.func
+};
+
+LDIFManageTable.defaultProps = {
+ rows: [],
+ confirmImport: noop,
+ confirmDelete: noop
+};
+
+ReferralTable.propTypes = {
+ rows: PropTypes.array,
+ loadModalHandler: PropTypes.func
+};
+
+ReferralTable.defaultProps = {
+ rows: [],
+ loadModalHandler: noop
+};
+
+IndexTable.propTypes = {
+ editable: PropTypes.bool,
+ rows: PropTypes.array,
+ editIndex: PropTypes.func,
+ reindexIndex: PropTypes.func,
+ deleteIndex: PropTypes.func,
+};
+
+IndexTable.defaultProps = {
+ editable: false,
+ rows: [],
+ editIndex: noop,
+ reindexIndex: noop,
+ deleteIndex: noop,
+};
+
+EncryptedAttrTable.propTypes = {
+ loadModalHandler: PropTypes.func,
+ rows: PropTypes.array,
+};
+
+EncryptedAttrTable.defaultProps = {
+ loadModalHandler: noop,
+ rows: [],
+};
+
+export {
+ ReferralTable,
+ IndexTable,
+ EncryptedAttrTable,
+ LDIFTable,
+ LDIFManageTable,
+ BackupTable
+};
diff --git a/src/cockpit/389-console/src/lib/database/indexes.jsx b/src/cockpit/389-console/src/lib/database/indexes.jsx
new file mode 100644
index 0000000..34c246b
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/indexes.jsx
@@ -0,0 +1,949 @@
+import cockpit from "cockpit";
+import React from "react";
+import { ConfirmPopup } from "../notifications.jsx";
+import { IndexTable } from "./databaseTables.jsx";
+import { ReindexModal } from "./databaseModal.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ Nav,
+ NavItem,
+ TabContent,
+ TabPane,
+ Modal,
+ Row,
+ Checkbox,
+ Col,
+ Icon,
+ Button,
+ Form,
+ noop
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import { Typeahead } from "react-bootstrap-typeahead";
+import "../../css/ds.css";
+
+export class SuffixIndexes extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ // indexes
+ showIndexModal: false,
+ showEditIndexModal: false,
+ showReindexModal: false,
+ reindexMsg: "",
+ editIndexName: "",
+ types: [],
+ attributes: [],
+ matchingRules: [],
+ mrs: [],
+ _mrs: [],
+ showConfirmReindex: false,
+ reindexAttrName: "",
+ showConfirmDeleteIndex: false,
+ deleteAttrName: "",
+ // Add indexes
+ addIndexName: [],
+ addIndexTypeEq: false,
+ addIndexTypePres: false,
+ addIndexTypeSub: false,
+ addIndexTypeApprox: false,
+ reindexOnAdd: false,
+ // Edit indexes
+ errObj: {},
+ _isMounted: true
+ };
+
+ this.loadIndexes = this.loadIndexes.bind(this);
+ this.showIndexModal = this.showIndexModal.bind(this);
+ this.closeIndexModal = this.closeIndexModal.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleTypeaheadChange = this.handleTypeaheadChange.bind(this);
+ this.saveIndex = this.saveIndex.bind(this);
+ this.saveEditIndex = this.saveEditIndex.bind(this);
+ this.reindexIndex = this.reindexIndex.bind(this);
+ this.deleteIndex = this.deleteIndex.bind(this);
+ this.showEditIndexModal = this.showEditIndexModal.bind(this);
+ this.closeEditIndexModal = this.closeEditIndexModal.bind(this);
+ this.showConfirmReindex = this.showConfirmReindex.bind(this);
+ this.closeConfirmReindex = this.closeConfirmReindex.bind(this);
+ this.showConfirmDeleteIndex = this.showConfirmDeleteIndex.bind(this);
+ this.closeConfirmDeleteIndex = this.closeConfirmDeleteIndex.bind(this);
+ this.closeReindexModal = this.closeReindexModal.bind(this);
+ }
+
+ componentWillMount () {
+ this.loadIndexes();
+ }
+
+ componentWillUnmount() {
+ this.state._isMounted = false;
+ }
+
+ loadIndexes () {
+ // Get all the attributes and matching rules now
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema", "matchingrules", "list"
+ ];
+ log_cmd("loadIndexes (suffix config)", "Get matching rules", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const mrContent = JSON.parse(content);
+ let mrs = [];
+ for (let i = 0; i < mrContent['items'].length; i++) {
+ if (mrContent['items'][i].name != "") {
+ mrs.push(mrContent['items'][i].name);
+ }
+ }
+
+ const idx_cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "index", "list", "--just-names", this.props.suffix
+ ];
+ log_cmd("loadIndexes (suffix config)", "Get current index list", idx_cmd);
+ cockpit
+ .spawn(idx_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let idxContent = JSON.parse(content);
+ let indexList = idxContent['items'];
+ const attr_cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "schema", "attributetypes", "list"
+ ];
+ log_cmd("loadIndexes (suffix config)", "Get attrs", attr_cmd);
+ cockpit
+ .spawn(attr_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const attrContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of attrContent['items']) {
+ if (indexList.indexOf(content.name) == -1) {
+ // Attribute is not a current index, add it to the list
+ // of available attributes to index
+ attrs.push(content.name);
+ }
+ }
+ if (this.state._isMounted) {
+ this.setState({
+ matchingRules: mrs,
+ attributes: attrs,
+ });
+ }
+ })
+ .fail(err => {
+ this.props.addNotification(
+ "error",
+ `Failed to get attributes - ${err}`
+ );
+ });
+ });
+ })
+ .fail(err => {
+ this.props.addNotification(
+ "error",
+ `Failed to get matching rules - ${err}`
+ );
+ });
+ }
+
+ //
+ // Index Modal functions
+ //
+ showIndexModal() {
+ this.setState({
+ showIndexModal: true,
+ errObj: {},
+ mrs: [],
+ addIndexName: [],
+ addIndexTypeEq: "",
+ addIndexTypeSub: "",
+ addIndexTypePres:"",
+ addIndexTypeApprox: "",
+ });
+ }
+
+ closeIndexModal() {
+ this.setState({
+ showIndexModal: false
+ });
+ }
+
+ handleChange(e) {
+ const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj
+ });
+ }
+
+ saveIndex() {
+ // Validate the form
+ if (!this.state.addIndexTypeEq && !this.state.addIndexTypePres &&
+ !this.state.addIndexTypeSub && !this.state.addIndexTypeApprox) {
+ this.props.addNotification(
+ "warning",
+ "You must select at least one 'Index Type'"
+ );
+ return;
+ }
+ if (this.state.addIndexName == "" || this.state.addIndexName.length == 0) {
+ this.props.addNotification(
+ "warning",
+ "You must select an attribute to index"
+ );
+ return;
+ }
+
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "index", "add", "--attr=" + this.state.addIndexName[0].id,
+ this.props.suffix,
+ ];
+
+ if (this.state.addIndexTypeEq) {
+ cmd.push('--index-type=eq');
+ }
+ if (this.state.addIndexTypePres) {
+ cmd.push('--index-type=pres');
+ }
+ if (this.state.addIndexTypeSub) {
+ cmd.push('--index-type=sub');
+ }
+ if (this.state.addIndexTypeApprox) {
+ cmd.push('--index-type=approx');
+ }
+ for (let i = 0; i < this.state.mrs.length; i++) {
+ cmd.push('--matching-rule=' + this.state.mrs[i].id);
+ }
+ if (this.state.reindexOnAdd) {
+ cmd.push('--reindex');
+ }
+
+ log_cmd("saveIndex", "Create new index", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ // this.loadIndexes();
+ this.props.reload(this.props.suffix);
+ this.closeIndexModal();
+ if (this.state.reindexOnAdd) {
+ this.reindexAttr(this.state.addIndexName[0].id);
+ }
+ this.props.addNotification(
+ "success",
+ `Successfully created new index`
+ );
+ })
+ .fail(err => {
+ // this.loadIndexes();
+ this.props.reload(this.props.suffix);
+ this.closeIndexModal();
+ this.props.addNotification(
+ "error",
+ `Error creating index - ${err}`
+ );
+ });
+ }
+
+ showEditIndexModal(item) {
+ // Set the state types and matching Rules
+ let currentMRS = [];
+ if (item.matchingrules !== undefined &&
+ item.matchingrules.length > 0 &&
+ item.matchingrules[0].length > 0) {
+ let parts = item.matchingrules[0].split(",").map(item => item.trim());
+ for (let part of parts) {
+ currentMRS.push({
+ id: part,
+ label: part
+ });
+ }
+ }
+ this.setState({
+ editIndexName: item.name[0],
+ types: item.types,
+ mrs: currentMRS,
+ _mrs: currentMRS,
+ showEditIndexModal: true,
+ errObj: {},
+ reindexOnAdd: false,
+ editIndexTypeEq: item.types[0].includes("eq"),
+ editIndexTypeSub: item.types[0].includes("sub"),
+ editIndexTypePres: item.types[0].includes("pres"),
+ editIndexTypeApprox: item.types[0].includes("approx"),
+ _eq: item.types[0].includes("eq"),
+ _sub: item.types[0].includes("sub"),
+ _pres: item.types[0].includes("pres"),
+ _approx: item.types[0].includes("approx"),
+ });
+ }
+
+ closeEditIndexModal() {
+ this.setState({
+ showEditIndexModal: false
+ });
+ }
+
+ handleTypeaheadChange(values, item) {
+ if (item == "matchingRules") {
+ this.setState({
+ mrs: values
+ });
+ } else if (item == "indexName") {
+ this.setState({
+ addIndexName: values
+ });
+ }
+ }
+
+ reindexAttr(attr) {
+ const reindex_cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "index", "reindex", "--wait", "--attr=" + attr, this.props.suffix,
+ ];
+
+ // Open spinner modal
+ const msg = <p>Indexing attribute: <b>{attr}</b> ...</p>;
+ this.setState({
+ showReindexModal: true,
+ reindexMsg: msg
+ });
+ log_cmd("reindexAttr", "index attribute", reindex_cmd);
+ cockpit
+ .spawn(reindex_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.addNotification(
+ "success",
+ "Attribute (" + attr + ") has successfully been reindexed"
+ );
+ this.setState({
+ showReindexModal: false,
+ });
+ })
+ .fail(err => {
+ this.props.addNotification(
+ "error",
+ `Error indexing attribute ${attr} - ${err}`
+ );
+ });
+ }
+
+ saveEditIndex() {
+ const origMRS = this.state._mrs;
+ const newMRS = this.state.mrs;
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "index", "set", "--attr=" + this.state.editIndexName,
+ this.props.suffix,
+ ];
+
+ // Make sure we have at least one index type
+ if (!this.state.editIndexTypeEq && !this.state.editIndexTypeSub &&
+ !this.state.editIndexTypePres && !this.state.editIndexTypeApprox) {
+ this.props.addNotification(
+ "warning",
+ "You must have at least one index type"
+ );
+ return;
+ }
+
+ // Check if we have to add mrs
+ for (let newMR of newMRS) {
+ let found = false;
+ for (let origMR of origMRS) {
+ if (origMR.id == newMR.id) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ cmd.push('--add-mr=' + newMR.id);
+ }
+ }
+ // Check if we have to remove mrs
+ for (let origMR of origMRS) {
+ let found = false;
+ for (let newMR of newMRS) {
+ if (newMR.id == origMR.id) {
+ console.log("Found mr no need to delete");
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ cmd.push('--del-mr=' + origMR.id);
+ }
+ }
+
+ // Check if we have to add/delete index types
+ if (this.state.editIndexTypeEq && !this.state._eq) {
+ cmd.push('--add-type=eq');
+ } else if (!this.state.editIndexTypeEq && this.state._eq) {
+ cmd.push('--del-type=eq');
+ }
+ if (this.state.editIndexTypeSub && !this.state._sub) {
+ cmd.push('--add-type=sub');
+ } else if (!this.state.editIndexTypeSub && this.state._sub) {
+ cmd.push('--del-type=sub');
+ }
+ if (this.state.editIndexTypePres && !this.state._pres) {
+ cmd.push('--add-type=pres');
+ } else if (!this.state.editIndexTypePres && this.state._pres) {
+ cmd.push('--del-type=pres');
+ }
+ if (this.state.editIndexTypeApprox && !this.state._approx) {
+ cmd.push('--add-type=approx');
+ } else if (!this.state.editIndexTypeApprox && this.state._approx) {
+ cmd.push('--del-type=approx');
+ }
+
+ if (cmd.length > 8) {
+ // We have changes, do it
+ log_cmd("saveEditIndex", "Edit index", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.closeEditIndexModal();
+ this.props.addNotification(
+ "success",
+ `Successfully edited index`
+ );
+ if (this.state.reindexOnAdd) {
+ this.reindexAttr(this.state.editIndexName);
+ }
+ })
+ .fail(err => {
+ this.props.reload(this.props.suffix);
+ this.closeEditIndexModal();
+ this.props.addNotification(
+ "error",
+ `Error editing index - ${err}`
+ );
+ });
+ }
+ }
+
+ showConfirmReindex(item) {
+ this.setState({
+ reindexAttrName: item.name,
+ showConfirmReindex: true
+ });
+ }
+
+ closeConfirmReindex(item) {
+ this.setState({
+ reindexAttrName: "",
+ showConfirmReindex: false
+ });
+ }
+
+ showConfirmDeleteIndex(item) {
+ this.setState({
+ deleteAttrName: item.name,
+ showConfirmDeleteIndex: true
+ });
+ }
+
+ closeConfirmDeleteIndex(item) {
+ this.setState({
+ deleteAttrName: "",
+ showConfirmDeleteIndex: false
+ });
+ }
+
+ reindexIndex(attr) {
+ this.reindexAttr(attr);
+ }
+
+ closeReindexModal() {
+ this.setState({
+ showReindexModal: false
+ });
+ }
+
+ deleteIndex(idxName) {
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "index", "delete", "--attr=" + idxName,
+ this.props.suffix,
+ ];
+
+ log_cmd("deleteIndex", "deleteEdit index", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ "Successfully deleted index: " + idxName
+ );
+ })
+ .fail(err => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Error deleting index - ${err}`
+ );
+ });
+ }
+
+ render() {
+ const reindex_attr = <b>{this.state.reindexAttrName}</b>;
+ const delete_attr = <b>{this.state.deleteAttrName}</b>;
+
+ return (
+ <div>
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem className="ds-nav-med" eventKey={1}>
+ <div dangerouslySetInnerHTML={{__html: 'Database Indexes'}} />
+ </NavItem>
+ <NavItem className="ds-nav-med" eventKey={2}>
+ <div dangerouslySetInnerHTML={{__html: 'System Indexes'}} />
+ </NavItem>
+ </Nav>
+ <TabContent>
+ <TabPane eventKey={1}>
+ <div className="ds-indent">
+ <IndexTable
+ editable
+ rows={this.props.indexRows}
+ editIndex={this.showEditIndexModal}
+ reindexIndex={this.showConfirmReindex}
+ deleteIndex={this.showConfirmDeleteIndex}
+ />
+ <button className="btn btn-primary ds-margin-top" type="button" onClick={this.showIndexModal} >Add Index</button>
+ </div>
+ </TabPane>
+ <TabPane eventKey={2}>
+ <div className="ds-indent">
+ <IndexTable
+ rows={this.props.systemIndexRows}
+ />
+ </div>
+ </TabPane>
+ </TabContent>
+
+ <AddIndexModal
+ showModal={this.state.showIndexModal}
+ closeHandler={this.closeIndexModal}
+ handleChange={this.handleChange}
+ saveHandler={this.saveIndex}
+ matchingRules={this.state.matchingRules}
+ attributes={this.state.attributes}
+ mrs={this.state.mrs}
+ attributeName={this.state.addIndexName}
+ handleTypeaheadChange={this.handleTypeaheadChange}
+ />
+ <EditIndexModal
+ showModal={this.state.showEditIndexModal}
+ closeHandler={this.closeEditIndexModal}
+ handleChange={this.handleChange}
+ saveHandler={this.saveEditIndex}
+ types={this.state.types}
+ mrs={this.state.mrs}
+ matchingRules={this.state.matchingRules}
+ indexName={this.state.editIndexName}
+ handleTypeaheadChange={this.handleTypeaheadChange}
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmReindex}
+ closeHandler={this.closeConfirmReindex}
+ actionFunc={this.reindexIndex}
+ actionParam={this.state.reindexAttrName}
+ msg="Are you sure you want to reindex this attribute?"
+ msgContent={reindex_attr}
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmDeleteIndex}
+ closeHandler={this.closeConfirmDeleteIndex}
+ actionFunc={this.deleteIndex}
+ actionParam={this.state.deleteAttrName}
+ msg="Are you sure you want to delete this attribute index?"
+ msgContent={delete_attr}
+ />
+ <ReindexModal
+ showModal={this.state.showReindexModal}
+ closeHandler={this.closeReindexModal}
+ msg={this.state.reindexMsg}
+ />
+ </div>
+ );
+ }
+}
+
+class AddIndexModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ matchingRules,
+ attributes,
+ mrs,
+ handleTypeaheadChange,
+ attributeName
+ } = this.props;
+
+ let availMR = [];
+ for (let mr of matchingRules) {
+ availMR.push({
+ id: mr,
+ label: mr
+ });
+ }
+ let availAttrs = [];
+ for (let attr of attributes) {
+ availAttrs.push({
+ id: attr,
+ label: attr
+ });
+ }
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Add Database Index
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <label className="ds-config-label" htmlFor="ds-index-form-list" title="Select an attribute to index">Select An Attribute</label>
+ <Typeahead
+ id="indexAttributeName"
+ onChange={values => {
+ handleTypeaheadChange(values, "indexName");
+ }}
+ selected={attributeName}
+ maxResults={1000}
+ options={availAttrs}
+ placeholder="Type a attribute name to index..."
+ />
+ <p className="ds-margin-top"><b>Index Types</b></p>
+ <div className="ds-indent">
+ <Row>
+ <Col sm={5}>
+ <Checkbox id="addIndexTypeEq" onChange={handleChange}> Equailty Indexing</Checkbox>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={5}>
+ <Checkbox id="addIndexTypePres" onChange={handleChange}> Presence Indexing</Checkbox>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={5}>
+ <Checkbox id="addIndexTypeSub" onChange={handleChange}> Substring Indexing</Checkbox>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={5}>
+ <Checkbox id="addIndexTypeApprox" onChange={handleChange}> Approximate Indexing</Checkbox>
+ </Col>
+ </Row>
+ </div>
+ <p />
+ <Row className="ds-margin-top-lg">
+ <Col sm={12} title="List of matching rules separated by a 'space'">
+ <p><b>Matching Rules</b></p>
+ <div className="ds-indent">
+ <Typeahead
+ multiple
+ id="matchingRules"
+ onChange={values => {
+ handleTypeaheadChange(values, "matchingRules");
+ }}
+ maxResults={1000}
+ selected={mrs}
+ options={availMR}
+ placeholder="Type a matching rule name..."
+ />
+ </div>
+ </Col>
+ </Row>
+ <hr />
+ <Row>
+ <Col sm={12}>
+ <Checkbox className="ds-float-right" id="reindexOnAdd" onChange={handleChange}>
+ Index attribute after creation
+ </Checkbox>
+ </Col>
+ </Row>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Create Index
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class EditIndexModal extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ mrs: [],
+ };
+ }
+
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ indexName,
+ types,
+ mrs,
+ matchingRules,
+ handleTypeaheadChange,
+ } = this.props;
+
+ let attrTypes = "";
+ if (types != "" && types.length > 0) {
+ attrTypes = types[0].split(",").map(item => item.trim());
+ }
+
+ const currentMrs = mrs;
+ let availMR = [];
+ for (let mr of matchingRules) {
+ availMR.push({
+ id: mr,
+ label: mr
+ });
+ }
+
+ // Default settings
+ let eq = <div>
+ <Checkbox id="editIndexTypeEq" onChange={handleChange}> Equailty Indexing</Checkbox>
+ </div>;
+ let pres = <div>
+ <Checkbox id="editIndexTypePres" onChange={handleChange}> Presence Indexing</Checkbox>
+ </div>;
+ let sub = <div>
+ <Checkbox id="editIndexTypeSub" onChange={handleChange}> Substring Indexing</Checkbox>
+ </div>;
+ let approx = <div>
+ <Checkbox id="editIndexTypeApprox" onChange={handleChange}> Approximate Indexing</Checkbox>
+ </div>;
+
+ if (attrTypes.includes('eq')) {
+ eq = <div>
+ <Checkbox id="editIndexTypeEq" onChange={handleChange} defaultChecked>
+ Equality Indexing
+ </Checkbox>
+ </div>;
+ }
+ if (attrTypes.includes('pres')) {
+ pres = <div>
+ <Checkbox id="editIndexTypePres" onChange={handleChange} defaultChecked>
+ Presence Indexing
+ </Checkbox>
+ </div>;
+ }
+ if (attrTypes.includes('sub')) {
+ sub = <div>
+ <Checkbox id="editIndexTypeSub" onChange={handleChange} defaultChecked>
+ Substring Indexing
+ </Checkbox>
+ </div>;
+ }
+ if (attrTypes.includes('approx')) {
+ approx = <div>
+ <Checkbox id="editIndexTypeApprox" onChange={handleChange} defaultChecked>
+ Approximate Indexing
+ </Checkbox>
+ </div>;
+ }
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div>
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Edit Database Index ({indexName})
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <p><Icon type="pf" style={{'marginRight': '15px'}} name="edit" /><font size="4"><b>{indexName}</b></font></p>
+ <hr />
+ <p><b>Index Types</b></p>
+ <div className="ds-indent">
+ <Row>
+ <Col sm={9}>
+ {eq}
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={9}>
+ {pres}
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={9}>
+ {sub}
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={9}>
+ {approx}
+ </Col>
+ </Row>
+ </div>
+ <p />
+ <Row className="ds-margin-top-lg">
+ <Col sm={12}>
+ <p><b>Matching Rules</b></p>
+ <div className="ds-indent">
+ <Typeahead
+ multiple
+ id="matchingRulesEdit"
+ onChange={values => {
+ handleTypeaheadChange(values, "matchingRules");
+ }}
+ selected={currentMrs}
+ options={availMR}
+ placeholder="Type a matching rule name..."
+ />
+ </div>
+ </Col>
+ </Row>
+ <hr />
+ <Row>
+ <Col sm={12}>
+ <Checkbox className="ds-float-right" id="reindexOnAdd" onChange={handleChange}>
+ Reindex Attribute After Saving
+ </Checkbox>
+ </Col>
+ </Row>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Save Index
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+// Property types and defaults
+
+SuffixIndexes.propTypes = {
+ systemIndexRows: PropTypes.array,
+ indexRows: PropTypes.array,
+ serverId: PropTypes.string,
+ suffix: PropTypes.string,
+ addNotification: PropTypes.func,
+ reload: PropTypes.func,
+};
+
+SuffixIndexes.defaultProps = {
+ systemIndexRows: [],
+ indexRows: [],
+ serverId: "",
+ suffix: "",
+ addNotification: noop,
+ reload: noop,
+};
+
+AddIndexModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ matchingRules: PropTypes.array,
+ attributes: PropTypes.array,
+ mrs: PropTypes.array,
+ attributeName: PropTypes.array,
+ handleTypeaheadChange: PropTypes.func,
+};
+
+AddIndexModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ matchingRules: [],
+ attributes: [],
+ mrs: [],
+ attributeName: [],
+ handleTypeaheadChange: noop,
+};
+
+EditIndexModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ matchingRules: PropTypes.array,
+ types: PropTypes.array,
+ mrs: PropTypes.array,
+ indexName: PropTypes.string,
+ handleTypeaheadChange: PropTypes.func,
+};
+
+EditIndexModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ matchingRules: [],
+ types: [],
+ mrs: [],
+ indexName: "",
+ handleTypeaheadChange: noop,
+};
diff --git a/src/cockpit/389-console/src/lib/database/referrals.jsx b/src/cockpit/389-console/src/lib/database/referrals.jsx
new file mode 100644
index 0000000..d86c8c4
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/referrals.jsx
@@ -0,0 +1,414 @@
+import cockpit from "cockpit";
+import React from "react";
+import { ConfirmPopup } from "../notifications.jsx";
+import { ReferralTable } from "./databaseTables.jsx";
+import {
+ Modal,
+ Row,
+ ControlLabel,
+ Col,
+ Icon,
+ Button,
+ Form,
+ noop
+} from "patternfly-react";
+import { log_cmd } from "../tools.jsx";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+export class SuffixReferrals extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ showConfirmRefDelete: false,
+ showRefModal: false,
+ removeRef: "",
+ refProtocol: "ldap://",
+ refHost: "",
+ refPort: "",
+ refSuffix: "",
+ refFilter: "",
+ refScope: "",
+ refAttrs: "",
+ refValue: "",
+ errObj: {},
+ };
+
+ // Delete referral and confirmation
+ this.showConfirmRefDelete = this.showConfirmRefDelete.bind(this);
+ this.closeConfirmRefDelete = this.closeConfirmRefDelete.bind(this);
+ this.deleteRef = this.deleteRef.bind(this);
+ this.saveRef = this.saveRef.bind(this);
+ // Referral modal
+ this.showRefModal = this.showRefModal.bind(this);
+ this.closeRefModal = this.closeRefModal.bind(this);
+ this.handleRefChange = this.handleRefChange.bind(this);
+ this.buildRef = this.buildRef.bind(this);
+ }
+
+ showConfirmRefDelete(item) {
+ this.setState({
+ removeRef: item.name,
+ showConfirmRefDelete: true
+ });
+ }
+
+ showRefModal() {
+ this.setState({
+ showRefModal: true,
+ errObj: {},
+ refValue: ""
+ });
+ }
+
+ closeConfirmRefDelete() {
+ this.setState({
+ showConfirmRefDelete: false
+ });
+ }
+
+ closeRefModal() {
+ this.setState({
+ showRefModal: false
+ });
+ }
+
+ deleteRef() {
+ // take state.removeRef and remove it from ldap
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "suffix", "set", "--del-referral=" + this.state.removeRef, this.props.suffix
+ ];
+ log_cmd("deleteRef", "Delete suffix referral", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ `Referral successfully deleted`
+ );
+ })
+ .fail(err => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Failure deleting referral - ${err}`
+ );
+ });
+ }
+
+ // Create referral
+ saveRef() {
+ // validate
+ let missingArgs = {
+ refHost: false,
+ refPort: false
+ };
+ let errors = false;
+ if (this.state.refHost == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing hostname for LDAP URL`
+ );
+ missingArgs.refHost = true;
+ errors = true;
+ }
+ if (this.state.refPort == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing port for LDAP URL`
+ );
+ missingArgs.refPort = true;
+ errors = true;
+ }
+ if (errors) {
+ this.setState({
+ errObj: missingArgs
+ });
+ return;
+ }
+
+ // Add referral
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "suffix", "set", "--add-referral=" + this.state.refValue, this.props.suffix
+ ];
+ log_cmd("saveRef", "Add referral", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.closeRefModal();
+ this.props.addNotification(
+ "success",
+ `Referral successfully created`
+ );
+ })
+ .fail(err => {
+ this.props.reload(this.props.suffix);
+ this.closeRefModal();
+ this.props.addNotification(
+ "error",
+ `Failure creating referral - ${err}`
+ );
+ });
+ }
+
+ buildRef() {
+ let previewRef = this.state.refProtocol + this.state.refHost;
+ let ref_port = this.state.refPort;
+ let ref_suffix = this.state.refSuffix;
+ let ref_attrs = this.state.refAttrs;
+ let ref_filter = this.state.refFilter;
+ let ref_scope = this.state.refScope;
+
+ if (ref_port != "") {
+ previewRef += ":" + ref_port;
+ }
+ if (ref_suffix != "" || ref_attrs != "" || ref_filter != "" || ref_scope != "") {
+ previewRef += "/" + encodeURIComponent(ref_suffix);
+ if (ref_attrs != "") {
+ previewRef += "?" + encodeURIComponent(ref_attrs);
+ } else if (ref_filter != "" || ref_scope != "") {
+ previewRef += "?";
+ }
+ if (ref_scope != "") {
+ previewRef += "?" + encodeURIComponent(ref_scope);
+ } else if (ref_filter != "") {
+ previewRef += "?";
+ }
+ if (ref_filter != "") {
+ previewRef += "?" + encodeURIComponent(ref_filter);
+ }
+ }
+
+ // Update referral value
+ this.setState({
+ refValue: previewRef
+ });
+ }
+
+ handleRefChange(e) {
+ const value = e.target.value;
+ const key = e.target.id;
+ let errObj = this.state.errObj;
+ let valueErr = false;
+
+ if (value == "") {
+ if (key == "refHost" || key == "refPort") {
+ valueErr = true;
+ }
+ }
+ errObj[e.target.id] = valueErr;
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj
+ }, this.buildRef);
+ }
+
+ render() {
+ let refs = [];
+ for (let row of this.props.rows) {
+ refs.push({name: row});
+ }
+
+ return (
+ <div className="ds-sub-header">
+ <ReferralTable
+ rows={refs}
+ loadModalHandler={this.showConfirmRefDelete}
+ />
+ <button className="btn btn-primary ds-margin-top" onClick={this.showRefModal} type="button">Create Referral</button>
+ <ConfirmPopup
+ showModal={this.state.showConfirmRefDelete}
+ closeHandler={this.closeConfirmRefDelete}
+ actionFunc={this.deleteRef}
+ actionParam={this.state.removeRef}
+ msg="Are you sure you want to delete this referral?"
+ msgContent={this.state.removeRef}
+ />
+ <AddReferralModal
+ showModal={this.state.showRefModal}
+ closeHandler={this.closeRefModal}
+ handleChange={this.handleRefChange}
+ saveHandler={this.saveRef}
+ previewValue={this.state.refValue}
+ error={this.state.errObj}
+ />
+ </div>
+ );
+ }
+}
+
+class AddReferralModal extends React.Component {
+ render() {
+ let {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ previewValue,
+ error
+ } = this.props;
+
+ if (previewValue == "") {
+ previewValue = "ldap://";
+ }
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ Add Database Referral
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <Row>
+ <Col sm={3}>
+ <ControlLabel>Protocol</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <select
+ defaultValue="ldap://" onChange={this.props.handleChange} className="btn btn-default dropdown" id="refProtocol">
+ <option>ldap://</option>
+ <option>ldaps://</option>
+ </select>
+ </Col>
+ </Row>
+ <p />
+ <Row>
+ <Col sm={3}>
+ <ControlLabel>Host Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <input className={error.refHost ? "ds-input-auto-bad" : "ds-input-auto"} type="text" onChange={handleChange} id="refHost" />
+ </Col>
+ </Row>
+ <p />
+ <Row>
+ <Col sm={3}>
+ <ControlLabel>Port Number</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <input className={error.refPort ? "ds-input-auto-bad" : "ds-input-auto"} type="text" onChange={handleChange} id="refPort" />
+ </Col>
+ </Row>
+ <p />
+ <Row>
+ <Col sm={3}>
+ <ControlLabel>Suffix</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <input className="ds-input-auto" onChange={handleChange} type="text" id="refSuffix" />
+ </Col>
+ </Row>
+ <p />
+ <Row title="Comma separated list of attributes to return">
+ <Col sm={3}>
+ <ControlLabel>Attributes</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <input className="ds-input-auto" onChange={handleChange} type="text" id="refAttrs" />
+ </Col>
+ </Row>
+ <p />
+ <Row>
+ <Col sm={3}>
+ <ControlLabel>Filter</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <input onChange={handleChange} className="ds-input-auto" type="text" id="refFilter" />
+ </Col>
+ </Row>
+ <p />
+ <Row>
+ <Col sm={3}>
+ <ControlLabel>Scope</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <select className="btn btn-default dropdown" onChange={handleChange} defaultValue="" name="refScope">
+ <option />
+ <option>sub</option>
+ <option>one</option>
+ <option>base</option>
+ </select>
+ </Col>
+ </Row>
+ <hr />
+ <Row>
+ <Col sm={3}>
+ <label className="ds-label-sm">Computed Referral</label>
+ </Col>
+ <Col sm={9}>
+ <input className="ds-input-auto" value={previewValue} readOnly />
+ </Col>
+ </Row>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ >
+ Create Referral
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+// Property types and defaults
+
+SuffixReferrals.propTypes = {
+ rows: PropTypes.array,
+ suffix: PropTypes.string,
+ reload: PropTypes.func,
+ addNotification: PropTypes.func,
+ serverId: PropTypes.string,
+};
+
+SuffixReferrals.defaultProps = {
+ rows: [],
+ suffix: "",
+ reload: noop,
+ addNotification: noop,
+ serverId: "",
+};
+
+AddReferralModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ previewValue: PropTypes.string,
+ error: PropTypes.object,
+};
+
+AddReferralModal.defaultProps = {
+ showModal: noop,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ previewValue: "",
+ error: {},
+};
diff --git a/src/cockpit/389-console/src/lib/database/suffix.jsx b/src/cockpit/389-console/src/lib/database/suffix.jsx
new file mode 100644
index 0000000..aa3b99c
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/suffix.jsx
@@ -0,0 +1,906 @@
+import cockpit from "cockpit";
+import React from "react";
+import { ConfirmPopup } from "../notifications.jsx";
+import { AttrEncryption } from "./attrEncryption.jsx";
+import { SuffixConfig } from "./suffixConfig.jsx";
+import { SuffixReferrals } from "./referrals.jsx";
+import { SuffixIndexes } from "./indexes.jsx";
+import { VLVIndexes } from "./vlvIndexes.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ ImportModal,
+ ExportModal,
+ ReindexModal,
+ CreateSubSuffixModal,
+ CreateLinkModal,
+} from "./databaseModal.jsx";
+import {
+ DropdownButton,
+ MenuItem,
+ Nav,
+ NavItem,
+ Row,
+ Col,
+ ControlLabel,
+ Icon,
+ TabContent,
+ TabPane,
+ TabContainer,
+ noop
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+export class Suffix extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ loading: false,
+ activeKey: 1,
+ notifications: [],
+ errObj: {},
+ refRows: this.props.data.refRows,
+ encAttrsRows: this.props.data.encAttrsRows,
+ vlvItems: this.props.data.vlvItems,
+ autoTuning: this.props.data.autoTuning,
+ // Suffix configuration
+ cachememsize: this.props.data.cachememsize,
+ cachesize: this.props.data.cachesize,
+ dncachememsize: this.props.data.dncachememsize,
+ readOnly: this.props.data.readOnly,
+ requireIndex: this.props.data.requireIndex,
+ _cachememsize: this.props.data.cachememsize,
+ _cachesize: this.props.data.cachesize,
+ _dncachememsize: this.props.data.dncachememsize,
+ _readOnly: this.props.data.readOnly,
+ _requireIndex: this.props.data.requireIndex,
+
+ // Import/Export modals
+ showImportModal: false,
+ showExportModal: false,
+ ldifLocation: "",
+ attrEncryption: false,
+ exportSpinner: false,
+ importSpinner: false,
+ showConfirmLDIFImport: false,
+ deleleLDIFName: "",
+ // Reindex all
+ showReindexConfirm: false,
+ // Create Sub Suffix
+ showSubSuffixModal: false,
+ subSuffixValue: "",
+ subSuffixBeName: "",
+ // Create Link
+ showLinkModal: false,
+ createLinkSuffix: "",
+ createLinkName: "",
+ createNsfarmserverurl: "",
+ createNsmultiplexorbinddn: "",
+ createNsmultiplexorcredentials: "",
+ createNsmultiplexorcredentialsConfirm: "",
+ createUseStartTLS: false,
+ createNsbindmechanism: "SIMPLE",
+ linkPwdMatch: false,
+ // Delete
+ showDeleteConfirm: false,
+ };
+
+ // General bindings
+ this.handleNavSelect = this.handleNavSelect.bind(this);
+ // Import modal
+ this.showImportModal = this.showImportModal.bind(this);
+ this.closeImportModal = this.closeImportModal.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.doImport = this.doImport.bind(this);
+ this.importLDIF = this.importLDIF.bind(this);
+ this.showConfirmLDIFImport = this.showConfirmLDIFImport.bind(this);
+ this.closeConfirmLDIFImport = this.closeConfirmLDIFImport.bind(this);
+ // Export modal
+ this.showExportModal = this.showExportModal.bind(this);
+ this.closeExportModal = this.closeExportModal.bind(this);
+ this.doExport = this.doExport.bind(this);
+ // Reindex Suffix Modal
+ this.showReindexConfirm = this.showReindexConfirm.bind(this);
+ this.closeReindexConfirm = this.closeReindexConfirm.bind(this);
+ this.doReindex = this.doReindex.bind(this);
+ // Create sub suffix modal
+ this.showSubSuffixModal = this.showSubSuffixModal.bind(this);
+ this.closeSubSuffixModal = this.closeSubSuffixModal.bind(this);
+ this.createSubSuffix = this.createSubSuffix.bind(this);
+ // Create link modal
+ this.showLinkModal = this.showLinkModal.bind(this);
+ this.closeLinkModal = this.closeLinkModal.bind(this);
+ this.createLink = this.createLink.bind(this);
+ this.handleLinkChange = this.handleLinkChange.bind(this);
+ // Suffix config
+ this.saveSuffixConfig = this.saveSuffixConfig.bind(this);
+ // Attr Encrypt Modal
+ this.showDeleteConfirm = this.showDeleteConfirm.bind(this);
+ this.closeDeleteConfirm = this.closeDeleteConfirm.bind(this);
+ this.doDelete = this.doDelete.bind(this);
+ }
+
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ //
+ // Import Modal
+ //
+ showImportModal() {
+ this.setState({
+ ldifLocation: "",
+ attrEncryption: false,
+ showImportModal: true,
+ importSpinner: false,
+ errObj: {},
+ });
+ }
+
+ closeImportModal() {
+ this.setState({
+ showImportModal: false
+ });
+ }
+
+ handleChange(e) {
+ const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj
+ });
+ }
+
+ showConfirmLDIFImport (item) {
+ // call deleteLDIF
+ this.setState({
+ showConfirmLDIFImport: true,
+ importLDIFName: item.name,
+ });
+ }
+
+ closeConfirmLDIFImport () {
+ // call importLDIF
+ this.setState({
+ showConfirmLDIFImport: false,
+ });
+ }
+
+ importLDIF (ldif) {
+ // Do import
+ let import_cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "import", this.props.suffix, ldif, "--encrypted"
+ ];
+
+ this.setState({
+ importSpinner: true,
+ });
+
+ log_cmd("doImport", "Do online import", import_cmd);
+ cockpit
+ .spawn(import_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.addNotification(
+ "success",
+ `Import successfully initiated`
+ );
+ this.setState({
+ showImportModal: false
+ });
+ })
+ .fail(err => {
+ this.props.addNotification(
+ "error",
+ `Error importing LDIF file - ${err}`
+ );
+ this.setState({
+ showImportModal: false
+ });
+ });
+ }
+
+ doImport() {
+ // Validate form before proceeding
+ if (this.state.ldifLocation != "") {
+ this.setState({
+ showConfirmLDIFImport: true,
+ importLDIFName: this.state.ldifLocation,
+ });
+ }
+ }
+
+ //
+ // Export modal
+ //
+ showExportModal() {
+ this.setState({
+ ldifLocation: "",
+ attrEncryption: false,
+ showExportModal: true,
+ exportSpinner: false,
+ errObj: {},
+ });
+ }
+
+ closeExportModal() {
+ this.setState({
+ showExportModal: false,
+ exportSpinner: false
+ });
+ }
+
+ doExport() {
+ let missingArgs = {ldifLocation: false};
+ if (this.state.ldifLocation == "") {
+ this.props.addNotification(
+ "warning",
+ `LDIF name is empty`
+ );
+ missingArgs.ldifLocation = true;
+ this.setState({
+ errObj: missingArgs
+ });
+ return;
+ }
+
+ // Do import
+ let export_cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "export", this.props.suffix, "--ldif=" + this.state.ldifLocation, "--encrypted"
+ ];
+
+ if (this.state.attrEncrpytion) {
+ export_cmd.push("--encrypted");
+ }
+
+ this.setState({
+ exportSpinner: true,
+ });
+
+ log_cmd("doExport", "Do online export", export_cmd);
+ cockpit
+ .spawn(export_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reloadLDIFs();
+ this.props.addNotification(
+ "success",
+ `Database export complete`
+ );
+ this.setState({
+ showExportModal: false,
+ });
+ })
+ .fail(err => {
+ this.loadLDIFs();
+ this.props.addNotification(
+ "error",
+ `Error exporting database - ${err}`
+ );
+ this.setState({
+ showExportModal: false,
+ });
+ });
+ }
+
+ //
+ // Reindex entire database
+ //
+ showReindexConfirm() {
+ this.setState({
+ showReindexConfirm: true
+ });
+ }
+
+ closeReindexConfirm() {
+ this.setState({
+ showReindexConfirm: false
+ });
+ }
+
+ closeReindexModal() {
+ this.setState({
+ showReindexModal: false
+ });
+ }
+
+ doReindex() {
+ // Show index status modal
+ this.setState({
+ showReindexModal: true
+ });
+ const cmd = ["dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "index", "reindex", "--wait", this.props.suffix];
+ log_cmd("doReindex", "Reindex all attributes", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.addNotification(
+ "success",
+ `Database has successfully been reindexed`
+ );
+ this.setState({
+ showReindexModal: false,
+ });
+ })
+ .fail(err => {
+ this.props.addNotification(
+ "error",
+ `Failed to reindex database - ${err}`
+ );
+ this.setState({
+ showReindexModal: false,
+ });
+ });
+ }
+
+ //
+ // Create sub suffix
+ //
+ showSubSuffixModal() {
+ this.setState({
+ showSubSuffixModal: true,
+ errObj: {},
+ });
+ }
+
+ closeSubSuffixModal() {
+ this.setState({
+ showSubSuffixModal: false
+ });
+ }
+
+ createSubSuffix() {
+ let missingArgs = {
+ createSuffix: false,
+ createBeName: false
+ };
+ let errors = false;
+ if (this.state.subSuffixValue == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing Suffix`
+ );
+ missingArgs.subSuffixValue = true;
+ errors = true;
+ }
+ if (this.state.subSuffixBeName == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing backend name`
+ );
+ missingArgs.subSuffixBeName = true;
+ errors = true;
+ }
+ if (errors) {
+ this.setState({
+ errObj: missingArgs
+ });
+ return;
+ }
+
+ // Create a new suffix
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "create", "--be-name", this.state.subSuffixBeName,
+ "--suffix=" + this.state.subSuffixValue + "," + this.props.suffix,
+ "--parent-suffix=" + this.props.suffix
+ ];
+
+ log_cmd("createSubSuffix", "Create a sub suffix", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.loadSuffixTree(false);
+ this.closeSubSuffixModal();
+ this.props.addNotification(
+ "success",
+ `Successfully created new sub-suffix`
+ );
+ })
+ .fail(err => {
+ this.props.loadSuffixTree(false);
+ this.closeSubSuffixModal();
+ this.props.addNotification(
+ "error",
+ `Error creating sub-suffix - ${err}`
+ );
+ });
+ }
+
+ //
+ // Create Chaining Link
+ //
+ showLinkModal() {
+ this.setState({
+ showLinkModal: true,
+ linkPwdMatch: true,
+ errObj: {},
+ });
+ }
+
+ closeLinkModal() {
+ this.setState({
+ showLinkModal: false
+ });
+ }
+
+ createLink() {
+ // Check for required paramters
+ let formError = false;
+ let missingArgs = {
+ createLinkSuffix: false,
+ createNsfarmserverurl: false,
+ createLinkName: false,
+ createNsmultiplexorbinddn: false,
+ createNsmultiplexorcredentials: false,
+ createNsmultiplexorcredentialsConfirm: false
+ };
+ if (this.state.createLinkSuffix == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing subsuffix!`
+ );
+ missingArgs.createLinkSuffix = true;
+ formError = true;
+ }
+ if (this.state.createNsfarmserverurl == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing Server URL!`
+ );
+ missingArgs.createNsfarmserverurl = true;
+ formError = true;
+ }
+ if (this.state.createLinkName == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing Link Name`
+ );
+ missingArgs.createLinkName = true;
+ formError = true;
+ }
+ if (this.state.createNsmultiplexorbinddn == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing Bind DN`
+ );
+ missingArgs.createNsmultiplexorbinddn = true;
+ formError = true;
+ }
+ // Check passwords match
+ if (this.state.createNsmultiplexorcredentials == "" &&
+ this.state.createNsmultiplexorcredentialsConfirm == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing Bind Password`
+ );
+ missingArgs.createNsmultiplexorcredentialsConfirm = true;
+ missingArgs.createNsmultiplexorcredentials = true;
+ formError = true;
+ }
+ if (this.state.createNsmultiplexorcredentials != this.state.createNsmultiplexorcredentialsConfirm) {
+ this.props.addNotification(
+ "warning",
+ `Passwords do not match`
+ );
+ missingArgs.createNsmultiplexorcredentialsConfirm = true;
+ missingArgs.createNsmultiplexorcredentials = false;
+ formError = true;
+ }
+ if (formError) {
+ this.setState({
+ errObj: missingArgs
+ });
+ return;
+ }
+
+ // Add chaining link
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "chaining", "link-create",
+ "--suffix=" + this.state.createLinkSuffix + "," + this.props.suffix,
+ "--server-url=" + this.state.createNsfarmserverurl,
+ "--bind-mech=" + this.state.createNsbindmechanism,
+ "--bind-dn=" + this.state.createNsmultiplexorbinddn,
+ "--bind-pw=" + this.state.createNsmultiplexorcredentials,
+ this.state.createLinkName
+ ];
+ if (this.state.createUseStartTLS) {
+ cmd.push("--use-starttls");
+ }
+ log_cmd("createLink", "Create database link", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.loadSuffixTree(false);
+ this.closeLinkModal();
+ this.props.addNotification(
+ "success",
+ `Successfully created database link`
+ );
+ })
+ .fail(err => {
+ this.props.loadSuffixTree(false);
+ this.closeLinkModal();
+ this.props.addNotification(
+ "error",
+ `Error creating database link - ${err}`
+ );
+ });
+ }
+
+ checkPasswords() {
+ let pwdMatch = false;
+ if (this.state.createNsmultiplexorcredentials == this.state.createNsmultiplexorcredentialsConfirm) {
+ pwdMatch = true;
+ }
+ this.setState({
+ linkPwdMatch: pwdMatch
+ });
+ }
+
+ handleLinkChange(e) {
+ // Check for matching credentials
+ const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj
+ }, this.checkPasswords);
+ }
+
+ //
+ // Delete suffix
+ //
+ showDeleteConfirm(item) {
+ this.setState({
+ showDeleteConfirm: true
+ });
+ }
+
+ closeDeleteConfirm() {
+ this.setState({
+ showDeleteConfirm: false
+ });
+ }
+
+ doDelete() {
+ // Delete suffix
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "delete", this.props.suffix
+ ];
+ log_cmd("doDelete", "Delete database", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.loadSuffixTree(true);
+ this.closeLinkModal();
+ this.props.addNotification(
+ "success",
+ `Successfully deleted database`
+ );
+ })
+ .fail(err => {
+ this.props.loadSuffixTree(true);
+ this.closeLinkModal();
+ this.props.addNotification(
+ "error",
+ `Error deleting database - ${err}`
+ );
+ });
+ }
+
+ // Save config
+ saveSuffixConfig() {
+ console.log("Save suffix config: ", this.props.suffix);
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'backend', 'suffix', 'set', this.props.suffix
+ ];
+ let requireRestart = false;
+ if (this.state._readOnly != this.state.readOnly) {
+ if (this.state.readOnly) {
+ cmd.push("--enable-readonly");
+ } else {
+ cmd.push("--disable-readonly");
+ }
+ }
+ if (this.state._requireIndex != this.state.requireIndex) {
+ if (this.state.requireIndex) {
+ cmd.push("--require-index");
+ } else {
+ cmd.push("--ignore-index");
+ }
+ }
+ if (this.state._cachememsize != this.state.cachememsize) {
+ cmd.push("--cache-memsize=" + this.state.cachememsize);
+ requireRestart = true;
+ }
+ if (this.state._cachesize != this.state.cachesize) {
+ cmd.push("--cache-size=" + this.state.cachesize);
+ requireRestart = true;
+ }
+ if (this.state._dncachememsize != this.state.dncachememsize) {
+ cmd.push("--dncache-memsize=" + this.state.dncachememsize);
+ requireRestart = true;
+ }
+ if (cmd.length > 7) {
+ log_cmd("saveSuffixConfig", "Save suffix config", cmd);
+ let msg = "Successfully updated suffix configuration";
+ if (requireRestart) {
+ msg = ("Successfully updated suffix configuration. These " +
+ "changes require the server to be restarted to take effect");
+ }
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ // Continue with the next mod
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ msg
+ );
+ })
+ .fail(err => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Error updating suffix configuration - ${err}`
+ );
+ });
+ }
+ }
+
+ //
+ // Render the component
+ //
+ render () {
+ let suffixIcon = "tree";
+ if (this.props.dbtype == "subsuffix") {
+ suffixIcon = "leaf";
+ }
+
+ const confirm_msg =
+ <span>
+ Are you sure you want to import: <b>{this.state.importLDIFName}</b> ?
+ </span>;
+
+ return (
+ <div className="container-fluid" id="suffix-page">
+ <Row>
+ <Col sm={10} className="ds-word-wrap">
+ <ControlLabel className="ds-suffix-header"><Icon type="fa" name={suffixIcon} /> {this.props.suffix} (<i>{this.props.bename}</i>)</ControlLabel>
+ </Col>
+ <Col sm={2}>
+ <div>
+ <DropdownButton bsStyle="primary" title="Suffix Tasks" id="mydropdown">
+ <MenuItem eventKey="1" onClick={this.showImportModal} title="Import an LDIF file to initialize the database">
+ Initialize Suffix
+ </MenuItem>
+ <MenuItem eventKey="2" onClick={this.showExportModal} title="Export the database to an LDIF file">
+ Export Suffix
+ </MenuItem>
+ <MenuItem eventKey="3" onClick={this.showReindexConfirm} title="Reindex the entire database">
+ Reindex Suffix
+ </MenuItem>
+ <MenuItem eventKey="4" onClick={this.showSubSuffixModal} title="Create a sub-suffix under this suffix">
+ Create Sub-Suffix
+ </MenuItem>
+ <MenuItem eventKey="5" onClick={this.showLinkModal} title="Create a database chaining link subtree">
+ Create Database Link
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem eventKey="6" onClick={this.showDeleteConfirm} title="This will permanently delete the database">
+ Delete Suffix
+ </MenuItem>
+ </DropdownButton>
+ </div>
+ </Col>
+ </Row>
+ <p />
+
+ <TabContainer id="basic-tabs-pf" onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <div>
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem eventKey={1}>
+ <div dangerouslySetInnerHTML={{__html: 'Settings'}} />
+ </NavItem>
+ <NavItem eventKey={2}>
+ <div dangerouslySetInnerHTML={{__html: 'Referrals'}} />
+ </NavItem>
+ <NavItem eventKey={3}>
+ <div dangerouslySetInnerHTML={{__html: 'Indexes'}} />
+ </NavItem>
+ <NavItem eventKey={5}>
+ <div dangerouslySetInnerHTML={{__html: 'VLV Indexes'}} />
+ </NavItem>
+ <NavItem eventKey={4}>
+ <div dangerouslySetInnerHTML={{__html: 'Encrypted Attributes'}} />
+ </NavItem>
+ </Nav>
+ <TabContent>
+
+ <TabPane eventKey={1}>
+ <SuffixConfig
+ cachememsize={this.state.cachememsize}
+ cachesize={this.state.cachesize}
+ dncachememsize={this.state.dncachememsize}
+ readOnly={this.state.readOnly}
+ requireIndex={this.state.requireIndex}
+ autoTuning={this.state.autoTuning}
+ handleChange={this.handleChange}
+ saveHandler={this.saveSuffixConfig}
+ />
+ </TabPane>
+
+ <TabPane eventKey={2}>
+ <SuffixReferrals
+ rows={this.props.data.refRows}
+ suffix={this.props.suffix}
+ reload={this.props.reloadRefs}
+ addNotification={this.props.addNotification}
+ serverId={this.props.serverId}
+ key={this.state.refRows}
+ />
+ </TabPane>
+
+ <TabPane eventKey={3}>
+ <div className="ds-indent ds-tab-table">
+ <TabContainer id="index-tabs" defaultActiveKey={1}>
+ <SuffixIndexes
+ systemIndexRows={this.props.data.systemIndexRows}
+ indexRows={this.props.data.indexRows}
+ suffix={this.props.suffix}
+ serverId={this.props.serverId}
+ addNotification={this.props.addNotification}
+ reload={this.props.reloadIndexes}
+ />
+ </TabContainer>
+ </div>
+ </TabPane>
+ <TabPane eventKey={4}>
+ <div className="ds-sub-header">
+ <AttrEncryption
+ rows={this.props.data.encAttrsRows}
+ suffix={this.props.suffix}
+ serverId={this.props.serverId}
+ addNotification={this.props.addNotification}
+ attrs={this.props.attrs}
+ reload={this.props.reloadAttrEnc}
+ />
+ </div>
+ </TabPane>
+ <TabPane eventKey={5}>
+ <VLVIndexes
+ suffix={this.props.suffix}
+ serverId={this.props.serverId}
+ vlvItems={this.props.data.vlvItems}
+ addNotification={this.props.addNotification}
+ attrs={this.props.attrs}
+ reload={this.props.reloadVLV}
+ />
+ </TabPane>
+ </TabContent>
+ </div>
+ </TabContainer>
+
+ <ConfirmPopup
+ showModal={this.state.showDeleteConfirm}
+ closeHandler={this.closeDeleteConfirm}
+ actionFunc={this.doDelete}
+ actionParam={this.props.suffix}
+ msg="Are you really sure you want to delete the database?"
+ msgContent={this.props.suffix}
+ />
+ <CreateLinkModal
+ showModal={this.state.showLinkModal}
+ closeHandler={this.closeLinkModal}
+ handleChange={this.handleLinkChange}
+ saveHandler={this.createLink}
+ suffix={this.props.suffix}
+ pwdMatch={this.state.linkPwdMatch}
+ error={this.state.errObj}
+ />
+ <CreateSubSuffixModal
+ showModal={this.state.showSubSuffixModal}
+ closeHandler={this.closeSubSuffixModal}
+ handleChange={this.handleChange}
+ saveHandler={this.createSubSuffix}
+ suffix={this.props.suffix}
+ error={this.state.errObj}
+ />
+ <ImportModal
+ showModal={this.state.showImportModal}
+ closeHandler={this.closeImportModal}
+ handleChange={this.handleChange}
+ saveHandler={this.doImport}
+ showConfirmImport={this.showConfirmLDIFImport}
+ spinning={this.state.importSpinner}
+ rows={this.props.LDIFRows}
+ suffix={this.props.suffix}
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmLDIFImport}
+ closeHandler={this.closeConfirmLDIFImport}
+ actionFunc={this.importLDIF}
+ actionParam={this.state.importLDIFName}
+ msg={confirm_msg}
+ msgContent="WARNING: This action will permanently overwrite the current database!"
+ />
+
+ <ExportModal
+ showModal={this.state.showExportModal}
+ closeHandler={this.closeExportModal}
+ handleChange={this.handleChange}
+ saveHandler={this.doExport}
+ spinning={this.state.exportSpinner}
+ error={this.state.errObj}
+ />
+ <ConfirmPopup
+ showModal={this.state.showReindexConfirm}
+ closeHandler={this.closeReindexConfirm}
+ actionFunc={this.doReindex}
+ actionParam={this.props.suffix}
+ msg="Are you sure you want to reindex all the attribute indexes?"
+ msgContent=""
+ />
+ <ReindexModal
+ showModal={this.state.showReindexModal}
+ closeHandler={this.closeReindexModal}
+ msg="Reindexing All Attribute Indexes"
+ />
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+Suffix.propTypes = {
+ serverId: PropTypes.string,
+ suffix: PropTypes.string,
+ bename: PropTypes.string,
+ loadSuffixTree: PropTypes.func,
+ reload: PropTypes.func,
+ reloadRefs: PropTypes.func,
+ reloadIndexes: PropTypes.func,
+ reloadVLV: PropTypes.func,
+ reloadAttrEnc: PropTypes.func,
+ reloadLDIFs: PropTypes.func,
+ addNotification: PropTypes.func,
+ dbtype: PropTypes.string,
+ data: PropTypes.object,
+ attrs: PropTypes.array,
+ LDIFRows: PropTypes.array,
+};
+
+Suffix.defaultProps = {
+ serverId: "",
+ suffix: "",
+ bename: "",
+ loadSuffixTree: noop,
+ reload: noop,
+ reloadRefs: noop,
+ reloadIndexes: noop,
+ reloadVLV: noop,
+ reloadAttrEnc: noop,
+ reloadLDIFs: noop,
+ addNotification: noop,
+ dbtype: "",
+ data: {},
+ attrs: [],
+ LDIFRows: []
+};
diff --git a/src/cockpit/389-console/src/lib/database/suffixConfig.jsx b/src/cockpit/389-console/src/lib/database/suffixConfig.jsx
new file mode 100644
index 0000000..ec9cd60
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/suffixConfig.jsx
@@ -0,0 +1,89 @@
+import React from "react";
+import "../../css/ds.css";
+import PropTypes from "prop-types";
+import { noop } from "patternfly-react";
+
+export class SuffixConfig extends React.Component {
+ render() {
+ let cacheInputs;
+ if (this.props.autoTuning) {
+ const cacheValue = this.props.cachesize + " (auto-sized)";
+ const cachememValue = this.props.cachememsize + " (auto-sized)";
+ cacheInputs =
+ <div>
+ <div title="The entry cache size setting is being auto-sized and is read-only - see Global Database Configuration">
+ <label htmlFor="cachememsize" className="ds-config-label-lrg">
+ Entry Cache Size (bytes)</label><input disabled value={cachememValue} className="ds-input" type="text" id="cachememsize" size="45" />
+ </div>
+ <div title="The entry cache max entries setting is being auto-sized and is read-only - see Global Database Configuration">
+ <label htmlFor="cachesize" className="ds-config-label-lrg">
+ Entry Cache Max Entries</label><input disabled value={cacheValue} className="ds-input" id="cachesize" type="text" size="45" />
+ </div>
+ <div>
+ <label htmlFor="dncachememsize" className="ds-config-label-lrg" title="the available memory space for the DN cache. The DN cache is similar to the entry cache for a database, only its table stores only the entry ID and the entry DN (nsslapd-dncachememsize).">
+ DN Cache Size (bytes)</label><input onChange={this.props.handleChange} value={this.props.dncachememsize} className="ds-input" type="text" id="dncachememsize" size="45" />
+ </div>
+ </div>;
+ } else {
+ cacheInputs =
+ <div>
+ <div>
+ <label htmlFor="cachememsize" className="ds-config-label-lrg" title="The size for the available memory space for the entry cache (nsslapd-cachememsize).">
+ Entry Cache Size (bytes)</label><input onChange={this.props.handleChange} value={this.props.cachememsize} className="ds-input" type="text" id="cachememsize" size="30" />
+ </div>
+ <div>
+ <label htmlFor="cachesize" className="ds-config-label-lrg" title="The number of entries to keep in the entry cache, use'-1' for unlimited (nsslapd-cachesize).">
+ Entry Cache Max Entries</label><input onChange={this.props.handleChange} value={this.props.cachesize} className="ds-input" type="text" id="cachesize" size="30" />
+ </div>
+ <div>
+ <label htmlFor="dncachememsize" className="ds-config-label-lrg" title="the available memory space for the DN cache. The DN cache is similar to the entry cache for a database, only its table stores only the entry ID and the entry DN (nsslapd-dncachememsize).">
+ DN Cache Size (bytes)</label><input onChange={this.props.handleChange} value={this.props.dncachememsize} className="ds-input" type="text" id="dncachememsize" size="30" />
+ </div>
+ </div>;
+ }
+ return (
+ <div className="ds-margin-top-lg">
+ <p />
+ {cacheInputs}
+ <p />
+ <div>
+ <div>
+ <input type="checkbox" onChange={this.props.handleChange} checked={this.props.readOnly} className="ds-config-checkbox" id="readOnly" /><label
+ htmlFor="readOnly" className="ds-label" title="Put database in Read-Only mode (nsslapd-readonly)."> Database Read-Only Mode</label>
+ </div>
+ <div>
+ <input type="checkbox" onChange={this.props.handleChange} checked={this.props.requireIndex} className="ds-config-checkbox" id="requireIndex" /><label
+ htmlFor="requireIndex" className="ds-label" title="Block unindexed searches on this suffix (nsslapd-require-index)."> Block Unindexed Searches</label>
+ </div>
+ </div>
+ <div className="ds-save-btn">
+ <button className="btn btn-primary save-button" onClick={this.props.saveHandler}>Save Configuration</button>
+ </div>
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+SuffixConfig.propTypes = {
+ cachememsize: PropTypes.string,
+ cachesize: PropTypes.string,
+ dncachememsize: PropTypes.string,
+ readOnly: PropTypes.bool,
+ requireIndex: PropTypes.bool,
+ autoTuning: PropTypes.bool,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+};
+
+SuffixConfig.defaultProps = {
+ cachememsize: "",
+ cachesize: "",
+ dncachememsize: "",
+ readOnly: false,
+ requireIndex: false,
+ autoTuning: false,
+ handleChange: noop,
+ saveHandler: noop,
+};
diff --git a/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx b/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx
new file mode 100644
index 0000000..674f8ec
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx
@@ -0,0 +1,951 @@
+import cockpit from "cockpit";
+import React from "react";
+import { ConfirmPopup } from "../notifications.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ DropdownButton,
+ MenuItem,
+ ListView,
+ ListViewItem,
+ ListViewIcon,
+ Modal,
+ Row,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Icon,
+ Button,
+ Form,
+ sortableHeaderCellFormatter,
+ actionHeaderCellFormatter,
+ tableCellFormatter,
+ noop
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import { Typeahead } from "react-bootstrap-typeahead";
+import { DSShortTable } from "../dsTable.jsx";
+import "../../css/ds.css";
+
+export class VLVIndexes extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ vlvItems: [],
+ showVLVModal: false,
+ showVLVEditModal: false,
+ showDeleteConfirm: false,
+ showReindexConfirm: false,
+ errObj: {},
+ vlvName: "",
+ vlvBase: "",
+ vlvScope: "subtree",
+ vlvFilter: "",
+ vlvSortList: [],
+ _vlvName: "",
+ _vlvBase: "",
+ _vlvScope: "",
+ _vlvFilter: "",
+ _vlvSortList: [],
+ };
+
+ // Create VLV Modal
+ this.showVLVModal = this.showVLVModal.bind(this);
+ this.closeVLVModal = this.closeVLVModal.bind(this);
+ this.handleVLVChange = this.handleVLVChange.bind(this);
+ this.handleVLVSortChange = this.handleVLVSortChange.bind(this);
+ this.saveVLV = this.saveVLV.bind(this);
+ this.saveEditVLV = this.saveEditVLV.bind(this);
+ this.deleteVLV = this.deleteVLV.bind(this);
+ this.reindexVLV = this.reindexVLV.bind(this);
+ // Edit VLV modal
+ this.showVLVEditModal = this.showVLVEditModal.bind(this);
+ this.closeVLVEditModal = this.closeVLVEditModal.bind(this);
+ this.showDeleteConfirm = this.showDeleteConfirm.bind(this);
+ this.closeDeleteConfirm = this.closeDeleteConfirm.bind(this);
+ this.showReindexConfirm = this.showReindexConfirm.bind(this);
+ this.closeReindexConfirm = this.closeReindexConfirm.bind(this);
+ }
+
+ //
+ // VLV index functions
+ //
+ showVLVModal() {
+ this.setState({
+ showVLVModal: true,
+ errObj: {},
+ vlvName: "",
+ vlvBase: "",
+ vlvScope: "subtree",
+ vlvFilter: "",
+ vlvSortList: [],
+ vlvSortRows: [],
+ });
+ }
+
+ closeVLVModal() {
+ this.setState({
+ showVLVModal: false
+ });
+ }
+
+ handleVLVChange(e) {
+ const value = e.target.value;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj
+ });
+ }
+
+ handleVLVSortChange(sorts) {
+ // "sorts" is a table obj that uses the key "sortName"
+ let sortList = [];
+ for (let sort of sorts) {
+ // Create array of sorts from array of objs
+ sortList.push(sort.sortName);
+ }
+ this.setState({
+ vlvSortList: sortList
+ });
+ }
+
+ // Edit VLVIndex
+ showVLVEditModal(e) {
+ const vlvName = e.target.name;
+ for (let item of this.props.vlvItems) {
+ const vlvAttrs = item.attrs;
+ if (vlvAttrs.cn[0] == vlvName) {
+ let sortRows = [];
+ let sortList = [];
+ for (let vlvSort of item.sorts) {
+ sortRows.push({sortName: vlvSort.attrs.vlvsort[0]});
+ sortList.push(vlvSort.attrs.vlvsort[0]);
+ }
+ this.setState({
+ showVLVEditModal: true,
+ errObj: {},
+ vlvName: vlvAttrs.cn[0],
+ vlvBase: vlvAttrs.vlvbase[0],
+ vlvScope: this.getScopeKey(vlvAttrs.vlvscope[0]),
+ vlvFilter: vlvAttrs.vlvfilter[0],
+ vlvSortList: sortList,
+ vlvSortRows: sortRows,
+ _vlvName: vlvAttrs.cn[0],
+ _vlvBase: vlvAttrs.vlvbase[0],
+ _vlvScope: this.getScopeKey(vlvAttrs.vlvscope[0]),
+ _vlvFilter: vlvAttrs.vlvfilter[0],
+ _vlvSortList: sortList,
+ _vlvSortRows: sortRows,
+ });
+ break;
+ }
+ }
+ }
+
+ closeVLVEditModal() {
+ this.setState({
+ showVLVEditModal: false
+ });
+ }
+
+ getScopeVal(scope) {
+ let mapping = {
+ 'subtree': '2',
+ 'one': '1',
+ 'base': '0'
+ };
+ return mapping[scope];
+ }
+
+ getScopeKey(scope) {
+ let mapping = {
+ '2': 'subtree',
+ '1': 'one',
+ '0': 'base'
+ };
+ return mapping[scope];
+ }
+
+ saveEditVLV() {
+ let missingArgs = {};
+ let errors = false;
+ if (this.state.vlvBase == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing VLV search base`
+ );
+ missingArgs.vlvBase = true;
+ errors = true;
+ }
+ if (this.state.vlvFilter == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing VLV search filter`
+ );
+ missingArgs.vlvFilter = true;
+ errors = true;
+ }
+ if (errors) {
+ this.setState({
+ errObj: missingArgs
+ });
+ return;
+ }
+
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "vlv-index", "edit-search", "--name=" + this.state.vlvName,
+ this.props.suffix
+ ];
+ if (this.state.vlvBase != this.state._vlvBase) {
+ cmd.push("--search-base=" + this.state.vlvBase);
+ }
+ if (this.state.vlvScope != this.state._vlvScope) {
+ cmd.push("--search-scope=" + this.getScopeVal(this.state.vlvScope));
+ }
+ if (this.state.vlvFilter != this.state._vlvFilter) {
+ cmd.push("--search-filter=" + this.state.vlvFilter);
+ }
+ if (cmd.length > 8) {
+ log_cmd("saveEditVLV", "Add vlv search", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.closeVLVEditModal();
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ "Successfully edited VLV search"
+ );
+ })
+ .fail(err => {
+ this.closeVLVEditModal();
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Failed to edit VLV search - ${err}`
+ );
+ });
+ }
+
+ // Check the sort indexes now
+ // Loop over sorts and create indexes or each one
+ let addIndexList = [];
+ let delIndexList = [];
+ for (let sort of this.state.vlvSortList) {
+ if (this.state._vlvSortList.indexOf(sort) == -1) {
+ // Add sort index
+ addIndexList.push(sort);
+ }
+ }
+ for (let sort of this.state._vlvSortList) {
+ if (this.state.vlvSortList.indexOf(sort) == -1) {
+ // Del sort index
+ delIndexList.push(sort);
+ }
+ }
+
+ // Add VLV index/sort
+ for (let index of addIndexList) {
+ cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "vlv-index", "add-index", "--parent-name=" + this.state.vlvName,
+ "--index-name=" + this.state.vlvName + " - " + index,
+ "--sort=" + index, this.props.suffix
+ ];
+ if (this.state.reindexVLV) {
+ cmd.push("--index-it");
+ }
+ log_cmd("saveEditVLV", "Add index", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.closeVLVEditModal();
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ "Successfully added VLV index " + this.state.vlvName + " - " + index
+ );
+ })
+ .fail(err => {
+ this.closeVLVEditModal();
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Failed to add VLV index entry - ${err}`
+ );
+ });
+ }
+
+ // Del VLV index/sort
+ for (let index of delIndexList) {
+ cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "vlv-index", "del-index", "--parent-name=" + this.state.vlvName,
+ "--sort=" + index, this.props.suffix
+ ];
+ if (this.state.reindexVLV) {
+ cmd.push("--index-it");
+ }
+ log_cmd("saveEditVLV", "delete index", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.closeVLVEditModal();
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ "Successfully added VLV index " + this.state.vlvName + " - " + index
+ );
+ })
+ .fail(err => {
+ this.closeVLVEditModal();
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Failed to add VLV index entry - ${err}`
+ );
+ });
+ }
+ }
+
+ saveVLV() {
+ let missingArgs = {};
+ let errors = false;
+ if (this.state.vlvName == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing VLV Search Name`
+ );
+ missingArgs.vlvName = true;
+ errors = true;
+ }
+ if (this.state.vlvBase == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing VLV search base`
+ );
+ missingArgs.vlvBase = true;
+ errors = true;
+ }
+ if (this.state.vlvFilter == "") {
+ this.props.addNotification(
+ "warning",
+ `Missing VLV search filter`
+ );
+ missingArgs.vlvFilter = true;
+ errors = true;
+ }
+ if (errors) {
+ this.setState({
+ errObj: missingArgs
+ });
+ return;
+ }
+
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "vlv-index", "add-search",
+ "--name=" + this.state.vlvName,
+ "--search-base=" + this.state.vlvBase,
+ "--search-filter=" + this.state.vlvFilter,
+ "--search-scope=" + this.getScopeVal(this.state.vlvScope),
+ this.props.suffix
+ ];
+ log_cmd("saveVLV", "Add vlv search", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ "Successfully added VLV search: " + this.state.vlvName
+ );
+ // Loop over sorts and create indexes or each one
+ for (let sort of this.state.vlvSortList) {
+ const indexName = this.state.vlvName + " - " + sort;
+ let idx_cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "vlv-index", "add-index",
+ "--parent-name=" + this.state.vlvName,
+ "--index-name=" + indexName,
+ "--sort=" + sort,
+ this.props.suffix
+ ];
+ if (this.state.reindexVLV) {
+ idx_cmd.push("--index");
+ }
+ log_cmd("saveVLV", "Add vlv index", idx_cmd);
+ cockpit
+ .spawn(idx_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ "Successfully added VLV index: " + indexName
+ );
+ })
+ .fail(err => {
+ this.props.addNotification(
+ "error",
+ `Failed create VLV index entry - ${err}`
+ );
+ });
+ }
+ })
+ .fail(err => {
+ this.props.addNotification(
+ "error",
+ `Failed create VLV search entry - ${err}`
+ );
+ });
+ this.closeVLVModal();
+ }
+
+ renderVLVActions(id) {
+ return (
+ <div>
+ <DropdownButton bsStyle="default" title="Actions" id={id}>
+ <MenuItem eventKey="1" name={id} onClick={this.showVLVEditModal}>
+ Edit VLV Index
+ </MenuItem>
+ <MenuItem eventKey="2" name={id} onClick={this.showReindexConfirm}>
+ Reindex VLV Index
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem eventKey="3" name={id} onClick={this.showDeleteConfirm}>
+ Delete VLV Index
+ </MenuItem>
+ </DropdownButton>
+ </div>
+ );
+ }
+
+ showDeleteConfirm (e) {
+ this.setState({
+ showDeleteConfirm: true,
+ deleteVLVName: e.target.name
+ });
+ }
+
+ closeDeleteConfirm () {
+ this.setState({
+ showDeleteConfirm: false,
+ });
+ }
+
+ deleteVLV(vlv) {
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "vlv-index", "del-search", "--name=" + vlv, this.props.suffix
+ ];
+ log_cmd("deleteVLV", "delete LV search and indexes", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ `Successfully deleted VLV index`
+ );
+ })
+ .fail(err => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Failed to deletre VLV index - ${err}`
+ );
+ });
+ }
+
+ showReindexConfirm (e) {
+ this.setState({
+ showReindexConfirm: true,
+ reindexVLVName: e.target.name
+ });
+ }
+
+ closeReindexConfirm () {
+ this.setState({
+ showReindexConfirm: false,
+ });
+ }
+
+ reindexVLV(vlv) {
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "backend", "vlv-index", "reindex", "--parent-name=" + vlv, this.props.suffix
+ ];
+ log_cmd("reindexVLV", "reindex VLV indexes", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ `Successfully completed VLV indexing`
+ );
+ })
+ .fail(err => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Failed to index VLV index - ${err}`
+ );
+ });
+ }
+
+ render() {
+ const vlvIndexes = this.props.vlvItems.map((vlvItem) =>
+ <ListViewItem
+ actions={this.renderVLVActions(vlvItem.attrs.cn[0])}
+ leftContent={<ListViewIcon name="list" />}
+ key={vlvItem.attrs.cn[0]}
+ heading={vlvItem.attrs.cn[0]}
+ >
+ <Row>
+ <Col sm={11} key={vlvItem.dn}>
+ <p key={vlvItem.dn + "-p"}><label className="ds-vlv-label">Base</label>{vlvItem.attrs.vlvbase[0]}</p>
+ <p><label className="ds-vlv-label">Filter</label>{vlvItem.attrs.vlvfilter[0]}</p>
+ <p><label className="ds-vlv-label">Scope</label>{this.getScopeKey(vlvItem.attrs.vlvscope[0])}</p>
+ <hr />
+ {
+ vlvItem.sorts.map(sort => {
+ let indexState;
+ if (sort.attrs.vlvenabled[0] == "0") {
+ indexState = <font size="1" color="#d01c8b"><b>Disabled</b></font>;
+ } else {
+ indexState = <font size="1" color="#4dac26"><b>Uses: </b>{sort.attrs.vlvuses[0]}</font>;
+ }
+ return (<p key={sort.dn + sort.attrs.vlvsort[0]}><label className="ds-vlv-label">Sort</label>{sort.attrs.vlvsort[0]} ({indexState})</p>);
+ })
+ }
+ </Col>
+ </Row>
+ </ListViewItem>
+ );
+
+ return (
+ <div className="ds-tab-table">
+ <h5>Virtual List View Indexes</h5>
+ <ListView>
+ {vlvIndexes}
+ </ListView>
+ <button className="btn btn-primary ds-margin-top" onClick={this.showVLVModal} type="button">Create VLV Index</button>
+ <AddVLVModal
+ showModal={this.state.showVLVModal}
+ closeHandler={this.closeVLVModal}
+ handleChange={this.handleVLVChange}
+ handleSortChange={this.handleVLVSortChange}
+ saveHandler={this.saveVLV}
+ error={this.state.errObj}
+ attrs={this.props.attrs}
+ />
+ <AddVLVModal
+ showModal={this.state.showVLVEditModal}
+ closeHandler={this.closeVLVEditModal}
+ handleChange={this.handleVLVChange}
+ handleSortChange={this.handleVLVSortChange}
+ saveHandler={this.saveEditVLV}
+ edit
+ error={this.state.errObj}
+ attrs={this.props.attrs}
+ vlvName={this.state.vlvName}
+ vlvBase={this.state.vlvBase}
+ vlvScope={this.state.vlvScope}
+ vlvFilter={this.state.vlvFilter}
+ vlvSortList={this.state.vlvSortRows}
+ />
+ <ConfirmPopup
+ showModal={this.state.showDeleteConfirm}
+ closeHandler={this.closeDeleteConfirm}
+ actionFunc={this.deleteVLV}
+ actionParam={this.state.deleteVLVName}
+ msg="Are you sure you want to delete this VLV index?"
+ msgContent={this.state.deleteVLVName}
+ />
+ <ConfirmPopup
+ showModal={this.state.showReindexConfirm}
+ closeHandler={this.closeReindexConfirm}
+ actionFunc={this.reindexVLV}
+ actionParam={this.state.reindexVLVName}
+ msg="Are you sure you want to reindex this VLV index?"
+ msgContent={this.state.reindexVLVName}
+ />
+ </div>
+ );
+ }
+}
+
+// Add and edit modal
+class AddVLVModal extends React.Component {
+ constructor(props) {
+ super(props);
+ let sortRows = [];
+ if (this.props.edit !== undefined && this.props.vlvSortList !== undefined) {
+ sortRows = this.props.vlvSortList;
+ }
+ this.state = {
+ sortRows: sortRows,
+ sortValue: "",
+ columns: [],
+ edit: false,
+ };
+
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+ this.updateSorts = this.updateSorts.bind(this);
+ this.handleVLVSortChange = this.handleVLVSortChange.bind(this);
+ this.close = this.close.bind(this);
+ this.save = this.save.bind(this);
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "VLV Sort Indexes",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ getColumns () {
+ return [
+ {
+ property: "sortName",
+ header: {
+ label: "VLV Sort Indexes",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ label: "",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.sortName[0]}>
+ <Button
+ onClick={() => {
+ this.handleVLVSortChange(
+ rowData
+ );
+ }}
+ >
+ Remove
+ </Button>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ];
+ }
+
+ handleVLVSortChange(e) {
+ // VLV index was removed from table
+ let rows = this.state.sortRows;
+ for (let i = 0; i < rows.length; i++) {
+ if (rows[i].sortName == e.sortName) {
+ rows.splice(i, 1);
+ this.setState({
+ sortRows: rows
+ });
+ this.props.handleSortChange(rows);
+ return;
+ }
+ }
+ }
+
+ handleTypeaheadChange(values) {
+ const value = values.join(' ');
+ this.setState({
+ sortValue: value
+ });
+ }
+
+ updateSorts() {
+ let rows = this.state.sortRows;
+ if (this.state.sortValue != "") {
+ rows.push({sortName: this.state.sortValue});
+ this.typeahead.getInstance().clear();
+ this.setState({
+ sortRows: rows,
+ sortValue: ""
+ });
+ this.props.handleSortChange(rows);
+ }
+ }
+
+ save() {
+ // reset the rows, and call the save handler
+ this.state.sortRows = [];
+ this.props.saveHandler();
+ }
+
+ close() {
+ this.state.sortRows = [];
+ this.props.closeHandler();
+ }
+
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ error,
+ attrs
+ } = this.props;
+ let title;
+ let nameInput;
+ let base = "";
+ let scope = "subtree";
+ let filter = "";
+ let sortTable;
+
+ if (this.props.edit) {
+ title = "Edit VLV Index";
+ nameInput = <input className="ds-input-auto" type="text" value={this.props.vlvName} readOnly />;
+ base = this.props.vlvBase;
+ scope = this.props.vlvScope;
+ filter = this.props.vlvFilter;
+ if (this.props.vlvSortList !== undefined) {
+ // {sortName: "value"},
+ this.state.sortRows = this.props.vlvSortList;
+ }
+ } else {
+ // Create new index
+ // this.state.sortRows = [];
+ title = "Create VLV Index";
+ nameInput = <input className={error.vlvName ? "ds-input-auto-bad" : "ds-input-auto"} type="text" onChange={handleChange} id="vlvName" />;
+ }
+
+ let vlvscope =
+ <Row>
+ <Col sm={3}>
+ <ControlLabel>Search Scope</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <select defaultValue={scope}
+ onChange={this.props.handleChange} className="btn btn-default dropdown" id="vlvScope">
+ <option>subtree</option>
+ <option>one</option>
+ <option>base</option>
+ </select>
+ </Col>
+ </Row>;
+
+ if (this.state.sortRows.length == 0) {
+ sortTable = <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{msg: "No sort indexes"}]}
+ />;
+ } else {
+ sortTable = <DSShortTable
+ getColumns={this.getColumns}
+ rowKey={"sortName"}
+ rows={this.state.sortRows}
+ />;
+ }
+
+ return (
+ <Modal show={showModal} onHide={this.close}>
+ <div>
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>
+ {title}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <Row>
+ <Col sm={3}>
+ <ControlLabel>VLV Index Name</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ {nameInput}
+ </Col>
+ </Row>
+ <p />
+ <Row>
+ <Col sm={3}>
+ <ControlLabel>Search Base</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <input className={error.vlvBase ? "ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange} type="text" id="vlvBase" defaultValue={base} />
+ </Col>
+ </Row>
+ <p />
+ <Row>
+ <Col sm={3}>
+ <ControlLabel>Search Filter</ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <input className={error.vlvFilter ? "ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange} type="text" id="vlvFilter" defaultValue={filter} />
+ </Col>
+ </Row>
+ <p />
+ {vlvscope}
+ <hr />
+ <div>
+ <p />
+ <div>
+ {sortTable}
+ <p />
+ <Typeahead
+ multiple
+ id="vlvsortindex"
+ onChange={values => {
+ this.handleTypeaheadChange(values);
+ }}
+ maxResults={1000}
+ options={attrs}
+ placeholder="Start typing attribute names to create a sort index"
+ ref={(typeahead) => { this.typeahead = typeahead }}
+ />
+ <p />
+ <button type="button" onClick={this.updateSorts}>Add Sort Index</button>
+ </div>
+ </div>
+ <hr />
+ <Row>
+ <Col sm={12}>
+ <Checkbox className="ds-float-right" id="reindexVLV" onChange={handleChange}>
+ Index VLV on Save
+ </Checkbox>
+ </Col>
+ </Row>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.close}
+ >
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={this.save}
+ >
+ Save VLV Index
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+// Property types and defaults
+
+VLVIndexes.propTypes = {
+ suffix: PropTypes.string,
+ serverId: PropTypes.string,
+ vlvItems: PropTypes.array,
+ addNotification: PropTypes.func,
+ attrs: PropTypes.array,
+ reload: PropTypes.func,
+};
+
+VLVIndexes.defaultProps = {
+ suffix: "",
+ serverId: "",
+ vlvItems: [],
+ addNotification: noop,
+ attrs: [],
+ reload: noop,
+};
+
+AddVLVModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleSortChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ edit: PropTypes.bool,
+ error: PropTypes.object,
+ attrs: PropTypes.array,
+ vlvName: PropTypes.string,
+ vlvBase: PropTypes.string,
+ vlvScope: PropTypes.string,
+ vlvFilter: PropTypes.string,
+ vlvSortList: PropTypes.array,
+};
+
+AddVLVModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ handleSortChange: noop,
+ saveHandler: noop,
+ edit: false,
+ error: {},
+ attrs: [],
+ vlvName: "",
+ vlvBase: "",
+ vlvScope: "",
+ vlvFilter: "",
+ vlvSortList: [],
+};
diff --git a/src/cockpit/389-console/src/lib/plugins/pluginTable.jsx b/src/cockpit/389-console/src/lib/dsTable.jsx
similarity index 54%
copy from src/cockpit/389-console/src/lib/plugins/pluginTable.jsx
copy to src/cockpit/389-console/src/lib/dsTable.jsx
index ca69207..4b17840 100644
--- a/src/cockpit/389-console/src/lib/plugins/pluginTable.jsx
+++ b/src/cockpit/389-console/src/lib/dsTable.jsx
@@ -3,13 +3,8 @@ import {
PaginationRow,
paginate,
Table,
- Button,
- noop,
- actionHeaderCellFormatter,
customHeaderFormattersDefinition,
defaultSortingOrder,
- sortableHeaderCellFormatter,
- tableCellFormatter,
TABLE_SORT_DIRECTION,
PAGINATION_VIEW
} from "patternfly-react";
@@ -18,17 +13,32 @@ import { orderBy } from "lodash";
import * as sort from "sortabular";
import * as resolve from "table-resolver";
import { compose } from "recompose";
-import { searchFilter } from "../tools.jsx";
-import CustomTableToolbar from "../customTableToolbar.jsx";
-import "../../css/ds.css";
+import { searchFilter } from "./tools.jsx";
+import CustomTableToolbar from "./customTableToolbar.jsx";
+import "../css/ds.css";
-class PluginTable extends React.Component {
+class DSTable extends React.Component {
+ // props:
+ // getColumns={this.getColumns}
+ // fieldsToSearch={this.state.fieldsToSearch}
+ // searchField={this.state.searchField}
+ // rowKey
+ // rows: table rows
+ //
constructor(props) {
super(props);
// Point the transform to your sortingColumns. React state can work for this purpose
// but you can use a state manager as well.
const getSortingColumns = () => this.state.sortingColumns || {};
+ let pagination_list = [6, 12, 24, 48, 96];
+ let pagination_per_page = 12;
+ if (this.props.toolBarPagination !== undefined) {
+ pagination_list = this.props.toolBarPagination;
+ }
+ if (this.props.toolBarPaginationPerPage !== undefined) {
+ pagination_per_page = this.props.toolBarPaginationPerPage;
+ }
const sortableTransform = sort.sort({
getSortingColumns,
@@ -51,6 +61,15 @@ class PluginTable extends React.Component {
strategy: sort.strategies.byProperty
});
+ // Update column formatter stuff
+ let customColumns = this.props.getColumns();
+ for (let customColumn of customColumns) {
+ if (customColumn.header.formatters.length == 0) {
+ customColumn.header.formatters = [sortingFormatter];
+ customColumn.header.transforms = [sortableTransform];
+ }
+ }
+
// Enables our custom header formatters extensions to reactabular
this.customHeaderFormatters = customHeaderFormattersDefinition;
@@ -69,7 +88,7 @@ class PluginTable extends React.Component {
this.state = {
searchFilterValue: "",
- fieldsToSearch: ["cn", "nsslapd-pluginType"],
+ fieldsToSearch: this.props.fieldsToSearch,
// Sort the first column in an ascending way by default.
sortingColumns: {
name: {
@@ -77,111 +96,13 @@ class PluginTable extends React.Component {
position: 0
}
},
- columns: [
- {
- property: "cn",
- header: {
- label: "Plugin Name",
- props: {
- index: 0,
- rowSpan: 1,
- colSpan: 1,
- sort: true
- },
- transforms: [sortableTransform],
- formatters: [sortingFormatter],
- customFormatters: [sortableHeaderCellFormatter]
- },
- cell: {
- props: {
- index: 0
- },
- formatters: [tableCellFormatter]
- }
- },
- {
- property: "nsslapd-pluginType",
- header: {
- label: "Plugin Type",
- props: {
- index: 1,
- rowSpan: 1,
- colSpan: 1,
- sort: true
- },
- transforms: [sortableTransform],
- formatters: [sortingFormatter],
- customFormatters: [sortableHeaderCellFormatter]
- },
- cell: {
- props: {
- index: 1
- },
- formatters: [tableCellFormatter]
- }
- },
- {
- property: "nsslapd-pluginEnabled",
- header: {
- label: "Enabled",
- props: {
- index: 2,
- rowSpan: 1,
- colSpan: 1,
- sort: true
- },
- transforms: [sortableTransform],
- formatters: [sortingFormatter],
- customFormatters: [sortableHeaderCellFormatter]
- },
- cell: {
- props: {
- index: 2
- },
- formatters: [tableCellFormatter]
- }
- },
- {
- property: "actions",
- header: {
- label: "Actions",
- props: {
- index: 3,
- rowSpan: 1,
- colSpan: 1
- },
- formatters: [actionHeaderCellFormatter]
- },
- cell: {
- props: {
- index: 3
- },
- formatters: [
- (value, { rowData }) => {
- return [
- <td key={rowData.cn[0]}>
- <Button
- onClick={() => {
- this.props.loadModalHandler(
- rowData
- );
- }}
- >
- Edit Plugin
- </Button>
- </td>
- ];
- }
- ]
- }
- }
- ],
+ columns: customColumns,
// pagination default states
pagination: {
page: 1,
- perPage: 12,
- perPageOptions: [6, 12, 24]
+ perPage: pagination_per_page,
+ perPageOptions: pagination_list
},
// page input value
@@ -285,20 +206,28 @@ class PluginTable extends React.Component {
sortingColumns,
pageChangeValue
} = this.state;
-
const filteredRows = this.filteredSearchedRows();
const sortedPaginatedRows = this.currentRows(filteredRows);
+ let dsSearchBar =
+ <CustomTableToolbar
+ searchFilterValue={this.state.searchFilterValue}
+ handleValueChange={this.handleSearchValueChange}
+ children={this.props.toolBarChildren}
+ className={this.props.toolBarClassName}
+ placeholder={this.props.toolBarPlaceholder}
+ modelToSearch={this.props.toolBarSearchField}
+ loading={this.props.toolBarLoading}
+ disableLoadingSpinner={this.props.toolBarDisableLoadingSpinner}
+ />;
+ if (this.props.noSearchBar) {
+ dsSearchBar = "";
+ }
return (
<div>
- <CustomTableToolbar
- modelToSearch="Plugins"
- searchFilterValue={this.state.searchFilterValue}
- handleValueChange={this.handleSearchValueChange}
- disableLoadingSpinner
- />
+ {dsSearchBar}
<Table.PfProvider
- className="display ds-repl-table"
+ className="display ds-db-table"
striped
hover
dataTable
@@ -322,7 +251,7 @@ class PluginTable extends React.Component {
/>
<Table.Body
rows={sortedPaginatedRows.rows}
- rowKey="cn"
+ rowKey={this.props.rowKey}
onRow={() => ({
role: "row"
})}
@@ -330,6 +259,7 @@ class PluginTable extends React.Component {
</Table.PfProvider>
<PaginationRow
viewType={PAGINATION_VIEW.TABLE}
+ className="ds-table-pagination"
pagination={pagination}
pageInputValue={pageChangeValue}
amountOfPages={sortedPaginatedRows.amountOfPages}
@@ -343,20 +273,140 @@ class PluginTable extends React.Component {
onNextPage={this.onNextPage}
onLastPage={this.onLastPage}
onSubmit={this.onSubmit}
+ id={this.props.searchField}
/>
</div>
);
}
}
-PluginTable.propTypes = {
+class DSShortTable extends React.Component {
+ // This table is designed for smaller tables that don't need pagination or filtering,
+ // and can be empty. So log a nice "empty" value we need the columns to be dynamic
+ // and relaoded every time we render
+ constructor(props) {
+ super(props);
+
+ // Enables our custom header formatters extensions to reactabular
+ this.customHeaderFormatters = customHeaderFormattersDefinition;
+ this.currentRows = this.currentRows.bind(this);
+
+ this.state = {
+ // Sort the first column in an ascending way by default.
+ sortingColumns: {
+ name: {
+ direction: TABLE_SORT_DIRECTION.ASC,
+ position: 0
+ }
+ },
+ };
+ }
+
+ currentRows(rows, columns) {
+ const { sortingColumns } = this.state;
+ return compose(
+ sort.sorter({
+ columns: columns,
+ sortingColumns,
+ sort: orderBy,
+ strategy: sort.strategies.byProperty
+ })
+ )(rows);
+ }
+
+ render() {
+ const sortingColumns = this.state.sortingColumns;
+ const getSortingColumns = () => this.state.sortingColumns || {};
+ const sortableTransform = sort.sort({
+ getSortingColumns,
+ onSort: selectedColumn => {
+ this.setState({
+ sortingColumns: sort.byColumn({
+ sortingColumns: this.state.sortingColumns,
+ sortingOrder: defaultSortingOrder,
+ selectedColumn
+ })
+ });
+ },
+ // Use property or index dependening on the sortingColumns structure specified
+ strategy: sort.strategies.byProperty
+ });
+
+ const sortingFormatter = sort.header({
+ sortableTransform,
+ getSortingColumns,
+ strategy: sort.strategies.byProperty
+ });
+
+ let columns = this.props.getColumns();
+ for (let column of columns) {
+ if (column.header.formatters.length == 0) {
+ column.header.formatters = [sortingFormatter];
+ column.header.transforms = [sortableTransform];
+ }
+ }
+ const sortedRows = this.currentRows(this.props.rows, columns);
+
+ return (
+ <div>
+ <Table.PfProvider
+ className="display ds-db-table"
+ striped
+ hover
+ dataTable
+ columns={columns}
+ components={{
+ header: {
+ cell: cellProps => {
+ return this.customHeaderFormatters({
+ cellProps,
+ columns,
+ sortingColumns,
+ rows: sortedRows.rows
+ });
+ }
+ }
+ }}
+ >
+ <Table.Header
+ className="ds-table-header"
+ headerRows={resolve.headerRows({ columns })}
+ />
+ <Table.Body
+ rows={sortedRows}
+ rowKey={this.props.rowKey}
+ onRow={() => ({
+ role: "row"
+ })}
+ />
+ </Table.PfProvider>
+ </div>
+ );
+ }
+}
+
+// Properties
+
+DSTable.propTypes = {
+ getColumns: PropTypes.func,
+ fieldsToSearch: PropTypes.array,
+ rowKey: PropTypes.string,
rows: PropTypes.array,
- loadModalHandler: PropTypes.func,
+ toolBarSearchField: PropTypes.string,
+ toolBarChildren: PropTypes.any,
+ toolBarClassName: PropTypes.string,
+ toolBarPlaceholder: PropTypes.string,
+ toolBarLoading: PropTypes.bool,
+ toolBarDisableLoadingSpinner: PropTypes.bool,
+ toolBarPagination: PropTypes.array,
+ toolBarPaginationPerPage: PropTypes.number,
+ noSearchBar: PropTypes.bool
};
-PluginTable.defaultProps = {
- rows: [],
- loadModalHandler: noop,
+DSShortTable.propTypes = {
+ getColumns: PropTypes.func,
+ rowKey: PropTypes.string,
+ rows: PropTypes.array
};
-export default PluginTable;
+export { DSTable, DSShortTable };
diff --git a/src/cockpit/389-console/src/lib/notifications.jsx b/src/cockpit/389-console/src/lib/notifications.jsx
index 06ad141..a2b23e5 100644
--- a/src/cockpit/389-console/src/lib/notifications.jsx
+++ b/src/cockpit/389-console/src/lib/notifications.jsx
@@ -1,6 +1,8 @@
import React from "react";
import PropTypes from "prop-types";
import {
+ Icon,
+ MessageDialog,
TimedToastNotification,
ToastNotificationList
} from "patternfly-react";
@@ -8,7 +10,6 @@ import {
class NotificationController extends React.Component {
render() {
const { notifications, removeNotificationAction } = this.props;
-
return (
<ToastNotificationList>
{notifications.map(notification => (
@@ -23,7 +24,11 @@ class NotificationController extends React.Component {
{notification.header && (
<strong>{notification.header}</strong>
)}
- {notification.message}
+ {notification.type == "error" ? (
+ <pre>{notification.message}</pre>
+ ) : (
+ <span>{notification.message}</span>
+ )}
</span>
</TimedToastNotification>
))}
@@ -41,4 +46,61 @@ NotificationController.defaultProps = {
notifications: []
};
-export default NotificationController;
+class ConfirmPopup extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {};
+
+ // Chaining OIDs
+ this.primaryAction = this.primaryAction.bind(this);
+ }
+
+ primaryAction() {
+ this.props.actionFunc(this.props.actionParam);
+ this.props.closeHandler();
+ }
+
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ } = this.props;
+
+ let secondaryContent = "";
+ if (this.props.msgContent !== undefined) {
+ if (this.props.msgContent.constructor === Array) {
+ // Comma separate the lines of this list
+ secondaryContent = this.props.msgContent.map((item) =>
+ <p key={item}><b>{item}</b></p>);
+ } else {
+ secondaryContent = <p><b>{this.props.msgContent}</b></p>;
+ }
+ }
+
+ const icon = <Icon type="pf" style={{'fontSize':'30px', 'marginRight': '15px'}}
+ name="warning-triangle-o" />;
+ const msg = <p className="lead">{this.props.msg}</p>;
+
+ return (
+ <React.Fragment>
+ <MessageDialog
+ className="ds-confirm"
+ show={showModal}
+ onHide={closeHandler}
+ primaryAction={this.primaryAction}
+ secondaryAction={closeHandler}
+ primaryActionButtonContent="Yes"
+ secondaryActionButtonContent="No"
+ title="Confirmation"
+ icon={icon}
+ primaryContent={msg}
+ secondaryContent={secondaryContent}
+ accessibleName="questionDialog"
+ accessibleDescription="questionDialogContent"
+ />
+ </React.Fragment>
+ );
+ }
+}
+
+export { NotificationController, ConfirmPopup };
diff --git a/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx b/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx
index d8358e9..7b21201 100644
--- a/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/pluginModal.jsx
@@ -67,7 +67,7 @@ class PluginEditModal extends React.Component {
controlId="currentPluginEnabled"
disabled={false}
>
- <Col componentClass={ControlLabel} sm={6}>
+ <Col componentClass={ControlLabel} sm={5}>
Plugin Status
</Col>
<Col sm={6}>
@@ -83,7 +83,7 @@ class PluginEditModal extends React.Component {
</FormGroup>
{Object.entries(modalFields).map(([id, value]) => (
<FormGroup key={id} controlId={id} disabled={false}>
- <Col componentClass={ControlLabel} sm={6}>
+ <Col componentClass={ControlLabel} sm={5}>
Plugin {id.replace("currentPlugin", "")}
</Col>
<Col sm={6}>
diff --git a/src/cockpit/389-console/src/lib/plugins/pluginTable.jsx b/src/cockpit/389-console/src/lib/plugins/pluginTable.jsx
index ca69207..6c29e6b 100644
--- a/src/cockpit/389-console/src/lib/plugins/pluginTable.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/pluginTable.jsx
@@ -1,82 +1,22 @@
import React from "react";
import {
- PaginationRow,
- paginate,
- Table,
Button,
noop,
actionHeaderCellFormatter,
- customHeaderFormattersDefinition,
- defaultSortingOrder,
sortableHeaderCellFormatter,
tableCellFormatter,
- TABLE_SORT_DIRECTION,
- PAGINATION_VIEW
} from "patternfly-react";
import PropTypes from "prop-types";
-import { orderBy } from "lodash";
-import * as sort from "sortabular";
-import * as resolve from "table-resolver";
-import { compose } from "recompose";
-import { searchFilter } from "../tools.jsx";
-import CustomTableToolbar from "../customTableToolbar.jsx";
+import { DSTable } from "../dsTable.jsx";
import "../../css/ds.css";
class PluginTable extends React.Component {
constructor(props) {
super(props);
- // Point the transform to your sortingColumns. React state can work for this purpose
- // but you can use a state manager as well.
- const getSortingColumns = () => this.state.sortingColumns || {};
-
- const sortableTransform = sort.sort({
- getSortingColumns,
- onSort: selectedColumn => {
- this.setState({
- sortingColumns: sort.byColumn({
- sortingColumns: this.state.sortingColumns,
- sortingOrder: defaultSortingOrder,
- selectedColumn
- })
- });
- },
- // Use property or index dependening on the sortingColumns structure specified
- strategy: sort.strategies.byProperty
- });
-
- const sortingFormatter = sort.header({
- sortableTransform,
- getSortingColumns,
- strategy: sort.strategies.byProperty
- });
-
- // Enables our custom header formatters extensions to reactabular
- this.customHeaderFormatters = customHeaderFormattersDefinition;
-
- this.handleSearchValueChange = this.handleSearchValueChange.bind(this);
- this.totalPages = this.totalPages.bind(this);
- this.onPageInput = this.onPageInput.bind(this);
- this.onSubmit = this.onSubmit.bind(this);
- this.setPage = this.setPage.bind(this);
- this.onPerPageSelect = this.onPerPageSelect.bind(this);
- this.onFirstPage = this.onFirstPage.bind(this);
- this.onPreviousPage = this.onPreviousPage.bind(this);
- this.onNextPage = this.onNextPage.bind(this);
- this.onLastPage = this.onLastPage.bind(this);
- this.currentRows = this.currentRows.bind(this);
- this.filteredSearchedRows = this.filteredSearchedRows.bind(this);
-
this.state = {
searchFilterValue: "",
fieldsToSearch: ["cn", "nsslapd-pluginType"],
- // Sort the first column in an ascending way by default.
- sortingColumns: {
- name: {
- direction: TABLE_SORT_DIRECTION.ASC,
- position: 0
- }
- },
columns: [
{
property: "cn",
@@ -88,8 +28,8 @@ class PluginTable extends React.Component {
colSpan: 1,
sort: true
},
- transforms: [sortableTransform],
- formatters: [sortingFormatter],
+ transforms: [],
+ formatters: [],
customFormatters: [sortableHeaderCellFormatter]
},
cell: {
@@ -109,8 +49,8 @@ class PluginTable extends React.Component {
colSpan: 1,
sort: true
},
- transforms: [sortableTransform],
- formatters: [sortingFormatter],
+ transforms: [],
+ formatters: [],
customFormatters: [sortableHeaderCellFormatter]
},
cell: {
@@ -130,8 +70,8 @@ class PluginTable extends React.Component {
colSpan: 1,
sort: true
},
- transforms: [sortableTransform],
- formatters: [sortingFormatter],
+ transforms: [],
+ formatters: [],
customFormatters: [sortableHeaderCellFormatter]
},
cell: {
@@ -176,173 +116,24 @@ class PluginTable extends React.Component {
}
}
],
-
- // pagination default states
- pagination: {
- page: 1,
- perPage: 12,
- perPageOptions: [6, 12, 24]
- },
-
- // page input value
- pageChangeValue: 1
};
+ this.getColumns = this.getColumns.bind(this);
}
- handleSearchValueChange(event) {
- this.setState({ searchFilterValue: event.target.value });
- }
-
- totalPages() {
- const { rows } = this.props;
- const { perPage } = this.state.pagination;
- return Math.ceil(rows.length / perPage);
- }
-
- onPageInput(e) {
- this.setState({ pageChangeValue: e.target.value });
- }
-
- onSubmit() {
- this.setPage(this.state.pageChangeValue);
- }
-
- setPage(value) {
- const page = Number(value);
- if (
- !Number.isNaN(value) &&
- value !== "" &&
- page > 0 &&
- page <= this.totalPages()
- ) {
- let newPaginationState = Object.assign({}, this.state.pagination);
- newPaginationState.page = page;
- this.setState({
- pagination: newPaginationState,
- pageChangeValue: page
- });
- }
- }
-
- onPerPageSelect(eventKey, e) {
- let newPaginationState = Object.assign({}, this.state.pagination);
- newPaginationState.perPage = eventKey;
- newPaginationState.page = 1;
- this.setState({ pagination: newPaginationState });
- }
-
- onFirstPage() {
- this.setPage(1);
- }
-
- onPreviousPage() {
- if (this.state.pagination.page > 1) {
- this.setPage(this.state.pagination.page - 1);
- }
- }
-
- onNextPage() {
- const { page } = this.state.pagination;
- if (page < this.totalPages()) {
- this.setPage(this.state.pagination.page + 1);
- }
- }
-
- onLastPage() {
- const { page } = this.state.pagination;
- const totalPages = this.totalPages();
- if (page < totalPages) {
- this.setPage(totalPages);
- }
- }
-
- currentRows(filteredRows) {
- const { sortingColumns, columns, pagination } = this.state;
- return compose(
- paginate(pagination),
- sort.sorter({
- columns: columns,
- sortingColumns,
- sort: orderBy,
- strategy: sort.strategies.byProperty
- })
- )(filteredRows);
- }
-
- filteredSearchedRows() {
- const { rows } = this.props;
- const { fieldsToSearch, searchFilterValue } = this.state;
- if (searchFilterValue) {
- return searchFilter(searchFilterValue, fieldsToSearch, rows);
- }
- return rows;
+ getColumns() {
+ return this.state.columns;
}
render() {
- const {
- columns,
- pagination,
- sortingColumns,
- pageChangeValue
- } = this.state;
-
- const filteredRows = this.filteredSearchedRows();
- const sortedPaginatedRows = this.currentRows(filteredRows);
-
return (
<div>
- <CustomTableToolbar
- modelToSearch="Plugins"
- searchFilterValue={this.state.searchFilterValue}
- handleValueChange={this.handleSearchValueChange}
- disableLoadingSpinner
- />
- <Table.PfProvider
- className="display ds-repl-table"
- striped
- hover
- dataTable
- columns={columns}
- components={{
- header: {
- cell: cellProps => {
- return this.customHeaderFormatters({
- cellProps,
- columns,
- sortingColumns,
- rows: sortedPaginatedRows.rows
- });
- }
- }
- }}
- >
- <Table.Header
- className="ds-table-header"
- headerRows={resolve.headerRows({ columns })}
- />
- <Table.Body
- rows={sortedPaginatedRows.rows}
- rowKey="cn"
- onRow={() => ({
- role: "row"
- })}
- />
- </Table.PfProvider>
- <PaginationRow
- viewType={PAGINATION_VIEW.TABLE}
- pagination={pagination}
- pageInputValue={pageChangeValue}
- amountOfPages={sortedPaginatedRows.amountOfPages}
- itemCount={sortedPaginatedRows.itemCount}
- itemsStart={sortedPaginatedRows.itemsStart}
- itemsEnd={sortedPaginatedRows.itemsEnd}
- onPerPageSelect={this.onPerPageSelect}
- onFirstPage={this.onFirstPage}
- onPreviousPage={this.onPreviousPage}
- onPageInput={this.onPageInput}
- onNextPage={this.onNextPage}
- onLastPage={this.onLastPage}
- onSubmit={this.onSubmit}
+ <DSTable
+ getColumns={this.getColumns}
+ fieldsToSearch={this.state.fieldsToSearch}
+ rowKey="cn"
+ rows={this.props.rows}
+ toolBarSearchField="Plugins"
+ toolBarDisableLoadingSpinner
/>
</div>
);
diff --git a/src/cockpit/389-console/src/monitor.html b/src/cockpit/389-console/src/monitor.html
index 4ce889b..ba35e5c 100644
--- a/src/cockpit/389-console/src/monitor.html
+++ b/src/cockpit/389-console/src/monitor.html
@@ -536,7 +536,7 @@
<option>10000</option>
</select><button id="accesslog-refresh-btn" class="ds-adj-btn">Refresh</button><label
class="ds-left-margin">Continuously Refresh<input type="checkbox" class="ds-sm-left-margin" id="accesslog-cont-refresh"></label>
- <textarea id="accesslog-area" class="ds-logarea"></textarea>
+ <textarea id="accesslog-area" class="ds-logarea" readonly></textarea>
<p></p>
</div>
@@ -557,7 +557,7 @@
<option>10000</option>
</select><button id="auditlog-refresh-btn" class="ds-adj-btn">Refresh</button><label
class="ds-left-margin">Continuously Refresh<input type="checkbox" class="ds-sm-left-margin" id="auditlog-cont-refresh"></label>
- <textarea id="auditlog-area" class="ds-logarea"></textarea>
+ <textarea id="auditlog-area" class="ds-logarea" readonly></textarea>
<p></p>
</div>
@@ -578,7 +578,7 @@
<option>10000</option>
</select><button id="auditfaillog-refresh-btn" class="ds-adj-btn">Refresh</button><label
class="ds-left-margin">Continuously Refresh<input type="checkbox" class="ds-sm-left-margin" id="auditfaillog-cont-refresh"></label>
- <textarea id="auditfaillog-area" class="ds-logarea"></textarea>
+ <textarea id="auditfaillog-area" class="ds-logarea" readonly></textarea>
<p></p>
</div>
@@ -616,7 +616,7 @@
<option>Debug</option>
</select>
</div>
- <textarea id="errorslog-area" class="ds-logarea"></textarea>
+ <textarea id="errorslog-area" class="ds-logarea" readonly></textarea>
<p></p>
</div>
@@ -914,5 +914,3 @@
</div>
</div>
</div>
-
-
diff --git a/src/cockpit/389-console/src/plugins.jsx b/src/cockpit/389-console/src/plugins.jsx
index 2b455ba..d2b6932 100644
--- a/src/cockpit/389-console/src/plugins.jsx
+++ b/src/cockpit/389-console/src/plugins.jsx
@@ -17,7 +17,7 @@ import ReferentialIntegrity from "./lib/plugins/referentialIntegrity.jsx";
import RetroChangelog from "./lib/plugins/retroChangelog.jsx";
import RootDNAccessControl from "./lib/plugins/rootDNAccessControl.jsx";
import USN from "./lib/plugins/usn.jsx";
-import NotificationController from "./lib/notifications.jsx";
+import { NotificationController } from "./lib/notifications.jsx";
import "./css/ds.css";
var cmd;
diff --git a/src/cockpit/389-console/src/servers.html b/src/cockpit/389-console/src/servers.html
index ef647fe..7d3c745 100644
--- a/src/cockpit/389-console/src/servers.html
+++ b/src/cockpit/389-console/src/servers.html
@@ -169,7 +169,7 @@
<p></p>
<div class="ds-footer">
- <button class="btn btn-primary save-button">Save</button>
+ <button class="btn btn-primary save-button">Save Configuration</button>
</div>
</div>
diff --git a/src/cockpit/389-console/src/servers.js b/src/cockpit/389-console/src/servers.js
index 204a14d..4d1865c 100644
--- a/src/cockpit/389-console/src/servers.js
+++ b/src/cockpit/389-console/src/servers.js
@@ -1313,7 +1313,7 @@ $(document).ready( function() {
// First check if backup name is already used
var check_cmd = [DSCTL, '-j', server_id, 'backups'];
- log_cmd('#restore-server-btn (click)', 'Restore server instance', check_cmd);
+ log_cmd('#ds-backup-btn (click)', 'Check backup name', check_cmd);
cockpit.spawn(check_cmd, { superuser: true, "err": "message", "environ": [ENV]}).done(function(data) {
var obj = JSON.parse(data);
var found_backup = false;
diff --git a/src/cockpit/389-console/webpack.config.js b/src/cockpit/389-console/webpack.config.js
index 2387cd9..8c89041 100644
--- a/src/cockpit/389-console/webpack.config.js
+++ b/src/cockpit/389-console/webpack.config.js
@@ -24,8 +24,6 @@ var info = {
index: ["./index.es6"]
},
files: [
- "backend.html",
- "backend.js",
"banner.html",
"css",
"ds.js",
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
index 974e218..0dae5eb 100644
--- a/src/lib389/lib389/__init__.py
+++ b/src/lib389/lib389/__init__.py
@@ -425,7 +425,6 @@ class DirSrv(SimpleLDAPObject, object):
self.serverid = serverid
# Do we have ldapi settings?
- # Do we really need .strip() on this?
self.ldapi_enabled = None
self.ldapi_socket = None
@@ -514,7 +513,7 @@ class DirSrv(SimpleLDAPObject, object):
# The lack of this value basically rules it out in most cases
self.ds_paths = Paths(instance=self)
else:
- self.ds_paths = Paths(args[SER_SERVERID_PROP], instance=self)
+ self.ds_paths = Paths(serverid=args[SER_SERVERID_PROP], instance=self)
# Settings from args of server attributes
self.serverid = args.get(SER_SERVERID_PROP, None)
# Probably local?
@@ -3039,6 +3038,49 @@ class DirSrv(SimpleLDAPObject, object):
self.log.debug("Deleting backup directory: ", del_dir)
shutil.rmtree(del_dir)
+ def getLDIFSuffix(self, filename):
+ suffix = ""
+ with open(filename, 'r') as ldif_file:
+ for line in ldif_file:
+ if line.startswith("dn: "):
+ parts = line.split(" ", 1)
+ suffix = parts[1].rstrip().lower()
+ break
+ return suffix
+
+ def ldifs(self, use_json=False):
+ # Return a list of backups from the bakdir
+ ldifdir = self.get_ldif_dir()
+ dirlist = [item for item in os.listdir(ldifdir)]
+
+ if use_json:
+ json_result = {'type': 'list', 'items': []}
+ for ldif in dirlist:
+ fullpath = ldifdir + "/" + ldif
+ ldif_date = os.path.getctime(fullpath)
+ ldif_date = datetime.fromtimestamp(ldif_date).strftime('%Y-%m-%d %H:%M:%S')
+ ldif_size = subprocess.check_output(['du', '-sh', fullpath]).split()[0].decode('utf-8')
+ ldif_suffix = self.getLDIFSuffix(fullpath)
+ if ldif_suffix == "":
+ ldif_suffix = "???"
+ if use_json:
+ json_item = [ldif, ldif_date, ldif_size, ldif_suffix]
+ json_result['items'].append(json_item)
+ else:
+ self.log.info('{} ({}), Created ({}), Size ({})'.format(ldif, ldif_suffix, ldif_date, ldif_size))
+
+ if use_json:
+ print(json.dumps(json_result))
+
+ return True
+
+ def del_ldif(self, ldifname):
+ # Delete backup directory
+ ldifdir = self.get_ldif_dir()
+ del_file = ldifdir + "/" + ldifname
+ self.log.debug("Deleting LDIF file: " + del_file)
+ os.remove(del_file)
+
def dbscan(self, bename=None, index=None, key=None, width=None, isRaw=False):
"""Wrapper around dbscan tool that analyzes and extracts information
from an import Directory Server database file
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
index eb1d60e..7535c06 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -624,17 +624,21 @@ class Backend(DSLdapObject):
if reindex:
self.reindex(attr_name)
- def reindex(self, attrs=None):
+ def reindex(self, attrs=None, wait=False):
"""Reindex the attributes for this backend
:param attrs - an optional list of attributes to index
+ :param wait - Set to true to wait for task to complete
"""
+ args = None
+ if wait:
+ args = {TASK_WAIT: True}
bename = ensure_str(self.get_attr_val_bytes('cn'))
reindex_task = Tasks(self._instance)
- reindex_task.reindex(benamebase=bename, attrname=attrs)
+ reindex_task.reindex(benamebase=bename, attrname=attrs, args=args)
def get_encrypted_attrs(self, just_names=False):
"""Get a list of the excrypted attributes
- :param just_names - If True only he encrypted attribute names are returned (instead of the full attribute entry)
+ :param just_names - If True only the encrypted attribute names are returned (instead of the full attribute entry)
:returns - a list of attributes
"""
attrs = EncryptedAttrs(self._instance, basedn=self._dn).list()
@@ -755,8 +759,7 @@ class Backends(DSLdapObjects):
"""Do an import of the suffix"""
if not ldifs:
- self.log.error("import_ldif: LDIF filename is missing")
- return False
+ raise ValueError("import_ldif: LDIF filename is missing")
ldif_paths = []
for ldif in list(ldifs):
if not ldif.startswith("/"):
@@ -800,6 +803,11 @@ class Backends(DSLdapObjects):
task_properties['nsFilename'] = os.path.join(self._instance.ds_paths.ldif_dir, ldif)
else:
task_properties['nsFilename'] = os.path.join(self._instance.ds_paths.ldif_dir, "%s.ldif" % ldif)
+ elif ldif is not None and ldif.startswith("/"):
+ if ldif.endswith(".ldif"):
+ task_properties['nsFilename'] = ldif
+ else:
+ task_properties['nsFilename'] = "%s.ldif" % ldif
else:
tnow = datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
task_properties['nsFilename'] = os.path.join(self._instance.ds_paths.ldif_dir,
diff --git a/src/lib389/lib389/chaining.py b/src/lib389/lib389/chaining.py
index c528df8..35949d3 100644
--- a/src/lib389/lib389/chaining.py
+++ b/src/lib389/lib389/chaining.py
@@ -11,8 +11,10 @@ from lib389._constants import *
from lib389.properties import *
from lib389.utils import ensure_str
from lib389.monitor import MonitorChaining
+from lib389.mappingTree import MappingTrees
from lib389._mapped_object import DSLdapObjects, DSLdapObject
from lib389.backend import Backends
+from lib389.rootdse import RootDSE
class ChainingConfig(DSLdapObject):
@@ -36,24 +38,22 @@ class ChainingConfig(DSLdapObject):
self._protected = True
self._dn = "cn=config,cn=chaining database,cn=plugins,cn=config"
- # Contorl OIDS from rootdsde - > supportedControl
- # Set nstransmittedcontrols
-
- # Chaining comps: nspossiblechainingcomponents
- # Set comps: nsactivechainingcomponents (DN)
-
- def get_controls(self, use_json=False):
+ def get_controls(self):
"""Get a list of the supported controls from the root DSE entry
:return list of OIDs
"""
- rootdse = self._instance.search_s("", ldap.SCOPE_BASE, self._object_filter, ['supportedControl'])[0]
- return rootdse.get_attr_vals('supportedControl', use_json)
+ rootdse = RootDSE(self._instance)
+ ctrls = rootdse.get_supported_ctrls()
+ ctrls.sort()
+ return ctrls
- def get_comps(self, use_json=False):
+ def get_comps(self):
"""Return a list of the available plugin components
:return list of plugin components
"""
- return self.get_attr_vals('nspossiblechainingcomponents', use_json)
+ comps = self.get_attr_vals_utf8_l('nspossiblechainingcomponents')
+ comps.sort()
+ return comps
class ChainingDefault(DSLdapObject):
@@ -100,6 +100,7 @@ class ChainingLink(DSLdapObject):
self._create_objectclasses = ['top', 'extensibleObject', BACKEND_OBJECTCLASS_VALUE]
self._protected = False
self._basedn = "cn=chaining database,cn=plugins,cn=config"
+ self._mts = MappingTrees(self._instance)
def get_monitor(self, rdn):
"""Get a MonitorChaining(DSLdapObject) for the chaining link
@@ -120,19 +121,13 @@ class ChainingLink(DSLdapObject):
Delete chaining entry
"""
- suffix = ensure_str(self.get_attr_val('nsslapd-suffix')).lower()
- rdn = ensure_str(self.get_attr_val('cn')).lower()
- be_insts = Backends(self._instance).list()
- for be in be_insts:
- be_suffix = ensure_str(be.get_attr_val('nsslapd-suffix')).lower()
- if suffix == be_suffix:
- # Remove chaining link backend
- try:
- be.remove('nsslapd-backend', rdn)
- except ldap.NO_SUCH_ATTRIBUTE:
- # It's not set in the backend, that's okay
- pass
- break
+ rdn = self.get_attr_val_utf8_l('cn')
+ try:
+ mt = self._mts.get(selector=rdn)
+ mt.delete()
+ except ldap.NO_SUCH_OBJECT:
+ # Righto, it's already gone! Do nothing ...
+ pass
# Delete the monitoring entry
monitor = self.get_monitor(rdn)
@@ -141,6 +136,27 @@ class ChainingLink(DSLdapObject):
# Delete the link
self.delete()
+ def create(self, rdn=None, properties=None, basedn=None):
+ """Create the link entry, and the mapping tree entry(if needed)
+ """
+
+ # Create chaining entry
+ super(ChainingLink, self).create(rdn, properties, basedn)
+
+ # Create mapping tree entry
+ dn_comps = ldap.explode_dn(properties['nsslapd-suffix'][0])
+ parent_suffix = ','.join(dn_comps[1:])
+ mt_properties = {
+ 'cn': properties['nsslapd-suffix'][0],
+ 'nsslapd-state': 'backend',
+ 'nsslapd-backend': properties['cn'][0],
+ 'nsslapd-parent-suffix': parent_suffix
+ }
+ try:
+ self._mts.ensure_state(properties=mt_properties)
+ except ldap.ALREADY_EXISTS:
+ pass
+
class ChainingLinks(DSLdapObjects):
"""DSLdapObjects that represents DN_LDBM base DN
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
index fdb5dd5..dbc8aa6 100644
--- a/src/lib389/lib389/cli_conf/backend.py
+++ b/src/lib389/lib389/cli_conf/backend.py
@@ -8,6 +8,7 @@
# --- END COPYRIGHT BLOCK ---
from lib389.backend import Backend, Backends, DatabaseConfig
+from lib389.chaining import (ChainingLinks)
from lib389.index import Index, VLVIndex, VLVSearches
from lib389.monitor import MonitorLDBM
from lib389.utils import ensure_str, is_a_dn, is_dn_parent
@@ -53,6 +54,7 @@ arg_to_attr = {
'rangelookthroughlimit': 'nsslapd-rangelookthroughlimit',
'backend_opt_level': 'nsslapd-backend-opt-level',
'deadlock_policy': 'nsslapd-db-deadlock-policy',
+ 'db_home_directory': 'nsslapd-db-home-directory',
# VLV attributes
'search_base': 'vlvbase',
'search_scope': 'vlvscope',
@@ -170,7 +172,7 @@ def backend_create(inst, basedn, log, args):
be = Backend(inst)
be.create(properties=props)
- print("The database was successfully created")
+ print("The database was sucessfully created")
def _recursively_del_backends(be):
@@ -197,7 +199,7 @@ def backend_delete(inst, basedn, log, args, warn=True):
_recursively_del_backends(be)
be.delete()
- print("The database, and any sub-suffixes, were successfully deleted")
+ print("The database, and any sub-suffixes, were sucessfully deleted")
def backend_import(inst, basedn, log, args):
@@ -216,7 +218,7 @@ def backend_import(inst, basedn, log, args):
if task.is_complete() and result == 0:
print("The import task has finished successfully")
else:
- raise ValueError("The import task has failed with the error code: ({})".format(result))
+ raise ValueError("Import task failed\n-------------------------\n{}".format(ensure_str(task.get_task_log())))
def backend_export(inst, basedn, log, args):
@@ -244,7 +246,16 @@ def backend_export(inst, basedn, log, args):
if task.is_complete() and result == 0:
print("The export task has finished successfully")
else:
- raise ValueError("The export task has failed with the error code: ({})".format(result))
+ raise ValueError("Export task failed\n-------------------------\n{}".format(ensure_str(task.get_task_log())))
+
+
+def is_db_link(inst, rdn):
+ links = ChainingLinks(inst).list()
+ for link in links:
+ cn = ensure_str(link.get_attr_val('cn')).lower()
+ if cn == rdn.lower():
+ return True
+ return False
def backend_get_subsuffixes(inst, basedn, log, args):
@@ -256,15 +267,27 @@ def backend_get_subsuffixes(inst, basedn, log, args):
# We have our parent, now find the children
mts = be._mts.list()
for mt in mts:
+ db_type = "suffix"
sub = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
+ sub_be = mt.get_attr_val_utf8_l('nsslapd-backend')
if sub == be_suffix:
- # We have a subsuffix
+ # We have a subsuffix (maybe a db link?)
+ if is_db_link(inst, sub_be):
+ db_type = "link"
+
if args.suffix:
- val = mt.get_attr_val_utf8_l('cn')
+ subsuffixes.append(mt.get_attr_val_utf8_l('cn'))
else:
- val = ("{} ({})".format(mt.get_attr_val_utf8_l('cn'),
- mt.get_attr_val_utf8_l('nsslapd-backend')))
- subsuffixes.append(val)
+ if args.json:
+ val = {"suffix": mt.get_attr_val_utf8_l('cn'),
+ "backend": mt.get_attr_val_utf8_l('nsslapd-backend'),
+ "type": db_type}
+ else:
+ val = ("{} ({}) Database Type: {}".format(
+ mt.get_attr_val_utf8_l('cn'),
+ mt.get_attr_val_utf8_l('nsslapd-backend'),
+ db_type))
+ subsuffixes.append(val)
break
if len(subsuffixes) > 0:
subsuffixes.sort()
@@ -280,6 +303,100 @@ def backend_get_subsuffixes(inst, basedn, log, args):
print("No sub-suffixes under this backend")
+def build_node(suffix, be_name, subsuf=False, link=False):
+ """Build the UI node for a suffix
+ """
+ icon = "glyphicon glyphicon-tree-conifer"
+ suffix_type = "suffix"
+ if subsuf:
+ icon = "glyphicon glyphicon-leaf"
+ suffix_type = "subsuffix"
+ if link:
+ icon = "glyphicon glyphicon-link"
+ suffix_type = "dblink"
+ return {
+ "text": suffix,
+ "id": suffix,
+ "selectable": True,
+ "icon": icon,
+ "type": suffix_type,
+ "be": be_name,
+ "nodes": []
+ }
+
+
+def backend_build_tree(inst, be_insts, nodes):
+ """Recursively build the tree
+ """
+ if len(nodes) == 0:
+ # Done
+ return
+
+ for node in nodes:
+ node_suffix = node['id']
+ # Get sub suffixes and chaining of node
+ for be in be_insts:
+ be_suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
+ if be_suffix == node_suffix.lower():
+ # We have our parent, now find the children
+ mts = be._mts.list()
+ for mt in mts:
+ sub = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
+ sub_be = mt.get_attr_val_utf8_l('nsslapd-backend')
+ if sub == be_suffix:
+ # We have a subsuffix (maybe a db link?)
+ link = False
+ if is_db_link(inst, sub_be):
+ link = True
+ node['nodes'].append(build_node(mt.get_attr_val_utf8_l('cn'), sub_be, subsuf=True, link=link))
+
+ # Recurse over the new subsuffixes
+ backend_build_tree(inst, be_insts, node['nodes'])
+ break
+
+
+def print_suffix_tree(nodes, level):
+ """Print all the nodes and children recursively
+ """
+ if len(nodes) > 0:
+ for node in nodes:
+ spaces = " " * level
+ print('{}- {}'.format(spaces, node['id']))
+ if len(node['nodes']) > 0:
+ print_suffix_tree(node['nodes'], level + 2)
+
+
+def backend_get_tree(inst, basedn, log, args):
+ """Build a tree model of all the suffixes/sub suffixes nad DB links
+ """
+ nodes = []
+
+ # Get the top suffixes
+ be_insts = MANY(inst).list()
+ for be in be_insts:
+ suffix = be.get_attr_val_utf8_l('nsslapd-suffix')
+ be_name = be.get_attr_val_utf8('cn')
+ mt = be._mts.get(suffix)
+ sub = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
+ if sub is not None:
+ continue
+ nodes.append(build_node(suffix, be_name))
+
+ # No suffixes, return empty list
+ if len(nodes) == 0:
+ return nodes
+
+ # Build the tree
+ be_insts = Backends(inst).list()
+ backend_build_tree(inst, be_insts, nodes)
+
+ # Done
+ if args.json:
+ print(json.dumps(nodes))
+ else:
+ print_suffix_tree(nodes, 1)
+
+
def backend_set(inst, basedn, log, args):
# Validate paried args
if args.enable and args.disable:
@@ -303,11 +420,15 @@ def backend_set(inst, basedn, log, args):
be.set('nsslapd-cachememsize', args.cache_memsize)
if args.dncache_memsize:
be.set('nsslapd-dncachememsize', args.dncache_memsize)
+ if args.require_index:
+ be.set('nsslapd-require-index', 'on')
+ if args.ignore_index:
+ be.set('nsslapd-require-index', 'off')
if args.enable:
be.enable()
if args.disable:
be.disable()
- print("The backend configuration was successfully updated")
+ print("The backend configuration was sucessfully updated")
def db_config_get(inst, basedn, log, args):
@@ -411,7 +532,7 @@ def backend_list_index(inst, basedn, log, args):
if args.just_names:
results.append(index.get_attr_val_utf8_l('cn'))
else:
- results.append(index.get_all_attrs_json())
+ results.append(json.loads(index.get_all_attrs_json()))
else:
if args.just_names:
print(index.get_attr_val_utf8_l('cn'))
@@ -431,7 +552,7 @@ def backend_del_index(inst, basedn, log, args):
def backend_reindex(inst, basedn, log, args):
be = _get_backend(inst, args.be_name)
- be.reindex(attrs=args.attr)
+ be.reindex(attrs=args.attr, wait=args.wait)
print("Successfully reindexed database")
@@ -456,7 +577,14 @@ def backend_attr_encrypt(inst, basedn, log, args):
if args.list:
results = be.get_encrypted_attrs(args.just_names)
if args.json:
- print(json.dumps({"type": "list", "items": results}))
+ json_results = []
+ if args.just_names:
+ json_results = results
+ else:
+ for result in results:
+ json_results.append(json.loads(result.get_all_attrs_json()))
+ print(json.dumps({"type": "list", "items": json_results}))
+
else:
if len(results) == 0:
print("There are no encrypted attributes for this backend")
@@ -479,14 +607,31 @@ def backend_list_vlv(inst, basedn, log, args):
results.append(vlv.get_attr_val_utf8_l('cn'))
else:
entry = vlv.get_attrs_vals_json(VLV_SEARCH_ATTRS)
- results.append(json.loads(entry))
+ indexes = vlv.get_sorts()
+ sorts = []
+ for idx in indexes:
+ index_entry = idx.get_attrs_vals_json(VLV_INDEX_ATTRS)
+ sorts.append(json.loads(index_entry))
+ entry = json.loads(entry) # Return entry to a dict
+ entry["sorts"] = sorts # Update dict
+ results.append(entry)
else:
if args.just_names:
print(vlv.get_attr_val_utf8_l('cn'))
else:
raw_entry = vlv.get_attrs_vals(VLV_SEARCH_ATTRS)
+ print('dn: ' + vlv.dn)
for k, v in list(raw_entry.items()):
print('{}: {}'.format(ensure_str(k), ensure_str(v[0])))
+ indexes = vlv.get_sorts()
+ sorts = []
+ print("Sorts:")
+ for idx in indexes:
+ entry = idx.get_attrs_vals(VLV_INDEX_ATTRS)
+ print(' - dn: ' + idx.dn)
+ for k, v in list(entry.items()):
+ print(' - {}: {}'.format(ensure_str(k), ensure_str(v[0])))
+ print()
if args.json:
print(json.dumps({"type": "list", "items": results}))
@@ -504,7 +649,6 @@ def backend_get_vlv(inst, basedn, log, args):
results.append(json.loads(entry))
else:
raw_entry = vlv.get_attrs_vals(VLV_SEARCH_ATTRS)
- print('VLV Search:')
print('dn: ' + vlv._dn)
for k, v in list(raw_entry.items()):
print('{}: {}'.format(ensure_str(k), ensure_str(v[0])))
@@ -516,10 +660,11 @@ def backend_get_vlv(inst, basedn, log, args):
results.append(json.loads(entry))
else:
raw_entry = idx.get_attrs_vals(VLV_INDEX_ATTRS)
- print('\nVLV Index:')
- print('dn: ' + idx._dn)
+ print('Sorts:')
+ print(' - dn: ' + idx._dn)
for k, v in list(raw_entry.items()):
- print('{}: {}'.format(ensure_str(k), ensure_str(v[0])))
+ print(' - {}: {}'.format(ensure_str(k), ensure_str(v[0])))
+ print()
if args.json:
print(json.dumps({"type": "list", "items": results}))
@@ -568,7 +713,7 @@ def backend_create_vlv_index(inst, basedn, log, args):
be = _get_backend(inst, args.be_name)
vlv_search = be.get_vlv_searches(vlv_name=args.parent_name)
vlv_search.add_sort(args.index_name, args.sort)
- if args.index:
+ if args.index_it:
vlv_search.reindex(args.be_name, vlv_index=args.index_name)
print("Successfully created new VLV index entry")
@@ -576,7 +721,7 @@ def backend_create_vlv_index(inst, basedn, log, args):
def backend_delete_vlv_index(inst, basedn, log, args):
be = _get_backend(inst, args.be_name)
vlv_search = be.get_vlv_searches(vlv_name=args.parent_name)
- vlv_search.delete_sort(args.index_name)
+ vlv_search.delete_sort(args.index_name, args.sort)
print("Successfully deleted VLV index entry")
@@ -625,6 +770,8 @@ def create_parser(subparsers):
set_backend_parser.set_defaults(func=backend_set)
set_backend_parser.add_argument('--enable-readonly', action='store_true', help='Set backend database to be read-only')
set_backend_parser.add_argument('--disable-readonly', action='store_true', help='Disable read-only mode for backend database')
+ set_backend_parser.add_argument('--require-index', action='store_true', help='Only allow indexed searches')
+ set_backend_parser.add_argument('--ignore-index', action='store_true', help='Allow all searches even if they are unindexed')
set_backend_parser.add_argument('--add-referral', help='Add a LDAP referral to the backend')
set_backend_parser.add_argument('--del-referral', help='Remove a LDAP referral to the backend')
set_backend_parser.add_argument('--enable', action='store_true', help='Enable the backend database')
@@ -643,10 +790,10 @@ def create_parser(subparsers):
# Create index
add_index_parser = index_subcommands.add_parser('add', help='Set configuration settings for a single backend')
add_index_parser.set_defaults(func=backend_add_index)
- add_index_parser.add_argument('--index-type', action='append', help='An indexing type: eq, sub, pres, or approximate')
+ add_index_parser.add_argument('--index-type', required=True, action='append', help='An indexing type: eq, sub, pres, or approximate')
add_index_parser.add_argument('--matching-rule', action='append', help='Matching rule for the index')
add_index_parser.add_argument('--reindex', action='store_true', help='After adding new index, reindex the database')
- add_index_parser.add_argument('--attr', help='The index attribute\'s name')
+ add_index_parser.add_argument('--attr', required=True, help='The index attribute\'s name')
add_index_parser.add_argument('be_name', help='The backend name or suffix to delete')
# Edit index
@@ -682,6 +829,7 @@ def create_parser(subparsers):
reindex_parser = index_subcommands.add_parser('reindex', help='Reindex the database (for a single index or all indexes')
reindex_parser.set_defaults(func=backend_reindex)
reindex_parser.add_argument('--attr', action='append', help='The index attribute\'s name to reindex. Skip this argument to reindex all attributes')
+ reindex_parser.add_argument('--wait', action='store_true', help='Wait for the index task to complete and report the status')
reindex_parser.add_argument('be_name', help='The backend name or suffix to reindex')
#############################################
@@ -691,7 +839,7 @@ def create_parser(subparsers):
vlv_subcommands = vlv_parser.add_subparsers(help="action")
# List VLV Searches
- list_vlv_search_parser = vlv_subcommands.add_parser('list', help='List VLV search definition entries')
+ list_vlv_search_parser = vlv_subcommands.add_parser('list', help='List VLV search and index entries')
list_vlv_search_parser.set_defaults(func=backend_list_vlv)
list_vlv_search_parser.add_argument('--just-names', action='store_true', help='List just the names of the VLV search entries')
list_vlv_search_parser.add_argument('be_name', help='The backend name of the VLV index')
@@ -709,7 +857,7 @@ def create_parser(subparsers):
add_vlv_search_parser.set_defaults(func=backend_create_vlv)
add_vlv_search_parser.add_argument('--name', required=True, help='Name of the VLV search entry')
add_vlv_search_parser.add_argument('--search-base', required=True, help='The VLV search base')
- add_vlv_search_parser.add_argument('--search-scope', required=True, help='The VLV search scope: 0 (base search), 1 (one-evel search), or 2 (subtree ssearch)')
+ add_vlv_search_parser.add_argument('--search-scope', required=True, help='The VLV search scope: 0 (base search), 1 (one-level search), or 2 (subtree search)')
add_vlv_search_parser.add_argument('--search-filter', required=True, help='The VLV search filter')
add_vlv_search_parser.add_argument('be_name', help='The backend name of the VLV index')
@@ -718,7 +866,7 @@ def create_parser(subparsers):
edit_vlv_search_parser.set_defaults(func=backend_edit_vlv)
edit_vlv_search_parser.add_argument('--name', required=True, help='Name of the VLV index')
edit_vlv_search_parser.add_argument('--search-base', help='The VLV search base')
- edit_vlv_search_parser.add_argument('--search-scope', help='The VLV search scope: 0 (base search), 1 (one-evel search), or 2 (subtree ssearch)')
+ edit_vlv_search_parser.add_argument('--search-scope', help='The VLV search scope: 0 (base search), 1 (one-level search), or 2 (subtree search)')
edit_vlv_search_parser.add_argument('--search-filter', help='The VLV search filter')
edit_vlv_search_parser.add_argument('--reindex', action='store_true', help='Reindex all the VLV database indexes')
edit_vlv_search_parser.add_argument('be_name', help='The backend name of the VLV index')
@@ -731,20 +879,21 @@ def create_parser(subparsers):
# Create VLV Index
add_vlv_index_parser = vlv_subcommands.add_parser('add-index', help='Create a VLV index under a VLV search entry(parent entry). '
- 'The VLV index just specifies the attributes to sort')
+ 'The VLV index just specifies the attributes to sort')
add_vlv_index_parser.set_defaults(func=backend_create_vlv_index)
add_vlv_index_parser.add_argument('--parent-name', required=True, help='Name, or "cn" attribute value, of the parent VLV search entry')
add_vlv_index_parser.add_argument('--index-name', required=True, help='Name of the new VLV index')
- add_vlv_index_parser.add_argument('--sort', help='A space separated list of attributes to sort for this VLV index')
- add_vlv_index_parser.add_argument('--index', action='store_true', help='Create the actual database index for this VLV index definition')
+ add_vlv_index_parser.add_argument('--sort', required=True, help='A space separated list of attributes to sort for this VLV index')
+ add_vlv_index_parser.add_argument('--index-it', action='store_true', help='Create the database index for this VLV index definition')
add_vlv_index_parser.add_argument('be_name', help='The backend name of the VLV index')
# Delete VLV Index
- add_vlv_index_parser = vlv_subcommands.add_parser('del-index', help='Delete a VLV index under a VLV search entry(parent entry).')
- add_vlv_index_parser.set_defaults(func=backend_delete_vlv_index)
- add_vlv_index_parser.add_argument('--parent-name', required=True, help='Name, or "cn" attribute value, of the parent VLV search entry')
- add_vlv_index_parser.add_argument('--index-name', required=True, help='Name of the VLV index to delete')
- add_vlv_index_parser.add_argument('be_name', help='The backend name of the VLV index')
+ del_vlv_index_parser = vlv_subcommands.add_parser('del-index', help='Delete a VLV index under a VLV search entry(parent entry).')
+ del_vlv_index_parser.set_defaults(func=backend_delete_vlv_index)
+ del_vlv_index_parser.add_argument('--parent-name', required=True, help='Name, or "cn" attribute value, of the parent VLV search entry')
+ del_vlv_index_parser.add_argument('--index-name', help='Name of the VLV index to delete')
+ del_vlv_index_parser.add_argument('--sort', help='Delete a VLV index that has this vlvsort value')
+ del_vlv_index_parser.add_argument('be_name', help='The backend name of the VLV index')
# Reindex VLV
reindex_vlv_parser = vlv_subcommands.add_parser('reindex', help='Index/reindex the VLV database index')
@@ -784,7 +933,7 @@ def create_parser(subparsers):
set_db_config_parser.add_argument('--directory', help='Specifies absolute path to database instance')
set_db_config_parser.add_argument('--dbcachesize', help='Specifies the database index cache size, in bytes.')
set_db_config_parser.add_argument('--logdirectory', help='Specifies the path to the directory that contains the database transaction logs')
- set_db_config_parser.add_argument('--durable_txn', help='Sets whether database transaction log entries are immediately written to the disk.')
+ set_db_config_parser.add_argument('--durable-txn', help='Sets whether database transaction log entries are immediately written to the disk.')
set_db_config_parser.add_argument('--txn-wait', help='Sets whether the server should should wait if there are no db locks available')
set_db_config_parser.add_argument('--checkpoint-interval', help='Sets the amount of time in seconds after which the Directory Server sends a '
'checkpoint entry to the database transaction log')
@@ -796,7 +945,7 @@ def create_parser(subparsers):
'the batch count (only works when txn-batch-val is set)')
set_db_config_parser.add_argument('--logbufsize', help='Specifies the transaction log information buffer size')
set_db_config_parser.add_argument('--locks', help='Sets the maximum number of database locks')
- set_db_config_parser.add_argument('--import-cache_autosize', help='Set to "on" or "off" to automatically set the size of the import '
+ set_db_config_parser.add_argument('--import-cache-autosize', help='Set to "on" or "off" to automatically set the size of the import '
'cache to be used during the the import process of LDIF files')
set_db_config_parser.add_argument('--cache-autosize', help='Sets the percentage of free memory that is used in total for the database '
'and entry cache. Set to "0" to disable this feature.')
@@ -815,6 +964,7 @@ def create_parser(subparsers):
set_db_config_parser.add_argument('--backend-opt-level', help='WARNING this parameter can trigger experimental code to improve write '
'performance. Valid values are: 0, 1, 2, or 4')
set_db_config_parser.add_argument('--deadlock-policy', help='Adjusts the backend database deadlock policy (Advanced setting)')
+ set_db_config_parser.add_argument('--db-home-directory', help='Sets the directory for the database mmapped files (Advanced setting)')
#######################################################
# Database & Suffix Monitor
@@ -900,3 +1050,9 @@ def create_parser(subparsers):
delete_parser = subcommands.add_parser('delete', help='Delete a backend database')
delete_parser.set_defaults(func=backend_delete)
delete_parser.add_argument('be_name', help='The backend name or suffix to delete')
+
+ #######################################################
+ # Get Suffix Tree (for use in web console)
+ #######################################################
+ get_tree_parser = subcommands.add_parser('get-tree', help='Get a representation of the suffix tree')
+ get_tree_parser.set_defaults(func=backend_get_tree)
\ No newline at end of file
diff --git a/src/lib389/lib389/cli_conf/chaining.py b/src/lib389/lib389/cli_conf/chaining.py
index e003fe4..783a9f3 100644
--- a/src/lib389/lib389/cli_conf/chaining.py
+++ b/src/lib389/lib389/cli_conf/chaining.py
@@ -6,15 +6,13 @@
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
+import json
from lib389.chaining import (
ChainingLink, ChainingLinks, ChainingConfig, ChainingDefault)
-from lib389.utils import ensure_str
from lib389.cli_base import (
- populate_attr_arguments,
_generic_list,
_generic_get,
_get_arg,
- _get_attributes,
)
arg_to_attr = {
@@ -29,8 +27,8 @@ arg_to_attr = {
'return_ref': 'nsreferralonscopedsearch',
'check_aci': 'nschecklocalaci',
'bind_attempts': 'nsbindretrylimit',
- 'size_limit': 'nsslapd_sizelimit',
- 'time_limit': 'nsslapd_timelimit',
+ 'size_limit': 'nsslapd-sizelimit',
+ 'time_limit': 'nsslapd-timelimit',
'hop_limit': 'nshoplimit',
'response_delay': 'nsmaxresponsedelay',
'test_response_delay': 'nsmaxtestresponsedelay',
@@ -56,8 +54,9 @@ def _get_link(inst, rdn):
found = False
links = ChainingLinks(inst).list()
for link in links:
- cn = ensure_str(link.get_attr_val('cn')).lower()
- if cn == rdn.lower():
+ cn = link.get_attr_val_utf8_l('cn')
+ suffix = link.get_attr_val_utf8_l('nsslapd-suffix')
+ if cn == rdn.lower() or suffix == rdn.lower():
found = True
return link
if not found:
@@ -66,10 +65,28 @@ def _get_link(inst, rdn):
def config_get(inst, basedn, log, args):
chain_cfg = ChainingConfig(inst)
- if args and args.json:
- print(chain_cfg.get_all_attrs_json())
+ if args.avail_controls or args.avail_comps:
+ if args.avail_controls:
+ ctrls = chain_cfg.get_controls()
+ if args.json:
+ print(json.dumps({"type": "list", "items": ctrls}))
+ else:
+ print("Available Components:")
+ for ctrl in ctrls:
+ print(ctrl)
+ if args.avail_comps:
+ comps = chain_cfg.get_comps()
+ if args.json:
+ print(json.dumps({"type": "list", "items": comps}))
+ else:
+ print("Available Controls:")
+ for comp in comps:
+ print(comp)
else:
- print(chain_cfg.display())
+ if args.json:
+ print(chain_cfg.get_all_attrs_json())
+ else:
+ print(chain_cfg.display())
def config_set(inst, basedn, log, args):
@@ -78,22 +95,26 @@ def config_set(inst, basedn, log, args):
# Add control
if args.add_control is not None:
- chain_cfg.add('nstransmittedcontrols', args.add_control)
+ for ctrl in args.add_control:
+ chain_cfg.add('nstransmittedcontrols', ctrl)
did_something = True
# Delete control
if args.del_control is not None:
- chain_cfg.remove('nstransmittedcontrols', args.del_control)
+ for ctrl in args.del_control:
+ chain_cfg.remove('nstransmittedcontrols', ctrl)
did_something = True
# Add component
if args.add_comp is not None:
- chain_cfg.add('nsactivechainingcomponents', args.add_comp)
+ for comp in args.add_comp:
+ chain_cfg.add('nsactivechainingcomponents', comp)
did_something = True
# Del component
if args.del_comp is not None:
- chain_cfg.remove('nsactivechainingcomponents', args.del_comp)
+ for comp in args.del_comp:
+ chain_cfg.remove('nsactivechainingcomponents', comp)
did_something = True
if did_something:
@@ -117,6 +138,10 @@ def def_config_set(inst, basedn, log, args):
replace_list = []
for attr, value in list(attrs.items()):
+ if value is False:
+ value = "off"
+ elif value is True:
+ value = "on"
if value == "":
# Delete value
chain_cfg.remove_all(attr)
@@ -134,13 +159,12 @@ def def_config_set(inst, basedn, log, args):
def create_link(inst, basedn, log, args, warn=True):
attrs = _args_to_attrs(args)
attrs['cn'] = args.CHAIN_NAME[0]
- create_link = ChainingLinks(inst)
- create_link.add_link(attrs)
+ links = ChainingLinks(inst)
+ links.add_link(attrs)
print('Successfully created database link')
def get_link(inst, basedn, log, args, warn=True):
-
rdn = _get_arg(args.CHAIN_NAME[0], msg="Enter 'cn' to retrieve")
_generic_get(inst, basedn, log.getChild('get_link'), ChainingLinks, rdn, args)
@@ -191,15 +215,15 @@ def create_parser(subparsers):
config_get_parser = subcommands.add_parser('config-get', help='Get the chaining controls and server component lists')
config_get_parser.set_defaults(func=config_get)
- config_get_parser.add_argument('--avail-controls', help="List available controls for chaining")
- config_get_parser.add_argument('--avail-comps', help="List available plugin components for chaining")
+ config_get_parser.add_argument('--avail-controls', action='store_true', help="List available controls for chaining")
+ config_get_parser.add_argument('--avail-comps', action='store_true', help="List available plugin components for chaining")
config_set_parser = subcommands.add_parser('config-set', help='Set the chaining controls and server component lists')
config_set_parser.set_defaults(func=config_set)
- config_set_parser.add_argument('--add-control', help="Add a transmitted control OID")
- config_set_parser.add_argument('--del-control', help="Delete a transmitted control OID")
- config_set_parser.add_argument('--add-comp', help="Add a chaining component")
- config_set_parser.add_argument('--del-comp', help="Delete a chaining component")
+ config_set_parser.add_argument('--add-control', action='append', help="Add a transmitted control OID")
+ config_set_parser.add_argument('--del-control', action='append', help="Delete a transmitted control OID")
+ config_set_parser.add_argument('--add-comp', action='append', help="Add a chaining component")
+ config_set_parser.add_argument('--del-comp', action='append', help="Delete a chaining component")
def_config_get_parser = subcommands.add_parser('config-get-def', help='Get the default creation parameters for new database links')
def_config_get_parser.set_defaults(func=def_config_get)
@@ -225,7 +249,7 @@ def create_parser(subparsers):
def_config_set_parser.add_argument('--response-delay',
help="The maximum amount of time it can take a remote server to respond to an LDAP operation request made by a database link before an error is suspected.")
def_config_set_parser.add_argument('--test-response-delay', help="Sets the duration of the test issued by the database link to check whether the remote server is responding.")
- def_config_set_parser.add_argument('--use-starttls', help="Specificies that the database links should StartTLS for its secure connections.")
+ def_config_set_parser.add_argument('--use-starttls', help="Set to \"on\" specifies that the database links should use StartTLS for its secure connections.")
create_link_parser = subcommands.add_parser('link-create', add_help=False, conflict_handler='resolve', parents=[def_config_set_parser],
help='Create a database link to a remote server')
@@ -240,7 +264,7 @@ def create_parser(subparsers):
get_link_parser = subcommands.add_parser('link-get', help='get chaining database link')
get_link_parser.set_defaults(func=get_link)
- get_link_parser.add_argument('CHAIN_NAME', nargs=1, help='The chaining link name to search for')
+ get_link_parser.add_argument('CHAIN_NAME', nargs=1, help='The chaining link name, or suffix, to retrieve')
edit_link_parser = subcommands.add_parser('link-set', add_help=False, conflict_handler='resolve',
parents=[def_config_set_parser], help='Edit a database link to a remote server')
@@ -262,5 +286,3 @@ def create_parser(subparsers):
list_link_parser = subcommands.add_parser('link-list', help='List database links')
list_link_parser.set_defaults(func=list_links)
-
-
diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py
index 7854fbe..ee7df9d 100644
--- a/src/lib389/lib389/cli_ctl/dbtasks.py
+++ b/src/lib389/lib389/cli_ctl/dbtasks.py
@@ -54,7 +54,7 @@ def dbtasks_ldif2db(inst, log, args):
def dbtasks_backups(inst, log, args):
if args.delete:
# Delete backup
- inst.del_backup(args.delete)
+ inst.del_backup(args.delete[0])
else:
# list backups
if not inst.backups(args.json):
@@ -65,6 +65,19 @@ def dbtasks_backups(inst, log, args):
log.info("backups successful")
+def dbtasks_ldifs(inst, log, args):
+ if args.delete:
+ # Delete LDIF file
+ inst.del_ldif(args.delete[0])
+ else:
+ # list LDIF files
+ if not inst.ldifs(args.json):
+ log.fatal("Failed to get list of LDIF files")
+ return False
+ else:
+ if args.json is None:
+ log.info("backups successful")
+
def create_parser(subcommands):
db2index_parser = subcommands.add_parser('db2index', help="Initialise a reindex of the server database. The server must be stopped for this to proceed.")
@@ -96,5 +109,10 @@ def create_parser(subcommands):
ldif2db_parser.set_defaults(func=dbtasks_ldif2db)
backups_parser = subcommands.add_parser('backups', help="List backup's found in the server's default backup directory")
- backups_parser.add_argument('--delete', help="Delete backup directory")
+ backups_parser.add_argument('--delete', nargs=1, help="Delete backup directory")
backups_parser.set_defaults(func=dbtasks_backups)
+
+ ldifs_parser = subcommands.add_parser('ldifs', help="List all the DLIF files located in the server's LDIF directory")
+ ldifs_parser.add_argument('--delete', nargs=1, help="Delete LDIF file")
+ ldifs_parser.set_defaults(func=dbtasks_ldifs)
+
diff --git a/src/lib389/lib389/index.py b/src/lib389/lib389/index.py
index 4c472a6..f5707c0 100644
--- a/src/lib389/lib389/index.py
+++ b/src/lib389/lib389/index.py
@@ -95,11 +95,15 @@ class VLVSearch(DSLdapObject):
new_index = VLVIndex(self._instance, dn=dn)
new_index.create(properties=props)
- def delete_sort(self, name):
+ def delete_sort(self, name, sort_attrs=None):
vlvsorts = VLVIndexes(self._instance, basedn=self._dn).list()
for vlvsort in vlvsorts:
sort_name = vlvsort.get_attr_val_utf8_l('cn').lower()
- if sort_name == name.lower():
+ sort = vlvsort.get_attr_val_utf8_l('vlvsort')
+ if sort_attrs is not None and sort_attrs == sort:
+ vlvsort.delete()
+ return
+ elif name is not None and sort_name == name.lower():
vlvsort.delete()
return
raise ValueError("Can not delete vlv sort index because it does not exist")
@@ -121,9 +125,10 @@ class VLVSearch(DSLdapObject):
else:
attrs = []
vlvsorts = VLVIndexes(self._instance, basedn=self._dn).list()
- for vlvsort in vlvsorts:
- attrs.append(ensure_str(vlvsort.get_attr_val_bytes('cn')))
- reindex_task.reindex(suffix=be_name, attrname=attrs, vlv=True)
+ if len(vlvsorts) > 0:
+ for vlvsort in vlvsorts:
+ attrs.append(ensure_str(vlvsort.get_attr_val_bytes('cn')))
+ reindex_task.reindex(suffix=be_name, attrname=attrs, vlv=True)
class VLVSearches(DSLdapObjects):
diff --git a/src/lib389/lib389/instance/remove.py b/src/lib389/lib389/instance/remove.py
index 2d8ce99..bde5e53 100644
--- a/src/lib389/lib389/instance/remove.py
+++ b/src/lib389/lib389/instance/remove.py
@@ -18,6 +18,7 @@ def remove_ds_instance(dirsrv, force=False):
"""
_log = dirsrv.log.getChild('remove_ds')
_log.debug("Removing instance %s" % dirsrv.serverid)
+
# Stop the instance (if running)
_log.debug("Stopping instance %s" % dirsrv.serverid)
dirsrv.stop()
diff --git a/src/lib389/lib389/rootdse.py b/src/lib389/lib389/rootdse.py
index aa17561..131a319 100644
--- a/src/lib389/lib389/rootdse.py
+++ b/src/lib389/lib389/rootdse.py
@@ -45,3 +45,6 @@ class RootDSE(DSLdapObject):
def supports_exop_ldapssotoken_revoke(self):
return self.present("supportedExtension", "2.16.840.1.113730.3.5.16")
+ def get_supported_ctrls(self):
+ return self.get_attr_vals_utf8('supportedControl')
+
diff --git a/src/lib389/lib389/tasks.py b/src/lib389/lib389/tasks.py
index daf3978..d98b3e0 100644
--- a/src/lib389/lib389/tasks.py
+++ b/src/lib389/lib389/tasks.py
@@ -37,12 +37,18 @@ class Task(DSLdapObject):
self._create_objectclasses = ['top', 'extensibleObject']
self._protected = False
self._exit_code = None
+ self._task_log = ""
def is_complete(self):
"""Return True if task is complete, else False."""
-
- self._exit_code = self.get_attr_val("nsTaskExitCode")
- if not self.exists() or self._exit_code is not None:
+ self._exit_code = self.get_attr_val_utf8("nsTaskExitCode")
+ self._task_log = self.get_attr_val_utf8("nsTaskLog")
+ if not self.exists():
+ self._log.debug("complete: task has self cleaned ...")
+ # The task cleaned it self up.
+ return True
+ elif self._exit_code is not None:
+ self._log.debug("complete status: %s -> %s" % (self._exit_code, self.status()))
return True
return False
@@ -56,6 +62,15 @@ class Task(DSLdapObject):
return None
return None
+ def get_task_log(self):
+ """Return task's exit code if task is complete, else None."""
+ if self.is_complete():
+ try:
+ return (self._task_log)
+ except TypeError:
+ return None
+ return None
+
def wait(self, timeout=120):
"""Wait until task is complete."""
@@ -591,7 +606,7 @@ class Tasks(object):
task
wait: True/[False] - If True, 'index' waits for the completion
of the task before to return
- :param vlv - this task is to reindex a VVL index
+ :param vlv - this task is to reindex a VLV index
:return None
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 9 months
[389-ds-base] branch master updated: Ticket 50243 - refint modrdn stress test
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
firstyear pushed a commit to branch master
in repository 389-ds-base.
The following commit(s) were added to refs/heads/master by this push:
new 45e8474 Ticket 50243 - refint modrdn stress test
45e8474 is described below
commit 45e8474529c8f2734dc03710764f3eed4a54b751
Author: William Brown <william(a)blackhats.net.au>
AuthorDate: Mon Feb 25 09:59:21 2019 +1000
Ticket 50243 - refint modrdn stress test
Bug Description: It was reported that modrdn of an ou which
contained many items could break refint in some cases.
Fix Description: Add a stress test to try to reproduce the issue
https://pagure.io/389-ds-base/issue/50243
Author: William Brown <william(a)blackhats.net.au>
Review by: spichugi (Thanks)
---
.../tests/suites/referint_plugin/rename_test.py | 177 +++++++++++++++++++++
src/lib389/lib389/plugins.py | 2 +-
src/lib389/lib389/replica.py | 6 +-
3 files changed, 182 insertions(+), 3 deletions(-)
diff --git a/dirsrvtests/tests/suites/referint_plugin/rename_test.py b/dirsrvtests/tests/suites/referint_plugin/rename_test.py
new file mode 100644
index 0000000..442c1a4
--- /dev/null
+++ b/dirsrvtests/tests/suites/referint_plugin/rename_test.py
@@ -0,0 +1,177 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2019 William Brown <william(a)blackhats.net.au>
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+#
+
+
+from lib389._constants import DEFAULT_SUFFIX
+from lib389.topologies import topology_m2
+
+from lib389.replica import ReplicationManager
+from lib389.idm.group import Groups
+from lib389.idm.user import nsUserAccounts
+from lib389.idm.organizationalunit import OrganizationalUnit as OrganisationalUnit
+
+from lib389.plugins import AutoMembershipPlugin, ReferentialIntegrityPlugin, AutoMembershipDefinitions, MemberOfPlugin
+
+UCOUNT = 400
+
+def _enable_plugins(inst, group_dn):
+ # Enable automember
+ amp = AutoMembershipPlugin(inst)
+ amp.enable()
+
+ # Create the automember definition
+ automembers = AutoMembershipDefinitions(inst)
+
+ automember = automembers.create(properties={
+ 'cn': 'testgroup_definition',
+ 'autoMemberScope': DEFAULT_SUFFIX,
+ 'autoMemberFilter': 'objectclass=nsAccount',
+ 'autoMemberDefaultGroup': group_dn,
+ 'autoMemberGroupingAttr': 'member:dn',
+ })
+
+ # Enable MemberOf
+ mop = MemberOfPlugin(inst)
+ mop.enable()
+
+ # Enable referint
+ rip = ReferentialIntegrityPlugin(inst)
+ # We only need to enable the plugin, the default configuration is sane and
+ # correctly coveres member as an enforced attribute.
+ rip.enable()
+
+ # Restart to make sure it's enabled and good to go.
+ inst.restart()
+
+def test_rename_large_subtree(topology_m2):
+ """
+ A report stated that the following configuration would lead
+ to an operation failure:
+
+ ou=int,ou=account,dc=...
+ ou=s1,ou=int,ou=account,dc=...
+ ou=s2,ou=int,ou=account,dc=...
+
+ rename ou=s1 to re-parent to ou=account, leaving:
+
+ ou=int,ou=account,dc=...
+ ou=s1,ou=account,dc=...
+ ou=s2,ou=account,dc=...
+
+ The ou=s1 if it has < 100 entries below, is able to be reparented.
+
+ If ou=s1 has > 400 entries, it fails.
+
+ Other conditions was the presence of referential integrity - so one would
+ assume that all users under s1 are a member of some group external to this.
+
+ :id: 5915c38d-b3c2-4b7c-af76-8a1e002e27f7
+
+ :setup: standalone instance
+
+ :steps: 1. Enable automember plugin
+ 2. Add UCOUNT users, and ensure they are members of a group.
+ 3. Enable refer-int plugin
+ 4. Move ou=s1 to a new parent
+
+ :expectedresults:
+ 1. The plugin is enabled
+ 2. The users are members of the group
+ 3. The plugin is enabled
+ 4. The rename operation of ou=s1 succeeds
+ """
+
+ st = topology_m2.ms["master1"]
+ m2 = topology_m2.ms["master2"]
+
+ # Create a default group
+ gps = Groups(st, DEFAULT_SUFFIX)
+ # Keep the group so we can get it's DN out.
+ group = gps.create(properties={
+ 'cn': 'default_group'
+ })
+
+ _enable_plugins(st, group.dn)
+ _enable_plugins(m2, group.dn)
+
+ # Now unlike normal, we bypass the plural-create method, because we need control
+ # over the exact DN of the OU to create.
+ # Create the ou=account
+
+ # We don't need to set a DN here because ...
+ ou_account = OrganisationalUnit(st)
+
+ # It's set in the .create step.
+ ou_account.create(
+ basedn = DEFAULT_SUFFIX,
+ properties={
+ 'ou': 'account'
+ })
+ # create the ou=int,ou=account
+ ou_int = OrganisationalUnit(st)
+ ou_int.create(
+ basedn = ou_account.dn,
+ properties={
+ 'ou': 'int'
+ })
+ # Create the ou=s1,ou=int,ou=account
+ ou_s1 = OrganisationalUnit(st)
+ ou_s1.create(
+ basedn = ou_int.dn,
+ properties={
+ 'ou': 's1'
+ })
+
+ # Pause replication
+ repl = ReplicationManager(DEFAULT_SUFFIX)
+ repl.disable_to_master(m2, [st, ])
+
+ # Create the users 1 -> UCOUNT in ou=s1
+ nsu = nsUserAccounts(st, basedn=ou_s1.dn, rdn=None)
+ for i in range(1000, 1000 + UCOUNT):
+ nsu.create_test_user(uid=i)
+
+ # Enable replication
+
+ repl.enable_to_master(m2, [st, ])
+
+ # Assert they are in the group as we expect
+ members = group.get_attr_vals_utf8('member')
+ assert len(members) == UCOUNT
+
+ # Wait for replication
+ repl.wait_for_replication(st, m2)
+
+ for i in range(0, 5):
+ # Move ou=s1 to ou=account as parent. We have to provide the rdn,
+ # even though it's not changing.
+ ou_s1.rename('ou=s1', newsuperior=ou_account.dn)
+
+ members = group.get_attr_vals_utf8('member')
+ assert len(members) == UCOUNT
+ # Check that we really did refer-int properly, and ou=int is not in the members.
+ for member in members:
+ assert 'ou=int' not in member
+
+ # Now move it back
+ ou_s1.rename('ou=s1', newsuperior=ou_int.dn)
+ members = group.get_attr_vals_utf8('member')
+ assert len(members) == UCOUNT
+ for member in members:
+ assert 'ou=int' in member
+
+ # Check everythig on the other side is good.
+ repl.wait_for_replication(st, m2)
+
+ group2 = Groups(m2, DEFAULT_SUFFIX).get('default_group')
+
+ members = group2.get_attr_vals_utf8('member')
+ assert len(members) == UCOUNT
+ for member in members:
+ assert 'ou=int' in member
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
index adc5eb3..c8b024b 100644
--- a/src/lib389/lib389/plugins.py
+++ b/src/lib389/lib389/plugins.py
@@ -911,7 +911,7 @@ class AutoMembershipDefinition(DSLdapObject):
:type dn: str
"""
- def __init__(self, instance, dn=None):
+ def __init__(self, instance, dn):
super(AutoMembershipDefinition, self).__init__(instance, dn)
self._rdn_attribute = 'cn'
self._must_attributes = ['cn', 'autoMemberScope', 'autoMemberFilter', 'autoMemberGroupingAttr']
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
index a462c98..9117661 100644
--- a/src/lib389/lib389/replica.py
+++ b/src/lib389/lib389/replica.py
@@ -1214,7 +1214,8 @@ class Replica(DSLdapObject):
scope=ldap.SCOPE_SUBTREE,
filterstr='(&(nsuniqueid=ffffffff-ffffffff-ffffffff-ffffffff)(objectclass=nstombstone))',
attrlist=['nsds50ruv'],
- serverctrls=self._server_controls, clientctrls=self._client_controls)[0]
+ serverctrls=self._server_controls, clientctrls=self._client_controls,
+ escapehatch='i am sure')[0]
data = ensure_list_str(ent.getValues('nsds50ruv'))
@@ -1233,7 +1234,8 @@ class Replica(DSLdapObject):
scope=ldap.SCOPE_SUBTREE,
filterstr='(&(nsuniqueid=ffffffff-ffffffff-ffffffff-ffffffff)(objectclass=nstombstone))',
attrlist=['nsds5agmtmaxcsn'],
- serverctrls=self._server_controls, clientctrls=self._client_controls)[0]
+ serverctrls=self._server_controls, clientctrls=self._client_controls,
+ escapehatch='i am sure')[0]
return ensure_list_str(ent.getValues('nsds5agmtmaxcsn'))
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 9 months
[389-ds-base] branch 389-ds-base-1.3.9 updated: Ticket 50238 - Failed modrdn can corrupt entry cache
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.3.9
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.3.9 by this push:
new 1bdf627 Ticket 50238 - Failed modrdn can corrupt entry cache
1bdf627 is described below
commit 1bdf62751db8a25779833e3245bb2d59914548ba
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Fri Feb 22 13:46:48 2019 -0500
Ticket 50238 - Failed modrdn can corrupt entry cache
Bug Description: Under certain conditions (found under IPA) when a backend
transaction plugin fails and causes a modrdn operation to
fail the entry cache no longer contains the original/pre
entry, but instead it has the post modrdn'ed entry with
the original entry's ID
Fix Description: Upon failure, if the post entry is in the cache, then swap
it out with the original entry.
https://pagure.io/389-ds-base/issue/50238
Reviewed by: firstyear, spichugi, & tboardaz (Thanks!!!)
---
dirsrvtests/tests/suites/betxns/betxn_test.py | 57 +++++++++++++++++++++++++++
ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 16 +++++---
2 files changed, 68 insertions(+), 5 deletions(-)
diff --git a/dirsrvtests/tests/suites/betxns/betxn_test.py b/dirsrvtests/tests/suites/betxns/betxn_test.py
index 1754964..48181a9 100644
--- a/dirsrvtests/tests/suites/betxns/betxn_test.py
+++ b/dirsrvtests/tests/suites/betxns/betxn_test.py
@@ -8,6 +8,7 @@
#
import pytest
import six
+import ldap
from lib389.tasks import *
from lib389.utils import *
from lib389.topologies import topology_st
@@ -248,6 +249,62 @@ def test_betxn_memberof(topology_st, dynamic_plugins):
log.info('test_betxn_memberof: PASSED')
+def test_betxn_modrdn_memberof(topology_st):
+ """Test modrdn operartions and memberOf
+
+ :id: 70d0b96e-b693-4bf7-bbf5-102a66ac5994
+
+ :setup: Standalone instance
+
+ :steps: 1. Enable and configure memberOf plugin
+ 2. Set memberofgroupattr="member" and memberofAutoAddOC="nsContainer"
+ 3. Create group and user outside of memberOf plugin scope
+ 4. Do modrdn to move group into scope
+ 5. Do modrdn to move group into scope (again)
+
+ :expectedresults:
+ 1. memberOf plugin plugin should be ON
+ 2. Set memberofgroupattr="member" and memberofAutoAddOC="nsContainer" should PASS
+ 3. Creating group and user should PASS
+ 4. Modrdn should fail with objectclass violation
+ 5. Second modrdn should also fail with objectclass violation
+ """
+
+ peoplebase = 'ou=people,%s' % DEFAULT_SUFFIX
+ memberof = MemberOfPlugin(topology_st.standalone)
+ memberof.enable()
+ memberof.set_autoaddoc('nsContainer') # Bad OC
+ memberof.set('memberOfEntryScope', peoplebase)
+ memberof.set('memberOfAllBackends', 'on')
+ topology_st.standalone.restart()
+
+ groups = Groups(topology_st.standalone, DEFAULT_SUFFIX)
+ group = groups.create(properties={
+ 'cn': 'group',
+ })
+
+ # Create user and add it to group
+ users = UserAccounts(topology_st.standalone, basedn=DEFAULT_SUFFIX)
+ user = users.create(properties=TEST_USER_PROPERTIES)
+ if not ds_is_older('1.3.7'):
+ user.remove('objectClass', 'nsMemberOf')
+
+ group.add_member(user.dn)
+
+ # Attempt modrdn that should fail, but the original entry should stay in the cache
+ with pytest.raises(ldap.OBJECTCLASS_VIOLATION):
+ group.rename('cn=group_to_people', newsuperior=peoplebase)
+
+ # Should fail, but not with NO_SUCH_OBJECT as the original entry should still be in the cache
+ with pytest.raises(ldap.OBJECTCLASS_VIOLATION):
+ group.rename('cn=group_to_people', newsuperior=peoplebase)
+
+ #
+ # Done
+ #
+ log.info('test_betxn_modrdn_memberof: PASSED')
+
+
if __name__ == '__main__':
# Run isolated
# -s for DEBUG mode
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
index 684b040..e4d0337 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
@@ -1411,14 +1411,20 @@ common_return:
"operation failed, the target entry is cleared from dncache (%s)\n", slapi_entry_get_dn(ec->ep_entry));
CACHE_REMOVE(&inst->inst_dncache, bdn);
CACHE_RETURN(&inst->inst_dncache, &bdn);
+ /*
+ * If the new/invalid entry (ec) is in the cache, that means we need to
+ * swap it out with the original entry (e) --> to undo the swap that
+ * modrdn_rename_entry_update_indexes() did.
+ */
+ if (cache_is_in_cache(&inst->inst_cache, ec)) {
+ if (cache_replace(&inst->inst_cache, ec, e) != 0) {
+ slapi_log_err(SLAPI_LOG_ALERT, "ldbm_back_modrdn",
+ "failed to replace cache entry after error\n");
+ }
+ }
}
- /* remove the new entry from the cache if the op failed -
- otherwise, leave it in */
if (ec && inst) {
- if (retval && cache_is_in_cache(&inst->inst_cache, ec)) {
- CACHE_REMOVE(&inst->inst_cache, ec);
- }
CACHE_RETURN(&inst->inst_cache, &ec);
}
ec = NULL;
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 9 months
[389-ds-base] branch 389-ds-base-1.4.0 updated: Ticket 50238 - Failed modrdn can corrupt entry cache
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.0 by this push:
new a063984 Ticket 50238 - Failed modrdn can corrupt entry cache
a063984 is described below
commit a06398439b4189064c97063816fff5b334431342
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Fri Feb 22 13:46:48 2019 -0500
Ticket 50238 - Failed modrdn can corrupt entry cache
Bug Description: Under certain conditions (found under IPA) when a backend
transaction plugin fails and causes a modrdn operation to
fail the entry cache no longer contains the original/pre
entry, but instead it has the post modrdn'ed entry with
the original entry's ID
Fix Description: Upon failure, if the post entry is in the cache, then swap
it out with the original entry.
https://pagure.io/389-ds-base/issue/50238
Reviewed by: firstyear, spichugi, & tboardaz (Thanks!!!)
---
dirsrvtests/tests/suites/betxns/betxn_test.py | 68 +++++++++++++++++++++++----
ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 16 +++++--
2 files changed, 71 insertions(+), 13 deletions(-)
diff --git a/dirsrvtests/tests/suites/betxns/betxn_test.py b/dirsrvtests/tests/suites/betxns/betxn_test.py
index 9a6e835..3b81434 100644
--- a/dirsrvtests/tests/suites/betxns/betxn_test.py
+++ b/dirsrvtests/tests/suites/betxns/betxn_test.py
@@ -8,6 +8,7 @@
#
import pytest
import six
+import ldap
from lib389.tasks import *
from lib389.utils import *
from lib389.topologies import topology_st
@@ -156,10 +157,6 @@ def test_betxn_memberof(topology_st):
5. Add operation should FAIL
"""
- ENTRY1_DN = 'cn=group1,' + DEFAULT_SUFFIX
- ENTRY2_DN = 'cn=group2,' + DEFAULT_SUFFIX
- PLUGIN_DN = 'cn=' + PLUGIN_MEMBER_OF + ',cn=plugins,cn=config'
-
memberof = MemberOfPlugin(topology_st.standalone)
memberof.enable()
memberof.set_autoaddoc('referral')
@@ -168,11 +165,11 @@ def test_betxn_memberof(topology_st):
groups = Groups(topology_st.standalone, DEFAULT_SUFFIX)
group1 = groups.create(properties={
- 'cn' : 'group1',
+ 'cn': 'group1',
})
group2 = groups.create(properties={
- 'cn' : 'group2',
+ 'cn': 'group2',
})
# We may need to mod groups to not have nsMemberOf ... ?
@@ -183,11 +180,10 @@ def test_betxn_memberof(topology_st):
# Add group2 to group1 - it should fail with objectclass violation
try:
group1.add_member(group2.dn)
- topology_st.standalone.modify_s(ENTRY1_DN, [(ldap.MOD_ADD, 'member', ENTRY2_DN)])
log.fatal('test_betxn_memberof: Group2 was incorrectly allowed to be added to group1')
assert False
except ldap.LDAPError as e:
- log.info('test_betxn_memberof: Group2 was correctly rejected (mod add): error')
+ log.info('test_betxn_memberof: Group2 was correctly rejected (mod add): error: ' + str(e))
#
# Done
@@ -195,6 +191,62 @@ def test_betxn_memberof(topology_st):
log.info('test_betxn_memberof: PASSED')
+def test_betxn_modrdn_memberof(topology_st):
+ """Test modrdn operartions and memberOf
+
+ :id: 70d0b96e-b693-4bf7-bbf5-102a66ac5994
+
+ :setup: Standalone instance
+
+ :steps: 1. Enable and configure memberOf plugin
+ 2. Set memberofgroupattr="member" and memberofAutoAddOC="nsContainer"
+ 3. Create group and user outside of memberOf plugin scope
+ 4. Do modrdn to move group into scope
+ 5. Do modrdn to move group into scope (again)
+
+ :expectedresults:
+ 1. memberOf plugin plugin should be ON
+ 2. Set memberofgroupattr="member" and memberofAutoAddOC="nsContainer" should PASS
+ 3. Creating group and user should PASS
+ 4. Modrdn should fail with objectclass violation
+ 5. Second modrdn should also fail with objectclass violation
+ """
+
+ peoplebase = 'ou=people,%s' % DEFAULT_SUFFIX
+ memberof = MemberOfPlugin(topology_st.standalone)
+ memberof.enable()
+ memberof.set_autoaddoc('nsContainer') # Bad OC
+ memberof.set('memberOfEntryScope', peoplebase)
+ memberof.set('memberOfAllBackends', 'on')
+ topology_st.standalone.restart()
+
+ groups = Groups(topology_st.standalone, DEFAULT_SUFFIX)
+ group = groups.create(properties={
+ 'cn': 'group',
+ })
+
+ # Create user and add it to group
+ users = UserAccounts(topology_st.standalone, basedn=DEFAULT_SUFFIX)
+ user = users.create(properties=TEST_USER_PROPERTIES)
+ if not ds_is_older('1.3.7'):
+ user.remove('objectClass', 'nsMemberOf')
+
+ group.add_member(user.dn)
+
+ # Attempt modrdn that should fail, but the original entry should stay in the cache
+ with pytest.raises(ldap.OBJECTCLASS_VIOLATION):
+ group.rename('cn=group_to_people', newsuperior=peoplebase)
+
+ # Should fail, but not with NO_SUCH_OBJECT as the original entry should still be in the cache
+ with pytest.raises(ldap.OBJECTCLASS_VIOLATION):
+ group.rename('cn=group_to_people', newsuperior=peoplebase)
+
+ #
+ # Done
+ #
+ log.info('test_betxn_modrdn_memberof: PASSED')
+
+
if __name__ == '__main__':
# Run isolated
# -s for DEBUG mode
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
index 684b040..e4d0337 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
@@ -1411,14 +1411,20 @@ common_return:
"operation failed, the target entry is cleared from dncache (%s)\n", slapi_entry_get_dn(ec->ep_entry));
CACHE_REMOVE(&inst->inst_dncache, bdn);
CACHE_RETURN(&inst->inst_dncache, &bdn);
+ /*
+ * If the new/invalid entry (ec) is in the cache, that means we need to
+ * swap it out with the original entry (e) --> to undo the swap that
+ * modrdn_rename_entry_update_indexes() did.
+ */
+ if (cache_is_in_cache(&inst->inst_cache, ec)) {
+ if (cache_replace(&inst->inst_cache, ec, e) != 0) {
+ slapi_log_err(SLAPI_LOG_ALERT, "ldbm_back_modrdn",
+ "failed to replace cache entry after error\n");
+ }
+ }
}
- /* remove the new entry from the cache if the op failed -
- otherwise, leave it in */
if (ec && inst) {
- if (retval && cache_is_in_cache(&inst->inst_cache, ec)) {
- CACHE_REMOVE(&inst->inst_cache, ec);
- }
CACHE_RETURN(&inst->inst_cache, &ec);
}
ec = NULL;
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 9 months
[389-ds-base] branch 389-ds-base-1.3.9 updated: Ticket 50236 - memberOf should be more robust
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.3.9
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.3.9 by this push:
new 70117f5 Ticket 50236 - memberOf should be more robust
70117f5 is described below
commit 70117f568e5fe7987644db8401f7d115e4c05224
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Thu Feb 21 16:58:05 2019 -0500
Ticket 50236 - memberOf should be more robust
Bug Description: When doing a modrdn, or any memberOf update, if the entry
already has the memberOf attribute with the same value
the operation is incorrectly rejected.
Fix Description: If we get an error 20 (type or value exists) return success.
Also fixed a coding mistake that causes the wrong error
code to be returned. This also required fixing the CI
test to check for the new correct errro code.
https://pagure.io/389-ds-base/issue/50236
Reviewed by: firstyear, spichugi, and tbordaz (Thanks!!!)
---
.../suites/memberof_plugin/regression_test.py | 48 ++++++++++++----------
ldap/servers/plugins/memberof/memberof.c | 35 ++++++++++++----
2 files changed, 53 insertions(+), 30 deletions(-)
diff --git a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
index 2b0c4ae..9d0ce35 100644
--- a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
+++ b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
@@ -11,13 +11,12 @@ import pytest
import os
import time
import ldap
-import subprocess
from random import sample
from lib389.utils import ds_is_older, ensure_list_bytes, ensure_bytes, ensure_str
from lib389.topologies import topology_m1h1c1 as topo, topology_st, topology_m2 as topo_m2
from lib389._constants import *
from lib389.plugins import MemberOfPlugin
-from lib389 import agreement, Entry
+from lib389 import Entry
from lib389.idm.user import UserAccount, UserAccounts, TEST_USER_PROPERTIES
from lib389.idm.group import Groups, Group
from lib389.replica import ReplicationManager
@@ -49,7 +48,7 @@ def add_users(topo_m2, users_num, suffix):
users_list = []
users = UserAccounts(topo_m2.ms["master1"], suffix, rdn=None)
log.info('Adding %d users' % users_num)
- for num in sample(range(1000), users_num):
+ for num in sample(list(range(1000)), users_num):
num_ran = int(round(num))
USER_NAME = 'test%05d' % num_ran
user = users.create(properties={
@@ -76,8 +75,8 @@ def config_memberof(server):
for ent in ents:
log.info('update %s to add nsDS5ReplicatedAttributeListTotal' % ent.dn)
server.agreement.setProperties(agmnt_dn=ents[0].dn,
- properties={RA_FRAC_EXCLUDE:'(objectclass=*) $ EXCLUDE memberOf',
- RA_FRAC_EXCLUDE_TOTAL_UPDATE:'(objectclass=*) $ EXCLUDE '})
+ properties={RA_FRAC_EXCLUDE: '(objectclass=*) $ EXCLUDE memberOf',
+ RA_FRAC_EXCLUDE_TOTAL_UPDATE: '(objectclass=*) $ EXCLUDE '})
def send_updates_now(server):
@@ -184,14 +183,14 @@ def test_memberof_with_repl(topo):
for i in range(3):
CN = '%s%d' % (GROUP_CN, i)
groups = Groups(M1, SUFFIX)
- testgroup = groups.create(properties={'cn' : CN})
+ testgroup = groups.create(properties={'cn': CN})
time.sleep(2)
test_groups.append(testgroup)
# Step 4
#Now start testing by adding differnt user to differn group
if not ds_is_older('1.3.7'):
- test_groups[0].remove('objectClass', 'nsMemberOf')
+ test_groups[0].remove('objectClass', 'nsMemberOf')
member_dn = test_users[0].dn
grp0_dn = test_groups[0].dn
@@ -211,7 +210,7 @@ def test_memberof_with_repl(topo):
# Step 7
for i in [grp0_dn, grp1_dn]:
for inst in [M1, H1, C1]:
- _find_memberof(inst, member_dn, i)
+ _find_memberof(inst, member_dn, i)
# Step 8
for i in [M1, H1, C1]:
@@ -225,7 +224,7 @@ def test_memberof_with_repl(topo):
# For negative testcase, we are using assertionerror
for inst in [M1, H1, C1]:
for i in [grp0_dn, member_dn]:
- with pytest.raises(AssertionError):
+ with pytest.raises(AssertionError):
_find_memberof(inst, i, grp1_dn)
# Step 11
@@ -369,7 +368,7 @@ def test_memberof_with_changelog_reset(topo_m2):
'objectclass': ensure_list_bytes(['top', 'groupOfNames'])}
for user in users_list:
- dic_of_attributes.setdefault('member',[])
+ dic_of_attributes.setdefault('member', [])
dic_of_attributes['member'].append(user.dn)
log.info('Adding the test group using async function')
@@ -427,7 +426,7 @@ def rename_entry(server, cn, from_subtree, to_subtree):
server.rename_s(dn, nrdn, newsuperior=to_subtree, delold=0)
-def _find_memberof(server, user_dn=None, group_dn=None, find_result=True):
+def _find_memberof_ext(server, user_dn=None, group_dn=None, find_result=True):
assert (server)
assert (user_dn)
assert (group_dn)
@@ -495,18 +494,19 @@ def test_memberof_group(topology_st):
dn2 = '%s,%s' % ('uid=test_m2', SUBTREE_1)
g1 = '%s,%s' % ('cn=g1', SUBTREE_1)
g2 = '%s,%s' % ('cn=g2', SUBTREE_2)
- _find_memberof(inst, dn1, g1, True)
- _find_memberof(inst, dn2, g1, True)
- _find_memberof(inst, dn1, g2, False)
- _find_memberof(inst, dn2, g2, False)
+ _find_memberof_ext(inst, dn1, g1, True)
+ _find_memberof_ext(inst, dn2, g1, True)
+ _find_memberof_ext(inst, dn1, g2, False)
+ _find_memberof_ext(inst, dn2, g2, False)
rename_entry(inst, 'cn=g2', SUBTREE_2, SUBTREE_1)
g2n = '%s,%s' % ('cn=g2-new', SUBTREE_1)
- _find_memberof(inst, dn1, g1, True)
- _find_memberof(inst, dn2, g1, True)
- _find_memberof(inst, dn1, g2n, True)
- _find_memberof(inst, dn2, g2n, True)
+ _find_memberof_ext(inst, dn1, g1, True)
+ _find_memberof_ext(inst, dn2, g1, True)
+ _find_memberof_ext(inst, dn1, g2n, True)
+ _find_memberof_ext(inst, dn2, g2n, True)
+
def _config_memberof_entrycache_on_modrdn_failure(server):
@@ -517,11 +517,13 @@ def _config_memberof_entrycache_on_modrdn_failure(server):
(ldap.MOD_REPLACE, 'memberOfEntryScope', peoplebase.encode()),
(ldap.MOD_REPLACE, 'memberOfAutoAddOC', b'nsMemberOf')])
+
def _disable_auto_oc_memberof(server):
MEMBEROF_PLUGIN_DN = ('cn=' + PLUGIN_MEMBER_OF + ',cn=plugins,cn=config')
server.modify_s(MEMBEROF_PLUGIN_DN,
[(ldap.MOD_REPLACE, 'memberOfAutoAddOC', b'nsContainer')])
+
@pytest.mark.ds49967
def test_entrycache_on_modrdn_failure(topology_st):
"""This test checks that when a modrdn fails, the destination entry is not returned by a search
@@ -657,7 +659,7 @@ def test_entrycache_on_modrdn_failure(topology_st):
topology_st.standalone.rename_s(group2_dn, 'cn=group_in2', newsuperior=peoplebase, delold=0)
topology_st.standalone.log.info("This is unexpected, modrdn should fail as the member entry have not the appropriate objectclass")
assert False
- except ldap.OPERATIONS_ERROR:
+ except ldap.OBJECT_CLASS_VIOLATION:
pass
# retrieve the entry having the specific description value
@@ -671,9 +673,11 @@ def test_entrycache_on_modrdn_failure(topology_st):
assert ent.dn == group2_dn
assert found
+
def _config_memberof_silent_memberof_failure(server):
_config_memberof_entrycache_on_modrdn_failure(server)
+
def test_silent_memberof_failure(topology_st):
"""This test checks that if during a MODRDN, the memberof plugin fails
then MODRDN also fails
@@ -817,7 +821,7 @@ def test_silent_memberof_failure(topology_st):
topology_st.standalone.rename_s(group2_dn, 'cn=group_in2', newsuperior=peoplebase, delold=0)
topology_st.standalone.log.info("This is unexpected, modrdn should fail as the member entry have not the appropriate objectclass")
assert False
- except ldap.OPERATIONS_ERROR:
+ except ldap.OBJECT_CLASS_VIOLATION:
pass
# Check the those entries have not memberof
@@ -843,7 +847,7 @@ def test_silent_memberof_failure(topology_st):
except ldap.OPERATIONS_ERROR:
pass
- # Check the those entries have not memberof
+ # Check the those entries do not have memberof
for i in (4, 5):
user_dn = 'cn=user%d,%s' % (i, peoplebase)
ent = topology_st.standalone.getEntry(user_dn, ldap.SCOPE_BASE, "(objectclass=*)", ['memberof'])
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
index 329a845..b54eb39 100644
--- a/ldap/servers/plugins/memberof/memberof.c
+++ b/ldap/servers/plugins/memberof/memberof.c
@@ -919,8 +919,7 @@ memberof_postop_modrdn(Slapi_PBlock *pb)
* entry that is being renamed. */
for (i = 0; configCopy.groupattrs && configCopy.groupattrs[i]; i++) {
if (0 == slapi_entry_attr_find(post_e, configCopy.groupattrs[i], &attr)) {
- if ((ret = memberof_moddn_attr_list(pb, &configCopy, pre_sdn,
- post_sdn, attr) != 0)) {
+ if ((ret = memberof_moddn_attr_list(pb, &configCopy, pre_sdn, post_sdn, attr)) != 0) {
slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
"memberof_postop_modrdn - Update failed for (%s), error (%d)\n",
slapi_sdn_get_dn(pre_sdn), ret);
@@ -1720,12 +1719,32 @@ memberof_modop_one_replace_r(Slapi_PBlock *pb, MemberOfConfig *config, int mod_o
replace_mod.mod_values = replace_val;
}
rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
- if (rc == LDAP_NO_SUCH_ATTRIBUTE) {
- /* the memberof values to be replaced do not exist
- * just add the new values */
- mods[0] = mods[1];
- mods[1] = NULL;
- rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
+ if (rc == LDAP_NO_SUCH_ATTRIBUTE || rc == LDAP_TYPE_OR_VALUE_EXISTS) {
+ if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
+ /*
+ * For some reason the new modrdn value is present, so retry
+ * the delete by itself and ignore the add op by tweaking
+ * the mod array.
+ */
+ mods[1] = NULL;
+ rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
+ } else {
+ /*
+ * The memberof value to be replaced does not exist so just
+ * add the new value. Shuffle the mod array to apply only
+ * the add operation.
+ */
+ mods[0] = mods[1];
+ mods[1] = NULL;
+ rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
+ if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
+ /*
+ * The entry already has the expected memberOf value, no
+ * problem just return success.
+ */
+ rc = LDAP_SUCCESS;
+ }
+ }
}
}
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 9 months
[389-ds-base] branch 389-ds-base-1.4.0 updated: Ticket 50236 - memberOf should be more robust
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.0 by this push:
new ea24c43 Ticket 50236 - memberOf should be more robust
ea24c43 is described below
commit ea24c43a9a4c8105d297f129a9ffe0c2fb830b2e
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Thu Feb 21 16:58:05 2019 -0500
Ticket 50236 - memberOf should be more robust
Bug Description: When doing a modrdn, or any memberOf update, if the entry
already has the memberOf attribute with the same value
the operation is incorrectly rejected.
Fix Description: If we get an error 20 (type or value exists) return success.
Also fixed a coding mistake that causes the wrong error
code to be returned. This also required fixing the CI
test to check for the new correct errro code.
https://pagure.io/389-ds-base/issue/50236
Reviewed by: firstyear, spichugi, and tbordaz (Thanks!!!)
---
.../suites/memberof_plugin/regression_test.py | 48 ++++++++++++----------
ldap/servers/plugins/memberof/memberof.c | 35 ++++++++++++----
2 files changed, 53 insertions(+), 30 deletions(-)
diff --git a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
index 2b0c4ae..9d0ce35 100644
--- a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
+++ b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
@@ -11,13 +11,12 @@ import pytest
import os
import time
import ldap
-import subprocess
from random import sample
from lib389.utils import ds_is_older, ensure_list_bytes, ensure_bytes, ensure_str
from lib389.topologies import topology_m1h1c1 as topo, topology_st, topology_m2 as topo_m2
from lib389._constants import *
from lib389.plugins import MemberOfPlugin
-from lib389 import agreement, Entry
+from lib389 import Entry
from lib389.idm.user import UserAccount, UserAccounts, TEST_USER_PROPERTIES
from lib389.idm.group import Groups, Group
from lib389.replica import ReplicationManager
@@ -49,7 +48,7 @@ def add_users(topo_m2, users_num, suffix):
users_list = []
users = UserAccounts(topo_m2.ms["master1"], suffix, rdn=None)
log.info('Adding %d users' % users_num)
- for num in sample(range(1000), users_num):
+ for num in sample(list(range(1000)), users_num):
num_ran = int(round(num))
USER_NAME = 'test%05d' % num_ran
user = users.create(properties={
@@ -76,8 +75,8 @@ def config_memberof(server):
for ent in ents:
log.info('update %s to add nsDS5ReplicatedAttributeListTotal' % ent.dn)
server.agreement.setProperties(agmnt_dn=ents[0].dn,
- properties={RA_FRAC_EXCLUDE:'(objectclass=*) $ EXCLUDE memberOf',
- RA_FRAC_EXCLUDE_TOTAL_UPDATE:'(objectclass=*) $ EXCLUDE '})
+ properties={RA_FRAC_EXCLUDE: '(objectclass=*) $ EXCLUDE memberOf',
+ RA_FRAC_EXCLUDE_TOTAL_UPDATE: '(objectclass=*) $ EXCLUDE '})
def send_updates_now(server):
@@ -184,14 +183,14 @@ def test_memberof_with_repl(topo):
for i in range(3):
CN = '%s%d' % (GROUP_CN, i)
groups = Groups(M1, SUFFIX)
- testgroup = groups.create(properties={'cn' : CN})
+ testgroup = groups.create(properties={'cn': CN})
time.sleep(2)
test_groups.append(testgroup)
# Step 4
#Now start testing by adding differnt user to differn group
if not ds_is_older('1.3.7'):
- test_groups[0].remove('objectClass', 'nsMemberOf')
+ test_groups[0].remove('objectClass', 'nsMemberOf')
member_dn = test_users[0].dn
grp0_dn = test_groups[0].dn
@@ -211,7 +210,7 @@ def test_memberof_with_repl(topo):
# Step 7
for i in [grp0_dn, grp1_dn]:
for inst in [M1, H1, C1]:
- _find_memberof(inst, member_dn, i)
+ _find_memberof(inst, member_dn, i)
# Step 8
for i in [M1, H1, C1]:
@@ -225,7 +224,7 @@ def test_memberof_with_repl(topo):
# For negative testcase, we are using assertionerror
for inst in [M1, H1, C1]:
for i in [grp0_dn, member_dn]:
- with pytest.raises(AssertionError):
+ with pytest.raises(AssertionError):
_find_memberof(inst, i, grp1_dn)
# Step 11
@@ -369,7 +368,7 @@ def test_memberof_with_changelog_reset(topo_m2):
'objectclass': ensure_list_bytes(['top', 'groupOfNames'])}
for user in users_list:
- dic_of_attributes.setdefault('member',[])
+ dic_of_attributes.setdefault('member', [])
dic_of_attributes['member'].append(user.dn)
log.info('Adding the test group using async function')
@@ -427,7 +426,7 @@ def rename_entry(server, cn, from_subtree, to_subtree):
server.rename_s(dn, nrdn, newsuperior=to_subtree, delold=0)
-def _find_memberof(server, user_dn=None, group_dn=None, find_result=True):
+def _find_memberof_ext(server, user_dn=None, group_dn=None, find_result=True):
assert (server)
assert (user_dn)
assert (group_dn)
@@ -495,18 +494,19 @@ def test_memberof_group(topology_st):
dn2 = '%s,%s' % ('uid=test_m2', SUBTREE_1)
g1 = '%s,%s' % ('cn=g1', SUBTREE_1)
g2 = '%s,%s' % ('cn=g2', SUBTREE_2)
- _find_memberof(inst, dn1, g1, True)
- _find_memberof(inst, dn2, g1, True)
- _find_memberof(inst, dn1, g2, False)
- _find_memberof(inst, dn2, g2, False)
+ _find_memberof_ext(inst, dn1, g1, True)
+ _find_memberof_ext(inst, dn2, g1, True)
+ _find_memberof_ext(inst, dn1, g2, False)
+ _find_memberof_ext(inst, dn2, g2, False)
rename_entry(inst, 'cn=g2', SUBTREE_2, SUBTREE_1)
g2n = '%s,%s' % ('cn=g2-new', SUBTREE_1)
- _find_memberof(inst, dn1, g1, True)
- _find_memberof(inst, dn2, g1, True)
- _find_memberof(inst, dn1, g2n, True)
- _find_memberof(inst, dn2, g2n, True)
+ _find_memberof_ext(inst, dn1, g1, True)
+ _find_memberof_ext(inst, dn2, g1, True)
+ _find_memberof_ext(inst, dn1, g2n, True)
+ _find_memberof_ext(inst, dn2, g2n, True)
+
def _config_memberof_entrycache_on_modrdn_failure(server):
@@ -517,11 +517,13 @@ def _config_memberof_entrycache_on_modrdn_failure(server):
(ldap.MOD_REPLACE, 'memberOfEntryScope', peoplebase.encode()),
(ldap.MOD_REPLACE, 'memberOfAutoAddOC', b'nsMemberOf')])
+
def _disable_auto_oc_memberof(server):
MEMBEROF_PLUGIN_DN = ('cn=' + PLUGIN_MEMBER_OF + ',cn=plugins,cn=config')
server.modify_s(MEMBEROF_PLUGIN_DN,
[(ldap.MOD_REPLACE, 'memberOfAutoAddOC', b'nsContainer')])
+
@pytest.mark.ds49967
def test_entrycache_on_modrdn_failure(topology_st):
"""This test checks that when a modrdn fails, the destination entry is not returned by a search
@@ -657,7 +659,7 @@ def test_entrycache_on_modrdn_failure(topology_st):
topology_st.standalone.rename_s(group2_dn, 'cn=group_in2', newsuperior=peoplebase, delold=0)
topology_st.standalone.log.info("This is unexpected, modrdn should fail as the member entry have not the appropriate objectclass")
assert False
- except ldap.OPERATIONS_ERROR:
+ except ldap.OBJECT_CLASS_VIOLATION:
pass
# retrieve the entry having the specific description value
@@ -671,9 +673,11 @@ def test_entrycache_on_modrdn_failure(topology_st):
assert ent.dn == group2_dn
assert found
+
def _config_memberof_silent_memberof_failure(server):
_config_memberof_entrycache_on_modrdn_failure(server)
+
def test_silent_memberof_failure(topology_st):
"""This test checks that if during a MODRDN, the memberof plugin fails
then MODRDN also fails
@@ -817,7 +821,7 @@ def test_silent_memberof_failure(topology_st):
topology_st.standalone.rename_s(group2_dn, 'cn=group_in2', newsuperior=peoplebase, delold=0)
topology_st.standalone.log.info("This is unexpected, modrdn should fail as the member entry have not the appropriate objectclass")
assert False
- except ldap.OPERATIONS_ERROR:
+ except ldap.OBJECT_CLASS_VIOLATION:
pass
# Check the those entries have not memberof
@@ -843,7 +847,7 @@ def test_silent_memberof_failure(topology_st):
except ldap.OPERATIONS_ERROR:
pass
- # Check the those entries have not memberof
+ # Check the those entries do not have memberof
for i in (4, 5):
user_dn = 'cn=user%d,%s' % (i, peoplebase)
ent = topology_st.standalone.getEntry(user_dn, ldap.SCOPE_BASE, "(objectclass=*)", ['memberof'])
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
index 329a845..b54eb39 100644
--- a/ldap/servers/plugins/memberof/memberof.c
+++ b/ldap/servers/plugins/memberof/memberof.c
@@ -919,8 +919,7 @@ memberof_postop_modrdn(Slapi_PBlock *pb)
* entry that is being renamed. */
for (i = 0; configCopy.groupattrs && configCopy.groupattrs[i]; i++) {
if (0 == slapi_entry_attr_find(post_e, configCopy.groupattrs[i], &attr)) {
- if ((ret = memberof_moddn_attr_list(pb, &configCopy, pre_sdn,
- post_sdn, attr) != 0)) {
+ if ((ret = memberof_moddn_attr_list(pb, &configCopy, pre_sdn, post_sdn, attr)) != 0) {
slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
"memberof_postop_modrdn - Update failed for (%s), error (%d)\n",
slapi_sdn_get_dn(pre_sdn), ret);
@@ -1720,12 +1719,32 @@ memberof_modop_one_replace_r(Slapi_PBlock *pb, MemberOfConfig *config, int mod_o
replace_mod.mod_values = replace_val;
}
rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
- if (rc == LDAP_NO_SUCH_ATTRIBUTE) {
- /* the memberof values to be replaced do not exist
- * just add the new values */
- mods[0] = mods[1];
- mods[1] = NULL;
- rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
+ if (rc == LDAP_NO_SUCH_ATTRIBUTE || rc == LDAP_TYPE_OR_VALUE_EXISTS) {
+ if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
+ /*
+ * For some reason the new modrdn value is present, so retry
+ * the delete by itself and ignore the add op by tweaking
+ * the mod array.
+ */
+ mods[1] = NULL;
+ rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
+ } else {
+ /*
+ * The memberof value to be replaced does not exist so just
+ * add the new value. Shuffle the mod array to apply only
+ * the add operation.
+ */
+ mods[0] = mods[1];
+ mods[1] = NULL;
+ rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
+ if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
+ /*
+ * The entry already has the expected memberOf value, no
+ * problem just return success.
+ */
+ rc = LDAP_SUCCESS;
+ }
+ }
}
}
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 9 months
[389-ds-base] branch 389-ds-base-1.3.9 updated: Bump version to 1.3.9.1
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.3.9
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.3.9 by this push:
new eee97e6 Bump version to 1.3.9.1
eee97e6 is described below
commit eee97e69f6e1df7949e1d645ad6108375c6283ac
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Wed Feb 20 09:39:45 2019 -0500
Bump version to 1.3.9.1
---
VERSION.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/VERSION.sh b/VERSION.sh
index 7f32fc4..44c045b 100644
--- a/VERSION.sh
+++ b/VERSION.sh
@@ -10,7 +10,7 @@ vendor="389 Project"
# PACKAGE_VERSION is constructed from these
VERSION_MAJOR=1
VERSION_MINOR=3
-VERSION_MAINT=9.0
+VERSION_MAINT=9.1
# NOTE: VERSION_PREREL is automatically set for builds made out of a git tree
VERSION_PREREL=
VERSION_DATE=$(date -u +%Y%m%d)
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 9 months
[389-ds-base] branch 389-ds-base-1.3.9 updated: Ticket 50026 - audit logs does not capture the operation where nsslapd-lookthroughlimit is modified
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
tbordaz pushed a commit to branch 389-ds-base-1.3.9
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.3.9 by this push:
new ebcd082 Ticket 50026 - audit logs does not capture the operation where nsslapd-lookthroughlimit is modified
ebcd082 is described below
commit ebcd08264a0d09ffa714209646abdffb28cdfb2e
Author: Thierry Bordaz <tbordaz(a)redhat.com>
AuthorDate: Wed Nov 14 15:09:59 2018 +0100
Ticket 50026 - audit logs does not capture the operation where nsslapd-lookthroughlimit is modified
Bug Description:
During a dse update (config, schema,..) the dse callback will process the mods
but can also modify them (SLAPI_MODIFY_MODS) leaving only ignored attributes.
A consequence is that later audit logging will only log the ignored attributes.
Fix Description:
Save a copy of the orignal mods before the dse callback and restore them
when dse callback completes.
https://pagure.io/389-ds-base/issue/50026
Reviewed by: Ludwig Krispenz
Platforms tested: F27
Flag Day: no
Doc impact: no
---
dirsrvtests/tests/suites/basic/basic_test.py | 50 ++++++++++++++++++++++++++++
ldap/servers/slapd/dse.c | 9 +++++
2 files changed, 59 insertions(+)
diff --git a/dirsrvtests/tests/suites/basic/basic_test.py b/dirsrvtests/tests/suites/basic/basic_test.py
index dc366cd..0f7536b 100644
--- a/dirsrvtests/tests/suites/basic/basic_test.py
+++ b/dirsrvtests/tests/suites/basic/basic_test.py
@@ -1069,6 +1069,56 @@ def test_critical_msg_on_empty_range_idl(topology_st):
# Step 5
assert not topology_st.standalone.searchErrorsLog('CRIT - list_candidates - NULL idl was recieved from filter_candidates_ext.')
+def audit_pattern_found(server, log_pattern):
+ file_obj = open(server.ds_paths.audit_log, "r")
+
+ found = None
+ # Use a while true iteration because 'for line in file: hit a
+ log.info('Audit log contains')
+ while True:
+ line = file_obj.readline()
+ log.info(line)
+ found = log_pattern.search(line)
+ if ((line == '') or (found)):
+ break
+
+ return found
+
+(a)pytest.mark.ds50026
+def test_ticketldbm_audit(topology_st):
+ """When updating LDBM config attributes, those attributes/values are not listed
+ in the audit log
+
+ :id: 5bf75c47-a283-430e-a65c-3c5fd8dbadb8
+ :setup: Standalone Instance
+ :steps:
+ 1. Enable audit log
+ 2. Update a set of config attrs in LDBM config
+ 3. Disable audit log (to restore the default config)
+ 4. Check that config attrs are listed in the audit log
+ :expectedresults:
+ 1. Should succeeds
+ 2. Should succeeds
+ 3. Should succeeds
+ 4. Should succeeds
+ """
+ inst = topology_st[0]
+
+ inst.config.enable_log('audit')
+
+ #inst.ds_paths.audit_log
+ attrs = ['nsslapd-lookthroughlimit', 'nsslapd-pagedidlistscanlimit', 'nsslapd-idlistscanlimit', 'nsslapd-db-locks']
+ mods = []
+ for attr in attrs:
+ mods.append((ldap.MOD_REPLACE, attr, b'10001'))
+ inst.modify_s(DN_CONFIG_LDBM, mods)
+ inst.config.enable_log('audit')
+
+ for attr in attrs:
+ log.info("Check %s is replaced in the audit log" % attr)
+ regex = re.compile("^replace: %s" % attr)
+ assert audit_pattern_found(inst, regex)
+
if __name__ == '__main__':
# Run isolated
diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c
index 932912c..b22c8e6 100644
--- a/ldap/servers/slapd/dse.c
+++ b/ldap/servers/slapd/dse.c
@@ -1695,6 +1695,7 @@ dse_modify(Slapi_PBlock *pb) /* JCM There should only be one exit point from thi
{
int err; /*House keeping stuff*/
LDAPMod **mods; /*Used to apply the modifications*/
+ LDAPMod **original_mods = NULL; /* some mods can be removed by callback, save them for later logging */
char *errbuf = NULL; /* To get error back */
struct dse *pdse;
Slapi_Entry *ec = NULL;
@@ -1761,6 +1762,7 @@ dse_modify(Slapi_PBlock *pb) /* JCM There should only be one exit point from thi
global_backend_lock_lock();
global_lock_owned = PR_TRUE;
}
+ original_mods = copy_mods(mods);
/* XXXmcs: should we expand objectclass values here?? */
/* give the dse callbacks the first crack at the modify */
@@ -1958,6 +1960,13 @@ done:
}
}
}
+ /* time to restore original mods */
+ if (original_mods) {
+ LDAPMod **mods_from_callback;
+ slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods_from_callback);
+ ldap_mods_free(mods_from_callback, 1 /* Free the Array and the Elements */);
+ slapi_pblock_set(pb, SLAPI_MODIFY_MODS, original_mods);
+ }
if (global_lock_owned) {
global_backend_lock_unlock();
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 9 months
[389-ds-base] branch 389-ds-base-1.3.9 updated: Ticket 50155 - password history check has no way to just check the current password
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.3.9
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.3.9 by this push:
new 4554617 Ticket 50155 - password history check has no way to just check the current password
4554617 is described below
commit 4554617ad3ddf3c5ea911dd42cce24cb60b0c4c4
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Tue Jan 15 18:06:37 2019 +0000
Ticket 50155 - password history check has no way to just check the current password
Description: Currently if you set passwordinhistory 1, it checks the last
recorded password and the current password. To get it to just
check the current password we need to allow "0" in passwordinhistory.
Then only check the current password, and not the entry's
passwordHistory attributes (if any).
Also added new "rebind" function to Accounts class to "rebind"
on the current connection.
https://pagure.io/389-ds-base/issue/50155
Reviewed by: firstyear & spichugi (Thanks!!)
---
ldap/servers/slapd/libglobs.c | 8 ++++----
ldap/servers/slapd/pw.c | 7 ++++++-
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
index 56b67b7..59f8d06 100644
--- a/ldap/servers/slapd/libglobs.c
+++ b/ldap/servers/slapd/libglobs.c
@@ -3139,8 +3139,8 @@ config_set_pw_maxfailure(const char *attrname, char *value, char *errorbuf, int
int
config_set_pw_inhistory(const char *attrname, char *value, char *errorbuf, int apply)
{
- int retVal = LDAP_SUCCESS;
- long history = 0;
+ int32_t retVal = LDAP_SUCCESS;
+ int64_t history = 0;
char *endp = NULL;
slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
@@ -3152,9 +3152,9 @@ config_set_pw_inhistory(const char *attrname, char *value, char *errorbuf, int a
errno = 0;
history = strtol(value, &endp, 10);
- if (*endp != '\0' || errno == ERANGE || history < 1 || history > 24) {
+ if (*endp != '\0' || errno == ERANGE || history < 0 || history > 24) {
slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
- "password history length \"%s\" is invalid. The password history must range from 1 to 24", value);
+ "password history length \"%s\" is invalid. The password history must range from 0 to 24", value);
retVal = LDAP_OPERATIONS_ERROR;
return retVal;
}
diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c
index 10b8e72..ec23ed1 100644
--- a/ldap/servers/slapd/pw.c
+++ b/ldap/servers/slapd/pw.c
@@ -983,7 +983,7 @@ check_pw_syntax_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, Slapi_Value **vals, c
if (pwpolicy->pw_history == 1) {
Slapi_Value **va = NULL;
attr = attrlist_find(e->e_attrs, "passwordHistory");
- if (attr && !valueset_isempty(&attr->a_present_values)) {
+ if (pwpolicy->pw_inhistory && attr && !valueset_isempty(&attr->a_present_values)) {
/* Resetting password history array if necessary. */
if (0 == update_pw_history(pb, sdn, NULL)) {
/* There was an update in the password history. Retry... */
@@ -1135,6 +1135,11 @@ update_pw_history(Slapi_PBlock *pb, const Slapi_DN *sdn, char *old_pw)
pwpolicy = new_passwdPolicy(pb, dn);
+ if (pwpolicy->pw_inhistory == 0){
+ /* We are only enforcing the current password, just return */
+ return res;
+ }
+
/* retrieve the entry */
e = get_entry(pb, dn);
if (e == NULL) {
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 10 months
[389-ds-base] branch 389-ds-base-1.4.0 updated: Ticket 50155 - password history check has no way to just check the current password
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.0
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.0 by this push:
new ba02f5a Ticket 50155 - password history check has no way to just check the current password
ba02f5a is described below
commit ba02f5a6f14595550c16883bb9279132dddb3bc1
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Tue Jan 15 18:06:37 2019 +0000
Ticket 50155 - password history check has no way to just check the current password
Description: Currently if you set passwordinhistory 1, it checks the last
recorded password and the current password. To get it to just
check the current password we need to allow "0" in passwordinhistory.
Then only check the current password, and not the entry's
passwordHistory attributes (if any).
Also added new "rebind" function to Accounts class to "rebind"
on the current connection.
https://pagure.io/389-ds-base/issue/50155
Reviewed by: firstyear & spichugi (Thanks!!)
---
.../tests/suites/password/pwp_history_test.py | 209 ++++++++++-----------
ldap/servers/slapd/libglobs.c | 8 +-
ldap/servers/slapd/pw.c | 7 +-
src/lib389/lib389/idm/account.py | 7 +
4 files changed, 120 insertions(+), 111 deletions(-)
diff --git a/dirsrvtests/tests/suites/password/pwp_history_test.py b/dirsrvtests/tests/suites/password/pwp_history_test.py
index 459f9b5..5972e82 100644
--- a/dirsrvtests/tests/suites/password/pwp_history_test.py
+++ b/dirsrvtests/tests/suites/password/pwp_history_test.py
@@ -7,10 +7,11 @@
# --- END COPYRIGHT BLOCK ---
#
import pytest
+import time
from lib389.tasks import *
from lib389.utils import *
from lib389.topologies import topology_st
-
+from lib389.idm.user import UserAccounts, TEST_USER_PROPERTIES
from lib389._constants import DN_DM, DEFAULT_SUFFIX, PASSWORD
logging.getLogger(__name__).setLevel(logging.DEBUG)
@@ -35,6 +36,10 @@ def test_basic(topology_st):
6. Attempt to change the password to previous passwords
7. Reset password by Directory Manager (admin reset)
8. Try and change the password to the previous password before the reset
+ 9. Test passwordInHistory set to "0" rejects only the current password
+ 10. Test passwordInHistory set to "2" rejects previous passwords
+
+
:expectedresults:
1. Password history policy should be configured successfully
2. User should be added successfully
@@ -47,10 +52,10 @@ def test_basic(topology_st):
7. Password should be successfully reset
8. Password change should be correctly rejected
with Constrant Violation error
+ 9. Success
+ 10. Success
"""
- USER_DN = 'uid=testuser,' + DEFAULT_SUFFIX
-
#
# Configure password history policy and add a test user
#
@@ -58,37 +63,25 @@ def test_basic(topology_st):
topology_st.standalone.config.replace_many(('passwordHistory', 'on'),
('passwordInHistory', '3'),
('passwordChange', 'on'),
- ('passwordStorageScheme', 'CLEAR'))
+ ('passwordStorageScheme', 'CLEAR'),
+ ('nsslapd-auditlog-logging-enabled', 'on'))
log.info('Configured password policy.')
except ldap.LDAPError as e:
log.fatal('Failed to configure password policy: ' + str(e))
assert False
time.sleep(1)
- try:
- topology_st.standalone.add_s(Entry((USER_DN, {
- 'objectclass': ['top', 'extensibleObject'],
- 'sn': 'user',
- 'cn': 'test user',
- 'uid': 'testuser',
- 'userpassword': 'password'})))
- except ldap.LDAPError as e:
- log.fatal('Failed to add test user' + USER_DN + ': error ' + str(e))
- assert False
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
+ user = users.create(properties=TEST_USER_PROPERTIES)
+ user.set('userpassword', 'password')
+ user.rebind('password')
#
# Test that password history is enforced.
#
- try:
- topology_st.standalone.simple_bind_s(USER_DN, 'password')
- except ldap.LDAPError as e:
- log.fatal('Failed to bind as user: ' + str(e))
- assert False
-
# Attempt to change password to the same password
try:
- topology_st.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
- 'userpassword', b'password')])
+ user.set('userpassword', 'password')
log.info('Incorrectly able to to set password to existing password.')
assert False
except ldap.CONSTRAINT_VIOLATION:
@@ -100,110 +93,109 @@ def test_basic(topology_st):
#
# Keep changing password until we fill the password history (3)
#
+ user.set('userpassword', 'password1')
+ user.rebind('password1')
+ user.set('userpassword', 'password2')
+ user.rebind('password2')
+ user.set('userpassword', 'password3')
+ user.rebind('password3')
+ user.set('userpassword', 'password4')
+ user.rebind('password4')
+ time.sleep(1)
- # password1
- try:
- topology_st.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
- 'userpassword', b'password1')])
- except ldap.LDAPError as e:
- log.fatal('Failed to change password: ' + str(e))
- assert False
- try:
- topology_st.standalone.simple_bind_s(USER_DN, 'password1')
- except ldap.LDAPError as e:
- log.fatal('Failed to bind as user using "password1": ' + str(e))
+ #
+ # Check that we only have 3 passwords stored in history
+ #
+ pwds = user.get_attr_vals('passwordHistory')
+ if len(pwds) != 3:
+ log.fatal('Incorrect number of passwords stored in history: %d' %
+ len(pwds))
+ log.error('password history: ' + str(pwds))
assert False
- time.sleep(1)
+ else:
+ log.info('Correct number of passwords found in history.')
- # password2
+ #
+ # Attempt to change the password to previous passwords
+ #
try:
- topology_st.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
- 'userpassword', b'password2')])
- except ldap.LDAPError as e:
- log.fatal('Failed to change password: ' + str(e))
+ user.set('userpassword', 'password1')
+ log.fatal('Incorrectly able to to set password to previous password1.')
+ log.error('password history: ' + str(user.get_attr_vals('passwordhistory')))
assert False
- try:
- topology_st.standalone.simple_bind_s(USER_DN, 'password2')
+ except ldap.CONSTRAINT_VIOLATION:
+ log.info('Password change correctly rejected')
except ldap.LDAPError as e:
- log.fatal('Failed to bind as user using "password2": ' + str(e))
+ log.fatal('Failed to attempt to change password: ' + str(e))
assert False
- time.sleep(1)
-
- # password3
try:
- topology_st.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
- 'userpassword', b'password3')])
- except ldap.LDAPError as e:
- log.fatal('Failed to change password: ' + str(e))
+ user.set('userpassword', 'password2')
+ log.fatal('Incorrectly able to to set password to previous password2.')
+ log.error('password history: ' + str(user.get_attr_vals('passwordhistory')))
assert False
- try:
- topology_st.standalone.simple_bind_s(USER_DN, 'password3')
+ except ldap.CONSTRAINT_VIOLATION:
+ log.info('Password change correctly rejected')
except ldap.LDAPError as e:
- log.fatal('Failed to bind as user using "password3": ' + str(e))
+ log.fatal('Failed to attempt to change password: ' + str(e))
assert False
- time.sleep(1)
-
- # password4
try:
- topology_st.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
- 'userpassword', b'password4')])
- except ldap.LDAPError as e:
- log.fatal('Failed to change password: ' + str(e))
+ user.set('userpassword', 'password3')
+ log.fatal('Incorrectly able to to set password to previous password3.')
+ log.error('password history: ' + str(user.get_attr_vals('passwordhistory')))
assert False
- try:
- topology_st.standalone.simple_bind_s(USER_DN, 'password4')
+ except ldap.CONSTRAINT_VIOLATION:
+ log.info('Password change correctly rejected')
except ldap.LDAPError as e:
- log.fatal('Failed to bind as user using "password4": ' + str(e))
+ log.fatal('Failed to attempt to change password: ' + str(e))
assert False
- time.sleep(1)
#
- # Check that we only have 3 passwords stored in history
+ # Reset password by Directory Manager(admin reset)
#
try:
- entry = topology_st.standalone.search_s(USER_DN, ldap.SCOPE_BASE,
- 'objectclass=*',
- ['passwordHistory'])
- pwds = entry[0].getValues('passwordHistory')
- if len(pwds) != 3:
- log.fatal('Incorrect number of passwords stored in histry: %d' %
- len(pwds))
- assert False
- else:
- log.info('Correct number of passwords found in history.')
+ topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
except ldap.LDAPError as e:
- log.fatal('Failed to get user entry: ' + str(e))
+ log.fatal('Failed to bind as rootDN: ' + str(e))
assert False
+ user.set('userpassword', 'password-reset')
+ time.sleep(1)
- #
- # Attempt to change the password to previous passwords
- #
+ # Try and change the password to the previous password before the reset
try:
- topology_st.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
- 'userpassword', b'password1')])
- log.info('Incorrectly able to to set password to previous password1.')
+ user.rebind('password-reset')
+ user.set('userpassword', 'password4')
+ log.fatal('Incorrectly able to to set password to previous password4.')
+ log.error('password history: ' + str(user.get_attr_vals('passwordhistory')))
assert False
except ldap.CONSTRAINT_VIOLATION:
log.info('Password change correctly rejected')
except ldap.LDAPError as e:
log.fatal('Failed to attempt to change password: ' + str(e))
assert False
- time.sleep(1)
+ #
+ # Test passwordInHistory to 0
+ #
try:
- topology_st.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
- 'userpassword', b'password2')])
- log.info('Incorrectly able to to set password to previous password2.')
+ topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
+ except ldap.LDAPError as e:
+ log.fatal('Failed to bind as rootDN: ' + str(e))
assert False
- except ldap.CONSTRAINT_VIOLATION:
- log.info('Password change correctly rejected')
+
+ try:
+ topology_st.standalone.config.replace('passwordInHistory', '0')
+ log.info('Configured passwordInHistory to 0.')
except ldap.LDAPError as e:
- log.fatal('Failed to attempt to change password: ' + str(e))
+ log.fatal('Failed to configure password policy (passwordInHistory to 0): ' + str(e))
assert False
+
+ # Verify the older passwords in the entry (passwordhistory) are ignored
+ user.rebind('password-reset')
+ user.set('userpassword', 'password4')
try:
- topology_st.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
- 'userpassword', b'password3')])
- log.info('Incorrectly able to to set password to previous password3.')
+ user.set('userpassword', 'password4')
+ log.fatal('Incorrectly able to to set password to current password4.')
+ log.error('password history: ' + str(user.get_attr_vals('passwordhistory')))
assert False
except ldap.CONSTRAINT_VIOLATION:
log.info('Password change correctly rejected')
@@ -211,8 +203,12 @@ def test_basic(topology_st):
log.fatal('Failed to attempt to change password: ' + str(e))
assert False
+ # Need to make one successful update so history list is reset
+ user.set('userpassword', 'password5')
+
#
- # Reset password by Directory Manager(admin reset)
+ # Set the history count back to a positive value and make sure things still work
+ # as expected
#
try:
topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
@@ -221,33 +217,34 @@ def test_basic(topology_st):
assert False
try:
- topology_st.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
- 'userpassword',
- b'password-reset')])
+ topology_st.standalone.config.replace('passwordInHistory', '2')
+ log.info('Configured passwordInHistory to 2.')
except ldap.LDAPError as e:
- log.fatal('Failed to attempt to reset password: ' + str(e))
+ log.fatal('Failed to configure password policy (passwordInHistory to 2): ' + str(e))
assert False
time.sleep(1)
- # Try and change the password to the previous password before the reset
try:
- topology_st.standalone.simple_bind_s(USER_DN, 'password-reset')
+ user.rebind('password5')
+ user.set('userpassword', 'password5')
+ log.fatal('Incorrectly able to to set password to current password5.')
+ log.error('password history: ' + str(user.get_attr_vals('passwordhistory')))
+ assert False
+ except ldap.CONSTRAINT_VIOLATION:
+ log.info('Password change correctly rejected')
except ldap.LDAPError as e:
- log.fatal('Failed to bind as user: ' + str(e))
+ log.fatal('Failed to attempt to change password: ' + str(e))
assert False
- time.sleep(1)
+ # Test that old password that was in history is not being checked
try:
- topology_st.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
- 'userpassword', b'password4')])
- log.info('Incorrectly able to to set password to previous password4.')
- assert False
- except ldap.CONSTRAINT_VIOLATION:
- log.info('Password change correctly rejected')
+ user.set('userpassword', 'password1')
except ldap.LDAPError as e:
log.fatal('Failed to attempt to change password: ' + str(e))
+ log.error('password history: ' + str(user.get_attr_vals('passwordhistory')))
assert False
+ # Done
log.info('Test suite PASSED.')
diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
index a31a340..67a48b3 100644
--- a/ldap/servers/slapd/libglobs.c
+++ b/ldap/servers/slapd/libglobs.c
@@ -3407,8 +3407,8 @@ config_set_pw_maxfailure(const char *attrname, char *value, char *errorbuf, int
int
config_set_pw_inhistory(const char *attrname, char *value, char *errorbuf, int apply)
{
- int retVal = LDAP_SUCCESS;
- long history = 0;
+ int32_t retVal = LDAP_SUCCESS;
+ int64_t history = 0;
char *endp = NULL;
slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
@@ -3420,9 +3420,9 @@ config_set_pw_inhistory(const char *attrname, char *value, char *errorbuf, int a
errno = 0;
history = strtol(value, &endp, 10);
- if (*endp != '\0' || errno == ERANGE || history < 1 || history > 24) {
+ if (*endp != '\0' || errno == ERANGE || history < 0 || history > 24) {
slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
- "password history length \"%s\" is invalid. The password history must range from 1 to 24", value);
+ "password history length \"%s\" is invalid. The password history must range from 0 to 24", value);
retVal = LDAP_OPERATIONS_ERROR;
return retVal;
}
diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c
index 7462a4f..5e7788a 100644
--- a/ldap/servers/slapd/pw.c
+++ b/ldap/servers/slapd/pw.c
@@ -1255,7 +1255,7 @@ check_pw_syntax_ext(Slapi_PBlock *pb, const Slapi_DN *sdn, Slapi_Value **vals, c
if (pwpolicy->pw_history == 1) {
Slapi_Value **va = NULL;
attr = attrlist_find(e->e_attrs, "passwordHistory");
- if (attr && !valueset_isempty(&attr->a_present_values)) {
+ if (pwpolicy->pw_inhistory && attr && !valueset_isempty(&attr->a_present_values)) {
/* Resetting password history array if necessary. */
if (0 == update_pw_history(pb, sdn, NULL)) {
/* There was an update in the password history. Retry... */
@@ -1418,6 +1418,11 @@ update_pw_history(Slapi_PBlock *pb, const Slapi_DN *sdn, char *old_pw)
pwpolicy = new_passwdPolicy(pb, dn);
+ if (pwpolicy->pw_inhistory == 0){
+ /* We are only enforcing the current password, just return */
+ return res;
+ }
+
/* retrieve the entry */
e = get_entry(pb, dn);
if (e == NULL) {
diff --git a/src/lib389/lib389/idm/account.py b/src/lib389/lib389/idm/account.py
index d25a585..a709473 100644
--- a/src/lib389/lib389/idm/account.py
+++ b/src/lib389/lib389/idm/account.py
@@ -54,6 +54,13 @@ class Account(DSLdapObject):
inst_clone.open(*args, **kwargs)
return inst_clone
+ def rebind(self, password):
+ """Rebind on the same connection
+ :param password: An entry password
+ :type password: str
+ """
+ self._instance.simple_bind_s(self.dn, password)
+
def sasl_bind(self, *args, **kwargs):
"""Open a new connection and bind with the entry via SASL.
You can pass arguments that will be pass to clone.
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 10 months