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.