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@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
389-commits@lists.fedoraproject.org