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 894d1b6 Issue 50592 - Port Replication Tab to ReactJS
894d1b6 is described below
commit 894d1b6fb8c70c39085eb76bbc2316d82340ba13
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Fri Nov 1 15:10:34 2019 -0400
Issue 50592 - Port Replication Tab to ReactJS
Description: Ported the replication tab to React. Made many
other improvements throughout the UI:
- Protected "Treeviews" by disable/enable as components are
relaoded
- Add a new Double Confirmation Modal/Popup
- Added a script (buildAndWatch.sh) for faster/more convenient
developing
- Added a new RUV fucntion for the CLI, and made other lib389
improvements:
- Added support for not only "dc" suffixes, but also
"o", "ou", and "cn"
- Also rebase cockpit and lib389 with 1.4.1 as things have gotten out of
sync
relates:
https://pagure.io/389-ds-base/issue/50592
Reviewed by: spichugi(Thanks!)
---
src/cockpit/389-console/buildAndRun.sh | 45 +
src/cockpit/389-console/node_modules.mk | 5 +-
src/cockpit/389-console/package-lock.json | 1831 +++++++++++++++++++-
src/cockpit/389-console/package.json | 6 +-
src/cockpit/389-console/src/banner.html | 4 +-
src/cockpit/389-console/src/css/ds.css | 1022 ++---------
src/cockpit/389-console/src/database.jsx | 171 +-
src/cockpit/389-console/src/ds.js | 76 +-
src/cockpit/389-console/src/index.es6 | 7 +
src/cockpit/389-console/src/index.html | 114 +-
src/cockpit/389-console/src/lib/customCollapse.jsx | 2 +-
.../src/lib/database/attrEncryption.jsx | 3 +-
.../389-console/src/lib/database/backups.jsx | 227 ++-
.../389-console/src/lib/database/chaining.jsx | 616 ++++---
.../src/lib/database/databaseConfig.jsx | 226 ++-
.../389-console/src/lib/database/databaseModal.jsx | 52 +-
.../src/lib/database/databaseTables.jsx | 7 +-
.../389-console/src/lib/database/indexes.jsx | 4 +-
.../389-console/src/lib/database/referrals.jsx | 29 +-
.../389-console/src/lib/database/suffix.jsx | 167 +-
.../389-console/src/lib/database/suffixConfig.jsx | 120 +-
.../389-console/src/lib/database/vlvIndexes.jsx | 23 +-
.../389-console/src/lib/monitor/accesslog.jsx | 13 +-
.../389-console/src/lib/monitor/auditfaillog.jsx | 17 +-
.../389-console/src/lib/monitor/auditlog.jsx | 17 +-
.../src/lib/monitor/chainingMonitor.jsx | 15 +-
.../389-console/src/lib/monitor/dbMonitor.jsx | 124 +-
.../389-console/src/lib/monitor/errorlog.jsx | 16 +-
.../389-console/src/lib/monitor/monitorModals.jsx | 15 +-
.../389-console/src/lib/monitor/replMonitor.jsx | 12 +-
.../389-console/src/lib/monitor/serverMonitor.jsx | 199 ++-
.../389-console/src/lib/monitor/snmpMonitor.jsx | 119 +-
.../389-console/src/lib/monitor/suffixMonitor.jsx | 111 +-
src/cockpit/389-console/src/lib/notifications.jsx | 123 ++
.../389-console/src/lib/plugins/accountPolicy.jsx | 7 +-
.../src/lib/plugins/attributeUniqueness.jsx | 2 +-
.../389-console/src/lib/plugins/autoMembership.jsx | 2 +-
src/cockpit/389-console/src/lib/plugins/dna.jsx | 3 +-
.../src/lib/plugins/linkedAttributes.jsx | 2 +-
.../389-console/src/lib/plugins/managedEntries.jsx | 3 +-
.../389-console/src/lib/plugins/memberOf.jsx | 86 +-
.../src/lib/plugins/passthroughAuthentication.jsx | 4 +-
.../src/lib/plugins/pluginBasicConfig.jsx | 29 +-
.../src/lib/plugins/referentialIntegrity.jsx | 17 +-
.../389-console/src/lib/plugins/retroChangelog.jsx | 8 +-
.../src/lib/plugins/rootDNAccessControl.jsx | 14 +-
src/cockpit/389-console/src/lib/plugins/usn.jsx | 1 -
.../389-console/src/lib/plugins/winsync.jsx | 179 +-
.../389-console/src/lib/replication/replAgmts.jsx | 1150 ++++++++++++
.../src/lib/replication/replChangelog.jsx | 395 +++++
.../389-console/src/lib/replication/replConfig.jsx | 640 +++++++
.../389-console/src/lib/replication/replModals.jsx | 1684 ++++++++++++++++++
.../389-console/src/lib/replication/replSuffix.jsx | 456 +++++
.../replTables.jsx} | 374 ++--
.../389-console/src/lib/replication/replTasks.jsx | 314 ++++
.../src/lib/replication/winsyncAgmts.jsx | 1210 +++++++++++++
.../src/lib/security/certificateManagement.jsx | 53 +-
.../389-console/src/lib/security/ciphers.jsx | 2 +-
.../src/lib/security/securityTables.jsx | 6 +-
src/cockpit/389-console/src/lib/tools.jsx | 28 +-
src/cockpit/389-console/src/monitor.jsx | 69 +-
src/cockpit/389-console/src/plugins.jsx | 10 +-
src/cockpit/389-console/src/replication.jsx | 1148 ++++++++++++
src/cockpit/389-console/src/schema.html | 249 ++-
src/cockpit/389-console/src/schema.js | 206 ++-
src/cockpit/389-console/src/security.jsx | 72 +-
src/cockpit/389-console/src/servers.html | 147 +-
src/cockpit/389-console/src/servers.js | 248 ++-
src/cockpit/389-console/webpack.config.js | 13 +-
src/lib389/cli/dsctl | 3 +-
src/lib389/cli/dsidm | 6 +-
src/lib389/lib389/__init__.py | 11 +-
src/lib389/lib389/_mapped_object.py | 19 +-
src/lib389/lib389/agreement.py | 10 +-
src/lib389/lib389/backend.py | 3 +-
src/lib389/lib389/chaining.py | 4 +-
src/lib389/lib389/cli_base/__init__.py | 10 +-
src/lib389/lib389/cli_conf/backend.py | 65 +-
src/lib389/lib389/cli_conf/monitor.py | 4 +-
.../lib389/cli_conf/plugins/posix_winsync.py | 22 +
src/lib389/lib389/cli_conf/replication.py | 34 +-
src/lib389/lib389/cli_conf/security.py | 5 +-
src/lib389/lib389/cli_idm/__init__.py | 2 -
src/lib389/lib389/cli_idm/account.py | 116 +-
src/lib389/lib389/cli_idm/role.py | 126 ++
src/lib389/lib389/config.py | 2 +-
.../lib389/configurations/config_001004000.py | 35 +-
src/lib389/lib389/configurations/sample.py | 53 +
src/lib389/lib389/dbgen.py | 2 +
src/lib389/lib389/idm/account.py | 170 +-
src/lib389/lib389/idm/group.py | 13 +-
src/lib389/lib389/idm/posixgroup.py | 9 +-
src/lib389/lib389/idm/role.py | 377 ++--
src/lib389/lib389/instance/options.py | 7 +-
src/lib389/lib389/instance/remove.py | 12 +-
src/lib389/lib389/instance/setup.py | 57 +-
src/lib389/lib389/ldclt.py | 53 +-
src/lib389/lib389/mappingTree.py | 30 +-
src/lib389/lib389/monitor.py | 13 +
src/lib389/lib389/plugins.py | 53 +
src/lib389/lib389/replica.py | 60 +-
src/lib389/lib389/schema.py | 8 +-
src/lib389/lib389/tasks.py | 15 +
src/lib389/lib389/tools.py | 99 +-
src/lib389/lib389/utils.py | 86 +-
src/lib389/setup.py | 1 +
106 files changed, 13153 insertions(+), 2811 deletions(-)
diff --git a/src/cockpit/389-console/buildAndRun.sh
b/src/cockpit/389-console/buildAndRun.sh
new file mode 100755
index 0000000..8021003
--- /dev/null
+++ b/src/cockpit/389-console/buildAndRun.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+#
+# Do a fresh build of the UI, and run it. While in this state all updates made
+# are built immediately. Just refresh the browser to test them.
+
+AUDIT=0
+while (( "$#" )); do
+ case "$1" in
+ -a|--audit)
+ AUDIT=1
+ break
+ ;;
+ -h|--help)
+ echo Usage:
+ echo This is a development script to quickly refresh the UI and watch it live
+ echo Options:
+ echo -a|--audit Audit the build
+ exit 0
+ ;;
+ -*|--*=)
+ echo "Error: Unsupported argument $1" >&2
+ echo "Available Options:" >&2
+ echo " -a|--audit Audit the build" >&2
+ exit 1
+ ;;
+ esac
+done
+
+printf "\nCleaning and installing npm packages ...\n\n"
+make -f node_modules.mk clean > /dev/null
+make -f node_modules.mk install > /dev/null
+if [ $? != 0 ]; then
+ exit 1
+fi
+
+if [ $AUDIT == 1 ]; then
+ printf "\nAuditing npm packages ...\n\n"
+ make -f node_modules.mk build-cockpit-plugin
+ if [ $? != 0 ]; then
+ exit 1
+ fi
+fi
+
+printf "\nBuilding and watching ...\n"
+node_modules/webpack/bin/webpack.js --watch
diff --git a/src/cockpit/389-console/node_modules.mk
b/src/cockpit/389-console/node_modules.mk
index 307e8e3..e88b994 100644
--- a/src/cockpit/389-console/node_modules.mk
+++ b/src/cockpit/389-console/node_modules.mk
@@ -2,7 +2,10 @@ install: package.json
npm ci
build-cockpit-plugin: webpack.config.js
- npm run audit-ci && npm run build && cp -r dist cockpit_dist
+ifndef SKIP_AUDIT_CI
+ npm run audit-ci
+endif
+ npm run build && cp -r dist cockpit_dist
eslint-fix:
npm run eslint:fix
diff --git a/src/cockpit/389-console/package-lock.json
b/src/cockpit/389-console/package-lock.json
index f8ca714..6b4ebfb 100644
--- a/src/cockpit/389-console/package-lock.json
+++ b/src/cockpit/389-console/package-lock.json
@@ -266,7 +266,6 @@
"version": "7.0.0",
"resolved":
"https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
"integrity":
"sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==",
- "dev": true,
"requires": {
"@babel/types": "^7.0.0"
}
@@ -297,8 +296,7 @@
"@babel/helper-plugin-utils": {
"version": "7.0.0",
"resolved":
"https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz",
- "integrity":
"sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==",
- "dev": true
+ "integrity":
"sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA=="
},
"@babel/helper-regex": {
"version": "7.0.0",
@@ -951,13 +949,144 @@
"version": "7.1.3",
"resolved":
"https://registry.npmjs.org/@babel/types/-/types-7.1.3.tgz",
"integrity":
"sha512-RpPOVfK+yatXyn8n4PB1NW6k9qjinrXrRR8ugBN8fD6hCy5RXI6PSbVqpOJBO9oSaY7Nom4ohj35feb0UR9hSA==",
- "dev": true,
"requires": {
"esutils": "^2.0.2",
"lodash": "^4.17.10",
"to-fast-properties": "^2.0.0"
}
},
+ "@emotion/babel-utils": {
+ "version": "0.6.10",
+ "resolved":
"https://registry.npmjs.org/@emotion/babel-utils/-/babel-utils-0.6.10.tgz",
+ "integrity":
"sha512-/fnkM/LTEp3jKe++T0KyTszVGWNKPNOUJfjNKLO17BzQ6QPxgbg3whayom1Qr2oLFH3V92tDymU+dT5q676uow==",
+ "requires": {
+ "@emotion/hash": "^0.6.6",
+ "@emotion/memoize": "^0.6.6",
+ "@emotion/serialize": "^0.9.1",
+ "convert-source-map": "^1.5.1",
+ "find-root": "^1.1.0",
+ "source-map": "^0.7.2"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.7.3",
+ "resolved":
"https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+ "integrity":
"sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
+ }
+ }
+ },
+ "@emotion/hash": {
+ "version": "0.6.6",
+ "resolved":
"https://registry.npmjs.org/@emotion/hash/-/hash-0.6.6.tgz",
+ "integrity":
"sha512-ojhgxzUHZ7am3D2jHkMzPpsBAiB005GF5YU4ea+8DNPybMk01JJUM9V9YRlF/GE95tcOm8DxQvWA2jq19bGalQ=="
+ },
+ "@emotion/memoize": {
+ "version": "0.6.6",
+ "resolved":
"https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz",
+ "integrity":
"sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ=="
+ },
+ "@emotion/serialize": {
+ "version": "0.9.1",
+ "resolved":
"https://registry.npmjs.org/@emotion/serialize/-/serialize-0.9.1.tgz",
+ "integrity":
"sha512-zTuAFtyPvCctHBEL8KZ5lJuwBanGSutFEncqLn/m9T1a6a93smBStK+bZzcNPgj4QS8Rkw9VTwJGhRIUVO8zsQ==",
+ "requires": {
+ "@emotion/hash": "^0.6.6",
+ "@emotion/memoize": "^0.6.6",
+ "@emotion/unitless": "^0.6.7",
+ "@emotion/utils": "^0.8.2"
+ }
+ },
+ "@emotion/stylis": {
+ "version": "0.7.1",
+ "resolved":
"https://registry.npmjs.org/@emotion/stylis/-/stylis-0.7.1.tgz",
+ "integrity":
"sha512-/SLmSIkN13M//53TtNxgxo57mcJk/UJIDFRKwOiLIBEyBHEcipgR6hNMQ/59Sl4VjCJ0Z/3zeAZyvnSLPG/1HQ=="
+ },
+ "@emotion/unitless": {
+ "version": "0.6.7",
+ "resolved":
"https://registry.npmjs.org/@emotion/unitless/-/unitless-0.6.7.tgz",
+ "integrity":
"sha512-Arj1hncvEVqQ2p7Ega08uHLr1JuRYBuO5cIvcA+WWEQ5+VmkOE3ZXzl04NbQxeQpWX78G7u6MqxKuNX3wvYZxg=="
+ },
+ "@emotion/utils": {
+ "version": "0.8.2",
+ "resolved":
"https://registry.npmjs.org/@emotion/utils/-/utils-0.8.2.tgz",
+ "integrity":
"sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw=="
+ },
+ "@fortawesome/fontawesome-common-types": {
+ "version": "0.2.19",
+ "resolved":
"https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.19.tgz",
+ "integrity":
"sha512-nd2Ul/CUs8U9sjofQYAALzOGpgkVJQgEhIJnOHaoyVR/LeC3x2mVg4eB910a4kS6WgLPebAY0M2fApEI497raQ=="
+ },
+ "@fortawesome/free-brands-svg-icons": {
+ "version": "5.9.0",
+ "resolved":
"https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.9.0.tgz",
+ "integrity":
"sha512-sOz1wFyslaHUak8tY6IEhSAV1mAWbCLssBR8yFQV6f065k8nUCkjyrcxW4RVl9+wiLXmeG1CJUABUJV9DiW+7Q==",
+ "requires": {
+ "@fortawesome/fontawesome-common-types": "^0.2.19"
+ }
+ },
+ "@patternfly/react-core": {
+ "version": "3.58.1",
+ "resolved":
"https://registry.npmjs.org/@patternfly/react-core/-/react-core-3.58.1.tgz",
+ "integrity":
"sha512-ahb7cbWsLWZu69sHnn3ZdB5tcpXAbgHcUbAttWuG01KVpleK50qkwZMWW0JzuDlUfnelpW3kbaVfNE14HZo+Eg==",
+ "requires": {
+ "@patternfly/react-icons": "^3.10.6",
+ "@patternfly/react-styles": "^3.4.6",
+ "@patternfly/react-tokens": "^2.6.5",
+ "(a)tippy.js/react": "^1.1.1",
+ "emotion": "^9.2.9",
+ "exenv": "^1.2.2",
+ "focus-trap-react": "^4.0.1"
+ }
+ },
+ "@patternfly/react-icons": {
+ "version": "3.10.13",
+ "resolved":
"https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-3.10.13.tgz",
+ "integrity":
"sha512-UFiofUekWTAAaoFKVYvasj2KsFAirnMHofgabv9FPHVfJREHY9hU7aV1LAX+bO16lSh8/Qo88TjcVY1nawOnuQ==",
+ "requires": {
+ "@fortawesome/free-brands-svg-icons": "^5.8.1"
+ }
+ },
+ "@patternfly/react-styles": {
+ "version": "3.4.6",
+ "resolved":
"https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-3.4.6.tgz",
+ "integrity":
"sha512-m9ynr85EMmBAxGhCguwRiGgpLDA9+YKtwF83ORK7ylHHsrK9JC/RFPvTj8d/80oUMmlzcL765ZwG7sT9qR3ybA==",
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.0.0-beta.48",
+ "camel-case": "^3.0.0",
+ "css": "^2.2.3",
+ "cssom": "^0.3.4",
+ "cssstyle": "^0.3.1",
+ "emotion": "^9.2.9",
+ "emotion-server": "^9.2.9",
+ "fbjs-scripts": "^0.8.3",
+ "fs-extra": "^6.0.1",
+ "jsdom": "^15.1.0",
+ "relative": "^3.0.2",
+ "resolve-from": "^4.0.0",
+ "typescript": "3.4.5"
+ },
+ "dependencies": {
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved":
"https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity":
"sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
+ }
+ }
+ },
+ "@patternfly/react-tokens": {
+ "version": "2.6.5",
+ "resolved":
"https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-2.6.5.tgz",
+ "integrity":
"sha512-HZ0LLAOugMmnD9n5T5Hp6vDnZ86ikm+MXu7kbfNqK9dTOVmEroPai0rUN6hnnPP4mCzFrLXmh4rzecDr2273yg=="
+ },
+ "(a)tippy.js/react": {
+ "version": "1.1.1",
+ "resolved":
"https://registry.npmjs.org/@tippy.js/react/-/react-1.1.1.tgz",
+ "integrity":
"sha512-TkL1VufxgUvTMouDoBGv2vTdtUxtLUaRpspI4Rv0DsoKe2Ex1E5bl/qISk434mhuAhEnXuemrcgTaPWrfDvmGw==",
+ "requires": {
+ "prop-types": "^15.6.2",
+ "tippy.js": "^3.2.0"
+ }
+ },
"@types/c3": {
"version": "0.6.4",
"resolved":
"https://registry.npmjs.org/@types/c3/-/c3-0.6.4.tgz",
@@ -1420,6 +1549,11 @@
"integrity":
"sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g==",
"dev": true
},
+ "abab": {
+ "version": "2.0.0",
+ "resolved":
"https://registry.npmjs.org/abab/-/abab-2.0.0.tgz",
+ "integrity":
"sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w=="
+ },
"abbrev": {
"version": "1.1.1",
"resolved":
"https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -1440,6 +1574,22 @@
"acorn": "^5.0.0"
}
},
+ "acorn-globals": {
+ "version": "4.3.2",
+ "resolved":
"https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz",
+ "integrity":
"sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==",
+ "requires": {
+ "acorn": "^6.0.1",
+ "acorn-walk": "^6.0.1"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "6.2.1",
+ "resolved":
"https://registry.npmjs.org/acorn/-/acorn-6.2.1.tgz",
+ "integrity":
"sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q=="
+ }
+ }
+ },
"acorn-jsx": {
"version": "4.1.1",
"resolved":
"https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz",
@@ -1449,11 +1599,15 @@
"acorn": "^5.0.3"
}
},
+ "acorn-walk": {
+ "version": "6.2.0",
+ "resolved":
"https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
+ "integrity":
"sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA=="
+ },
"ajv": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz",
"integrity":
"sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==",
- "dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -1470,20 +1624,51 @@
"ajv-keywords": {
"version": "3.2.0",
"resolved":
"https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz",
- "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=",
- "dev": true
+ "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo="
},
"amdefine": {
"version": "1.0.1",
"resolved":
"https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
},
+ "ansi-colors": {
+ "version": "1.1.0",
+ "resolved":
"https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
+ "integrity":
"sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==",
+ "requires": {
+ "ansi-wrap": "^0.1.0"
+ }
+ },
+ "ansi-cyan": {
+ "version": "0.1.1",
+ "resolved":
"https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz",
+ "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=",
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
"ansi-escapes": {
"version": "3.1.0",
"resolved":
"https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
"integrity":
"sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==",
"dev": true
},
+ "ansi-gray": {
+ "version": "0.1.1",
+ "resolved":
"https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz",
+ "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=",
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
+ "ansi-red": {
+ "version": "0.1.1",
+ "resolved":
"https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz",
+ "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=",
+ "requires": {
+ "ansi-wrap": "0.1.0"
+ }
+ },
"ansi-regex": {
"version": "2.1.1",
"resolved":
"https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
@@ -1494,6 +1679,11 @@
"resolved":
"https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
+ "ansi-wrap": {
+ "version": "0.1.0",
+ "resolved":
"https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz",
+ "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768="
+ },
"anymatch": {
"version": "2.0.0",
"resolved":
"https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
@@ -1522,7 +1712,6 @@
"version": "1.0.10",
"resolved":
"https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity":
"sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
@@ -1536,8 +1725,7 @@
"arr-flatten": {
"version": "1.1.0",
"resolved":
"https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
- "integrity":
"sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
- "dev": true
+ "integrity":
"sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg=="
},
"arr-union": {
"version": "3.1.0",
@@ -1545,6 +1733,11 @@
"integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
"dev": true
},
+ "array-equal": {
+ "version": "1.0.0",
+ "resolved":
"https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
+ "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM="
+ },
"array-find-index": {
"version": "1.0.2",
"resolved":
"https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
@@ -1560,6 +1753,11 @@
"es-abstract": "^1.7.0"
}
},
+ "array-slice": {
+ "version": "0.2.3",
+ "resolved":
"https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz",
+ "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU="
+ },
"array-union": {
"version": "1.0.2",
"resolved":
"https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@@ -1671,8 +1869,7 @@
"async-limiter": {
"version": "1.0.0",
"resolved":
"https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
- "integrity":
"sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==",
- "dev": true
+ "integrity":
"sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"asynckit": {
"version": "0.4.0",
@@ -1682,8 +1879,7 @@
"atob": {
"version": "2.1.2",
"resolved":
"https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
- "integrity":
"sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
- "dev": true
+ "integrity":
"sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
"audit-ci": {
"version": "1.7.0",
@@ -1929,6 +2125,69 @@
"resolved":
"https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
"integrity":
"sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
},
+ "babel-code-frame": {
+ "version": "6.26.0",
+ "resolved":
"https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
+ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+ "requires": {
+ "chalk": "^1.1.3",
+ "esutils": "^2.0.2",
+ "js-tokens": "^3.0.2"
+ },
+ "dependencies": {
+ "js-tokens": {
+ "version": "3.0.2",
+ "resolved":
"https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
+ }
+ }
+ },
+ "babel-core": {
+ "version": "6.26.3",
+ "resolved":
"https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz",
+ "integrity":
"sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==",
+ "requires": {
+ "babel-code-frame": "^6.26.0",
+ "babel-generator": "^6.26.0",
+ "babel-helpers": "^6.24.1",
+ "babel-messages": "^6.23.0",
+ "babel-register": "^6.26.0",
+ "babel-runtime": "^6.26.0",
+ "babel-template": "^6.26.0",
+ "babel-traverse": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0",
+ "convert-source-map": "^1.5.1",
+ "debug": "^2.6.9",
+ "json5": "^0.5.1",
+ "lodash": "^4.17.4",
+ "minimatch": "^3.0.4",
+ "path-is-absolute": "^1.0.1",
+ "private": "^0.1.8",
+ "slash": "^1.0.0",
+ "source-map": "^0.5.7"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved":
"https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity":
"sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved":
"https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved":
"https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+ }
+ }
+ },
"babel-eslint": {
"version": "9.0.0",
"resolved":
"https://registry.npmjs.org/babel-eslint/-/babel-eslint-9.0.0.tgz",
@@ -1943,6 +2202,126 @@
"eslint-visitor-keys": "^1.0.0"
}
},
+ "babel-generator": {
+ "version": "6.26.1",
+ "resolved":
"https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz",
+ "integrity":
"sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==",
+ "requires": {
+ "babel-messages": "^6.23.0",
+ "babel-runtime": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "detect-indent": "^4.0.0",
+ "jsesc": "^1.3.0",
+ "lodash": "^4.17.4",
+ "source-map": "^0.5.7",
+ "trim-right": "^1.0.1"
+ },
+ "dependencies": {
+ "jsesc": {
+ "version": "1.3.0",
+ "resolved":
"https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
+ "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s="
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved":
"https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+ }
+ }
+ },
+ "babel-helper-builder-react-jsx": {
+ "version": "6.26.0",
+ "resolved":
"https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz",
+ "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=",
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "esutils": "^2.0.2"
+ }
+ },
+ "babel-helper-call-delegate": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz",
+ "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=",
+ "requires": {
+ "babel-helper-hoist-variables": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-define-map": {
+ "version": "6.26.0",
+ "resolved":
"https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz",
+ "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=",
+ "requires": {
+ "babel-helper-function-name": "^6.24.1",
+ "babel-runtime": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "lodash": "^4.17.4"
+ }
+ },
+ "babel-helper-function-name": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz",
+ "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=",
+ "requires": {
+ "babel-helper-get-function-arity": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-get-function-arity": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz",
+ "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=",
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-hoist-variables": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz",
+ "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=",
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-optimise-call-expression": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz",
+ "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=",
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helper-replace-supers": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz",
+ "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=",
+ "requires": {
+ "babel-helper-optimise-call-expression": "^6.24.1",
+ "babel-messages": "^6.23.0",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-helpers": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz",
+ "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=",
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1"
+ }
+ },
"babel-loader": {
"version": "8.0.4",
"resolved":
"https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.4.tgz",
@@ -1955,6 +2334,372 @@
"util.promisify": "^1.0.0"
}
},
+ "babel-messages": {
+ "version": "6.23.0",
+ "resolved":
"https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
+ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-check-es2015-constants": {
+ "version": "6.22.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz",
+ "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=",
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-emotion": {
+ "version": "9.2.11",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-9.2.11.tgz",
+ "integrity":
"sha512-dgCImifnOPPSeXod2znAmgc64NhaaOjGEHROR/M+lmStb3841yK1sgaDYAYMnlvWNz8GnpwIPN0VmNpbWYZ+VQ==",
+ "requires": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@emotion/babel-utils": "^0.6.4",
+ "@emotion/hash": "^0.6.2",
+ "@emotion/memoize": "^0.6.1",
+ "@emotion/stylis": "^0.7.0",
+ "babel-plugin-macros": "^2.0.0",
+ "babel-plugin-syntax-jsx": "^6.18.0",
+ "convert-source-map": "^1.5.0",
+ "find-root": "^1.1.0",
+ "mkdirp": "^0.5.1",
+ "source-map": "^0.5.7",
+ "touch": "^2.0.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.5.7",
+ "resolved":
"https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+ }
+ }
+ },
+ "babel-plugin-macros": {
+ "version": "2.6.1",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.6.1.tgz",
+ "integrity":
"sha512-6W2nwiXme6j1n2erPOnmRiWfObUhWH7Qw1LMi9XZy8cj+KtESu3T6asZvtk5bMQQjX8te35o7CFueiSdL/2NmQ==",
+ "requires": {
+ "@babel/runtime": "^7.4.2",
+ "cosmiconfig": "^5.2.0",
+ "resolve": "^1.10.0"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.5.5",
+ "resolved":
"https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz",
+ "integrity":
"sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==",
+ "requires": {
+ "regenerator-runtime": "^0.13.2"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.3",
+ "resolved":
"https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
+ "integrity":
"sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
+ },
+ "resolve": {
+ "version": "1.11.1",
+ "resolved":
"https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz",
+ "integrity":
"sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==",
+ "requires": {
+ "path-parse": "^1.0.6"
+ }
+ }
+ }
+ },
+ "babel-plugin-syntax-class-properties": {
+ "version": "6.13.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
+ "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94="
+ },
+ "babel-plugin-syntax-flow": {
+ "version": "6.18.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
+ "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0="
+ },
+ "babel-plugin-syntax-jsx": {
+ "version": "6.18.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
+ "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
+ },
+ "babel-plugin-syntax-object-rest-spread": {
+ "version": "6.13.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
+ "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U="
+ },
+ "babel-plugin-syntax-trailing-function-commas": {
+ "version": "6.22.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz",
+ "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM="
+ },
+ "babel-plugin-transform-class-properties": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz",
+ "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=",
+ "requires": {
+ "babel-helper-function-name": "^6.24.1",
+ "babel-plugin-syntax-class-properties": "^6.8.0",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-arrow-functions": {
+ "version": "6.22.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz",
+ "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=",
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-block-scoped-functions": {
+ "version": "6.22.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz",
+ "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=",
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-block-scoping": {
+ "version": "6.26.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz",
+ "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=",
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "babel-template": "^6.26.0",
+ "babel-traverse": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "lodash": "^4.17.4"
+ }
+ },
+ "babel-plugin-transform-es2015-classes": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz",
+ "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=",
+ "requires": {
+ "babel-helper-define-map": "^6.24.1",
+ "babel-helper-function-name": "^6.24.1",
+ "babel-helper-optimise-call-expression": "^6.24.1",
+ "babel-helper-replace-supers": "^6.24.1",
+ "babel-messages": "^6.23.0",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-computed-properties": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz",
+ "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=",
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-destructuring": {
+ "version": "6.23.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz",
+ "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=",
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-for-of": {
+ "version": "6.23.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz",
+ "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=",
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-function-name": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz",
+ "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=",
+ "requires": {
+ "babel-helper-function-name": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-literals": {
+ "version": "6.22.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz",
+ "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=",
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-modules-commonjs": {
+ "version": "6.26.2",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz",
+ "integrity":
"sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==",
+ "requires": {
+ "babel-plugin-transform-strict-mode": "^6.24.1",
+ "babel-runtime": "^6.26.0",
+ "babel-template": "^6.26.0",
+ "babel-types": "^6.26.0"
+ }
+ },
+ "babel-plugin-transform-es2015-object-super": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz",
+ "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=",
+ "requires": {
+ "babel-helper-replace-supers": "^6.24.1",
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-parameters": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz",
+ "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=",
+ "requires": {
+ "babel-helper-call-delegate": "^6.24.1",
+ "babel-helper-get-function-arity": "^6.24.1",
+ "babel-runtime": "^6.22.0",
+ "babel-template": "^6.24.1",
+ "babel-traverse": "^6.24.1",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-shorthand-properties": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz",
+ "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=",
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-plugin-transform-es2015-spread": {
+ "version": "6.22.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz",
+ "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=",
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es2015-template-literals": {
+ "version": "6.22.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz",
+ "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=",
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es3-member-expression-literals": {
+ "version": "6.22.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es3-member-expression-literals/-/babel-plugin-transform-es3-member-expression-literals-6.22.0.tgz",
+ "integrity": "sha1-cz00RPPsxBvvjtGmpOCWV7iWnrs=",
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-es3-property-literals": {
+ "version": "6.22.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-es3-property-literals/-/babel-plugin-transform-es3-property-literals-6.22.0.tgz",
+ "integrity": "sha1-sgeNWELiKr9A9z6M3pzTcRq9V1g=",
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-flow-strip-types": {
+ "version": "6.22.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz",
+ "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=",
+ "requires": {
+ "babel-plugin-syntax-flow": "^6.18.0",
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-object-rest-spread": {
+ "version": "6.26.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz",
+ "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=",
+ "requires": {
+ "babel-plugin-syntax-object-rest-spread": "^6.8.0",
+ "babel-runtime": "^6.26.0"
+ }
+ },
+ "babel-plugin-transform-react-display-name": {
+ "version": "6.25.0",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz",
+ "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=",
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-react-jsx": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz",
+ "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=",
+ "requires": {
+ "babel-helper-builder-react-jsx": "^6.24.1",
+ "babel-plugin-syntax-jsx": "^6.8.0",
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-plugin-transform-strict-mode": {
+ "version": "6.24.1",
+ "resolved":
"https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz",
+ "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=",
+ "requires": {
+ "babel-runtime": "^6.22.0",
+ "babel-types": "^6.24.1"
+ }
+ },
+ "babel-preset-fbjs": {
+ "version": "2.3.0",
+ "resolved":
"https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-2.3.0.tgz",
+ "integrity":
"sha512-ZOpAI1/bN0Y3J1ZAK9gRsFkHy9gGgJoDRUjtUCla/129LC7uViq9nIK22YdHfey8szohYoZY3f9L2lGOv0Edqw==",
+ "requires": {
+ "babel-plugin-check-es2015-constants": "^6.8.0",
+ "babel-plugin-syntax-class-properties": "^6.8.0",
+ "babel-plugin-syntax-flow": "^6.8.0",
+ "babel-plugin-syntax-jsx": "^6.8.0",
+ "babel-plugin-syntax-object-rest-spread": "^6.8.0",
+ "babel-plugin-syntax-trailing-function-commas": "^6.8.0",
+ "babel-plugin-transform-class-properties": "^6.8.0",
+ "babel-plugin-transform-es2015-arrow-functions": "^6.8.0",
+ "babel-plugin-transform-es2015-block-scoped-functions":
"^6.8.0",
+ "babel-plugin-transform-es2015-block-scoping": "^6.8.0",
+ "babel-plugin-transform-es2015-classes": "^6.8.0",
+ "babel-plugin-transform-es2015-computed-properties":
"^6.8.0",
+ "babel-plugin-transform-es2015-destructuring": "^6.8.0",
+ "babel-plugin-transform-es2015-for-of": "^6.8.0",
+ "babel-plugin-transform-es2015-function-name": "^6.8.0",
+ "babel-plugin-transform-es2015-literals": "^6.8.0",
+ "babel-plugin-transform-es2015-modules-commonjs": "^6.8.0",
+ "babel-plugin-transform-es2015-object-super": "^6.8.0",
+ "babel-plugin-transform-es2015-parameters": "^6.8.0",
+ "babel-plugin-transform-es2015-shorthand-properties":
"^6.8.0",
+ "babel-plugin-transform-es2015-spread": "^6.8.0",
+ "babel-plugin-transform-es2015-template-literals": "^6.8.0",
+ "babel-plugin-transform-es3-member-expression-literals":
"^6.8.0",
+ "babel-plugin-transform-es3-property-literals": "^6.8.0",
+ "babel-plugin-transform-flow-strip-types": "^6.8.0",
+ "babel-plugin-transform-object-rest-spread": "^6.8.0",
+ "babel-plugin-transform-react-display-name": "^6.8.0",
+ "babel-plugin-transform-react-jsx": "^6.8.0"
+ }
+ },
+ "babel-register": {
+ "version": "6.26.0",
+ "resolved":
"https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz",
+ "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=",
+ "requires": {
+ "babel-core": "^6.26.0",
+ "babel-runtime": "^6.26.0",
+ "core-js": "^2.5.0",
+ "home-or-tmp": "^2.0.0",
+ "lodash": "^4.17.4",
+ "mkdirp": "^0.5.1",
+ "source-map-support": "^0.4.15"
+ }
+ },
"babel-runtime": {
"version": "6.26.0",
"resolved":
"https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
@@ -1971,6 +2716,77 @@
}
}
},
+ "babel-template": {
+ "version": "6.26.0",
+ "resolved":
"https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
+ "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=",
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "babel-traverse": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0",
+ "lodash": "^4.17.4"
+ }
+ },
+ "babel-traverse": {
+ "version": "6.26.0",
+ "resolved":
"https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
+ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=",
+ "requires": {
+ "babel-code-frame": "^6.26.0",
+ "babel-messages": "^6.23.0",
+ "babel-runtime": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0",
+ "debug": "^2.6.8",
+ "globals": "^9.18.0",
+ "invariant": "^2.2.2",
+ "lodash": "^4.17.4"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved":
"https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity":
"sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "globals": {
+ "version": "9.18.0",
+ "resolved":
"https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
+ "integrity":
"sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ=="
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved":
"https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
+ "babel-types": {
+ "version": "6.26.0",
+ "resolved":
"https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
+ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "esutils": "^2.0.2",
+ "lodash": "^4.17.4",
+ "to-fast-properties": "^1.0.3"
+ },
+ "dependencies": {
+ "to-fast-properties": {
+ "version": "1.0.3",
+ "resolved":
"https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
+ "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc="
+ }
+ }
+ },
+ "babylon": {
+ "version": "6.18.0",
+ "resolved":
"https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
+ "integrity":
"sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ=="
+ },
"balanced-match": {
"version": "1.0.0",
"resolved":
"https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -2178,6 +2994,11 @@
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
"dev": true
},
+ "browser-process-hrtime": {
+ "version": "0.1.3",
+ "resolved":
"https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz",
+ "integrity":
"sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw=="
+ },
"browserify-aes": {
"version": "1.2.0",
"resolved":
"http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
@@ -2354,6 +3175,21 @@
"unset-value": "^1.0.0"
}
},
+ "caller-callsite": {
+ "version": "2.0.0",
+ "resolved":
"https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
+ "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
+ "requires": {
+ "callsites": "^2.0.0"
+ },
+ "dependencies": {
+ "callsites": {
+ "version": "2.0.0",
+ "resolved":
"https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
+ "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA="
+ }
+ }
+ },
"caller-path": {
"version": "0.1.0",
"resolved":
"https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
@@ -2369,6 +3205,15 @@
"integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
"dev": true
},
+ "camel-case": {
+ "version": "3.0.0",
+ "resolved":
"https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
+ "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
+ "requires": {
+ "no-case": "^2.2.0",
+ "upper-case": "^1.1.1"
+ }
+ },
"camelcase": {
"version": "2.1.1",
"resolved":
"https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
@@ -2574,6 +3419,11 @@
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
+ "color-support": {
+ "version": "1.1.3",
+ "resolved":
"https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
+ "integrity":
"sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="
+ },
"combined-stream": {
"version": "1.0.7",
"resolved":
"https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
@@ -2660,7 +3510,6 @@
"version": "1.6.0",
"resolved":
"https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
"integrity":
"sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
- "dev": true,
"requires": {
"safe-buffer": "~5.1.1"
}
@@ -2711,6 +3560,28 @@
"resolved":
"https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
+ "cosmiconfig": {
+ "version": "5.2.1",
+ "resolved":
"https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
+ "integrity":
"sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==",
+ "requires": {
+ "import-fresh": "^2.0.0",
+ "is-directory": "^0.3.1",
+ "js-yaml": "^3.13.1",
+ "parse-json": "^4.0.0"
+ },
+ "dependencies": {
+ "parse-json": {
+ "version": "4.0.0",
+ "resolved":
"https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+ "requires": {
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1"
+ }
+ }
+ }
+ },
"create-ecdh": {
"version": "4.0.3",
"resolved":
"https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
@@ -2721,6 +3592,30 @@
"elliptic": "^6.0.0"
}
},
+ "create-emotion": {
+ "version": "9.2.12",
+ "resolved":
"https://registry.npmjs.org/create-emotion/-/create-emotion-9.2.12.tgz",
+ "integrity":
"sha512-P57uOF9NL2y98Xrbl2OuiDQUZ30GVmASsv5fbsjF4Hlraip2kyAvMm+2PoYUvFFw03Fhgtxk3RqZSm2/qHL9hA==",
+ "requires": {
+ "@emotion/hash": "^0.6.2",
+ "@emotion/memoize": "^0.6.1",
+ "@emotion/stylis": "^0.7.0",
+ "@emotion/unitless": "^0.6.2",
+ "csstype": "^2.5.2",
+ "stylis": "^3.5.0",
+ "stylis-rule-sheet": "^0.0.10"
+ }
+ },
+ "create-emotion-server": {
+ "version": "9.2.12",
+ "resolved":
"https://registry.npmjs.org/create-emotion-server/-/create-emotion-server-9.2.12.tgz",
+ "integrity":
"sha512-ET+E6A5MkQTEBNDYAnjh6+0cB33qStFXhtflkZNPEaOmvzYlB/xcPnpUk4J7ul3MVa8PCQx2Ei5g2MGY/y1n+g==",
+ "requires": {
+ "html-tokenize": "^2.0.0",
+ "multipipe": "^1.0.2",
+ "through": "^2.3.8"
+ }
+ },
"create-hash": {
"version": "1.2.0",
"resolved":
"http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
@@ -2785,6 +3680,24 @@
"randomfill": "^1.0.3"
}
},
+ "css": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",
+ "integrity":
"sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==",
+ "requires": {
+ "inherits": "^2.0.3",
+ "source-map": "^0.6.1",
+ "source-map-resolve": "^0.5.2",
+ "urix": "^0.1.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved":
"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity":
"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ }
+ }
+ },
"css-element-queries": {
"version": "1.2.0",
"resolved":
"https://registry.npmjs.org/css-element-queries/-/css-element-queries-1.2.0.tgz",
@@ -2866,6 +3779,19 @@
"integrity":
"sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true
},
+ "cssom": {
+ "version": "0.3.8",
+ "resolved":
"https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
+ "integrity":
"sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
+ },
+ "cssstyle": {
+ "version": "0.3.1",
+ "resolved":
"https://registry.npmjs.org/cssstyle/-/cssstyle-0.3.1.tgz",
+ "integrity":
"sha512-tNvaxM5blOnxanyxI6panOsnfiyLRj3HV4qjqqS45WPNS1usdYWRUQjqTEEELK73lpeP/1KoIGYUwrBn/VcECA==",
+ "requires": {
+ "cssom": "0.3.x"
+ }
+ },
"csstype": {
"version": "2.5.8",
"resolved":
"https://registry.npmjs.org/csstype/-/csstype-2.5.8.tgz",
@@ -2898,6 +3824,16 @@
"assert-plus": "^1.0.0"
}
},
+ "data-urls": {
+ "version": "1.1.0",
+ "resolved":
"https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz",
+ "integrity":
"sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==",
+ "requires": {
+ "abab": "^2.0.0",
+ "whatwg-mimetype": "^2.2.0",
+ "whatwg-url": "^7.0.0"
+ }
+ },
"datatables.net": {
"version": "1.10.19",
"resolved":
"https://registry.npmjs.org/datatables.net/-/datatables.net-1.10.19.tgz",
@@ -2971,14 +3907,12 @@
"decode-uri-component": {
"version": "0.2.0",
"resolved":
"https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
- "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
- "dev": true
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
},
"deep-is": {
"version": "0.1.3",
"resolved":
"https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
- "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
- "dev": true
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
},
"define-properties": {
"version": "1.1.3",
@@ -3081,6 +4015,14 @@
"minimalistic-assert": "^1.0.0"
}
},
+ "detect-indent": {
+ "version": "4.0.0",
+ "resolved":
"https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
+ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
+ "requires": {
+ "repeating": "^2.0.0"
+ }
+ },
"diffie-hellman": {
"version": "5.0.3",
"resolved":
"http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
@@ -3142,6 +4084,14 @@
"integrity":
"sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
"dev": true
},
+ "domexception": {
+ "version": "1.0.1",
+ "resolved":
"https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
+ "integrity":
"sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
+ "requires": {
+ "webidl-conversions": "^4.0.2"
+ }
+ },
"drmonty-datatables-colvis": {
"version": "1.1.2",
"resolved":
"https://registry.npmjs.org/drmonty-datatables-colvis/-/drmonty-datatables-colvis-1.1.2.tgz",
@@ -3151,6 +4101,14 @@
"jquery": ">=1.7.0"
}
},
+ "duplexer2": {
+ "version": "0.1.4",
+ "resolved":
"https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+ "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
+ "requires": {
+ "readable-stream": "^2.0.2"
+ }
+ },
"duplexify": {
"version": "3.6.1",
"resolved":
"https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz",
@@ -3196,8 +4154,24 @@
"emojis-list": {
"version": "2.1.0",
"resolved":
"https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
- "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
- "dev": true
+ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k="
+ },
+ "emotion": {
+ "version": "9.2.12",
+ "resolved":
"https://registry.npmjs.org/emotion/-/emotion-9.2.12.tgz",
+ "integrity":
"sha512-hcx7jppaI8VoXxIWEhxpDW7I+B4kq9RNzQLmsrF6LY8BGKqe2N+gFAQr0EfuFucFlPs2A9HM4+xNj4NeqEWIOQ==",
+ "requires": {
+ "babel-plugin-emotion": "^9.2.11",
+ "create-emotion": "^9.2.12"
+ }
+ },
+ "emotion-server": {
+ "version": "9.2.12",
+ "resolved":
"https://registry.npmjs.org/emotion-server/-/emotion-server-9.2.12.tgz",
+ "integrity":
"sha512-Bhjdl7eNoIeiAVa2QPP5d+1nP/31SiO/K1P/qI9cdXCydg91NwGYmteqhhge8u7PF8fLGTEVQfcPwj21815eBw==",
+ "requires": {
+ "create-emotion-server": "^9.2.12"
+ }
},
"encoding": {
"version": "0.1.12",
@@ -3293,6 +4267,31 @@
"resolved":
"https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
+ "escodegen": {
+ "version": "1.11.1",
+ "resolved":
"https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz",
+ "integrity":
"sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==",
+ "requires": {
+ "esprima": "^3.1.3",
+ "estraverse": "^4.2.0",
+ "esutils": "^2.0.2",
+ "optionator": "^0.8.1",
+ "source-map": "~0.6.1"
+ },
+ "dependencies": {
+ "esprima": {
+ "version": "3.1.3",
+ "resolved":
"https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
+ "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved":
"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity":
"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "optional": true
+ }
+ }
+ },
"eslint": {
"version": "5.7.0",
"resolved":
"https://registry.npmjs.org/eslint/-/eslint-5.7.0.tgz",
@@ -3763,6 +4762,11 @@
"acorn-jsx": "^4.1.1"
}
},
+ "esprima": {
+ "version": "4.0.1",
+ "resolved":
"https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity":
"sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
+ },
"esquery": {
"version": "1.0.1",
"resolved":
"https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
@@ -3784,14 +4788,12 @@
"estraverse": {
"version": "4.2.0",
"resolved":
"https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
- "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
- "dev": true
+ "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
},
"esutils": {
"version": "2.0.2",
"resolved":
"https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
- "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
- "dev": true
+ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
},
"events": {
"version": "1.1.1",
@@ -3839,6 +4841,11 @@
}
}
},
+ "exenv": {
+ "version": "1.2.2",
+ "resolved":
"https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz",
+ "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50="
+ },
"expand-brackets": {
"version": "2.1.4",
"resolved":
"https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
@@ -4008,6 +5015,17 @@
"resolved":
"https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
+ "fancy-log": {
+ "version": "1.3.3",
+ "resolved":
"https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz",
+ "integrity":
"sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==",
+ "requires": {
+ "ansi-gray": "^0.1.1",
+ "color-support": "^1.1.3",
+ "parse-node-version": "^1.0.0",
+ "time-stamp": "^1.0.0"
+ }
+ },
"fast-deep-equal": {
"version": "2.0.1",
"resolved":
"https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
@@ -4021,8 +5039,7 @@
"fast-levenshtein": {
"version": "2.0.6",
"resolved":
"https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
- "dev": true
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
},
"fbjs": {
"version": "0.8.17",
@@ -4045,6 +5062,35 @@
}
}
},
+ "fbjs-scripts": {
+ "version": "0.8.3",
+ "resolved":
"https://registry.npmjs.org/fbjs-scripts/-/fbjs-scripts-0.8.3.tgz",
+ "integrity":
"sha512-aUJ/uEzMIiBYuj/blLp4sVNkQQ7ZEB/lyplG1IzzOmZ83meiWecrGg5jBo4wWrxXmO4RExdtsSV1QkTjPt2Gag==",
+ "requires": {
+ "ansi-colors": "^1.0.1",
+ "babel-core": "^6.7.2",
+ "babel-preset-fbjs": "^2.1.2",
+ "core-js": "^2.4.1",
+ "cross-spawn": "^5.1.0",
+ "fancy-log": "^1.3.2",
+ "object-assign": "^4.0.1",
+ "plugin-error": "^0.1.2",
+ "semver": "^5.1.0",
+ "through2": "^2.0.0"
+ },
+ "dependencies": {
+ "cross-spawn": {
+ "version": "5.1.0",
+ "resolved":
"https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+ "requires": {
+ "lru-cache": "^4.0.1",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ }
+ }
+ },
"figures": {
"version": "2.0.0",
"resolved":
"https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@@ -4064,6 +5110,49 @@
"object-assign": "^4.0.1"
}
},
+ "file-loader": {
+ "version": "4.1.0",
+ "resolved":
"https://registry.npmjs.org/file-loader/-/file-loader-4.1.0.tgz",
+ "integrity":
"sha512-ajDk1nlByoalZAGR4b0H6oD+EGlWnyW1qbSxzaUc7RFiqmn+RbXQQRbTc72jsiUIlVusJ4Et58ltds8ZwTfnAw==",
+ "requires": {
+ "loader-utils": "^1.2.3",
+ "schema-utils": "^2.0.0"
+ },
+ "dependencies": {
+ "big.js": {
+ "version": "5.2.2",
+ "resolved":
"https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+ "integrity":
"sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
+ },
+ "json5": {
+ "version": "1.0.1",
+ "resolved":
"https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity":
"sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
+ "loader-utils": {
+ "version": "1.2.3",
+ "resolved":
"https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
+ "integrity":
"sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^2.0.0",
+ "json5": "^1.0.1"
+ }
+ },
+ "schema-utils": {
+ "version": "2.0.1",
+ "resolved":
"https://registry.npmjs.org/schema-utils/-/schema-utils-2.0.1.tgz",
+ "integrity":
"sha512-HJFKJ4JixDpRur06QHwi8uu2kZbng318ahWEKgBjc0ZklcE4FDvmm2wghb448q0IRaABxIESt8vqPFvwgMB80A==",
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ }
+ }
+ },
"fill-range": {
"version": "4.0.0",
"resolved":
"https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
@@ -4098,6 +5187,11 @@
"pkg-dir": "^2.0.0"
}
},
+ "find-root": {
+ "version": "1.1.0",
+ "resolved":
"https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity":
"sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
+ },
"find-up": {
"version": "1.1.2",
"resolved":
"https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
@@ -4129,6 +5223,23 @@
"readable-stream": "^2.0.4"
}
},
+ "focus-trap": {
+ "version": "3.0.0",
+ "resolved":
"https://registry.npmjs.org/focus-trap/-/focus-trap-3.0.0.tgz",
+ "integrity":
"sha512-jTFblf0tLWbleGjj2JZsAKbgtZTdL1uC48L8FcmSDl4c2vDoU4NycN1kgV5vJhuq1mxNFkw7uWZ1JAGlINWvyw==",
+ "requires": {
+ "tabbable": "^3.1.0",
+ "xtend": "^4.0.1"
+ }
+ },
+ "focus-trap-react": {
+ "version": "4.0.1",
+ "resolved":
"https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-4.0.1.tgz",
+ "integrity":
"sha512-UUZKVEn5cFbF6yUnW7lbXNW0iqN617ShSqYKgxctUvWw1wuylLtyVmC0RmPQNnJ/U+zoKc/djb0tZMs0uN/0QQ==",
+ "requires": {
+ "focus-trap": "^3.0.0"
+ }
+ },
"font-awesome": {
"version": "4.7.0",
"resolved":
"https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
@@ -4189,6 +5300,16 @@
"readable-stream": "^2.0.0"
}
},
+ "fs-extra": {
+ "version": "6.0.1",
+ "resolved":
"https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz",
+ "integrity":
"sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
"fs-write-stream-atomic": {
"version": "1.0.10",
"resolved":
"https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
@@ -4931,6 +6052,40 @@
"resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
"integrity":
"sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw=="
},
+ "handlebars": {
+ "version": "4.3.3",
+ "resolved":
"https://registry.npmjs.org/handlebars/-/handlebars-4.3.3.tgz",
+ "integrity":
"sha512-VupOxR91xcGojfINrzMqrvlyYbBs39sXIrWa7YdaQWeBudOlvKEGvCczMfJPgnuwHE/zyH1M6J+IUP6cgDVyxg==",
+ "requires": {
+ "neo-async": "^2.6.0",
+ "optimist": "^0.6.1",
+ "source-map": "^0.6.1",
+ "uglify-js": "^3.1.4"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.20.0",
+ "resolved":
"https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
+ "integrity":
"sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==",
+ "optional": true
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved":
"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity":
"sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ },
+ "uglify-js": {
+ "version": "3.6.0",
+ "resolved":
"https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
+ "integrity":
"sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==",
+ "optional": true,
+ "requires": {
+ "commander": "~2.20.0",
+ "source-map": "~0.6.1"
+ }
+ }
+ }
+ },
"har-schema": {
"version": "2.0.0",
"resolved":
"https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
@@ -5060,11 +6215,95 @@
"resolved":
"https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity":
"sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
},
+ "home-or-tmp": {
+ "version": "2.0.0",
+ "resolved":
"https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
+ "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=",
+ "requires": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.1"
+ }
+ },
"hosted-git-info": {
"version": "2.7.1",
"resolved":
"https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
"integrity":
"sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w=="
},
+ "html-encoding-sniffer": {
+ "version": "1.0.2",
+ "resolved":
"https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
+ "integrity":
"sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==",
+ "requires": {
+ "whatwg-encoding": "^1.0.1"
+ }
+ },
+ "html-tokenize": {
+ "version": "2.0.0",
+ "resolved":
"https://registry.npmjs.org/html-tokenize/-/html-tokenize-2.0.0.tgz",
+ "integrity": "sha1-izqaXetHXK5qb5ZxYA0sIKspglE=",
+ "requires": {
+ "buffer-from": "~0.1.1",
+ "inherits": "~2.0.1",
+ "minimist": "~0.0.8",
+ "readable-stream": "~1.0.27-1",
+ "through2": "~0.4.1"
+ },
+ "dependencies": {
+ "buffer-from": {
+ "version": "0.1.2",
+ "resolved":
"https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz",
+ "integrity":
"sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg=="
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "resolved":
"https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+ },
+ "minimist": {
+ "version": "0.0.10",
+ "resolved":
"https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+ "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
+ },
+ "object-keys": {
+ "version": "0.4.0",
+ "resolved":
"https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz",
+ "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY="
+ },
+ "readable-stream": {
+ "version": "1.0.34",
+ "resolved":
"https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "0.0.1",
+ "string_decoder": "~0.10.x"
+ }
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved":
"https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ },
+ "through2": {
+ "version": "0.4.2",
+ "resolved":
"https://registry.npmjs.org/through2/-/through2-0.4.2.tgz",
+ "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=",
+ "requires": {
+ "readable-stream": "~1.0.17",
+ "xtend": "~2.1.1"
+ }
+ },
+ "xtend": {
+ "version": "2.1.2",
+ "resolved":
"https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz",
+ "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=",
+ "requires": {
+ "object-keys": "~0.4.0"
+ }
+ }
+ }
+ },
"htmlparser": {
"version": "1.7.7",
"resolved":
"https://registry.npmjs.org/htmlparser/-/htmlparser-1.7.7.tgz",
@@ -5128,6 +6367,30 @@
"integrity":
"sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
"dev": true
},
+ "import-fresh": {
+ "version": "2.0.0",
+ "resolved":
"https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
+ "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
+ "requires": {
+ "caller-path": "^2.0.0",
+ "resolve-from": "^3.0.0"
+ },
+ "dependencies": {
+ "caller-path": {
+ "version": "2.0.0",
+ "resolved":
"https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
+ "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
+ "requires": {
+ "caller-callsite": "^2.0.0"
+ }
+ },
+ "resolve-from": {
+ "version": "3.0.0",
+ "resolved":
"https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g="
+ }
+ }
+ },
"import-local": {
"version": "2.0.0",
"resolved":
"https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
@@ -5345,6 +6608,11 @@
"resolved":
"https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
},
+ "ip-regex": {
+ "version": "2.1.0",
+ "resolved":
"https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
+ "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk="
+ },
"is-accessor-descriptor": {
"version": "0.1.6",
"resolved":
"https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
@@ -5444,6 +6712,11 @@
}
}
},
+ "is-directory": {
+ "version": "0.3.1",
+ "resolved":
"https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
+ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE="
+ },
"is-extendable": {
"version": "0.1.1",
"resolved":
"https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
@@ -5648,11 +6921,91 @@
"resolved":
"https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity":
"sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
+ "js-yaml": {
+ "version": "3.13.1",
+ "resolved":
"https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+ "integrity":
"sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
"jsbn": {
"version": "0.1.1",
"resolved":
"https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
},
+ "jsdom": {
+ "version": "15.1.1",
+ "resolved":
"https://registry.npmjs.org/jsdom/-/jsdom-15.1.1.tgz",
+ "integrity":
"sha512-cQZRBB33arrDAeCrAEWn1U3SvrvC8XysBua9Oqg1yWrsY/gYcusloJC3RZJXuY5eehSCmws8f2YeliCqGSkrtQ==",
+ "requires": {
+ "abab": "^2.0.0",
+ "acorn": "^6.1.1",
+ "acorn-globals": "^4.3.2",
+ "array-equal": "^1.0.0",
+ "cssom": "^0.3.6",
+ "cssstyle": "^1.2.2",
+ "data-urls": "^1.1.0",
+ "domexception": "^1.0.1",
+ "escodegen": "^1.11.1",
+ "html-encoding-sniffer": "^1.0.2",
+ "nwsapi": "^2.1.4",
+ "parse5": "5.1.0",
+ "pn": "^1.1.0",
+ "request": "^2.88.0",
+ "request-promise-native": "^1.0.7",
+ "saxes": "^3.1.9",
+ "symbol-tree": "^3.2.2",
+ "tough-cookie": "^3.0.1",
+ "w3c-hr-time": "^1.0.1",
+ "w3c-xmlserializer": "^1.1.2",
+ "webidl-conversions": "^4.0.2",
+ "whatwg-encoding": "^1.0.5",
+ "whatwg-mimetype": "^2.3.0",
+ "whatwg-url": "^7.0.0",
+ "ws": "^7.0.0",
+ "xml-name-validator": "^3.0.0"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "6.2.1",
+ "resolved":
"https://registry.npmjs.org/acorn/-/acorn-6.2.1.tgz",
+ "integrity":
"sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q=="
+ },
+ "cssstyle": {
+ "version": "1.4.0",
+ "resolved":
"https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz",
+ "integrity":
"sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==",
+ "requires": {
+ "cssom": "0.3.x"
+ }
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved":
"https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity":
"sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+ },
+ "tough-cookie": {
+ "version": "3.0.1",
+ "resolved":
"https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",
+ "integrity":
"sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==",
+ "requires": {
+ "ip-regex": "^2.1.0",
+ "psl": "^1.1.28",
+ "punycode": "^2.1.1"
+ }
+ },
+ "ws": {
+ "version": "7.1.1",
+ "resolved":
"https://registry.npmjs.org/ws/-/ws-7.1.1.tgz",
+ "integrity":
"sha512-o41D/WmDeca0BqYhsr3nJzQyg9NF5X8l/UdnFNux9cS3lwB+swm8qGWX5rn+aD6xfBU3rGmtHij7g7x6LxFU3A==",
+ "requires": {
+ "async-limiter": "^1.0.0"
+ }
+ }
+ }
+ },
"jsesc": {
"version": "2.5.1",
"resolved":
"https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz",
@@ -5662,8 +7015,7 @@
"json-parse-better-errors": {
"version": "1.0.2",
"resolved":
"https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
- "integrity":
"sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
- "dev": true
+ "integrity":
"sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
},
"json-schema": {
"version": "0.2.3",
@@ -5689,8 +7041,15 @@
"json5": {
"version": "0.5.1",
"resolved":
"http://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
- "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
- "dev": true
+ "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE="
+ },
+ "jsonfile": {
+ "version": "4.0.0",
+ "resolved":
"https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+ "requires": {
+ "graceful-fs": "^4.1.6"
+ }
},
"jsprim": {
"version": "1.4.1",
@@ -5735,7 +7094,6 @@
"version": "0.3.0",
"resolved":
"https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
- "dev": true,
"requires": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
@@ -5845,6 +7203,11 @@
"resolved":
"https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
"integrity":
"sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ=="
},
+ "lodash.sortby": {
+ "version": "4.7.0",
+ "resolved":
"https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
+ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg="
+ },
"lodash.tail": {
"version": "4.1.1",
"resolved":
"https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz",
@@ -5868,6 +7231,11 @@
"signal-exit": "^3.0.0"
}
},
+ "lower-case": {
+ "version": "1.1.4",
+ "resolved":
"https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
+ "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw="
+ },
"lru-cache": {
"version": "4.1.3",
"resolved":
"https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz",
@@ -6003,6 +7371,11 @@
"brorand": "^1.0.1"
}
},
+ "mime": {
+ "version": "2.4.4",
+ "resolved":
"https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
+ "integrity":
"sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA=="
+ },
"mime-db": {
"version": "1.37.0",
"resolved":
"https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
@@ -6154,6 +7527,15 @@
"integrity":
"sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
},
+ "multipipe": {
+ "version": "1.0.2",
+ "resolved":
"https://registry.npmjs.org/multipipe/-/multipipe-1.0.2.tgz",
+ "integrity": "sha1-zBPv2DPJzamfIk+GhGG44aP9k50=",
+ "requires": {
+ "duplexer2": "^0.1.2",
+ "object-assign": "^4.1.0"
+ }
+ },
"mute-stream": {
"version": "0.0.7",
"resolved":
"https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
@@ -6193,8 +7575,7 @@
"neo-async": {
"version": "2.6.0",
"resolved":
"https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz",
- "integrity":
"sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==",
- "dev": true
+ "integrity":
"sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA=="
},
"nice-try": {
"version": "1.0.5",
@@ -6202,6 +7583,14 @@
"integrity":
"sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
},
+ "no-case": {
+ "version": "2.3.2",
+ "resolved":
"https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
+ "integrity":
"sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
+ "requires": {
+ "lower-case": "^1.1.1"
+ }
+ },
"node-fetch": {
"version": "1.7.3",
"resolved":
"https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
@@ -6356,6 +7745,11 @@
"resolved":
"https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
},
+ "nwsapi": {
+ "version": "2.1.4",
+ "resolved":
"https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz",
+ "integrity":
"sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw=="
+ },
"oauth-sign": {
"version": "0.9.0",
"resolved":
"https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
@@ -6454,11 +7848,31 @@
"mimic-fn": "^1.0.0"
}
},
+ "optimist": {
+ "version": "0.6.1",
+ "resolved":
"https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+ "requires": {
+ "minimist": "~0.0.1",
+ "wordwrap": "~0.0.2"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "0.0.10",
+ "resolved":
"https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+ "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
+ },
+ "wordwrap": {
+ "version": "0.0.3",
+ "resolved":
"https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+ "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
+ }
+ }
+ },
"optionator": {
"version": "0.8.2",
"resolved":
"https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
"integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
- "dev": true,
"requires": {
"deep-is": "~0.1.3",
"fast-levenshtein": "~2.0.4",
@@ -6581,6 +7995,16 @@
"error-ex": "^1.2.0"
}
},
+ "parse-node-version": {
+ "version": "1.0.1",
+ "resolved":
"https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
+ "integrity":
"sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA=="
+ },
+ "parse5": {
+ "version": "5.1.0",
+ "resolved":
"https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz",
+ "integrity":
"sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ=="
+ },
"pascalcase": {
"version": "0.1.1",
"resolved":
"https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
@@ -6627,8 +8051,7 @@
"path-parse": {
"version": "1.0.6",
"resolved":
"https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
- "integrity":
"sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
- "dev": true
+ "integrity":
"sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
},
"path-type": {
"version": "1.1.0",
@@ -6831,12 +8254,58 @@
}
}
},
+ "plugin-error": {
+ "version": "0.1.2",
+ "resolved":
"https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz",
+ "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=",
+ "requires": {
+ "ansi-cyan": "^0.1.1",
+ "ansi-red": "^0.1.1",
+ "arr-diff": "^1.0.1",
+ "arr-union": "^2.0.1",
+ "extend-shallow": "^1.1.2"
+ },
+ "dependencies": {
+ "arr-diff": {
+ "version": "1.1.0",
+ "resolved":
"https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz",
+ "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=",
+ "requires": {
+ "arr-flatten": "^1.0.1",
+ "array-slice": "^0.2.3"
+ }
+ },
+ "arr-union": {
+ "version": "2.1.0",
+ "resolved":
"https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz",
+ "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0="
+ },
+ "extend-shallow": {
+ "version": "1.1.4",
+ "resolved":
"https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz",
+ "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=",
+ "requires": {
+ "kind-of": "^1.1.0"
+ }
+ },
+ "kind-of": {
+ "version": "1.1.0",
+ "resolved":
"https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
+ "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ="
+ }
+ }
+ },
"pluralize": {
"version": "7.0.0",
"resolved":
"https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
"integrity":
"sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==",
"dev": true
},
+ "pn": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
+ "integrity":
"sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA=="
+ },
"popper.js": {
"version": "1.15.0",
"resolved":
"https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz",
@@ -6967,14 +8436,12 @@
"prelude-ls": {
"version": "1.1.2",
"resolved":
"https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
- "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
- "dev": true
+ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
},
"private": {
"version": "0.1.8",
"resolved":
"https://registry.npmjs.org/private/-/private-0.1.8.tgz",
- "integrity":
"sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==",
- "dev": true
+ "integrity":
"sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg=="
},
"process": {
"version": "0.11.10",
@@ -7489,6 +8956,24 @@
}
}
},
+ "relative": {
+ "version": "3.0.2",
+ "resolved":
"https://registry.npmjs.org/relative/-/relative-3.0.2.tgz",
+ "integrity": "sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8=",
+ "requires": {
+ "isobject": "^2.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "2.1.0",
+ "resolved":
"https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ }
+ }
+ },
"remove-trailing-separator": {
"version": "1.1.0",
"resolved":
"https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@@ -7549,6 +9034,24 @@
}
}
},
+ "request-promise-core": {
+ "version": "1.1.2",
+ "resolved":
"https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz",
+ "integrity":
"sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==",
+ "requires": {
+ "lodash": "^4.17.11"
+ }
+ },
+ "request-promise-native": {
+ "version": "1.0.7",
+ "resolved":
"https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz",
+ "integrity":
"sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==",
+ "requires": {
+ "request-promise-core": "1.1.2",
+ "stealthy-require": "^1.1.1",
+ "tough-cookie": "^2.3.3"
+ }
+ },
"require-directory": {
"version": "2.1.1",
"resolved":
"https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -7604,8 +9107,7 @@
"resolve-url": {
"version": "0.2.1",
"resolved":
"https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
- "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
- "dev": true
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
},
"restore-cursor": {
"version": "2.0.0",
@@ -7720,6 +9222,14 @@
}
}
},
+ "saxes": {
+ "version": "3.1.11",
+ "resolved":
"https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz",
+ "integrity":
"sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==",
+ "requires": {
+ "xmlchars": "^2.1.1"
+ }
+ },
"scheduler": {
"version": "0.11.3",
"resolved":
"https://registry.npmjs.org/scheduler/-/scheduler-0.11.3.tgz",
@@ -7851,7 +9361,6 @@
"version": "1.2.0",
"resolved":
"https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
- "dev": true,
"requires": {
"shebang-regex": "^1.0.0"
}
@@ -7859,8 +9368,7 @@
"shebang-regex": {
"version": "1.0.0",
"resolved":
"https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
- "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
- "dev": true
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
},
"signal-exit": {
"version": "3.0.2",
@@ -7876,8 +9384,7 @@
"slash": {
"version": "1.0.0",
"resolved":
"https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
- "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
- "dev": true
+ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU="
},
"slice-ansi": {
"version": "1.0.0",
@@ -8048,7 +9555,6 @@
"version": "0.5.2",
"resolved":
"https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
"integrity":
"sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
- "dev": true,
"requires": {
"atob": "^2.1.1",
"decode-uri-component": "^0.2.0",
@@ -8057,11 +9563,25 @@
"urix": "^0.1.0"
}
},
+ "source-map-support": {
+ "version": "0.4.18",
+ "resolved":
"https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
+ "integrity":
"sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==",
+ "requires": {
+ "source-map": "^0.5.6"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.5.7",
+ "resolved":
"https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+ }
+ }
+ },
"source-map-url": {
"version": "0.4.0",
"resolved":
"https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
- "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
- "dev": true
+ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM="
},
"spdx-correct": {
"version": "3.0.2",
@@ -8103,8 +9623,7 @@
"sprintf-js": {
"version": "1.0.3",
"resolved":
"https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
- "dev": true
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"sshpk": {
"version": "1.16.0",
@@ -8166,6 +9685,11 @@
"readable-stream": "^2.0.1"
}
},
+ "stealthy-require": {
+ "version": "1.1.1",
+ "resolved":
"https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
+ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
+ },
"stream-browserify": {
"version": "2.0.1",
"resolved":
"https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
@@ -8282,6 +9806,16 @@
}
}
},
+ "stylis": {
+ "version": "3.5.4",
+ "resolved":
"https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz",
+ "integrity":
"sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q=="
+ },
+ "stylis-rule-sheet": {
+ "version": "0.0.10",
+ "resolved":
"https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz",
+ "integrity":
"sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw=="
+ },
"supports-color": {
"version": "2.0.0",
"resolved":
"https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
@@ -8292,6 +9826,16 @@
"resolved":
"https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity":
"sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
},
+ "symbol-tree": {
+ "version": "3.2.4",
+ "resolved":
"https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity":
"sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
+ },
+ "tabbable": {
+ "version": "3.1.2",
+ "resolved":
"https://registry.npmjs.org/tabbable/-/tabbable-3.1.2.tgz",
+ "integrity":
"sha512-wjB6puVXTYO0BSFtCmWQubA/KIn7Xvajw0x0l6eJUudMG/EAiJvIUnyNX6xO4NpGrJ16lbD0eUseB9WxW0vlpQ=="
+ },
"table": {
"version": "5.1.0",
"resolved":
"https://registry.npmjs.org/table/-/table-5.1.0.tgz",
@@ -8394,19 +9938,22 @@
"through": {
"version": "2.3.8",
"resolved":
"http://registry.npmjs.org/through/-/through-2.3.8.tgz",
- "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
- "dev": true
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"through2": {
"version": "2.0.3",
"resolved":
"https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
"integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
- "dev": true,
"requires": {
"readable-stream": "^2.1.5",
"xtend": "~4.0.1"
}
},
+ "time-stamp": {
+ "version": "1.1.0",
+ "resolved":
"https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz",
+ "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM="
+ },
"timers-browserify": {
"version": "2.0.10",
"resolved":
"https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz",
@@ -8416,6 +9963,14 @@
"setimmediate": "^1.0.4"
}
},
+ "tippy.js": {
+ "version": "3.4.1",
+ "resolved":
"https://registry.npmjs.org/tippy.js/-/tippy.js-3.4.1.tgz",
+ "integrity":
"sha512-ZiyGP9WZyCCcjxKM4G88cm4U1r1ytjeMDGa5FSKPaPzwc/3yZJVZsb1ffcmqUMCpryRp5LNxRNGKLzbs11sb/Q==",
+ "requires": {
+ "popper.js": "^1.14.6"
+ }
+ },
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@@ -8434,8 +9989,7 @@
"to-fast-properties": {
"version": "2.0.0",
"resolved":
"https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
- "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
- "dev": true
+ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
},
"to-object-path": {
"version": "0.3.0",
@@ -8479,6 +10033,24 @@
"repeat-string": "^1.6.1"
}
},
+ "touch": {
+ "version": "2.0.2",
+ "resolved":
"https://registry.npmjs.org/touch/-/touch-2.0.2.tgz",
+ "integrity":
"sha512-qjNtvsFXTRq7IuMLweVgFxmEuQ6gLbRs2jQxL80TtZ31dEKWYIxRXquij6w6VimyDek5hD3PytljHmEtAs2u0A==",
+ "requires": {
+ "nopt": "~1.0.10"
+ },
+ "dependencies": {
+ "nopt": {
+ "version": "1.0.10",
+ "resolved":
"https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+ "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
+ "requires": {
+ "abbrev": "1"
+ }
+ }
+ }
+ },
"tough-cookie": {
"version": "2.4.3",
"resolved":
"https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
@@ -8488,6 +10060,21 @@
"punycode": "^1.4.1"
}
},
+ "tr46": {
+ "version": "1.0.1",
+ "resolved":
"https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
+ "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
+ "requires": {
+ "punycode": "^2.1.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "2.1.1",
+ "resolved":
"https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity":
"sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+ }
+ }
+ },
"trim-newlines": {
"version": "1.0.0",
"resolved":
"https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
@@ -8496,8 +10083,7 @@
"trim-right": {
"version": "1.0.1",
"resolved":
"https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
- "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
- "dev": true
+ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM="
},
"true-case-path": {
"version": "1.0.3",
@@ -8536,7 +10122,6 @@
"version": "0.3.2",
"resolved":
"https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
- "dev": true,
"requires": {
"prelude-ls": "~1.1.2"
}
@@ -8552,6 +10137,11 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"dev": true
},
+ "typescript": {
+ "version": "3.4.5",
+ "resolved":
"https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz",
+ "integrity":
"sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw=="
+ },
"ua-parser-js": {
"version": "0.7.19",
"resolved":
"https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz",
@@ -8683,6 +10273,11 @@
"imurmurhash": "^0.1.4"
}
},
+ "universalify": {
+ "version": "0.1.2",
+ "resolved":
"https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity":
"sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+ },
"unset-value": {
"version": "1.0.0",
"resolved":
"https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
@@ -8729,6 +10324,11 @@
"integrity":
"sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==",
"dev": true
},
+ "upper-case": {
+ "version": "1.1.3",
+ "resolved":
"https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
+ "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
+ },
"uri-js": {
"version": "4.2.2",
"resolved":
"https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
@@ -8747,8 +10347,7 @@
"urix": {
"version": "0.1.0",
"resolved":
"https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
- "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
- "dev": true
+ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI="
},
"url": {
"version": "0.11.0",
@@ -8768,6 +10367,50 @@
}
}
},
+ "url-loader": {
+ "version": "2.1.0",
+ "resolved":
"https://registry.npmjs.org/url-loader/-/url-loader-2.1.0.tgz",
+ "integrity":
"sha512-kVrp/8VfEm5fUt+fl2E0FQyrpmOYgMEkBsv8+UDP1wFhszECq5JyGF33I7cajlVY90zRZ6MyfgKXngLvHYZX8A==",
+ "requires": {
+ "loader-utils": "^1.2.3",
+ "mime": "^2.4.4",
+ "schema-utils": "^2.0.0"
+ },
+ "dependencies": {
+ "big.js": {
+ "version": "5.2.2",
+ "resolved":
"https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+ "integrity":
"sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
+ },
+ "json5": {
+ "version": "1.0.1",
+ "resolved":
"https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+ "integrity":
"sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "requires": {
+ "minimist": "^1.2.0"
+ }
+ },
+ "loader-utils": {
+ "version": "1.2.3",
+ "resolved":
"https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
+ "integrity":
"sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
+ "requires": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^2.0.0",
+ "json5": "^1.0.1"
+ }
+ },
+ "schema-utils": {
+ "version": "2.0.1",
+ "resolved":
"https://registry.npmjs.org/schema-utils/-/schema-utils-2.0.1.tgz",
+ "integrity":
"sha512-HJFKJ4JixDpRur06QHwi8uu2kZbng318ahWEKgBjc0ZklcE4FDvmm2wghb448q0IRaABxIESt8vqPFvwgMB80A==",
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ }
+ }
+ },
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
@@ -8837,6 +10480,24 @@
"indexof": "0.0.1"
}
},
+ "w3c-hr-time": {
+ "version": "1.0.1",
+ "resolved":
"https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
+ "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=",
+ "requires": {
+ "browser-process-hrtime": "^0.1.2"
+ }
+ },
+ "w3c-xmlserializer": {
+ "version": "1.1.2",
+ "resolved":
"https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz",
+ "integrity":
"sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==",
+ "requires": {
+ "domexception": "^1.0.1",
+ "webidl-conversions": "^4.0.2",
+ "xml-name-validator": "^3.0.0"
+ }
+ },
"warning": {
"version": "3.0.0",
"resolved":
"https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
@@ -8856,6 +10517,11 @@
"neo-async": "^2.5.0"
}
},
+ "webidl-conversions": {
+ "version": "4.0.2",
+ "resolved":
"https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+ "integrity":
"sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="
+ },
"webpack": {
"version": "4.22.0",
"resolved":
"https://registry.npmjs.org/webpack/-/webpack-4.22.0.tgz",
@@ -9171,11 +10837,34 @@
}
}
},
+ "whatwg-encoding": {
+ "version": "1.0.5",
+ "resolved":
"https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz",
+ "integrity":
"sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==",
+ "requires": {
+ "iconv-lite": "0.4.24"
+ }
+ },
"whatwg-fetch": {
"version": "3.0.0",
"resolved":
"https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
"integrity":
"sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
},
+ "whatwg-mimetype": {
+ "version": "2.3.0",
+ "resolved":
"https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
+ "integrity":
"sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g=="
+ },
+ "whatwg-url": {
+ "version": "7.0.0",
+ "resolved":
"https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz",
+ "integrity":
"sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==",
+ "requires": {
+ "lodash.sortby": "^4.7.0",
+ "tr46": "^1.0.1",
+ "webidl-conversions": "^4.0.2"
+ }
+ },
"which": {
"version": "1.3.1",
"resolved":
"https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -9200,8 +10889,7 @@
"wordwrap": {
"version": "1.0.0",
"resolved":
"https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
- "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
- "dev": true
+ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
},
"worker-farm": {
"version": "1.6.0",
@@ -9246,6 +10934,16 @@
"ultron": "~1.1.0"
}
},
+ "xml-name-validator": {
+ "version": "3.0.0",
+ "resolved":
"https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
+ "integrity":
"sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw=="
+ },
+ "xmlchars": {
+ "version": "2.1.1",
+ "resolved":
"https://registry.npmjs.org/xmlchars/-/xmlchars-2.1.1.tgz",
+ "integrity":
"sha512-7hew1RPJ1iIuje/Y01bGD/mXokXxegAgVS+e+E0wSi2ILHQkYAH1+JXARwTjZSM4Z4Z+c73aKspEcqj+zPPL/w=="
+ },
"xregexp": {
"version": "4.0.0",
"resolved":
"https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz",
@@ -9255,8 +10953,7 @@
"xtend": {
"version": "4.0.1",
"resolved":
"https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
- "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
- "dev": true
+ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
},
"y18n": {
"version": "3.2.1",
diff --git a/src/cockpit/389-console/package.json b/src/cockpit/389-console/package.json
index ddf44b5..420fb6f 100644
--- a/src/cockpit/389-console/package.json
+++ b/src/cockpit/389-console/package.json
@@ -49,7 +49,10 @@
"webpack-cli": "^3.1.0"
},
"dependencies": {
+ "@patternfly/react-core": "^3.58.1",
"bootstrap": "^4.3.1",
+ "file-loader": "^4.1.0",
+ "handlebars": "^4.3.3",
"node-sass": "4.11.0",
"patternfly": "^3.59.3",
"patternfly-react": "^2.34.3",
@@ -60,6 +63,7 @@
"react-dom": "16.6.1",
"react-switch": "^5.0.0",
"recompose": "0.30.0",
- "table-resolver": "4.1.1"
+ "table-resolver": "4.1.1",
+ "url-loader": "^2.1.0"
}
}
diff --git a/src/cockpit/389-console/src/banner.html
b/src/cockpit/389-console/src/banner.html
index d609aec..16b4107 100644
--- a/src/cockpit/389-console/src/banner.html
+++ b/src/cockpit/389-console/src/banner.html
@@ -1,6 +1,6 @@
-389 Directory Server Management <div class="dropdown ds-server-action">
<select class="btn btn-default dropdown ds-dropdown-server"
+389 Directory Server Management <div class="dropdown ds-server-action">
<select class="btn btn-default dropdown"
title="Directory Server Instance List" id="select-server">
- </select></div><div class="dropdown
ds-float-right"><button class="btn btn-primary dropdown-toggle
ds-action-btn"
+ </select></div><div class="dropdown
ds-float-right"><button class="btn btn-primary ds-action-btn"
type="button" id="server-list-menu"
data-toggle="dropdown">Actions<span
class="caret"></span>
</button><ul class="dropdown-menu pull-right"
role="menu">
<li role=""><a role="menuitem"
id="start-server-btn" href="#">Start Instance</a></li>
diff --git a/src/cockpit/389-console/src/css/ds.css
b/src/cockpit/389-console/src/css/ds.css
index 8082d1e..fdc79e7 100644
--- a/src/cockpit/389-console/src/css/ds.css
+++ b/src/cockpit/389-console/src/css/ds.css
@@ -8,19 +8,6 @@
margin-bottom: 20px;
}
-.ds-logo-hr {
- padding: 0 !important;
- margin-top: -20px !important;
- line-height: 0;
-}
-
-.ds-title-hr {
- margin: 0 !important;
- padding: 0 !important;
- margin-right: 10px !important;
- margin-bottom: 10px !important;
-}
-
.ds-container {
margin: 0;
padding: 0 !important;
@@ -32,13 +19,8 @@
vertical-align: top !important;
}
-.ds-fixed {
- padding: 0 !important;
- padding-bottom: 10px;
- width: 175px !important;
- min-width: 175px !important;
- margin: 2px 2px 2px 0px !important;
- line-height: 0;
+.ds-left-align {
+ right: 0 !important;
}
/* Main nav page index.html */
@@ -47,101 +29,14 @@
padding-top: 115px; /* this pushes the content below fixed nav bar */
padding-bottom: 50px;
margin-top: 0;
- margin-left: 25px;
margin-right: 10px;
margin-bottom: 10px;
}
-.ds-flex {
- /* background-color: #f7f7f7; */
- background-color: white !important;
- flex-grow: 1;
- border: 1px groove black;
- border-style: groove;
- margin: 6px 2px 2px 10px;
- margin-left: 0px !important;
- margin-right: 10px;
- margin-bottom: 10px;
- padding-left: 15px;
- padding-right: 15px;
- padding-top: 0px;
- padding-bottom: 0px;
- min-height: 500px;
- line-height: 0;
- border-radius: 5px;
-}
-
-.ds-repl-state-good {
- margin: auto;
- width: 30px;
- height: 30px;
- border-radius: 50%;
- font-size: 12px;
- font-weight: bold;
- color: white;
- line-height: 1;
- text-align: center;
- background-color: #4dac26;
- border-color: black;
- padding-top: 8px;
- align-content: center !important;
- vertical-align: middle !important;
-}
-
-.ds-repl-state-bad {
- margin: auto;
- width: 30px;
- height: 30px;
- border-radius: 50%;
- font-size: 12px;
- font-weight: bold;
- color: white;
- line-height: 1;
- text-align: center;
- background-color: #d01c8b;
- border-color: black;
- padding-top: 8px;
- align-content: center !important;
- vertical-align: middle !important;
-}
-
.ds-chart-right {
margin-left: 65px;
}
-.ds-chart-left {
- margin-left: 25px;
- margin-bottom: 9px;
-}
-
-.ds-chart-center {
- margin-left: 100px;
- margin-bottom: 15px;
-}
-
-.ds-repl-lag-bad {
- font-weight: bold;
- color: #7b3294;
-}
-
-.ds-button-border {
- line-height: 0;
- margin: 0px;
- padding-top: 0px;
- padding-bottom: 10px;
-}
-
-.ds-button {
- height: 30px;
- width: 160px !important;
- margin-top: 5px;
- float: none;
- left: auto;
- line-height: normal;
- overflow: visible;
- position: static;
-}
-
.ds-oc-add-del-btn {
padding: 0 !important;
margin: 10px !important;
@@ -159,6 +54,11 @@
margin-right: 9px;
}
+/* This can probably be removed once we switch to PF React 4 - suffix action button */
+.ds-action-button {
+ color: white;
+}
+
.ds-oc-may-buttons {
padding0: !important;
padding-left: 3px;
@@ -167,72 +67,6 @@
margin-right: 9px;
}
-.ds-panel {
- margin: 0px 0px 0px 0px;
- padding-top: 10px;
- padding-left: 10px;
- line-height: 0;
-}
-
-.ds-dropdown {
- padding: 0px;
- padding-left: 5px;
- height: 35px;
- width: 300px !important;
- margin-top: 10px;
- text-align: left;
- outline: 0 !important;
-}
-
-.ds-dropdown-server {
- margin-top: 0px !important;
- margin-left: 6px;
- padding: 0px !important;
- padding-left: 10px !important;
- padding-right: 10px !important;
- height: 30px;
- text-align: left;
-}
-
-.ds-passwd-dropdown {
- height: 35px !important;
- padding: 0px !important;
- outline: 0 !important;
- width: 150px !important;
-}
-
-.ds-log-dropdown {
- margin-left: 10px;
- padding: 0px !important;
- outline: 0 !important;
- width: 65px !important;
-}
-
-.ds-rootdn-passwd-dropdown {
- margin-left: 30px;
- margin-top: 5px;
- padding: 10px;
- outline: 0 !important;
- width: 175px;
- border-radius: 5px;
-}
-
-.ds-table {
- padding: 0px;
- border: 1px solid #909090;
- line-height: 1;
- margin-top: 10px !important;
- margin-bottom: 10px !important;
-}
-
-.ds-referral-table {
- padding: 0px !important;
- margin-top: 10px !important;
- margin-bottom: 10px !important;
- border: 1px solid #909090;
- line-height: 1;
-}
-
.ds-refresh {
background-color: #0088ce;
background-image: linear-gradient(to bottom,#39a5dc 0,#0088ce 100%);
@@ -246,6 +80,10 @@
font-size: 13px !important;
}
+.ds-switch {
+ margin-top: 2px;
+}
+
.ds-refresh:hover {
color: DarkGray;
background-color: white;
@@ -282,15 +120,8 @@ td {
width: 525px;
}
-.ds-hr-pwp {
- display: block;
- margin-top: 0.5em;
- margin-bottom: 0.5em;
- margin-left: 0 !important;
- border-style: inset;
- border-width: 1px;
+.ds-no-padding {
padding: 0px !important;
- width: 835px;
}
.ds-tree {
@@ -303,45 +134,20 @@ td {
min-width: 300px;
min-height: 400px;
max-height: 400px;
- margin: 25px 10px 2px 0px;
+ margin-bottom: 5px;
overflow: auto;
padding-top: 5px;
}
-.ds-monitor-tree {
- font-size: 0.5;
- background-color: #f8f8f8;
- border: 1px solid #909090;
- color: black;
- line-height: 2;
- width: 275px;
- min-width: 275px;
- min-height: 400px;
- max-height: 400px;
- margin: 10px;
- overflow: auto;
-}
-
-.ds-treenode {
- border: 0 !important;
-}
-
-.ds-treenode ul {
- border: 0 !important;
-}
-
-.ds-treenode li {
- border: 0 !important;
+.ds-disabled {
+ pointer-events: none;
+ opacity: 0.7;
}
.ds-split {
width: 400px;
}
-.ds-modal-header {
- font-size: 16px;
-}
-
.ds-input {
margin-top: 5px !important;
padding-right: 5px !important;
@@ -349,12 +155,12 @@ td {
}
.ds-input-bad {
- border-color: red;
+ border-color: red !important;
}
.ds-input-auto-bad {
width: 100%;
- border-color: red;
+ border-color: red !important;
padding-left: 5px;
}
@@ -376,40 +182,6 @@ td {
max-width: 65px !important;
}
-.ds-pw-list-input {
- margin-top: 5px;
- padding-right: 5px;
- padding-left: 5px !important;
- min-width: 150px !important;
- max-width: 150px !important;
-}
-
-.ds-ro-input {
- margin-top: 5px;
- padding-left: 5px;
- margin-right: 5px;
- background-color: #f8f8f8 !important;
-}
-
-.ds-input-lrg {
- width: 315px !important;
- height: 37px !important;
- margin-top: 5px;
- padding-right: 10px;
- height: 37px !important;
-}
-
-.ds-input-long {
- width: 500px !important;
-}
-
-.ds-input-max {
- margin-left: 10px;
- margin-bottom: 20px;
- width: 500px;
- max-width: 600px;
-}
-
.ds-lag-report {
min-width: 800px !important;
}
@@ -418,25 +190,12 @@ td {
max-width: 600px !important;
}
-.ds-role-input {
- height: 30px !important;
- min-height: 30px;
- margin-top: 5px;
- margin-left: 7px;
- border-radius: 3px;
- padding-left: 5px !important;
-}
-
.ds-history-input {
margin-top: !important;
margin-right: 5px;
margin-left: 40px;
}
-.ds-form-input {
- margin-left: 10px;
-}
-
.ds-divider {
width: 35px;
}
@@ -449,49 +208,6 @@ td {
width: 15px;
}
-.ds-div-right {
- width: 50% !important;
- margin: 2px 2px 2px 2px;
-}
-
-.ds-div {
- width: 50% !important;
- margin: 2px 2px 2px 2px;
-}
-
-.ds-button-save {
- width: 100px;
- margin: 2px 2px 2px 215px;
-}
-
-.ds-changelog-button {
- width: 150px;
- height: 32px;
- margin-top: 0px !important;
- margin-left: 5px;
- margin-bottom: 4px;
-}
-
-.ds-ref-button {
- margin-right: 20px;
- min-width: 110px !important;
- max-width: 110px !important;
-}
-
-.ds-repl-manager {
- margin-top: 5px;
- padding: 5px;
- resize: none;
- width: 305px;
- height: 100px;
- white-space: pre !important;
-}
-
-.ds-roles {
- padding: 5px !important;
- margin-bottom: 5px;
-}
-
.ds-repl-managers-list {
width: 420px;
height: 200px;
@@ -535,20 +251,6 @@ td {
overflow: auto;
}
-.ds-agmt-wiz-dropdown {
- width: 175px !important;
- text-align: left;
-}
-
-.ds-agmt-wiz-dropdown a {
- padding: 0px !important;
- padding-left: 10px !important;
- line-height: 0 !important;
- height: 40px;
- width: 175px !important;
- text-align: left;
-}
-
.ds-dblink-dropdown {
width: 140px !important;
text-align: left;
@@ -569,20 +271,6 @@ td {
height: 37px !important;
}
-.ds-agmt-dropdown-button {
- width: 120px;
-}
-
-.ds-agmt-button {
- height: 40px;
- width: 210px;
-}
-
-.ds-sasl-button {
- height: 35px;
- width: 175px;
-}
-
.ds-repl-table {
background-color: white !important;
padding: 0px !important;
@@ -623,117 +311,16 @@ td {
line-height: 1;
}
-.ds-table-header th {
- text-align: center !important;
-}
-
-.ds-plugin-table {
- padding: 0px;
- border: 1px solid #909090;
- line-height: 1;
- table-layout: fixed;
- clear: both;
- word-wrap: break-word;
- text-align: center;
-}
-
-.ds-plugin-header th {
- width: 100%;
- text-align: center !important;
-}
-
-.ds-plugin-panel {
- background-color: #f7f7f7;
- flex-grow: 1;
- border: 1px groove black;
- border-style: groove;
- margin: 6px 2px 2px 10px;
- margin-left: 10px;
- margin-right: 10px;
- margin-bottom: 10px;
- padding-left: 15px;
- padding-right: 15px;
- padding-top: 0px;
- padding-bottom: 0px;
-}
-
-.ds-monitor-panel {
- background-color: #f7f7f7;
- flex-grow: 1;
- border: 1px groove black;
- border-style: groove;
- margin: 6px 2px 2px 10px;
- margin-left: 10px;
- margin-right: 10px;
- margin-bottom: 10px;
- padding-left: 15px;
- padding-right: 15px;
- padding-top: 0px;
- padding-bottom: 10px;
-}
-
.ds-cipher-width {
max-width: 350px !important;
min-width: 350px !important;
}
-/*
- * Popup modal stuff
-*/
-
-/* Extra styles for the cancel button */
-.cancelbtn {
- padding: 14px 20px;
- background-color: #f44336;
-}
-
-/* Float cancel and signup buttons and add an equal width */
-.cancelbtn,
-.savebtn {
- float: left;
- width: 50%;
-}
-
-/* Add padding to container elements */
-.container {
- width: 650px;
- padding-left: 10px;
- padding-right: 10px;
- padding-bottom: 10px;
-}
-
-.ds-modal {
- width: 450px;
-}
-
.ds-modal-wide {
width: 875px !important;
min-width: 875px !important;
vertical-align: middle;
-}
-
-.ds-log-panel {
- padding: 0 18px;
- max-height: 0 !important;
- overflow: hidden !important;
- transition: max-height 0.2s ease-out !important;
- margin: 2px;
- display: none;
-}
-
-.ds-suffix-panel {
- padding: 0 18px;
- max-height: 0 !important;
- overflow: hidden !important;
- transition: max-height 0.2s ease-out !important;
- margin: 2px;
- display: none !important;
-}
-
-.ds-agmt-wiz-button {
- height: 40px;
- width: 630px;
- font-weight: bold;
+ margin-left: -100px !important;
}
.ds-button-right {
@@ -743,14 +330,6 @@ td {
display: inline-block !important;
}
-.ds-close-button-right {
- height: 40px;
- width: 150px !important;
- margin-top: 5px;
- margin-bottom: 5px;
- float: right !important;
-}
-
.ds-button-left {
width: 120px;
max-width: 150px;
@@ -791,12 +370,6 @@ td {
margin: 5px 5px 5px 10px;
}
-.ds-agmt-schedule-time {
- width: 75px;
- padding: 10px;
- margin: 5px;
-}
-
.ds-agmt-schedule-checkbox {
margin: 8px !important;
padding: 5px;
@@ -808,13 +381,6 @@ td {
margin-right: 5px !important;
}
-.ds-mgr-checkbox {
- margin-left: 10px !important;
- margin-top: 10px !important;
- margin-bottom: 10px !important;
- margin-right: 5px !important;
-}
-
.ds-server-checkbox {
margin-top: 15px !important;
margin-right: 12px !important;
@@ -824,171 +390,74 @@ td {
.ds-send-expiring-checkbox {
margin-top: 12px !important;
margin-right: 12px !important;
- padding: 5px;
-}
-.ds-nowrap-td {
- overflow: hidden !important;
- white-space: nowrap !important;
-}
-
-.ds-repl-bind-group-label {
- width: 85px;
- margin-bottom: 25px !important;
-}
-
-.ds-config-label {
- margin-top: 10px;
- width: 200px !important;
- margin-bottom: 10px !important;
-}
-
-.ds-config-label-xlrg {
- margin-top: 10px;
- width: 180px !important;
- margin-bottom: 10px !important;
-}
-
-.ds-config-label-lrg {
- margin-top: 10px;
- width: 160px !important;
- margin-bottom: 10px !important;
-}
-
-.ds-config-label-med {
- margin-top: 10px;
- width: 100px !important;
- margin-bottom: 10px !important;
-}
-
-.ds-config-label-sm {
- margin-top: 10px;
- margin-right: 10px;
- margin-bottom: 10px !important;
-}
-
-.ds-config-sub-label {
- margin-top: 10px;
- width: 200px !important;
- margin-bottom: 10px !important;
- padding-left: 20px;
+ padding: 5px;
}
-.ds-label-lrg {
- width: 200px;
+.ds-config-label {
+ margin-top: 10px;
+ width: 225px !important;
margin-bottom: 10px !important;
}
-.ds-label-med {
- width: 180px;
+.ds-config-label-xlrg {
+ margin-top: 10px;
+ width: 180px !important;
margin-bottom: 10px !important;
}
-.ds-monitor-label {
- width: 155px;
+.ds-config-label-lrg {
margin-top: 10px;
+ width: 160px !important;
margin-bottom: 10px !important;
}
-.ds-monitor-label-med {
- width: 175px;
+.ds-config-label-med {
margin-top: 10px;
+ width: 125px !important;
margin-bottom: 10px !important;
}
-.ds-label-sm {
- width: 130px !important;
+.ds-config-sub-label {
+ margin-top: 10px;
+ width: 225px !important;
margin-bottom: 10px !important;
+ padding-left: 20px;
}
-.ds-label-xsm {
- width: 100px !important;
+.ds-label-sm {
+ width: 175px !important;
margin-bottom: 10px !important;
}
.ds-cleanallruv-label {
- width: 110px !important;
+ width: 150px !important;
margin-bottom: 10px !important;
}
-.ds-cache-label {
- width: 210px !important;
- margin-left: 20px !important;
-}
-
-.ds-repl-config-label {
- width: 210px !important;
- margin-top: 20px;
- margin-bottom: 20px;
-}
-
.ds-expire-label {
margin-top: 7px;
margin-bottom: 7px;
width: 285px !important;
}
-.ds-pw-list-label {
- margin-top: 7px !important;
- margin-bottom: 7px !important;
- width: 200px !important;
-}
-
-.ds-passwd-label {
- width: 300px !important;
-}
-
.ds-minage-label {
padding: 10px;
}
-.ds-history-label {
- padding: 10px;
- margin-left: 50px;
-}
-
-.ds-lockout-label {
- padding: 10px;
- margin-left: 20px;
-}
-
-.ds-server-label {
- padding-top: 20px;
- padding-bottom: 10px;
- padding-right: 60px !important;
-}
-
.ds-config-diskmon-label {
width: 210px !important;
padding-left: 30px;
margin-top: 10px;
}
-.ds-config-ndn-label {
- padding-left: 40px;
- padding-right: 10px;
-}
-
.ds-config-indent-sm-label {
margin-top: 10px;
margin-bottom: 10px;
- width: 175px;
+ width: 195px;
margin-left: 40px !important;
padding-right: 10px !important;
}
-.ds-config-indent-med-label {
- margin-left: 80px !important;
- padding-right: 10px !important;
-}
-
-.ds-config-indent-large-label {
- margin-top: 7px;
- margin-bottom: 7px;
- margin-left: 120px !important;
- padding-right: 10px !important;
- width: 175px;
-}
-
.ds-expired-div {
padding-left: 30px !important;
}
@@ -999,38 +468,6 @@ td {
margin-left: 30px !important;
}
-.ds-loglevel-list {
- width: 400px;
- margin-top: 10px;
- margin-left: 20px;
- min-height: 400px !important;
-}
-
-.ds-accordion-spacing {
- margin-bottom: 10px !important;
- padding-bottom: 10px !important;
-}
-.ds-button-wide {
- margin: 10px;
- height: 40px;
- width: 210px;
-}
-
-.ds-adj-btn {
- padding: 0 !important;
- margin-left: 10px !important;
- width: 100px !important;
-}
-
-.ds-task-panel {
- background-color: white;
- border: 1px groove black;
- border-style: groove;
- margin: 10px;
- width: 235px;
- line-height: 0;
-}
-
.ds-modal-row {
margin-left: 20px;
margin-right: 0px !important;
@@ -1049,18 +486,6 @@ td {
width: 85px;
}
-.ds-chaining-form-list {
- width: 100%;
-}
-
-.ds-vlv-sort-list {
- width: 100%;
-}
-
-.ds-index-form-list {
- width: 100%;
-}
-
.ds-oc-form-list {
width: 232px !important;
max-height: 350px !important;
@@ -1110,6 +535,7 @@ textarea {
line-height: 1;
font-family: monospace !important;
overflow-y: scroll;
+ font-size: 12px;
}
.ds-conflict {
@@ -1134,18 +560,6 @@ option {
color: #181818;
}
-.jstree-wholerow-hovered {
- background: linear-gradient(to right, #0a676f 0%, #e5e5e5 100%) !important;
- opacity: 0.05;
-}
-
-.jstree-wholerow-clicked {
- background-color: #0a676f !important;
- background: linear-gradient(to right, #0a676f 0%, #e5e5e5 100%) !important;
- opacity: 0.15 !important;
- transition: opacity 0.25s ease-in-out !important;
-}
-
.ds-server-action {
padding: 0 !important;
margin-left: 10px;
@@ -1163,6 +577,7 @@ option {
.ds-nav {
padding: 0 !important;
+ background-color: #f3f3f3;
}
.ds-nav-tab a {
@@ -1178,7 +593,18 @@ option {
float: left;
}
-.ds-footer{
+.ds-inline-btn {
+ margin-top: -3px;
+ margin-left: 10px;
+ display: inline-block;
+}
+
+.spinner-sm {
+ height: 18px !important;
+ Width: 18px !important;
+}
+
+.ds-footer {
background-color: #f5f5f5 !important;
margin-left: -25px;
padding: 10px;
@@ -1189,19 +615,6 @@ option {
border-top: 1px solid #e2e2e2 !important;
}
-.modal-footer {
- background-color: #f5f5f5 !important;
- padding: 10px;
- bottom: 0;
- width: 100%;
- height: 50px;
- border-top: .5px solid #e2e2e2 !important;
-}
-
-.modal-header {
- border-bottom: .5px solid #e2e2e2 !important;
-}
-
.ds-nav-bar {
position: fixed;
top: 0;
@@ -1232,7 +645,7 @@ option {
border: 0;
position: relative;
overflow: hidden;
- width: 700px;
+ width: 100%;
text-align: left;
font: inherit;
}
@@ -1243,7 +656,7 @@ option {
height: 1px;
background: #228bc0;
position: absolute;
- width: 700px;
+ width: 100%;
top: 50% !important;
margin-left: 10px;
text-align: left;
@@ -1268,12 +681,9 @@ option {
}
.ds-margin-top {
- margin-top: 10px;
+ margin-top: 10px !important;
}
-.ds-margin-top-sm {
- margin-top: 9px;
-}
.ds-margin-top-med {
margin-top: 15px !important;
}
@@ -1286,14 +696,17 @@ option {
margin-top: 35px !important;
}
+.ds-margin-top-xxlg {
+ margin-top: 45px !important;
+}
+
.ds-modal-spinner {
margin-top: 10px;
text-align: center;
}
-.ds-accordion-header {
- margin-bottom: 15px;
- margin-top: 0px;
+.modal {
+ overflow-y:auto;
}
/* wizard accordions are narrower */
@@ -1337,31 +750,21 @@ option {
margin-left: 15px !important;
}
-.ds-right-indent {
- margin-right: 15px !important;
+.ds-left-indent-md {
+ margin-left: 30px !important;
}
-.ds-gap {
- margin-bottom: 40px !important;
+.ds-right-indent {
+ margin-right: 15px !important;
}
.ds-tree-content {
margin-left: 25px;
- margin-top: 25px;
width: 100%;
}
-.ds-select {
- height: 30px;
- outline: 0 !important;
- padding: 0px !important;
- padding-left: 5px;
- padding-right: 10px;
- text-align: left;
-}
-
-.ds-center {
- text-align: center;
+.ds-inst-indent {
+ margin-left: 240px;
}
.ds-left-margin {
@@ -1372,10 +775,6 @@ option {
margin-left: 5px !important;
}
-.ds-table-btn {
- height: 30px;
-}
-
.ds-td {
padding-left: 10px !important;
padding-top: 8px !important;
@@ -1401,65 +800,36 @@ option {
text-align: center;
}
-.ds-table-btn {
- width: 110px;
- text-align: center;
-}
-
.ds-page-content {
margin-top: 30px !important;
}
-.ds-checkbox-group {
- margin-top: 10px !important;
-}
-
-.ds-alert-header {
- background-color: #f2dede;
-}
-
-.ds-sec-dropdown {
- width: 80px;
-}
-
-.ds-agmt-dropdown {
- width: 120px;
-}
-
-.ds-first {
- margin-top: 10px;
-}
-
-.ds-save-btn {
- margin-top: 20px !important;
-}
-
.ds-spacing-sm {
margin-right: 10px;
}
-.ds-spacing-med {
- margin-right: 20px;
-}
-
.navbar-default {
margin-bottom: 0px;
}
-.ds-center {
- text-align: center;
-}
-
.ds-modal-error {
color: red;
}
+.ds-clear-text {
+ opacity: 0;
+}
+
+.ds-margin-left-sm {
+ margin-left: 30px !important;
+}
+
.ds-margin-left {
margin-left: 40px !important;
}
.ds-margin-left-piechart {
- margin-left: 130px !important;
+ margin-left: 140px !important;
}
.ds-nested-modal {
@@ -1484,10 +854,6 @@ option {
margin-top: -2px;
}
-.ds-suffix-cfg-div {
- min-width: 500px;
-}
-
.ds-loading-spinner {
position: fixed;
top: 25%;
@@ -1495,21 +861,22 @@ option {
transform: translate(-25%, -50%);
}
-.ds-loader {
- font-size: 15px;
-}
-
-.ds-popup {
- min-width: 350px !important;
+.ds-loading-spinner-tree {
+ position: fixed;
+ top: 40%;
+ left: 40%;
+ transform: translate(-40%, -40%);
}
-.ds-disable-repl-btn {
- margin-left: 15px !important;
- max-height: 25px !important;
+.ds-loading {
+ position: fixed;
+ top: 25%;
+ left: 35%;
+ transform: translate(-25%, -35%);
}
-.ds-repl-form {
- max-width: 350px !important;
+.ds-popup {
+ min-width: 350px !important;
}
.ds-input-auto {
@@ -1526,21 +893,16 @@ option {
bottom: 10px;
}
-.ds-plugin-button {
- margin-right: 5px;
- margin-left: 5px;
-}
-
-.ds-raise-button {
- margin-top: -5px !important;
-}
-
.ds-plugin-spinner {
margin-top: 20px;
margin-bottom: 10px;
margin-left: 80px;
}
+.ds-h4 {
+ font-size: 18px !important;
+}
+
@media screen and (max-width: 1300px) {
.ds-plugin-spinner {
margin-left: 0px;
@@ -1560,6 +922,10 @@ option {
border-radius: 3px;
}
+.nav-pills > li.active > a {
+ color: #fff !important;
+}
+
.ds-plugin-tab-header {
position: relative;
top: -16px;
@@ -1572,10 +938,6 @@ option {
}
}
-.control-label {
- text-align: left !important;
-}
-
.rbt-token {
background-color: #ededed;
color: #363636;
@@ -1601,24 +963,20 @@ option {
border: 1px solid #d1d1d1;
}
-.ds-vlv-label {
- width: 50px;
-}
-
.list-view-pf-view {
margin-top: 10px;
}
.ds-fadein {
- visibility: visible;
- opacity: 1;
- transition: opacity 0.5s linear;
+ visibility: visible;
+ opacity: 1;
+ transition: opacity 0.5s linear;
}
.ds-fadeout {
- visibility: hidden;
- opacity: 0;
- transition: visibility 0s 0s, opacity 1s linear;
+ visibility: hidden;
+ opacity: 0;
+ transition: visibility 0s 0s, opacity 1s linear;
}
.ds-col-append {
@@ -1630,12 +988,12 @@ option {
.ds-word-wrap {
word-wrap: break-word;
- padding-top: 3px;
}
.ds-suffix-header {
- font-size: 16px;
+ font-size: 18px;
margin-bottom: 15px;
+ padding-top: 5px;
}
.ds-header {
@@ -1690,173 +1048,25 @@ input {
width: 100%;
}
-/* Dual List CSS */
-.dual-list-pf-arrows {
- display: inline-block;
- margin: auto;
- position: relative;
- bottom: 170px;
- font-size: 23px;
- color: #bbb;
-}
-@media only screen and (max-width: 600px) {
- .dual-list-pf-arrows {
- display: block;
- position: inherit;
- margin: 5px 0;
- padding-left: 79px;
- }
-}
-
-.dual-list-pf-arrows span {
- display: block;
- margin: 25px;
- cursor: pointer;
- transition: color 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
- transform: rotate(-90deg);
-}
-@media only screen and (max-width: 600px) {
- .dual-list-pf-arrows span {
- display: inline;
- margin: 0 20px 0 0;
- }
-}
-
-.dual-list-pf-arrows span:hover {
- color: #8b8d8f;
-}
-
-.dual-list-pf-body {
- height: 375px;
- width: 320px;
- overflow-y: scroll;
- overflow-x: auto;
- display: inline-grid;
- align-content: flex-start;
-}
-
-.dual-list-pf-body::-webkit-scrollbar {
- width: 12px;
- height: 12px;
- background: #fafafa;
-}
-
-.dual-list-pf-body::-webkit-scrollbar-thumb {
- background: #d1d1d1;
- border-radius: 6px;
- border: 3px solid transparent;
- background-clip: content-box;
-}
-
-.dual-list-pf-body::-webkit-scrollbar-thumb:hover {
- background: #bbb;
- border-radius: 6px;
- border: 3px solid transparent;
- background-clip: content-box;
-}
-
-.dual-list-pf-filter {
- margin-left: 20px;
-}
-
-.dual-list-pf-filter input {
- background-color: #f5f5f5;
- border: 1px solid #ededed;
- width: 145px;
- padding: 0 22px 0 5px;
- margin-top: 3px;
- margin-bottom: 3px;
-}
-
-.dual-list-pf-filter .search-icon {
- position: relative;
- right: 20px;
- bottom: 1px;
- color: #bbb;
-}
-
-.dual-list-pf-filter ::-webkit-input-placeholder {
- font-style: italic;
-}
-
-.dual-list-pf-footer {
- padding: 10px;
- border-top: 1px solid #d1d1d1;
-}
-
-.dual-list-pf-heading {
- border-bottom: 1px solid #d1d1d1;
-}
-
-.dual-list-pf-item {
- padding: 5px 0;
- margin-bottom: 0;
- font-weight: 400;
- transition: background 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94), color 0.3s
ease-out;
- cursor: pointer;
- white-space: nowrap;
-}
-
-.dual-list-pf-item input[type='checkbox'] {
- position: relative;
- left: 10px;
- vertical-align: top;
- cursor: pointer;
-}
-
-.dual-list-pf-item.selected {
- background-color: #0088ce;
- color: white;
-}
-
-.dual-list-pf-item.disabled {
- cursor: not-allowed;
- background: #f5f5f5;
- color: #8b8d8f;
-}
-
-.dual-list-pf-item.disabled input[type='checkbox'] {
- cursor: not-allowed;
-}
-
-.dual-list-pf-item.child {
- padding-left: 22px;
-}
-
-.dual-list-pf-item:hover:not(.selected):not(.disabled) {
- background-color: #bee1f4;
- color: inherit;
-}
-
-.dual-list-pf-item-label {
- margin-left: 20px;
-}
-
-.dual-list-pf-main-checkbox {
- position: relative;
- left: 10px;
- vertical-align: text-top;
- cursor: pointer;
+th {
+ text-align: center;
}
-.dual-list-pf-no-items {
- margin-top: 30px;
+td {
text-align: center;
}
-.dual-list-pf-selector {
- display: inline-block;
- border: 1px solid #d1d1d1;
- user-select: none;
+hr {
+ margin-top: 20px;
+ margin-bottom: 20px;
}
-.dual-list-pf-sort-icon {
- cursor: pointer;
+h3 {
+ margin-top: 20px;
+ margin-bottom: 20px;
}
-.dropdown-kebab-pf.btn-group {
- margin-left: 10px;
- float: right;
- margin-right: 10px;
+h4 {
+ margin-top: 15px;
+ margin-bottom: 15px;
}
-/* End of dual list */
diff --git a/src/cockpit/389-console/src/database.jsx
b/src/cockpit/389-console/src/database.jsx
index e5adf79..b1c3953 100644
--- a/src/cockpit/389-console/src/database.jsx
+++ b/src/cockpit/389-console/src/database.jsx
@@ -13,9 +13,13 @@ import {
Modal,
Icon,
Form,
+ Row,
+ Col,
+ ControlLabel,
Button,
noop,
TreeView,
+ Radio,
Spinner
} from "patternfly-react";
import PropTypes from "prop-types";
@@ -41,7 +45,11 @@ export class Database extends React.Component {
showSuffixModal: false,
createSuffix: "",
createBeName: "",
- createRootNode: false,
+ createSuffixEntry: false,
+ createSampleEntries: false,
+ noSuffixInit: true,
+ disableTree: false,
+
// DB config
globalDBConfig: {},
configUpdated: 0,
@@ -67,6 +75,7 @@ export class Database extends React.Component {
this.removeNotification = this.removeNotification.bind(this);
this.addNotification = this.addNotification.bind(this);
this.handleChange = this.handleChange.bind(this);
+ this.handleRadioChange = this.handleRadioChange.bind(this);
this.loadGlobalConfig = this.loadGlobalConfig.bind(this);
this.loadLDIFs = this.loadLDIFs.bind(this);
this.loadBackups = this.loadBackups.bind(this);
@@ -75,7 +84,6 @@ export class Database extends React.Component {
// 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);
@@ -94,6 +102,7 @@ export class Database extends React.Component {
// Other
this.loadSuffixTree = this.loadSuffixTree.bind(this);
+ this.enableTree = this.enableTree.bind(this);
}
componentWillMount () {
@@ -441,6 +450,13 @@ export class Database extends React.Component {
}
selectNode(selectedNode) {
+ if (selectedNode.selected) {
+ return;
+ }
+ this.setState({
+ disableTree: true // Disable the tree to allow node to be fully loaded
+ });
+
if (selectedNode.id == "dbconfig" ||
selectedNode.id == "chaining-config" ||
selectedNode.id == "backups") {
@@ -541,10 +557,32 @@ export class Database extends React.Component {
showSuffixModal () {
this.setState({
showSuffixModal: true,
+ createSuffixEntry: false,
+ createSampleEntries: false,
+ noSuffixInit: true,
errObj: {},
});
}
+ handleRadioChange(e) {
+ // Handle the create suffix init option radio button group
+ let noInit = false;
+ let addSuffix = false;
+ let addSample = false;
+ if (e.target.id == "noSuffixInit") {
+ noInit = true;
+ } else if (e.target.id == "createSuffixEntry") {
+ addSuffix = true;
+ } else { // createSampleEntries
+ addSample = true;
+ }
+ this.setState({
+ noSuffixInit: noInit,
+ createSuffixEntry: addSuffix,
+ createSampleEntries: addSample
+ });
+ }
+
handleChange(e) {
const value = e.target.type === 'checkbox' ? e.target.checked :
e.target.value;
let valueErr = false;
@@ -570,7 +608,7 @@ export class Database extends React.Component {
let errors = false;
let missingArgs = {
createSuffix: false,
- createBeName: false
+ createBeName: false,
};
if (this.state.createSuffix == "") {
@@ -601,9 +639,12 @@ export class Database extends React.Component {
"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) {
+ if (this.state.createSampleEntries) {
cmd.push('--create-entries');
}
+ if (this.state.createSuffixEntry) {
+ cmd.push('--create-suffix');
+ }
log_cmd("createSuffix", "Create a new backend", cmd);
cockpit
@@ -616,6 +657,7 @@ export class Database extends React.Component {
);
// Refresh tree
this.loadSuffixTree(false);
+ this.loadSuffixList();
})
.fail(err => {
let errMsg = JSON.parse(err);
@@ -942,8 +984,38 @@ export class Database extends React.Component {
suffixLoading: false
});
});
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error attribute encryption for ${suffix}
- ${errMsg.desc}`
+ );
+ this.setState({
+ suffixLoading: false
+ });
});
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error loading VLV indexes for ${suffix} -
${errMsg.desc}`
+ );
+ this.setState({
+ suffixLoading: false
+ });
});
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error loading config for ${suffix} - ${errMsg.desc}`
+ );
+ this.setState({
+ suffixLoading: false
+ });
});
}
@@ -1013,9 +1085,20 @@ export class Database extends React.Component {
});
}
+ enableTree () {
+ this.setState({
+ disableTree: false
+ });
+ }
+
render() {
const { nodes } = this.state;
let db_element = "";
+ let disabled = "tree-view-container";
+ if (this.state.disableTree) {
+ disabled = "tree-view-container ds-disabled";
+ }
+
if (this.state.loaded) {
if (this.state.node_name == DB_CONFIG || this.state.node_name ==
"") {
db_element =
@@ -1024,6 +1107,7 @@ export class Database extends React.Component {
addNotification={this.addNotification}
reload={this.loadGlobalConfig}
data={this.state.globalDBConfig}
+ enableTree={this.enableTree}
key={this.state.configUpdated}
/>;
} else if (this.state.node_name == CHAINING_CONFIG) {
@@ -1033,6 +1117,7 @@ export class Database extends React.Component {
addNotification={this.addNotification}
reload={this.loadChainingConfig}
data={this.state.chainingConfig}
+ enableTree={this.enableTree}
key={this.state.chainingUpdated}
/>;
} else if (this.state.node_name == BACKUP_CONFIG) {
@@ -1043,6 +1128,7 @@ export class Database extends React.Component {
backups={this.state.BackupRows}
suffixes={this.state.suffixList}
ldifs={this.state.LDIFRows}
+ enableTree={this.enableTree}
reload={this.loadBackups}
/>;
} else if (this.state.node_name != "") {
@@ -1050,10 +1136,9 @@ export class Database extends React.Component {
if (this.state.dbtype == "suffix" || this.state.dbtype ==
"subsuffix") {
if (this.state.suffixLoading) {
db_element =
- <div className="ds-loading-spinner
ds-center">
- <p />
+ <div className="ds-margin-top ds-loading-spinner
ds-center">
<h4>Loading suffix configuration for
<b>{this.state.node_text} ...</b></h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top-lg"
loading size="md" />
</div>;
} else {
db_element =
@@ -1073,6 +1158,7 @@ export class Database extends React.Component {
dbtype={this.state.dbtype}
data={this.state[this.state.node_text]}
attrs={this.state.attributes}
+ enableTree={this.enableTree}
key={this.state.node_text}
/>;
}
@@ -1080,10 +1166,9 @@ export class Database extends React.Component {
// Chaining
if (this.state.chainingLoading) {
db_element =
- <div className="ds-loading-spinner
ds-center">
- <p />
+ <div className="ds-margin-top ds-loading-spinner
ds-center">
<h4>Loading chaining configuration for
<b>{this.state.node_text} ...</b></h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top-lg"
loading size="md" />
</div>;
} else {
db_element =
@@ -1094,6 +1179,7 @@ export class Database extends React.Component {
loadSuffixTree={this.loadSuffixTree}
addNotification={this.addNotification}
data={this.state[this.state.node_text]}
+ enableTree={this.enableTree}
reload={this.loadChainingLink}
/>;
}
@@ -1110,7 +1196,7 @@ export class Database extends React.Component {
<div className="ds-container">
<div>
<div className="ds-tree">
- <div className="tree-view-container"
id="db-tree"
+ <div className={disabled} id="db-tree"
style={treeViewContainerStyles}>
<TreeView
nodes={nodes}
@@ -1133,7 +1219,11 @@ export class Database extends React.Component {
showModal={this.state.showSuffixModal}
closeHandler={this.closeSuffixModal}
handleChange={this.handleChange}
+ handleRadioChange={this.handleRadioChange}
saveHandler={this.createSuffix}
+ noInit={this.state.noSuffixInit}
+ addSuffix={this.state.createSuffixEntry}
+ addSample={this.state.createSampleEntries}
error={this.state.errObj}
/>
</div>
@@ -1147,7 +1237,11 @@ class CreateSuffixModal extends React.Component {
showModal,
closeHandler,
handleChange,
+ handleRadioChange,
saveHandler,
+ noInit,
+ addSuffix,
+ addSample,
error
} = this.props;
@@ -1169,20 +1263,39 @@ class CreateSuffixModal extends React.Component {
</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>
+ <Row title="Database suffix, like
'dc=example,dc=com'. The suffix must be a valid LDAP Distiguished Name
(DN)">
+ <Col sm={3}>
+ <ControlLabel>Suffix DN</ControlLabel>
+ </Col>
+ <Col sm={5}>
+ <input onChange={handleChange}
className={error.createSuffix ? "ds-input-bad" : "ds-input"}
type="text" id="createSuffix" size="40" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The
name for the backend database, like 'userroot'. The name can be a combination of
alphanumeric characters, dashes (-), and underscores (_). No other characters are allowed,
and the name must be unique across all backends.">
+ <Col sm={3}>
+ <ControlLabel>Database
Name</ControlLabel>
+ </Col>
+ <Col sm={5}>
+ <input onChange={handleChange}
className={error.createBeName ? "ds-input-bad" : "ds-input"}
type="text" id="createBeName" size="40" />
+ </Col>
+ </Row>
+ <hr />
+ <div>
+ <Row className="ds-indent">
+ <Radio name="radioGroup"
id="noSuffixInit" onChange={handleRadioChange} checked={noInit} inline>
+ Do Not Initialize Database
+ </Radio>
+ </Row>
+ <Row className="ds-indent">
+ <Radio name="radioGroup"
id="createSuffixEntry" onChange={handleRadioChange} checked={addSuffix}
inline>
+ Create The Top Suffix Entry
+ </Radio>
+ </Row>
+ <Row className="ds-indent">
+ <Radio name="radioGroup"
id="createSampleEntries" onChange={handleRadioChange} checked={addSample}
inline>
+ Add Sample Entries
+ </Radio>
+ </Row>
</div>
</Form>
</Modal.Body>
@@ -1221,7 +1334,11 @@ CreateSuffixModal.propTypes = {
showModal: PropTypes.bool,
closeHandler: PropTypes.func,
handleChange: PropTypes.func,
+ handleRadioChange: PropTypes.func,
saveHandler: PropTypes.func,
+ noInit: PropTypes.bool,
+ addSuffix: PropTypes.bool,
+ addSample: PropTypes.bool,
error: PropTypes.object,
};
@@ -1229,6 +1346,10 @@ CreateSuffixModal.defaultProps = {
showModal: false,
closeHandler: noop,
handleChange: noop,
+ handleRadioChange: noop,
saveHandler: noop,
+ noInit: true,
+ addSuffix: false,
+ addSample: false,
error: {},
};
diff --git a/src/cockpit/389-console/src/ds.js b/src/cockpit/389-console/src/ds.js
index e023fdc..7b4db77 100644
--- a/src/cockpit/389-console/src/ds.js
+++ b/src/cockpit/389-console/src/ds.js
@@ -10,7 +10,7 @@ var dn_regex = new RegExp( "^([A-Za-z]+=.*)" );
var server_page_loaded = 0;
var security_page_loaded = 1;
var db_page_loaded = 1;
-var repl_page_loaded = 0;
+var repl_page_loaded = 1;
var plugin_page_loaded = 1;
var schema_page_loaded = 0;
var monitor_page_loaded = 1;
@@ -77,7 +77,19 @@ function valid_dn (dn){
function valid_num (val){
// Validate value is a number
- return !isNaN(val);
+ let result = !isNaN(val);
+ return result;
+}
+
+function valid_port (val){
+ // Validate value is a number and between 1 and 65535
+ let result = !isNaN(val);
+ if (result) {
+ if (val < 1 || val > 65535) {
+ result = false;
+ }
+ }
+ return result;
}
function tableize (val) {
@@ -249,7 +261,7 @@ function get_insts() {
}
if (server_id != "None") {
- $("#ds-banner").html("Managing Instance <select class=\"btn
btn-default dropdown ds-dropdown-server\"
id=\"select-server\"></select>");
+ $("#ds-banner").html("Managing Instance <select class=\"btn
btn-default dropdown\" id=\"select-server\"></select>");
}
// Populate the server instance drop down
@@ -340,38 +352,19 @@ function popup_success(msg) {
// This is called when any Save button is clicked on the main page. We call
// all the save functions for all the pages here. This is not used for modal forms
function save_all () {
- save_config(); // Server Config Page
+ save_config(); // Server Config Page
}
-function load_repl_suffix_dropdowns() {
- // Update replication drop downs (agmts mainly)
- var repl_dropdowns = ['select-repl-agmt-suffix',
'select-repl-winsync-suffix',
- 'cleanallruv-suffix',
'monitor-repl-backend-list'];
- var repl_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id +
'.socket','replication', 'list'];
- log_cmd('load_repl_suffix_dropdowns', 'get replicated suffix list',
repl_cmd);
- cockpit.spawn(repl_cmd, { superuser: true, "err": "message",
"environ": [ENV]}).done(function(data) {
- // Update dropdowns
- for (var idx in repl_dropdowns) {
- $("#" + repl_dropdowns[idx]).find('option').remove();
- }
- var obj = JSON.parse(data);
- for (var idx in obj['items']) {
- for (var list in repl_dropdowns){
- $("#" + repl_dropdowns[list]).append('<option value="'
+ obj['items'][idx] + '" selected="selected">' +
obj['items'][idx] +'</option>');
- }
- }
- if (obj['items'].length == 0){
- // Disable create agmt buttons
- $("#create-agmt").prop("disabled", true);
- $("#winsync-create-agmt").prop("disabled", true);
- $("#create-cleanallruv-btn").prop("disabled", true);
- } else {
- // Enable repl agmt buttons
- $("#create-agmt").prop("disabled", false);
- $("#winsync-create-agmt").prop("disabled", false);
- $("#create-cleanallruv-btn").prop("disabled", false);
+var progress = 10;
+
+function update_progress () {
+ progress += 10;
+ if (progress > 100) {
+ progress = 100;
}
- });
+ $("#ds-progress-label").text(progress + "%");
+ $("#ds-progress-bar").attr("aria-valuenow", progress);
+ $("#ds-progress-bar").css("width", progress + "%");
}
var loading_cfg = 0;
@@ -382,6 +375,8 @@ function load_config (refresh){
return;
}
loading_cfg = 1;
+ progress = 10;
+ update_progress();
// Load the configuration for all the pages.
var dropdowns = ['local-pwp-suffix', 'select-repl-cfg-suffix'];
@@ -415,20 +410,11 @@ function load_config (refresh){
get_and_set_config();
get_and_set_sasl();
get_and_set_localpwp();
+ update_progress();
// Schema page
get_and_set_schema_tables();
-
- // Replication page
- get_and_set_repl_config();
- get_and_set_repl_agmts();
- get_and_set_repl_winsync_agmts();
- get_and_set_cleanallruv();
-
- // Security page
- // Database page
- // Plugin page
- // Monitoring page
+ update_progress();
// Initialize the tabs
$(".ds-tab-list").css( 'color', '#777');
@@ -486,4 +472,8 @@ $(window.document).ready(function() {
$(".all-pages").hide();
$("#security-content").show();
});
+ $("#replication-tab").on("click", function() {
+ $(".all-pages").hide();
+ $("#replication-content").show();
+ });
});
diff --git a/src/cockpit/389-console/src/index.es6
b/src/cockpit/389-console/src/index.es6
index 71e9c5e..6797dd5 100644
--- a/src/cockpit/389-console/src/index.es6
+++ b/src/cockpit/389-console/src/index.es6
@@ -4,6 +4,7 @@ import { Plugins } from "./plugins.jsx";
import { Database } from "./database.jsx";
import { Monitor } from "./monitor.jsx";
import { Security } from "./security.jsx";
+import { Replication } from "./replication.jsx";
var serverIdElem;
@@ -31,6 +32,12 @@ function renderReactDOM(clear) {
document.getElementById("database")
);
+ // Replication tab
+ ReactDOM.render(
+ <Replication serverId={serverIdElem} key={tabKey} />,
+ document.getElementById("replication")
+ );
+
// Monitor tab
ReactDOM.render(
<Monitor serverId={serverIdElem} key={tabKey} />,
diff --git a/src/cockpit/389-console/src/index.html
b/src/cockpit/389-console/src/index.html
index c93f754..6a0479c 100644
--- a/src/cockpit/389-console/src/index.html
+++ b/src/cockpit/389-console/src/index.html
@@ -12,7 +12,6 @@
<script src="static/moment.min.js"></script>
<script src="static/dataTables.datetime-moment.js"></script>
<script src="static/jquery.timepicker.min.js"></script>
- <script src="static/jstree.min.js"></script>
<script src="static/bootstrap.min.js"></script>
<script src="static/bootpopup.min.js"></script>
<script src="static/patternfly.min.js"></script>
@@ -21,7 +20,6 @@
<script src="ds.js"></script>
<script src="schema.js"></script>
<script src="servers.js"></script>
- <script src="replication.js"></script>
<link href="static/bootstrap.min.css" rel="stylesheet">
<link href="static/jquery.dataTables.min.css" type="text/css"
rel="stylesheet">
<link href="static/jquery.timepicker.min.css" type="text/css"
rel="stylesheet">
@@ -34,9 +32,19 @@
<body>
<div id="reload-page" hidden></div>
- <div id="loading-page" class="ds-center ds-loading-spinner"
hidden>
- <h3 id="loading-msg">Loading Directory Server
Configuration...</h3>
- <p><span class="spinner spinner-lg
spinner-inline"></span></p>
+ <div id="loading-page" class="ds-center ds-loading" hidden>
+ <h4 id="loading-msg">Loading Directory Server
Configuration...</h4>
+ <p class="ds-margin-top-lg"><span class="spinner spinner-lg
spinner-inline"></span></p>
+
+ <div class="progress ds-margin-top-lg">
+ <div class="progress-bar" role="progressbar"
id="ds-progress-bar"
+ aria-valuenow="20" aria-valuemin="0"
+ aria-valuemax="100" style="width: 20%;"
+ >
+ <span id="ds-progress-label">20%</span>
+ </div>
+ </div>
+
</div>
<div id="everything" hidden>
<div class="ds-nav-bar">
@@ -50,7 +58,7 @@
<!-- Server Config navtab -->
<li class="dropdown ds-nav-tab">
- <a href="#0" class="dropdown-toggle ds-tab-list"
data-toggle="dropdown" id="server-tab">
+ <a href="#0" class="ds-tab-list"
data-toggle="dropdown" id="server-tab">
Server Settings
<b class="caret"></b>
</a>
@@ -93,22 +101,15 @@
</li>
<!-- Replication navtab -->
- <li class="dropdown ds-nav-tab">
- <a href="#0" class="dropdown-toggle ds-tab-list"
data-toggle="dropdown" id="replication-tab">
- Replication
- <b class="caret"></b>
- </a>
- <ul class="dropdown-menu ds-nav-item">
- <li><a href="#0" class="ds-nav-choice"
id="repl-config-btn"
parent-id="replication-tab">Configuration</a></li>
- <li><a href="#0" class="ds-nav-choice"
id="repl-agmts-btn"
parent-id="replication-tab">Agreements</a></li>
- <li><a href="#0" class="ds-nav-choice"
id="repl-winsync-btn" parent-id="replication-tab">Winsync
Agreements</a></li>
- <li><a href="#0" class="ds-nav-choice"
id="repl-tasks-btn" parent-id="replication-tab">Replication
Tasks</a></li>
- </ul>
+ <li class="ds-nav-tab">
+ <a href="#0" class="ds-tab-list ds-tab-standalone"
id="replication-tab">
+ Replication
+ </a>
</li>
<!-- Schema navtab -->
<li class="dropdown ds-nav-tab">
- <a href="#0" class="dropdown-toggle ds-tab-list"
data-toggle="dropdown" id="schema-tab">
+ <a href="#0" class="ds-tab-list"
data-toggle="dropdown" id="schema-tab">
Schema
<b class="caret"></b>
</a>
@@ -242,8 +243,8 @@
</div>
<div class="modal-body">
<form class="form-horizontal">
- <label for="reload-dir" class="ds-config-label"
title="The name of the database link.">Schema File
Directory</label><input
- class="ds-input" type="text" placeholder="Leave
empty to use default schema location" id="reload-dir">
+ <label for="reload-dir" class="ds-config-label"
title="The name of the database link.">Schema File
Directory:</label><input
+ class="ds-input-auto" size="40" type="text"
placeholder="Leave empty to use default schema location"
id="reload-dir">
</form>
<div id="reload-spinner" class="ds-center" hidden>
<p></p>
@@ -369,53 +370,63 @@
<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="create-inst-header">Create New Server Instance</h4>
+ <h4 class="modal-title ds-center"
id="create-inst-header">Create New Server Instance</h4>
</div>
<div class="modal-body">
<form class="form-horizontal">
<p class="ds-modal-error"></p>
<div class="ds-inline">
<div>
- <label for="create-inst-serverid"
class="ds-config-label" title="The instance name, this is what gets
appended to \"slapi-\"">
- Instance Name</label><input class="ds-input
ds-inst-input" type="text" id="create-inst-serverid"
placeholder="Your_Instance_Name" required />
+ <label for="create-inst-serverid"
class="ds-config-label ds-input-right" title="The instance name, this is
what gets appended to 'slapi-'. The instance name can only contain letters,
numbers, and: # % : - _">
+ Instance Name</label><input class="ds-input
ds-inst-input ds-left-margin" size="40" type="text"
id="create-inst-serverid" placeholder="Your_Instance_Name" required
/>
</div>
<div>
- <label for="create-inst-port"
class="ds-config-label" title="The server port number">
- Port</label><input class="ds-input ds-inst-input"
type="text" value="389" id="create-inst-port" required
/>
+ <label for="create-inst-port" class="ds-config-label
ds-input-right" title="The server port number">
+ Port</label><input class="ds-input ds-inst-input
ds-left-margin" size="40" type="text" value="389"
id="create-inst-port" required />
</div>
<div>
- <label for="create-inst-secureport"
class="ds-config-label" title="The secure port number for TLS
connections">
- Secure Port</label><input class="ds-input
ds-inst-input" type="text" value="636"
id="create-inst-secureport" required />
+ <label for="create-inst-secureport"
class="ds-config-label ds-input-right" title="The secure port number for
TLS connections">
+ Secure Port</label><input class="ds-input ds-inst-input
ds-left-margin" size="40" type="text" value="636"
id="create-inst-secureport" required />
+ </div>
+ <div class="ds-inst-indent">
+ <input type="checkbox"
class="ds-config-checkbox" id="create-inst-tls" checked><label
+ for="create-inst-tls" class="ds-label"
title="Create a self-signed certificate database">Create Self-Signed TLS
Certificate DB</label>
</div>
<div>
- <label for="create-inst-rootdn"
class="ds-config-label" title="The DN for the unrestricted user">
- Directory Manager DN</label><input class="ds-input
ds-inst-input" autocomplete="username" value="cn=Directory
Manager" type="text" id="create-inst-rootdn" required />
+ <label for="create-inst-rootdn"
class="ds-config-label ds-input-right" title="The DN for the unrestricted
user">
+ Directory Manager DN</label><input class="ds-input
ds-inst-input ds-left-margin" size="40" autocomplete="username"
value="cn=Directory Manager" type="text"
id="create-inst-rootdn" required />
</div>
<div>
- <label for="rootdn-pw" class="ds-config-label"
title="Directory Manager password.">Directory Manager
Password</label><input
- class="ds-input ds-inst-input" type="password"
autocomplete="new-password" placeholder="Enter password"
id="rootdn-pw" name="name" required>
+ <label for="rootdn-pw" class="ds-config-label
ds-input-right" title="Directory Manager password.">Directory Manager
Password</label><input
+ class="ds-input ds-inst-input ds-left-margin"
size="40" type="password" autocomplete="new-password"
placeholder="Enter password" id="rootdn-pw" name="name"
required>
</div>
<div>
- <label for="rootdn-pw-confirm"
class="ds-config-label" title="Confirm password">Confirm
Password</label><input
- class="ds-input ds-inst-input" type="password"
autocomplete="new-password" placeholder="Confirm password"
id="rootdn-pw-confirm" name="name" required>
+ <label for="rootdn-pw-confirm" class="ds-config-label
ds-input-right" title="Confirm password">Confirm
Password</label><input
+ class="ds-input ds-inst-input ds-left-margin"
size="40" type="password" autocomplete="new-password"
placeholder="Confirm password" id="rootdn-pw-confirm"
name="name" required>
</div>
<hr>
+ <h5 class="ds-center">Optional Database
Settings</h5>
<div>
- <label for="backend-name"
class="ds-config-label" title="The backend name, like
'userroot'">Backend Name (optional)</label><input
- class="ds-input ds-inst-input" type="text"
id="backend-name">
+ <label for="backend-suffix" class="ds-config-label
ds-input-right" title="Database suffix, like 'dc=example,dc=com'. The
suffix must be a valid LDAP Distiguished Name (DN)">Database
Suffix</label><input
+ class="ds-input ds-inst-input ds-left-margin"
size="40" placeholder="e.g. dc=example,dc=com" type="text"
id="backend-suffix">
</div>
<div>
- <label for="backend-suffix"
class="ds-config-label" title="Database suffix, like
'dc=example,dc=com'">Backend Suffix (optional)</label><input
- class="ds-input ds-inst-input" type="text"
id="backend-suffix">
+ <label for="backend-name" class="ds-config-label
ds-input-right" title="The name for the backend database, like
'userroot'. The name can be a combination of alphanumeric characters, dashes (-),
and underscores (_). No other characters are allowed, and the name must be unique across
all backends.">Database Name</label><input
+ class="ds-input ds-inst-input ds-left-margin"
placeholder="e.g. userRoot" size="40" type="text"
id="backend-name">
</div>
- <div>
- <label for="create-sample-entries"
class="ds-config-label" title="Create sample entries in the
suffix">Create Sample Entries </label><input
- type="checkbox" class="ds-input
ds-config-checkbox" id="create-sample-entries">
- </div>
- <hr>
- <div>
- <input type="checkbox"
class="ds-config-checkbox" id="create-inst-tls" checked><label
- for="create-inst-tls" class="ds-label"
title="Create a self-signed certificate database">Create Self Signed
Certificate DB</label>
+ <div class="ds-inst-indent ds-margin-top">
+ <div>
+ <input type="radio" name="ds-radio-group"
id="no-init" checked /><label class="ds-left-margin"
for="no-init"
+ title="Do not initialize the backend database">
Do Not Initialize Database</label>
+ </div>
+ <div>
+ <input type="radio" name="ds-radio-group"
id="create-suffix-entry" /><label class="ds-left-margin"
for="create-suffix-entry"
+ title="Create the suffix entry with a basic READ
ACI"> Create Suffix Entry</label>
+ </div>
+ <div>
+ <input type="radio" name="ds-radio-group"
id="create-sample-entries" /><label class="ds-left-margin"
for="create-sample-entries"
+ title="Create sample entries under the suffix">
Create Suffix Entry And Add Sample Entries</label>
+ </div>
</div>
<div id="create-inst-spinner" class="ds-center"
hidden>
<hr>
@@ -446,10 +457,8 @@
</div>
<div class="modal-body">
<form class="form-horizontal">
- <div class="ds-inline">
- <label for="backup-name" title="Enter a directory name
for the backup">
- Backup Name</label><input class="ds-input
ds-left-margin" type="text" id="backup-name"/>
- </div>
+ <label for="backup-name" title="Enter a name for the
backup subdirectory located under the server's backup directory
(nsslapd-bakdir)">
+ Name For The Backup</label><input
class="ds-input-auto" type="text" id="backup-name"/>
</form>
<div id="backup-spinner" class="ds-center" hidden>
<p></p>
@@ -484,6 +493,12 @@
</div>
<div id="server-content" class="all-pages" hidden>
+ <div id="server-settings"></div>
+ <div id="server-tuning"></div>
+ <div id="server-sasl"></div>
+ <div id="server-pwp"></div>
+ <div id="server-ldapi"></div>
+ <div id="server-logs"></div>
</div>
<div id="security-content" class="all-pages" hidden>
@@ -495,6 +510,7 @@
</div>
<div id="replication-content" class="all-pages" hidden>
+ <div id="replication"></div>
</div>
<div id="schema-content" class="all-pages" hidden>
diff --git a/src/cockpit/389-console/src/lib/customCollapse.jsx
b/src/cockpit/389-console/src/lib/customCollapse.jsx
index b992982..a9dc471 100644
--- a/src/cockpit/389-console/src/lib/customCollapse.jsx
+++ b/src/cockpit/389-console/src/lib/customCollapse.jsx
@@ -30,7 +30,7 @@ class CustomCollapse extends React.Component {
/>{" "}
{open ? textOpened : textClosed}
</Button>
- <div className="ds-accordion-panel">{open &&
children}</div>
+ <div className="ds-margin-top">{open &&
children}</div>
</div>
);
}
diff --git a/src/cockpit/389-console/src/lib/database/attrEncryption.jsx
b/src/cockpit/389-console/src/lib/database/attrEncryption.jsx
index 9bc86bc..f34f6f8 100644
--- a/src/cockpit/389-console/src/lib/database/attrEncryption.jsx
+++ b/src/cockpit/389-console/src/lib/database/attrEncryption.jsx
@@ -129,8 +129,7 @@ export class AttrEncryption extends React.Component {
rows={this.props.rows}
loadModalHandler={this.showConfirmAttrDelete}
/>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={6}>
<Typeahead
id="attrEncrypt"
diff --git a/src/cockpit/389-console/src/lib/database/backups.jsx
b/src/cockpit/389-console/src/lib/database/backups.jsx
index 065e8a1..6204673 100644
--- a/src/cockpit/389-console/src/lib/database/backups.jsx
+++ b/src/cockpit/389-console/src/lib/database/backups.jsx
@@ -8,6 +8,7 @@ import {
TabContent,
TabPane,
TabContainer,
+ Checkbox,
Col,
Button,
Spinner,
@@ -19,7 +20,7 @@ import {
Row,
noop
} from "patternfly-react";
-import { log_cmd } from "../tools.jsx";
+import { log_cmd, bad_file_name } from "../tools.jsx";
import PropTypes from "prop-types";
import "../../css/ds.css";
@@ -30,7 +31,8 @@ export class Backups extends React.Component {
activeKey: 1,
showConfirmBackupDelete: false,
showConfirmBackup: false,
- showConfirmRestore: false,
+ showConfirmRestoreReplace: false,
+ showConfirmLDIFReplace: false,
showRestoreSpinningModal: false,
showDelBackupSpinningModal: false,
showBackupModal: false,
@@ -40,10 +42,12 @@ export class Backups extends React.Component {
// LDIF
showConfirmLDIFDelete: false,
showConfirmLDIFImport: false,
+ showConfirmRestore: false,
showLDIFSpinningModal: false,
showLDIFDeleteSpinningModal: false,
showExportModal: false,
exportSpinner: false,
+ includeReplData: false,
ldifName: "",
ldifSuffix: "",
errObj: {}
@@ -68,6 +72,8 @@ export class Backups extends React.Component {
this.closeRestoreSpinningModal = this.closeRestoreSpinningModal.bind(this);
this.showDelBackupSpinningModal = this.showDelBackupSpinningModal.bind(this);
this.closeDelBackupSpinningModal = this.closeDelBackupSpinningModal.bind(this);
+ this.validateBackup = this.validateBackup.bind(this);
+ this.closeConfirmRestoreReplace = this.closeConfirmRestoreReplace.bind(this);
// LDIFS
this.importLDIF = this.importLDIF.bind(this);
this.deleteLDIF = this.deleteLDIF.bind(this);
@@ -80,6 +86,12 @@ export class Backups extends React.Component {
this.doExport = this.doExport.bind(this);
this.showExportModal = this.showExportModal.bind(this);
this.closeExportModal = this.closeExportModal.bind(this);
+ this.validateLDIF = this.validateLDIF.bind(this);
+ this.closeConfirmLDIFReplace = this.closeConfirmLDIFReplace.bind(this);
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
}
showExportModal () {
@@ -87,7 +99,8 @@ export class Backups extends React.Component {
showExportModal: true,
exportSpinner: false,
ldifName: "",
- ldifSuffix: this.props.suffixes[0]
+ ldifSuffix: this.props.suffixes[0],
+ includeReplData: false,
});
}
@@ -97,6 +110,12 @@ export class Backups extends React.Component {
});
}
+ closeConfirmLDIFReplace () {
+ this.setState({
+ showConfirmLDIFReplace: false
+ });
+ }
+
showLDIFSpinningModal () {
this.setState({
showLDIFSpinningModal: true
@@ -233,10 +252,16 @@ export class Backups extends React.Component {
});
}
+ closeConfirmRestoreReplace () {
+ this.setState({
+ showConfirmRestoreReplace: false,
+ });
+ }
+
importLDIF() {
this.showLDIFSpinningModal();
- const cmd = [
+ let cmd = [
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-"
+ this.props.serverId + ".socket",
"backend", "import", this.state.ldifSuffix,
this.state.ldifName, "--encrypted"
];
@@ -263,7 +288,7 @@ export class Backups extends React.Component {
deleteLDIF (e) {
this.showLDIFDeleteSpinningModal();
- const cmd = [
+ let cmd = [
"dsctl", this.props.serverId, "ldifs",
"--delete", this.state.ldifName
];
log_cmd("deleteLDIF", "Deleting LDIF", cmd);
@@ -288,20 +313,38 @@ export class Backups extends React.Component {
});
}
- doBackup () {
- this.setState({
- backupSpinning: true
- });
+ validateBackup() {
+ for (let i = 0; i < this.props.backups.length; i++) {
+ if (this.state.backupName == this.props.backups[i]['name']) {
+ this.setState({
+ showConfirmRestoreReplace: true
+ });
+ return;
+ }
+ }
+ this.doBackup();
+ }
+ doBackup () {
let cmd = [
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-"
+ this.props.serverId + ".socket",
"backup", "create"
];
-
if (this.state.backupName != "") {
+ if (bad_file_name(this.state.backupName)) {
+ this.props.addNotification(
+ "warning",
+ `Backup name should not be a path. All backups are stored in the
server's backup directory`
+ );
+ return;
+ }
cmd.push(this.state.backupName);
}
+ this.setState({
+ backupSpinning: true
+ });
+
log_cmd("doBackup", "Add backup task", cmd);
cockpit
.spawn(cmd, { superuser: true, err: "message" })
@@ -325,8 +368,15 @@ export class Backups extends React.Component {
}
restoreBackup () {
- this.showRestoreSpinningModal();
+ if (this.props.suffixes.length == 0) {
+ this.props.addNotification(
+ "error",
+ `There are no databases defined to restore`
+ );
+ return;
+ }
+ this.showRestoreSpinningModal();
const cmd = [
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-"
+ this.props.serverId + ".socket",
"backup", "restore", this.state.backupName
@@ -398,13 +448,43 @@ export class Backups extends React.Component {
});
}
+ validateLDIF() {
+ let ldifname = this.state.ldifName;
+ if (!ldifname.endsWith(".ldif")) {
+ // dsconf/dsctl adds ".ldif" if not set, so that's what we need
to check
+ ldifname = ldifname + ".ldif";
+ }
+ for (let i = 0; i < this.props.ldifs.length; i++) {
+ if (ldifname == this.props.ldifs[i]['name']) {
+ this.setState({
+ showConfirmLDIFReplace: true
+ });
+ return;
+ }
+ }
+ this.doExport();
+ }
+
doExport() {
- let missingArgs = {ldifLocation: false};
- if (this.state.ldifLocation == "") {
+ let missingArgs = {ldifName: false};
+ if (this.state.ldifName == "") {
this.props.addNotification(
"warning",
`LDIF name is empty`
);
+ missingArgs.ldifName = true;
+ this.setState({
+ errObj: missingArgs
+ });
+ return;
+ }
+
+ // Must not be a path
+ if (bad_file_name(this.state.ldifName)) {
+ this.props.addNotification(
+ "warning",
+ `LDIF name should not be a path. All export files are stored in the
server's LDIF directory`
+ );
missingArgs.ldifLocation = true;
this.setState({
errObj: missingArgs
@@ -418,6 +498,10 @@ export class Backups extends React.Component {
"backend", "export", this.state.ldifSuffix,
"--encrypted", "--ldif=" + this.state.ldifName
];
+ if (this.state.includeReplData) {
+ export_cmd.push("--replication");
+ }
+
this.setState({
exportSpinner: true,
});
@@ -472,13 +556,22 @@ export class Backups extends React.Component {
confirmDelete={this.showConfirmBackupDelete}
/>
</div>
- <p />
- <Button
- bsStyle="primary"
- onClick={this.showBackupModal}
-
-
Create Backup
- </Button>
+ <div className="ds-inline">
+ <Button
+ bsStyle="primary"
+ onClick={this.showBackupModal}
+ className="ds-margin-top"
+
+
Create Backup
+ </Button>
+ <Button
+ bsStyle="default"
+ onClick={this.props.reload}
+ className="ds-left-margin
ds-margin-top"
+
+
Refresh Backups
+ </Button>
+ </div>
</TabPane>
<TabPane eventKey={2}>
@@ -489,13 +582,22 @@ export class Backups extends React.Component {
confirmDelete={this.showConfirmLDIFDelete}
/>
</div>
- <p />
- <Button
- bsStyle="primary"
- onClick={this.showExportModal}
-
-
Create LDIF Export
- </Button>
+ <div className="ds-inline">
+ <Button
+ bsStyle="primary"
+ onClick={this.showExportModal}
+ className="ds-margin-top"
+
+
Create LDIF Export
+ </Button>
+ <Button
+ bsStyle="default"
+ onClick={this.props.reload}
+ className="ds-left-margin
ds-margin-top"
+
+
Refresh LDIFs
+ </Button>
+ </div>
</TabPane>
</TabContent>
</div>
@@ -505,7 +607,7 @@ export class Backups extends React.Component {
showModal={this.state.showExportModal}
closeHandler={this.closeExportModal}
handleChange={this.handleChange}
- saveHandler={this.doExport}
+ saveHandler={this.validateLDIF}
spinning={this.state.exportSpinner}
error={this.state.errObj}
suffixes={this.props.suffixes}
@@ -514,7 +616,7 @@ export class Backups extends React.Component {
showModal={this.state.showBackupModal}
closeHandler={this.closeBackupModal}
handleChange={this.handleChange}
- saveHandler={this.doBackup}
+ saveHandler={this.validateBackup}
spinning={this.state.backupSpinning}
error={this.state.errObj}
/>
@@ -570,7 +672,20 @@ export class Backups extends React.Component {
msg="Are you sure you want to delete this backup?"
msgContent={this.state.backupName}
/>
-
+ <ConfirmPopup
+ showModal={this.state.showConfirmRestoreReplace}
+ closeHandler={this.closeConfirmRestoreReplace}
+ actionFunc={this.doBackup}
+ msg="Replace Existing Backup"
+ msgContent="A backup already eixsts with the same name, do you
want to replace it?"
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmLDIFReplace}
+ closeHandler={this.closeConfirmLDIFReplace}
+ actionFunc={this.doExport}
+ msg="Replace Existing LDIF File"
+ msgContent="A LDIF file already eixsts with the same name, do
you want to replace it?"
+ />
</div>
);
}
@@ -591,8 +706,8 @@ class ExportModal extends React.Component {
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 className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md" />Exporting
database... <font size="2">(You can safely close this
window)</font>
</div>
</Row>;
}
@@ -627,8 +742,7 @@ class ExportModal extends React.Component {
</select>
</Col>
</Row>
- <p />
- <Row title="Name of exported LDIF file, if left blank
the data and time will be used as the file name">
+ <Row className="ds-margin-top" title="Name
of exported LDIF file, if left blank the data and time will be used as the file
name">
<Col sm={3}>
<ControlLabel>LDIF File
Name</ControlLabel>
</Col>
@@ -641,7 +755,17 @@ class ExportModal extends React.Component {
/>
</Col>
</Row>
- <p />
+ <Row className="ds-margin-top-xlg">
+ <Col sm={12} className="ds-margin-left">
+ <Checkbox
+ id="includeReplData"
+ onChange={handleChange}
+ title="Include the replication metadata
needed to restore or initialize another replica."
+
+
Include Replication Data
+ </Checkbox>
+ </Col>
+ </Row>
{spinner}
</Form>
</Modal.Body>
@@ -680,8 +804,8 @@ export class BackupModal extends React.Component {
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 className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md" />Backing up
databases... <font size="2">(You can safely close this
window)</font>
</div>
</Row>;
}
@@ -717,7 +841,6 @@ export class BackupModal extends React.Component {
/>
</Col>
</Row>
- <p />
{spinner}
</Form>
</Modal.Body>
@@ -733,7 +856,7 @@ export class BackupModal extends React.Component {
bsStyle="primary"
onClick={saveHandler}
- Do Backup
+ Create Backup
</Button>
</Modal.Footer>
</div>
@@ -769,9 +892,8 @@ class RestoreModal extends React.Component {
<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>
+ <Spinner loading inline size="md" />
Restoring backup <b>{msg}</b> ...
+ <p className="ds-margin-top"><font
size="2"> (You can safely close this window)</font></p>
</div>
</Form>
</Modal.Body>
@@ -817,9 +939,8 @@ class DeleteBackupModal extends React.Component {
<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>
+ <Spinner loading inline size="md" />
Deleting backup <b>{msg}</b> ...
+ <p className="ds-margin-top"><font
size="2"> (You can safely close this window)</font></p>
</div>
</Form>
</Modal.Body>
@@ -865,9 +986,8 @@ class ImportingModal extends React.Component {
<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>
+ <Spinner loading inline size="md" />
Importing LDIF <b>{msg}</b> ...
+ <p className="ds-margin-top"><font
size="2"> (You can safely close this window)</font></p>
</div>
</Form>
</Modal.Body>
@@ -913,9 +1033,8 @@ class DeletingLDIFModal extends React.Component {
<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>
+ <Spinner loading inline size="md" />
Deleting LDIF file <b>{msg}</b> ...
+ <p className="ds-margin-top"><font
size="2"> (You can safely close this window)</font></p>
</div>
</Form>
</Modal.Body>
@@ -982,11 +1101,13 @@ ImportingModal.propTypes = {
Backups.propTypes = {
backups: PropTypes.array,
ldifs: PropTypes.array,
- reload: PropTypes.func
+ reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
Backups.defaultProps = {
backups: [],
ldifs: [],
- reload: noop
+ reload: noop,
+ enableTree: noop,
};
diff --git a/src/cockpit/389-console/src/lib/database/chaining.jsx
b/src/cockpit/389-console/src/lib/database/chaining.jsx
index c169e7b..749d414 100644
--- a/src/cockpit/389-console/src/lib/database/chaining.jsx
+++ b/src/cockpit/389-console/src/lib/database/chaining.jsx
@@ -1,6 +1,6 @@
import cockpit from "cockpit";
import React from "react";
-import { ConfirmPopup } from "../notifications.jsx";
+import { ConfirmPopup, DoubleConfirmModal } from "../notifications.jsx";
import CustomCollapse from "../customCollapse.jsx";
import { log_cmd } from "../tools.jsx";
import {
@@ -11,7 +11,6 @@ import {
Row,
Col,
ControlLabel,
- Checkbox,
FormControl,
noop
} from "patternfly-react";
@@ -98,6 +97,10 @@ export class ChainingDatabaseConfig extends React.Component {
this.closeConfirmCompDelete = this.closeConfirmCompDelete.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
handleChange(e) {
// Generic
const value = e.target.type === 'checkbox' ? e.target.checked :
e.target.value;
@@ -454,7 +457,7 @@ export class ChainingDatabaseConfig extends React.Component {
);
return (
- <div className="container-fluid"
id="db-global-page">
+ <div id="chaining-page">
<h3 className="ds-config-header">Database Chaining
Settings</h3>
<hr />
<div className="ds-container">
@@ -492,66 +495,143 @@ export class ChainingDatabaseConfig extends React.Component {
</div>
</div>
</div>
-
- <h4 className="ds-sub-header"><br />Default
Database Link Creation Settings</h4>
+ <h4 className="ds-margin-top ds-sub-header
ds-center">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>
+ <Form horizontal>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The size
limit of entries returned over a database link (nsslapd-sizelimit).">
+ Size Limit
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto"
type="text" id="defSizeLimit" onChange={this.handleChange}
defaultValue={this.state.defSizeLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The
maximum number of operations per connections. (nsconcurrentoperationslimit).">
+ Max Operations Per Conn
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto"
type="text" id="defConcurOpLimit" onChange={this.handleChange}
defaultValue={this.state.defConcurOpLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The time
limit of an operation over a database link (nsslapd-timelimit).">
+ Time Limit
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto"
type="text" id="defTimeLimit" onChange={this.handleChange}
defaultValue={this.state.defTimeLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The
maximum number of operations per connections. (nsconcurrentoperationslimit).">
+ Connection Lifetime
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto"
type="text" id="defConnLife" onChange={this.handleChange}
defaultValue={this.state.defConnLife} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The
maximum number of TCP connections the database link establishes with the remote server.
(nsbindconnectionslimit).">
+ Max TCP Connections
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto"
type="text" id="defBindConnLimit" onChange={this.handleChange}
defaultValue={this.state.defBindConnLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The
number of seconds that pass before the server checks for abandoned operations.
(nsabandonedsearchcheckinterval).">
+ Abandoned Op Check Interval
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto"
type="text" id="defSearchCheck" onChange={this.handleChange}
defaultValue={this.state.defSearchCheck} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The
maximum number of connections allowed over the database link.
(nsoperationconnectionslimit).">
+ Max LDAP Connections
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto"
type="text" id="defOpConnLimit" onChange={this.handleChange}
defaultValue={this.state.defOpConnLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The
number of seconds that pass before the server checks for abandoned operations.
(nsabandonedsearchcheckinterval).">
+ Abandoned Op Check Interval
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto"
type="text" id="defSearchCheck" onChange={this.handleChange}
defaultValue={this.state.defSearchCheck} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The
maximum number of connections allowed over the database link.
(nsoperationconnectionslimit).">
+ Max Binds Per Connection
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto"
type="text" id="defOpConnLimit" onChange={this.handleChange}
defaultValue={this.state.defOpConnLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The
maximum number of times a request can be forwarded from one database link to another.
(nshoplimit).">
+ Database Link Hop Limit
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto"
type="text" id="defHopLimit" onChange={this.handleChange}
defaultValue={this.state.defHopLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The
amount of time before the bind attempt times out. (nsbindtimeout).">
+ Bind Timeout
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto"
type="text" id="defBindTimeout" onChange={this.handleChange}
defaultValue={this.state.defBindTimeout} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="The
number of times the database link tries to bind with the remote server after a connection
failure. (nsbindretrylimit).">
+ Bind Retry Limit
+ </Col>
+ <Col sm={8}>
+ <input className="ds-input-auto"
type="text" id="defBindRetryLimit" onChange={this.handleChange}
defaultValue={this.state.defBindRetryLimit} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="Sets
whether ACIs are evaluated on the database link as well as the remote data server
(nschecklocalaci).">
+ Check Local ACIs
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox"
onChange={this.handleChange} defaultChecked={this.state.defCheckAci}
className="ds-config-checkbox" id="nsusdefCheckAciestarttls" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="Sets
whether referrals are returned by scoped searches (meaning 'one-level' or
'subtree' scoped searches). (nsreferralonscopedsearch).">
+ Send Referral On Scoped Search
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox"
onChange={this.handleChange} defaultChecked={this.state.defRefOnScoped}
className="ds-config-checkbox" id="defRefOnScoped" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="Sets
whether ACIs are evaluated on the database link as well as the remote data server
(nschecklocalaci).">
+ Allow Proxied Authentication
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox"
onChange={this.handleChange} defaultChecked={this.state.defProxy}
className="ds-config-checkbox" id="defProxy" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4} title="Sets
whether referrals are returned by scoped searches (meaning 'one-level' or
'subtree' scoped searches). (nsreferralonscopedsearch).">
+ Use StartTLS
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox"
onChange={this.handleChange} defaultChecked={this.state.defUseStartTLS}
className="ds-config-checkbox" id="defUseStartTLS" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg">
+ <Col sm={5}>
+ <button className="btn btn-primary save-button"
onClick={this.save_chaining_config}>Save Default Settings</button>
+ </Col>
+ </Row>
+ </Form>
<ChainControlsModal
showModal={this.state.showOidModal}
@@ -598,6 +678,8 @@ export class ChainingConfig extends React.Component {
errObj: {},
showDeleteConfirm: false,
linkPwdMatch: true,
+ modalSpinning: false,
+ modalChecked: false,
// Settings
nsfarmserverurl: this.props.data.nsfarmserverurl,
nsmultiplexorbinddn: this.props.data.nsmultiplexorbinddn,
@@ -655,15 +737,23 @@ export class ChainingConfig extends React.Component {
this.closeDeleteConfirm = this.closeDeleteConfirm.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
showDeleteConfirm () {
this.setState({
- showDeleteConfirm: true
+ showDeleteConfirm: true,
+ modalSpinning: false,
+ modalChecked: false,
});
}
closeDeleteConfirm () {
this.setState({
- showDeleteConfirm: false
+ showDeleteConfirm: false,
+ modalSpinning: false,
+ modalChecked: false,
});
}
@@ -874,91 +964,28 @@ export class ChainingConfig extends React.Component {
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}
- 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">
+ <div>
<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 sm={10} className="ds-word-wrap">
+ <ControlLabel
className="ds-suffix-header"><Icon type="fa"
name="link" /> <b>{this.props.suffix}</b>
(<i>{this.props.bename}</i>)</ControlLabel>
</Col>
<Col sm={2}>
<Button
- bsStyle="primary"
+ bsStyle="danger"
onClick={this.showDeleteConfirm}
Delete Link
</Button>
</Col>
</Row>
- <h4>Database Link Configuration</h4>
- <hr />
- <Form horizontal autoComplete="off">
+ <Form horizontal autoComplete="off"
className="ds-margin-top-xlg">
<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 componentClass={ControlLabel} sm={4}>
+ Remote Server LDAP URL
</Col>
- <Col sm={7}>
+ <Col sm={8}>
<FormControl
type="text"
id="nsfarmserverurl"
@@ -968,12 +995,11 @@ export class ChainingConfig extends React.Component {
/>
</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>
+ <Row className="ds-margin-top" title="The
distinguished name (DN) of the entry to authenticate to the remote server.
(nsmultiplexorbinddn)">
+ <Col componentClass={ControlLabel} sm={4}>
+ Remote Server Bind DN
</Col>
- <Col sm={7}>
+ <Col sm={8}>
<FormControl
type="text"
id="nsmultiplexorbinddn"
@@ -983,12 +1009,11 @@ export class ChainingConfig extends React.Component {
/>
</Col>
</Row>
- <p />
- <Row title="The password for the authenticating entry.
(nsmultiplexorcredentials)">
- <Col sm={3}>
- <ControlLabel>Bind DN Password</ControlLabel>
+ <Row className="ds-margin-top" title="The password
for the authenticating entry. (nsmultiplexorcredentials)">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind DN Password
</Col>
- <Col sm={7}>
+ <Col sm={8}>
<FormControl
type="password"
id="nsmultiplexorcredentials"
@@ -998,12 +1023,11 @@ export class ChainingConfig extends React.Component {
/>
</Col>
</Row>
- <p />
- <Row title="Confirm the password for the authenticating
entry. (nsmultiplexorcredentials)">
- <Col sm={3}>
- <ControlLabel>Confirm Password</ControlLabel>
+ <Row className="ds-margin-top" title="Confirm the
password for the authenticating entry. (nsmultiplexorcredentials)">
+ <Col componentClass={ControlLabel} sm={4}>
+ Confirm Password
</Col>
- <Col sm={7}>
+ <Col sm={8}>
<FormControl
type="password"
id="nsmultiplexorcredentials_confirm"
@@ -1013,12 +1037,11 @@ export class ChainingConfig extends React.Component {
/>
</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>
+ <Row className="ds-margin-top" title="The
authentication mechanism. Simple (user name and password), SASL/DIGEST-MD5, or
SASL>GSSAPI. (nsbindmechanism)">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind Mechanism
</Col>
- <Col sm={7}>
+ <Col sm={8}>
<select value={this.state.nsbindmechanism}
className="btn btn-default dropdown
ds-dblink-dropdown"
onChange={this.handleChange}
@@ -1030,93 +1053,214 @@ export class ChainingConfig extends React.Component {
</select>
</Col>
</Row>
- <p />
- <Row>
- <Col sm={9}>
- {useStartTLSCheckBox}
+ <Row className="ds-margin-top" title="Use StartTLS
for connections to the remote server. (nsusestarttls)">
+ <Col componentClass={ControlLabel} sm={4}>
+ Use StartTLS
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox"
onChange={this.props.handleChange} defaultChecked={this.state.nsusestarttls}
className="ds-config-checkbox" id="nsusestarttls" />
</Col>
</Row>
</Form>
- <p />
- <CustomCollapse>
- <div className="ds-accordion-panel">
- <div className="ds-margin-left">
- <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>
+ <CustomCollapse className="ds-margin-top">
+ <Form horizontal className="ds-margin-top
ds-margin-left">
+ <Row className="ds-margin-top" title="The size
limit of entries returned over a database link (nsslapd-sizelimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Size Limit
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="sizelimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.sizelimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The time
limit of an operation over a database link (nsslapd-timelimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Time Limit
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="timelimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.timelimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The
maximum number of TCP connections the database link establishes with the remote server.
(nsbindconnectionslimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Max TCP Connections
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="bindconnlimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.bindconnlimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The
maximum number of connections allowed over the database link.
(nsoperationconnectionslimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Max LDAP Connections
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="opconnlimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.opconnlimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The
maximum number of concurrent bind operations per TCP connection.
(nsconcurrentbindlimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Max Binds Per Connection
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="concurrbindlimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.concurrbindlimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The
amount of time before the bind attempt times out. (nsbindtimeout).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind Timeout
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="bindtimeout"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.bindtimeout}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The
number of times the database link tries to bind with the remote server after a connection
failure. (nsbindretrylimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind Retry Limit
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="bindretrylimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.bindretrylimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The
maximum number of operations per connections. (nsconcurrentoperationslimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Max Operations Per Connection
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="concurroplimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.concurroplimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The life
of a database link connection to the remote server in seconds. 0 is unlimited
(nsconnectionlife).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Connection Lifetime
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="connlifetime"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.connlifetime}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The
number of seconds that pass before the server checks for abandoned operations.
(nsabandonedsearchcheckinterval).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Abandoned Op Check Interval
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="searchcheckinterval"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.searchcheckinterval}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The
maximum number of times a request can be forwarded from one database link to another.
(nshoplimit).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Hop Limit
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="hoplimit"
+ className="ds-input-auto"
+ onChange={this.handleChange}
+ defaultValue={this.state.hoplimit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Allow
proxied authentication to the remote server. (nsproxiedauthorization).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Allow Proxied Authentication
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox"
onChange={this.props.handleChange} defaultChecked={this.state.nsproxiedauthorization}
className="ds-config-checkbox" id="nsproxiedauthorization" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Sets
whether ACIs are evaluated on the database link as well as the remote data server
(nschecklocalaci).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Check Local ACIs
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox"
onChange={this.props.handleChange} defaultChecked={this.state.nschecklocalaci}
className="ds-config-checkbox" id="nschecklocalaci" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Sets
whether referrals are returned by scoped searches (meaning 'one-level' or
'subtree' scoped searches). (nsreferralonscopedsearch).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Send Referral On Scoped Search
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox"
onChange={this.props.handleChange} defaultChecked={this.state.nsreferralonscopedsearch}
className="ds-config-checkbox" id="nsreferralonscopedsearch" />
+ </Col>
+ </Row>
+ </Form>
<hr />
</CustomCollapse>
- <div className="ds-save-btn">
+ <div className="ds-margin-top-lg">
<button onClick={this.saveLink} className="btn
btn-primary">Save Configuration</button>
</div>
- <ConfirmPopup
+ <DoubleConfirmModal
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}
+ handleChange={this.handleChange}
+ actionHandler={this.deleteLink}
+ spinning={this.state.modalSpinning}
+ item={this.props.suffix}
+ checked={this.state.modalChecked}
+ mTitle="Delete Database Link"
+ mMsg="Are you really sure you want to delete this database
link?"
+ mSpinningMsg="Deleting Database Linkt ..."
+ mBtnName="Delete Database Link"
/>
</div>
);
@@ -1161,7 +1305,7 @@ export class ChainControlsModal extends React.Component {
<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>
+ <select id="avail-chaining-oid-list"
onChange={handleChange} className="ds-width-auto" size="10"
multiple>
{oids}
</select>
</div>
@@ -1222,7 +1366,7 @@ export class ChainCompsModal extends React.Component {
<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>
+ <select id="avail-chaining-comp-list"
onChange={handleChange} className="ds-width-auto" size="10"
multiple>
{comps}
</select>
</div>
@@ -1288,6 +1432,7 @@ ChainingDatabaseConfig.propTypes = {
addNotification: PropTypes.func,
reload: PropTypes.func,
data: PropTypes.object,
+ enableTree: PropTypes.func,
};
ChainingDatabaseConfig.defaultProps = {
@@ -1295,6 +1440,7 @@ ChainingDatabaseConfig.defaultProps = {
addNotification: noop,
reload: noop,
data: {},
+ enableTree: PropTypes.noop,
};
ChainingConfig.propTypes = {
@@ -1305,6 +1451,7 @@ ChainingConfig.propTypes = {
addNotification: PropTypes.func,
data: PropTypes.object,
reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
ChainingConfig.defaultProps = {
@@ -1315,4 +1462,5 @@ ChainingConfig.defaultProps = {
addNotification: noop,
data: {},
reload: noop,
+ enableTree: noop,
};
diff --git a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
index edb0557..5adac79 100644
--- a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
+++ b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
@@ -3,6 +3,10 @@ import React from "react";
import CustomCollapse from "../customCollapse.jsx";
import { log_cmd } from "../tools.jsx";
import {
+ Row,
+ Col,
+ ControlLabel,
+ Form,
Spinner,
noop
} from "patternfly-react";
@@ -55,6 +59,10 @@ export class GlobalDatabaseConfig extends React.Component {
this.save_db_config = this.save_db_config.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
select_auto_cache (e) {
this.setState({
db_cache_auto: !this.state.db_cache_auto
@@ -210,8 +218,8 @@ export class GlobalDatabaseConfig extends React.Component {
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"
+ title="Enable database and entry cache auto-tuning using a
percentage of the system's current resources (nsslapd-cache-autosize).">
+ Memory Percentage</label><input
className="ds-input" type="text"
id="autosizee" size="10"
onChange={this.handleChange}
value={this.state.autosize} />
</div>
@@ -228,7 +236,7 @@ export class GlobalDatabaseConfig extends React.Component {
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"
+ Database Cache Size</label><input
className="ds-input" type="text"
id="dbcachesize" size="10"
onChange={this.handleChange} value={this.state.dbcachesize} />
</div>;
db_auto_checked = false;
@@ -246,8 +254,8 @@ export class GlobalDatabaseConfig extends React.Component {
} 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"
+ title="The size of the database cache in bytes used in the bulk
import process. (nsslapd-import-cachesize).">
+ Import Cache Size</label><input
className="ds-input" type="text" id="importcachesize"
size="10" onChange={this.handleChange}
value={this.state.importcachesize} />
</div>;
import_auto_checked = false;
@@ -256,58 +264,96 @@ export class GlobalDatabaseConfig extends React.Component {
let spinner = "";
if (this.state.loading) {
spinner =
- <div className="ds-loading-spinner ds-center">
- <p />
+ <div className="ds-loading-spinner ds-margin-top
ds-center">
<h4>Loading global database configuration ...</h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top" loading
size="md" />
</div>;
}
return (
- <div className="container-fluid"
id="db-global-page">
+ <div 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>
+ <Form horizontal>
+ <Row
+ 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)."
+ className="ds-margin-top"
+
+ <Col
componentClass={ControlLabel} sm={5}>
+ Database Look Though Limit
+ </Col>
+ <Col sm={4}>
+ <input
+ id="looklimit"
+ value={this.state.looklimit}
+ onChange={this.handleChange}
className="ds-input-auto" type="text"
+ />
+ </Col>
+ </Row>
+ <Row
+ title="The number of entry IDs that are searched during
a search operation (nsslapd-idlistscanlimit)."
+ className="ds-margin-top"
+
+ <Col
componentClass={ControlLabel} sm={5}>
+ ID List Scan Limit
+ </Col>
+ <Col sm={4}>
+ <input
+ id="idscanlimit"
+ value={this.state.idscanlimit}
+ onChange={this.handleChange}
className="ds-input-auto" type="text"
+ />
+ </Col>
+ </Row>
+ <Row
+ 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)."
+ className="ds-margin-top"
+
+ <Col
componentClass={ControlLabel} sm={5}>
+ Paged Search Look Through Limit
+ </Col>
+ <Col sm={4}>
+ <input
+ id="pagelooklimit"
+ value={this.state.pagelooklimit}
+ onChange={this.handleChange}
className="ds-input-auto" type="text"
+ />
+ </Col>
+ </Row>
+ <Row
+ title="The number of entry IDs that are searched,
specifically, for a search operation using the simple paged results control
(nsslapd-pagedidlistscanlimit)."
+ className="ds-margin-top"
+
+ <Col
componentClass={ControlLabel} sm={5}>
+ Paged Search ID List Scan Limit
+ </Col>
+ <Col sm={4}>
+ <input
+ id="pagescanlimit"
+ value={this.state.pagescanlimit}
+ onChange={this.handleChange}
className="ds-input-auto" type="text"
+ />
+ </Col>
+ </Row>
+ <Row
+ 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)."
+ className="ds-margin-top"
+
+ <Col
componentClass={ControlLabel} sm={5}>
+ Range Search Look Through Limit
+ </Col>
+ <Col sm={4}>
+ <input
+ id="rangelooklimit"
+ value={this.state.rangelooklimit}
+ onChange={this.handleChange}
className="ds-input-auto" type="text"
+ />
+ </Col>
+ </Row>
+ </Form>
+
<div className="ds-container">
<div>
<h4 className="ds-sub-header">Database Cache
Settings</h4>
@@ -333,46 +379,54 @@ export class GlobalDatabaseConfig extends React.Component {
</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 className="ds-margin-top">
+ <div className="ds-margin-left">
+ <Form horizontal>
+ <Row className="ds-margin-top"
title="Database Transaction Log Location (nsslapd-db-logdirectory).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Transaction Logs Directory
+ </Col>
+ <Col sm={8}>
+ <input id="txnlogdir"
value={this.state.txnlogdir} onChange={this.handleChange}
className="ds-input-auto" type="text" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="Location for database memory mapped files. You must specify a subdirectory of
a tempfs type filesystem (nsslapd-db-home-directory).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Database Home Directory
+ </Col>
+ <Col sm={8}>
+ <input id="dbhomedir"
value={this.state.dbhomedir} onChange={this.handleChange}
className="ds-input-auto" type="text" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="The number of database locks (nsslapd-db-locks).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Database Locks
+ </Col>
+ <Col sm={8}>
+ <input id="dblocks"
value={this.state.dblocks} onChange={this.handleChange}
className="ds-input-auto" type="text" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="Amount of time in seconds after which the Directory Server sends a checkpoint
entry to the database transaction log (nsslapd-db-checkpoint-interval).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Database Checkpoint Interval
+ </Col>
+ <Col sm={8}>
+ <input id="chxpoint"
value={this.state.chxpoint} onChange={this.handleChange}
className="ds-input-auto" type="text" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="The interval in seconds when the database is compacted
(nsslapd-db-compactdb-interval).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Database Compact Interval
+ </Col>
+ <Col sm={8}>
+ <input id="compactinterval"
value={this.state.compactinterval} onChange={this.handleChange}
className="ds-input-auto" type="text" />
+ </Col>
+ </Row>
+ </Form>
</div>
- <p />
</div>
</CustomCollapse>
- <p />
- <div className="ds-save-btn">
+ <div className="ds-margin-top-lg">
<button className="btn btn-primary save-button"
onClick={this.save_db_config}>Save
Configuration</button>
</div>
@@ -389,6 +443,7 @@ GlobalDatabaseConfig.propTypes = {
addNotification: PropTypes.func,
data: PropTypes.object,
reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
GlobalDatabaseConfig.defaultProps = {
@@ -396,4 +451,5 @@ GlobalDatabaseConfig.defaultProps = {
addNotification: noop,
data: {},
reload: noop,
+ enableTree: noop,
};
diff --git a/src/cockpit/389-console/src/lib/database/databaseModal.jsx
b/src/cockpit/389-console/src/lib/database/databaseModal.jsx
index 01db9bb..881b0b4 100644
--- a/src/cockpit/389-console/src/lib/database/databaseModal.jsx
+++ b/src/cockpit/389-console/src/lib/database/databaseModal.jsx
@@ -2,8 +2,10 @@ import React from "react";
import {
Modal,
Row,
+ Checkbox,
Col,
ControlLabel,
+ Radio,
Icon,
Button,
Form,
@@ -44,7 +46,7 @@ class CreateLinkModal extends React.Component {
</Modal.Title>
</Modal.Header>
<Modal.Body>
- <Form horizontal autoComplete="off">
+ <Form horizontal>
<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>
@@ -110,8 +112,12 @@ class CreateSubSuffixModal extends React.Component {
showModal,
closeHandler,
handleChange,
+ handleRadioChange,
saveHandler,
suffix,
+ noInit,
+ addSuffix,
+ addSample,
error
} = this.props;
@@ -133,7 +139,7 @@ class CreateSubSuffixModal extends React.Component {
</Modal.Header>
<Modal.Body>
<Form horizontal autoComplete="off">
- <Row title="Database Suffix DN
(nsslapd-suffix)">
+ <Row title="Database suffix, like
'dc=example,dc=com'. The suffix must be a valid LDAP Distiguished Name
(DN)">
<Col sm={3}>
<ControlLabel>Sub-Suffix
DN</ControlLabel>
</Col>
@@ -149,10 +155,9 @@ class CreateSubSuffixModal extends React.Component {
<ControlLabel><b><font
color="blue">,{suffix}</font></b></ControlLabel>
</Col>
</Row>
- <p />
- <Row title="Database backend name
(nsslapd-backend)">
+ <Row className="ds-margin-top" title="The
name for the backend database, like 'userroot'. The name can be a combination of
alphanumeric characters, dashes (-), and underscores (_). No other characters are allowed,
and the name must be unique across all backends.">
<Col sm={3}>
- <ControlLabel>Backend
Name</ControlLabel>
+ <ControlLabel>Database
Name</ControlLabel>
</Col>
<Col sm={9}>
<FormControl
@@ -163,6 +168,24 @@ class CreateSubSuffixModal extends React.Component {
/>
</Col>
</Row>
+ <hr />
+ <div>
+ <Row className="ds-indent">
+ <Radio name="radioGroup"
id="noSuffixInit" onChange={handleRadioChange} checked={noInit} inline>
+ Do Not Initialize Database
+ </Radio>
+ </Row>
+ <Row className="ds-indent">
+ <Radio name="radioGroup"
id="createSuffixEntry" onChange={handleRadioChange} checked={addSuffix}
inline>
+ Create The Top Sub-Suffix Entry
+ </Radio>
+ </Row>
+ <Row className="ds-indent">
+ <Radio name="radioGroup"
id="createSampleEntries" onChange={handleRadioChange} checked={addSample}
inline>
+ Add Sample Entries
+ </Radio>
+ </Row>
+ </div>
</Form>
</Modal.Body>
<Modal.Footer>
@@ -200,7 +223,7 @@ class ExportModal extends React.Component {
if (spinning) {
spinner =
<Row>
- <div className="ds-modal-spinner">
+ <div className="ds-margin-top-lg ds-modal-spinner">
<Spinner loading inline size="lg" />Exporting
database... <font size="1">(You can safely close this
window)</font>
</div>
</Row>;
@@ -237,7 +260,17 @@ class ExportModal extends React.Component {
/>
</Col>
</Row>
- <p />
+ <Row className="ds-margin-top-xlg">
+ <Col sm={12} className="ds-margin-left">
+ <Checkbox
+ id="includeReplData"
+ onChange={handleChange}
+ title="Include the replication metadata
needed to restore or initialize another replica."
+
+
Include Replication Data
+ </Checkbox>
+ </Col>
+ </Row>
{spinner}
</Form>
</Modal.Body>
@@ -279,8 +312,7 @@ class ImportModal extends React.Component {
if (spinning) {
spinner =
<Row>
- <div className="ds-modal-spinner">
- <p />
+ <div className="ds-margin-top-lg ds-modal-spinner">
<Spinner loading inline size="lg" />Importing
LDIF file... <font size="1">(You can safely close this
window)</font>
</div>
</Row>;
@@ -308,7 +340,6 @@ class ImportModal extends React.Component {
</Modal.Title>
</Modal.Header>
<Modal.Body>
- <p />
<LDIFTable
rows={suffixRows}
confirmImport={this.props.showConfirmImport}
@@ -335,7 +366,6 @@ class ImportModal extends React.Component {
</Button>
</Col>
</Row>
- <p />
{spinner}
</Form>
</Modal.Body>
diff --git a/src/cockpit/389-console/src/lib/database/databaseTables.jsx
b/src/cockpit/389-console/src/lib/database/databaseTables.jsx
index 5d90f02..b8715e8 100644
--- a/src/cockpit/389-console/src/lib/database/databaseTables.jsx
+++ b/src/cockpit/389-console/src/lib/database/databaseTables.jsx
@@ -60,6 +60,7 @@ class ReferralTable extends React.Component {
return [
<td key={rowData.name[0]}>
<Button
+ bsStyle="primary"
onClick={() => {
this.props.loadModalHandler(rowData);
}}
@@ -228,7 +229,8 @@ class IndexTable extends React.Component {
return [
<td key={rowData.name[0]}>
<DropdownButton id={rowData.name[0]}
- bsStyle="default"
title="Actions">
+ className="ds-action-button"
+ bsStyle="primary"
title="Actions">
<MenuItem eventKey="1"
onClick={() => {
this.props.editIndex(rowData);
}}
@@ -331,6 +333,7 @@ class EncryptedAttrTable extends React.Component {
return [
<td key={rowData.name[0]}>
<Button
+ bsStyle="primary"
onClick={() => {
this.props.loadModalHandler(rowData);
}}
@@ -680,6 +683,7 @@ class LDIFManageTable extends React.Component {
return [
<td key={rowData.name[0]}>
<DropdownButton id={rowData.name[0]}
+ className="ds-action-button"
bsStyle="primary"
title="Actions">
<MenuItem eventKey="1"
onClick={() => {
this.props.confirmImport(rowData);
@@ -855,6 +859,7 @@ class BackupTable extends React.Component {
return [
<td key={rowData.name[0]}>
<DropdownButton id={rowData.name[0]}
+ className="ds-action-button"
bsStyle="primary"
title="Actions">
<MenuItem eventKey="1"
onClick={() => {
this.props.confirmRestore(rowData);
diff --git a/src/cockpit/389-console/src/lib/database/indexes.jsx
b/src/cockpit/389-console/src/lib/database/indexes.jsx
index f8e157d..d33db25 100644
--- a/src/cockpit/389-console/src/lib/database/indexes.jsx
+++ b/src/cockpit/389-console/src/lib/database/indexes.jsx
@@ -631,7 +631,7 @@ class AddIndexModal extends React.Component {
</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>
+ <label className="ds-config-label"
htmlFor="indexAttributeName" title="Select an attribute to
index">Select An Attribute</label>
<Typeahead
id="indexAttributeName"
onChange={values => {
@@ -665,7 +665,6 @@ class AddIndexModal extends React.Component {
</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>
@@ -836,7 +835,6 @@ class EditIndexModal extends React.Component {
</Col>
</Row>
</div>
- <p />
<Row className="ds-margin-top-lg">
<Col sm={12}>
<p><b>Matching Rules</b></p>
diff --git a/src/cockpit/389-console/src/lib/database/referrals.jsx
b/src/cockpit/389-console/src/lib/database/referrals.jsx
index 22f9c50..13f4c15 100644
--- a/src/cockpit/389-console/src/lib/database/referrals.jsx
+++ b/src/cockpit/389-console/src/lib/database/referrals.jsx
@@ -12,7 +12,7 @@ import {
Form,
noop
} from "patternfly-react";
-import { log_cmd } from "../tools.jsx";
+import { log_cmd, valid_port } from "../tools.jsx";
import PropTypes from "prop-types";
import "../../css/ds.css";
@@ -122,6 +122,13 @@ export class SuffixReferrals extends React.Component {
);
missingArgs.refPort = true;
errors = true;
+ } else if (!valid_port(this.state.refPort)) {
+ this.props.addNotification(
+ "error",
+ `Invalid port number, please use a number between 1 and 65535`
+ );
+ missingArgs.refPort = true;
+ errors = true;
}
if (errors) {
this.setState({
@@ -288,8 +295,7 @@ class AddReferralModal extends React.Component {
</select>
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Host Name</ControlLabel>
</Col>
@@ -297,8 +303,7 @@ class AddReferralModal extends React.Component {
<input className={error.refHost ?
"ds-input-auto-bad" : "ds-input-auto"} type="text"
onChange={handleChange} id="refHost" />
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Port Number</ControlLabel>
</Col>
@@ -306,8 +311,7 @@ class AddReferralModal extends React.Component {
<input className={error.refPort ?
"ds-input-auto-bad" : "ds-input-auto"} type="text"
onChange={handleChange} id="refPort" />
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Suffix</ControlLabel>
</Col>
@@ -315,8 +319,7 @@ class AddReferralModal extends React.Component {
<input className="ds-input-auto"
onChange={handleChange} type="text" id="refSuffix" />
</Col>
</Row>
- <p />
- <Row title="Comma separated list of attributes to
return">
+ <Row className="ds-margin-top" title="Comma
separated list of attributes to return">
<Col sm={3}>
<ControlLabel>Attributes</ControlLabel>
</Col>
@@ -324,8 +327,7 @@ class AddReferralModal extends React.Component {
<input className="ds-input-auto"
onChange={handleChange} type="text" id="refAttrs" />
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Filter</ControlLabel>
</Col>
@@ -333,13 +335,12 @@ class AddReferralModal extends React.Component {
<input onChange={handleChange}
className="ds-input-auto" type="text" id="refFilter" />
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Scope</ControlLabel>
</Col>
<Col sm={9}>
- <select className="btn btn-default
dropdown" onChange={handleChange} defaultValue=""
name="refScope">
+ <select className="btn btn-default
dropdown" onChange={handleChange} defaultValue=""
id="refScope">
<option />
<option>sub</option>
<option>one</option>
diff --git a/src/cockpit/389-console/src/lib/database/suffix.jsx
b/src/cockpit/389-console/src/lib/database/suffix.jsx
index 3f3bc82..9be54a8 100644
--- a/src/cockpit/389-console/src/lib/database/suffix.jsx
+++ b/src/cockpit/389-console/src/lib/database/suffix.jsx
@@ -1,12 +1,12 @@
import cockpit from "cockpit";
import React from "react";
-import { ConfirmPopup } from "../notifications.jsx";
+import { DoubleConfirmModal } 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 { log_cmd, bad_file_name } from "../tools.jsx";
import {
ImportModal,
ExportModal,
@@ -28,6 +28,15 @@ import {
TabContainer,
noop
} from "patternfly-react";
+
+// PR React 4 example
+// import {
+// Dropdown,
+// DropdownToggle,
+// DropdownItem,
+// DropdownSeparator,
+// } from "@patternfly/react-core";
+
import PropTypes from "prop-types";
import "../../css/ds.css";
@@ -64,12 +73,19 @@ export class Suffix extends React.Component {
importSpinner: false,
showConfirmLDIFImport: false,
deleleLDIFName: "",
+ modalChecked: false,
+ modalSpinning: false,
+ includeReplData: false,
// Reindex all
showReindexConfirm: false,
// Create Sub Suffix
showSubSuffixModal: false,
subSuffixValue: "",
subSuffixBeName: "",
+ createSuffixEntry: false,
+ noSuffixInit: true,
+ createSampleEntries: false,
+
// Create Link
showLinkModal: false,
createLinkSuffix: "",
@@ -91,6 +107,7 @@ export class Suffix extends React.Component {
this.showImportModal = this.showImportModal.bind(this);
this.closeImportModal = this.closeImportModal.bind(this);
this.handleChange = this.handleChange.bind(this);
+ this.handleRadioChange = this.handleRadioChange.bind(this);
this.doImport = this.doImport.bind(this);
this.importLDIF = this.importLDIF.bind(this);
this.showConfirmLDIFImport = this.showConfirmLDIFImport.bind(this);
@@ -114,12 +131,15 @@ export class Suffix extends React.Component {
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);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
handleNavSelect(key) {
this.setState({ activeKey: key });
}
@@ -162,6 +182,8 @@ export class Suffix extends React.Component {
this.setState({
showConfirmLDIFImport: true,
importLDIFName: item.name,
+ modalChecked: false,
+ modalSpinning: false,
});
}
@@ -169,6 +191,8 @@ export class Suffix extends React.Component {
// call importLDIF
this.setState({
showConfirmLDIFImport: false,
+ modalChecked: false,
+ modalSpinning: false,
});
}
@@ -226,6 +250,7 @@ export class Suffix extends React.Component {
attrEncryption: false,
showExportModal: true,
exportSpinner: false,
+ includeReplData: false,
errObj: {},
});
}
@@ -251,7 +276,20 @@ export class Suffix extends React.Component {
return;
}
- // Do import
+ // Must not be a path
+ if (bad_file_name(this.state.ldifLocation)) {
+ this.props.addNotification(
+ "warning",
+ `LDIF name should not be a path. All export files are stored in the
server's LDIF directory`
+ );
+ missingArgs.ldifLocation = true;
+ this.setState({
+ errObj: missingArgs
+ });
+ return;
+ }
+
+ // Do Export
let export_cmd = [
"dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-"
+ this.props.serverId + ".socket",
"backend", "export", this.props.suffix,
"--ldif=" + this.state.ldifLocation
@@ -261,6 +299,10 @@ export class Suffix extends React.Component {
export_cmd.push("--encrypted");
}
+ if (this.state.includeReplData) {
+ export_cmd.push("--replication");
+ }
+
this.setState({
exportSpinner: true,
});
@@ -280,7 +322,7 @@ export class Suffix extends React.Component {
})
.fail(err => {
let errMsg = JSON.parse(err);
- this.loadLDIFs();
+ this.props.reloadLDIFs();
this.props.addNotification(
"error",
`Error exporting database - ${errMsg.desc}`
@@ -296,13 +338,17 @@ export class Suffix extends React.Component {
//
showReindexConfirm() {
this.setState({
- showReindexConfirm: true
+ showReindexConfirm: true,
+ modalChecked: false,
+ modalSpinning: false,
});
}
closeReindexConfirm() {
this.setState({
- showReindexConfirm: false
+ showReindexConfirm: false,
+ modalChecked: false,
+ modalSpinning: false,
});
}
@@ -389,13 +435,20 @@ export class Suffix extends React.Component {
}
// Create a new suffix
- const cmd = [
+ let 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
];
+ if (this.state.createSampleEntries) {
+ cmd.push('--create-entries');
+ }
+ if (this.state.createSuffixEntry) {
+ cmd.push('--create-suffix');
+ }
+
log_cmd("createSubSuffix", "Create a sub suffix", cmd);
cockpit
.spawn(cmd, { superuser: true, err: "message" })
@@ -517,7 +570,7 @@ export class Suffix extends React.Component {
this.state.createLinkName
];
if (this.state.createUseStartTLS) {
- cmd.push("--use-starttls");
+ cmd.push("--use-starttls=on");
}
log_cmd("createLink", "Create database link", cmd);
cockpit
@@ -566,18 +619,41 @@ export class Suffix extends React.Component {
}, this.checkPasswords);
}
+ handleRadioChange(e) {
+ // Handle the create suffix init option radio button group
+ let noInit = false;
+ let addSuffix = false;
+ let addSample = false;
+ if (e.target.id == "noSuffixInit") {
+ noInit = true;
+ } else if (e.target.id == "createSuffixEntry") {
+ addSuffix = true;
+ } else { // createSampleEntries
+ addSample = true;
+ }
+ this.setState({
+ noSuffixInit: noInit,
+ createSuffixEntry: addSuffix,
+ createSampleEntries: addSample
+ });
+ }
+
//
// Delete suffix
//
showDeleteConfirm(item) {
this.setState({
- showDeleteConfirm: true
+ showDeleteConfirm: true,
+ modalSpinning: false,
+ modalChecked: false
});
}
closeDeleteConfirm() {
this.setState({
- showDeleteConfirm: false
+ showDeleteConfirm: false,
+ modalChecked: false,
+ modalSpinning: false,
});
}
@@ -680,20 +756,15 @@ export class Suffix extends React.Component {
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">
+ <div 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>
+ <ControlLabel
className="ds-suffix-header"><Icon type="fa" name={suffixIcon}
/> <b>{this.props.suffix}</b>
(<i>{this.props.bename}</i>)</ControlLabel>
</Col>
<Col sm={2}>
<div>
- <DropdownButton bsStyle="primary"
title="Suffix Tasks" id="mydropdown">
+ <DropdownButton className="ds-action-button"
bsStyle="primary" title="Suffix Tasks" id="mydropdown"
pullRight>
<MenuItem eventKey="1"
onClick={this.showImportModal} title="Import an LDIF file to initialize the
database">
Initialize Suffix
</MenuItem>
@@ -717,10 +788,9 @@ export class Suffix extends React.Component {
</div>
</Col>
</Row>
- <p />
<TabContainer id="basic-tabs-pf"
onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
- <div>
+ <div className="ds-margin-top-xlg">
<Nav bsClass="nav nav-tabs nav-tabs-pf">
<NavItem eventKey={1}>
<div dangerouslySetInnerHTML={{__html:
'Settings'}} />
@@ -803,14 +873,18 @@ export class Suffix extends React.Component {
</TabContent>
</div>
</TabContainer>
-
- <ConfirmPopup
+ <DoubleConfirmModal
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}
+ handleChange={this.handleChange}
+ actionHandler={this.doDelete}
+ spinning={this.state.modalSpinning}
+ item={this.props.suffix}
+ checked={this.state.modalChecked}
+ mTitle="Delete Replication Agreement"
+ mMsg="Are you really sure you want to delete the
database?"
+ mSpinningMsg="Deleting database ..."
+ mBtnName="Delete Database"
/>
<CreateLinkModal
showModal={this.state.showLinkModal}
@@ -825,8 +899,12 @@ export class Suffix extends React.Component {
showModal={this.state.showSubSuffixModal}
closeHandler={this.closeSubSuffixModal}
handleChange={this.handleChange}
+ handleRadioChange={this.handleRadioChange}
saveHandler={this.createSubSuffix}
suffix={this.props.suffix}
+ noInit={this.state.noSuffixInit}
+ addSuffix={this.state.createSuffixEntry}
+ addSample={this.state.createSampleEntries}
error={this.state.errObj}
/>
<ImportModal
@@ -839,15 +917,19 @@ export class Suffix extends React.Component {
rows={this.props.LDIFRows}
suffix={this.props.suffix}
/>
- <ConfirmPopup
+ <DoubleConfirmModal
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!"
+ handleChange={this.handleChange}
+ actionHandler={this.importLDIF}
+ spinning={this.state.modalSpinning}
+ item={this.state.importLDIFName}
+ checked={this.state.modalChecked}
+ mTitle="Initialize Database From LDIF"
+ mMsg="Are you sure you want to initialize the database (it will
permanently overwrite the current database)?"
+ mSpinningMsg="Initializing Database ..."
+ mBtnName="Initialize Database"
/>
-
<ExportModal
showModal={this.state.showExportModal}
closeHandler={this.closeExportModal}
@@ -856,13 +938,18 @@ export class Suffix extends React.Component {
spinning={this.state.exportSpinner}
error={this.state.errObj}
/>
- <ConfirmPopup
+ <DoubleConfirmModal
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=""
+ handleChange={this.handlChange}
+ actionHandler={this.doReindex}
+ spinning={this.state.modalSpinning}
+ item={this.props.suffix}
+ checked={this.state.modalChecked}
+ mTitle="Reindex All Attributes"
+ mMsg="Are you sure you want to reindex all the attribute
indexes?"
+ mSpinningMsg="Reindexing Database ..."
+ mBtnName="Reindex"
/>
<ReindexModal
showModal={this.state.showReindexModal}
@@ -892,6 +979,7 @@ Suffix.propTypes = {
data: PropTypes.object,
attrs: PropTypes.array,
LDIFRows: PropTypes.array,
+ enableTree: PropTypes.func,
};
Suffix.defaultProps = {
@@ -909,5 +997,6 @@ Suffix.defaultProps = {
dbtype: "",
data: {},
attrs: [],
- LDIFRows: []
+ LDIFRows: [],
+ enableTree: PropTypes.noop,
};
diff --git a/src/cockpit/389-console/src/lib/database/suffixConfig.jsx
b/src/cockpit/389-console/src/lib/database/suffixConfig.jsx
index ec9cd60..e9b71b2 100644
--- a/src/cockpit/389-console/src/lib/database/suffixConfig.jsx
+++ b/src/cockpit/389-console/src/lib/database/suffixConfig.jsx
@@ -1,7 +1,13 @@
import React from "react";
import "../../css/ds.css";
import PropTypes from "prop-types";
-import { noop } from "patternfly-react";
+import {
+ noop,
+ Row,
+ Col,
+ ControlLabel,
+ Form,
+} from "patternfly-react";
export class SuffixConfig extends React.Component {
render() {
@@ -10,53 +16,83 @@ export class SuffixConfig extends React.Component {
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>;
+ <Form horizontal>
+ <Row className="ds-margin-top" title="The entry
cache size in bytes setting is being auto-sized and is read-only - see Global Database
Configuration">
+ <Col componentClass={ControlLabel} sm={4}>
+ Entry Cache Size
+ </Col>
+ <Col sm={8}>
+ <input disabled value={cachememValue}
className="ds-input-auto" type="text" id="cachememsize"
/>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The entry
cache max entries setting is being auto-sized and is read-only - see Global Database
Configuration">
+ <Col componentClass={ControlLabel} sm={4}>
+ Entry Cache Max Entries
+ </Col>
+ <Col sm={8}>
+ <input disabled value={cacheValue}
className="ds-input-auto" type="text" id="cachesize" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The available
memory space in bytes 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).">
+ <Col componentClass={ControlLabel} sm={4}>
+ DN Cache Size
+ </Col>
+ <Col sm={8}>
+ <input onChange={this.props.handleChange}
value={this.props.dncachememsize} className="ds-input-auto"
type="text" id="dncachememsize" />
+ </Col>
+ </Row>
+ </Form>;
} 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>;
+ <Form horizontal>
+ <Row className="ds-margin-top" title="The size for
the available memory space in bytes for the entry cache (nsslapd-cachememsize).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Entry Cache Size
+ </Col>
+ <Col sm={8}>
+ <input onChange={this.props.handleChange}
value={this.props.cachememsize} className="ds-input-auto" type="text"
id="cachememsize" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The number of
entries to keep in the entry cache, use'-1' for unlimited
(nsslapd-cachesize).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Entry Cache Max Entries
+ </Col>
+ <Col sm={8}>
+ <input onChange={this.props.handleChange}
value={this.props.cachesize} className="ds-input-auto" type="text"
id="cachesize" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="the available
memory space in bytes 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).">
+ <Col componentClass={ControlLabel} sm={4}>
+ DN Cache Size
+ </Col>
+ <Col sm={8}>
+ <input onChange={this.props.handleChange}
value={this.props.dncachememsize} className="ds-input-auto"
type="text" id="dncachememsize" />
+ </Col>
+ </Row>
+ </Form>;
}
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">
+ <Form horizontal>
+ <Row className="ds-margin-top-lg" title="Put
database in Read-Only mode (nsslapd-readonly).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Database Read-Only Mode
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox"
onChange={this.props.handleChange} checked={this.props.readOnly}
className="ds-config-checkbox" id="readOnly" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Block
unindexed searches on this suffix (nsslapd-require-index).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Block Unindexed Searches
+ </Col>
+ <Col sm={8}>
+ <input type="checkbox"
onChange={this.props.handleChange} checked={this.props.requireIndex}
className="ds-config-checkbox" id="requireIndex" />
+ </Col>
+ </Row>
+ </Form>
+ <div className="ds-margin-top-lg">
<button className="btn btn-primary save-button"
onClick={this.props.saveHandler}>Save Configuration</button>
</div>
</div>
diff --git a/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx
b/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx
index a4df2b9..582bfd2 100644
--- a/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx
+++ b/src/cockpit/389-console/src/lib/database/vlvIndexes.jsx
@@ -517,9 +517,9 @@ export class VLVIndexes extends React.Component {
<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>
+ <p key={vlvItem.dn + "-p"}><label
className="ds-divider-lrg">Base</label>{vlvItem.attrs.vlvbase[0]}</p>
+ <p><label
className="ds-divider-lrg">Filter</label>{vlvItem.attrs.vlvfilter[0]}</p>
+ <p><label
className="ds-divider-lrg">Scope</label>{this.getScopeKey(vlvItem.attrs.vlvscope[0])}</p>
<hr />
{
vlvItem.sorts.map(sort => {
@@ -529,7 +529,7 @@ export class VLVIndexes extends React.Component {
} 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>);
+ return (<p key={sort.dn +
sort.attrs.vlvsort[0]}><label
className="ds-divider-lrg">Sort</label>{sort.attrs.vlvsort[0]}
({indexState})</p>);
})
}
</Col>
@@ -831,8 +831,7 @@ class AddVLVModal extends React.Component {
{nameInput}
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Search Base</ControlLabel>
</Col>
@@ -841,8 +840,7 @@ class AddVLVModal extends React.Component {
onChange={handleChange} type="text"
id="vlvBase" defaultValue={base} />
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>Search
Filter</ControlLabel>
</Col>
@@ -851,16 +849,14 @@ class AddVLVModal extends React.Component {
onChange={handleChange} type="text"
id="vlvFilter" defaultValue={filter} />
</Col>
</Row>
- <p />
{vlvscope}
<hr />
<div>
- <p />
- <div>
+ <div className="ds-margin-top">
{sortTable}
- <p />
<Typeahead
multiple
+ className="ds-margin-top"
id="vlvsortindex"
onChange={values => {
this.handleTypeaheadChange(values);
@@ -870,8 +866,7 @@ class AddVLVModal extends React.Component {
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>
+ <button className="ds-margin-top"
type="button" onClick={this.updateSorts}>Add Sort Index</button>
</div>
</div>
<hr />
diff --git a/src/cockpit/389-console/src/lib/monitor/accesslog.jsx
b/src/cockpit/389-console/src/lib/monitor/accesslog.jsx
index e373c08..42053f4 100644
--- a/src/cockpit/389-console/src/lib/monitor/accesslog.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/accesslog.jsx
@@ -17,6 +17,10 @@ export class AccessLogMonitor extends React.Component {
textarea.scrollTop = textarea.scrollHeight;
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
render() {
let spinner = "";
if (this.props.reloading) {
@@ -58,7 +62,7 @@ export class AccessLogMonitor extends React.Component {
</div>;
return (
- <div id="monitor-log-access-page"
className="container-fluid">
+ <div id="monitor-log-access-page">
<Row>
<Col sm={3}>
<ControlLabel className="ds-suffix-header">
@@ -73,8 +77,7 @@ export class AccessLogMonitor extends React.Component {
{spinner}
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top-lg">
<Col sm={6}>
{selectLines}
</Col>
@@ -102,6 +105,7 @@ AccessLogMonitor.propTypes = {
reloading: PropTypes.bool,
refreshing: PropTypes.bool,
lines: PropTypes.string,
+ enableTree: PropTypes.func,
};
AccessLogMonitor.defaultProps = {
@@ -111,7 +115,8 @@ AccessLogMonitor.defaultProps = {
reload: noop,
reloading: false,
refreshing: false,
- line: "50"
+ line: "50",
+ enableTree: noop,
};
export default AccessLogMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/auditfaillog.jsx
b/src/cockpit/389-console/src/lib/monitor/auditfaillog.jsx
index d533fcc..b2ddca9 100644
--- a/src/cockpit/389-console/src/lib/monitor/auditfaillog.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/auditfaillog.jsx
@@ -17,6 +17,10 @@ export class AuditFailLogMonitor extends React.Component {
textarea.scrollTop = textarea.scrollHeight;
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
render() {
let spinner = "";
if (this.props.reloading) {
@@ -58,9 +62,9 @@ export class AuditFailLogMonitor extends React.Component {
</div>;
return (
- <div id="monitor-log-auditfail-page"
className="container-fluid">
+ <div id="monitor-log-auditfail-page">
<Row>
- <Col sm={3}>
+ <Col sm={4}>
<ControlLabel className="ds-suffix-header">
Audit Failure Log
<Icon className="ds-left-margin ds-refresh"
@@ -69,12 +73,11 @@ export class AuditFailLogMonitor extends React.Component {
/>
</ControlLabel>
</Col>
- <Col sm={9} className="ds-float-left">
+ <Col sm={8} className="ds-float-left">
{spinner}
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top-lg">
<Col sm={6}>
{selectLines}
</Col>
@@ -102,6 +105,7 @@ AuditFailLogMonitor.propTypes = {
reloading: PropTypes.bool,
refreshing: PropTypes.bool,
lines: PropTypes.string,
+ enableTree: PropTypes.func,
};
AuditFailLogMonitor.defaultProps = {
@@ -111,7 +115,8 @@ AuditFailLogMonitor.defaultProps = {
reload: noop,
reloading: false,
refreshing: false,
- line: "50"
+ line: "50",
+ enableTree: noop,
};
export default AuditFailLogMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/auditlog.jsx
b/src/cockpit/389-console/src/lib/monitor/auditlog.jsx
index ae84c6b..8d1c9f3 100644
--- a/src/cockpit/389-console/src/lib/monitor/auditlog.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/auditlog.jsx
@@ -17,6 +17,10 @@ export class AuditLogMonitor extends React.Component {
textarea.scrollTop = textarea.scrollHeight;
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
render() {
let spinner = "";
if (this.props.reloading) {
@@ -58,9 +62,9 @@ export class AuditLogMonitor extends React.Component {
</div>;
return (
- <div id="monitor-log-audit-page"
className="container-fluid">
+ <div id="monitor-log-audit-page">
<Row>
- <Col sm={3}>
+ <Col sm={4}>
<ControlLabel className="ds-suffix-header">
Audit Log
<Icon className="ds-left-margin ds-refresh"
@@ -69,12 +73,11 @@ export class AuditLogMonitor extends React.Component {
/>
</ControlLabel>
</Col>
- <Col sm={9} className="ds-float-left">
+ <Col sm={8} className="ds-float-left">
{spinner}
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top-lg">
<Col sm={6}>
{selectLines}
</Col>
@@ -102,6 +105,7 @@ AuditLogMonitor.propTypes = {
reloading: PropTypes.bool,
refreshing: PropTypes.bool,
lines: PropTypes.string,
+ enableTree: PropTypes.func,
};
AuditLogMonitor.defaultProps = {
@@ -111,7 +115,8 @@ AuditLogMonitor.defaultProps = {
reload: noop,
reloading: false,
refreshing: false,
- line: "50"
+ line: "50",
+ enableTree: noop,
};
export default AuditLogMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/chainingMonitor.jsx
b/src/cockpit/389-console/src/lib/monitor/chainingMonitor.jsx
index 9c108ff..717e03d 100644
--- a/src/cockpit/389-console/src/lib/monitor/chainingMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/chainingMonitor.jsx
@@ -6,9 +6,14 @@ import {
Col,
ControlLabel,
Icon,
+ noop
} from "patternfly-react";
export class ChainingMonitor extends React.Component {
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
render() {
return (
<div id="monitor-server-page"
className="container-fluid">
@@ -23,9 +28,7 @@ export class ChainingMonitor extends React.Component {
</ControlLabel>
</Col>
</Row>
- <p />
- <hr />
- <div>
+ <div className="ds-margin-top-lg">
<Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>
@@ -165,13 +168,15 @@ export class ChainingMonitor extends React.Component {
ChainingMonitor.propTypes = {
suffix: PropTypes.string,
bename: PropTypes.string,
- data: PropTypes.object
+ data: PropTypes.object,
+ enableTree: PropTypes.func,
};
ChainingMonitor.defaultProps = {
suffix: "",
bename: "",
- data: {}
+ data: {},
+ enableTree: noop,
};
export default ChainingMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx
b/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx
index f5449dc..ea5dcd8 100644
--- a/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx
@@ -12,6 +12,7 @@ import {
Row,
Col,
ControlLabel,
+ Form,
Icon,
noop,
} from "patternfly-react";
@@ -22,10 +23,15 @@ export class DatabaseMonitor extends React.Component {
super(props);
this.state = {
activeKey: 1,
+ disableTabs: false,
};
this.handleNavSelect = this.handleNavSelect.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
handleNavSelect(key) {
this.setState({ activeKey: key });
}
@@ -82,7 +88,7 @@ export class DatabaseMonitor extends React.Component {
}
return (
- <div id="db-content" className="container-fluid">
+ <div id="db-content">
<Row>
<Col sm={12} className="ds-word-wrap">
<ControlLabel className="ds-suffix-header">
@@ -94,7 +100,7 @@ export class DatabaseMonitor extends React.Component {
</ControlLabel>
</Col>
</Row>
- <TabContainer id="basic-tabs-pf"
onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <TabContainer className="ds-margin-top-lg"
id="basic-tabs-pf" onSelect={this.handleNavSelect}
activeKey={this.state.activeKey}>
<div>
<Nav bsClass="nav nav-tabs nav-tabs-pf">
<NavItem eventKey={1}>
@@ -125,83 +131,70 @@ export class DatabaseMonitor extends React.Component {
<b className="ds-left-margin">DB
Cache Hit Ratio</b>
</div>
<hr />
- <div>
+ <Form horizontal>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Database Cache Hit Ratio
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Database Cache Hit Ratio
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.dbcachehitratio} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Database Cache Tries
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Database Cache Tries
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.dbcachetries} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Database Cache Hits
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Database Cache Hits
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.dbcachehits} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Cache Pages Read
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Cache Pages Read
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.dbcachepagein} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Cache Pages Written
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Cache Pages Written
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.dbcachepageout} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Read-Only Page Evictions
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Read-Only Page Evictions
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.dbcacheroevict} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Read-Write Page Evictions
- </ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Read-Write Page Evictions
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.dbcacherwevict} size="28" readOnly />
</Col>
</Row>
- </div>
+ </Form>
</TabPane>
<TabPane eventKey={2}>
<div className="ds-margin-top-lg">
<div className="ds-container">
+ <div className="ds-divider" />
<div className="ds-left-margin">
<DonutChart
id="monitor-db-cache-ndn-hitratio-chart"
@@ -219,6 +212,8 @@ export class DatabaseMonitor extends React.Component {
/>
<b>NDN Cache Hit Ratio</b>
</div>
+ <div className="ds-divider" />
+ <div className="ds-divider" />
<div className="ds-chart-right">
<PieChart
id="monitor-db-cache-ndn-util-chart"
@@ -243,6 +238,7 @@ export class DatabaseMonitor extends React.Component {
}}
title={{type: 'pie'}}
legend={{show: true, position:
'right'}}
+ unloadBeforeLoad
/>
<b>NDN Cache Utilization</b>
<div>
@@ -251,98 +247,80 @@ export class DatabaseMonitor extends React.Component {
</div>
</div>
<hr />
- <div>
+ <Form horizontal>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- NDN Cache Hit Ratio
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ NDN Cache Hit Ratio
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.normalizeddncachehitratio} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- NDN Cache Tries
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ NDN Cache Tries
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.normalizeddncachetries} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- NDN Cache Hits
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ NDN Cache Hits
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.normalizeddncachehits} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- NDN Cache Evictions
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ NDN Cache Evictions
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.normalizeddncacheevictions} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- NDN Cache Max Size
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ NDN Cache Max Size
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.maxnormalizeddncachesize} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- NDN Current Cache Size
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ NDN Current Cache Size
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.currentnormalizeddncachesize} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- NDN Cache DN Count
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ NDN Cache DN Count
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.currentnormalizeddncachecount} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- NDN Cache Thread Size
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ NDN Cache Thread Size
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.normalizeddncachethreadsize} size="28" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- NDN Cache Thread Slots
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ NDN Cache Thread Slots
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.normalizeddncachethreadslots} size="28" readOnly />
</Col>
</Row>
- </div>
+ </Form>
</div>
</TabPane>
</TabContent>
@@ -357,12 +335,14 @@ export class DatabaseMonitor extends React.Component {
DatabaseMonitor.propTypes = {
data: PropTypes.object,
- reload: PropTypes.func
+ reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
DatabaseMonitor.defaultProps = {
data: {},
- reload: noop
+ reload: noop,
+ enableTree: noop,
};
export default DatabaseMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/errorlog.jsx
b/src/cockpit/389-console/src/lib/monitor/errorlog.jsx
index 335a6d6..b8c28fc 100644
--- a/src/cockpit/389-console/src/lib/monitor/errorlog.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/errorlog.jsx
@@ -17,6 +17,10 @@ export class ErrorLogMonitor extends React.Component {
textarea.scrollTop = textarea.scrollHeight;
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
render() {
let spinner = "";
if (this.props.reloading) {
@@ -58,7 +62,7 @@ export class ErrorLogMonitor extends React.Component {
</div>;
return (
- <div id="monitor-log-errors-page"
className="container-fluid">
+ <div id="monitor-log-errors-page">
<Row>
<Col sm={3}>
<ControlLabel className="ds-suffix-header">
@@ -73,8 +77,7 @@ export class ErrorLogMonitor extends React.Component {
{spinner}
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top-lg">
<Col sm={5}>
{selectLines}
</Col>
@@ -122,8 +125,8 @@ ErrorLogMonitor.propTypes = {
handleSevLevel: PropTypes.func,
refreshing: PropTypes.bool,
handleRefresh: PropTypes.func,
- lines: PropTypes.string
-
+ lines: PropTypes.string,
+ enableTree: PropTypes.func,
};
ErrorLogMonitor.defaultProps = {
@@ -134,7 +137,8 @@ ErrorLogMonitor.defaultProps = {
handleSevLevel: noop,
refreshing: false,
handleRefresh: noop,
- lines: "50"
+ lines: "50",
+ enableTree: noop,
};
export default ErrorLogMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
index 5593cb0..519acb7 100644
--- a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
@@ -76,8 +76,7 @@ class ReplLoginModal extends React.Component {
/>
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<Col sm={3}>
<ControlLabel>
Password
@@ -559,8 +558,7 @@ class ConflictCompareModal extends React.Component {
<Row>
<textarea
className="ds-conflict" value={conflict} readOnly />
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<p>Child Entries:
<b>{conflictChildren}</b></p>
</Row>
</Col>
@@ -572,8 +570,7 @@ class ConflictCompareModal extends React.Component {
<Row>
<textarea
className="ds-conflict" value={valid} readOnly />
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<p>Child Entries:
<b>{validChildren}</b></p>
</Row>
</Col>
@@ -598,8 +595,7 @@ class ConflictCompareModal extends React.Component {
<input onChange={handleConvertChange}
type="text" placeholder="Enter new RDN here" size="30"
/>
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<h4>Or, you can replace, or swap, the
<b>Valid Entry</b> (and its child entries) with the <b>Conflict
Entry</b></h4>
</Row>
<Row>
@@ -615,8 +611,7 @@ class ConflictCompareModal extends React.Component {
</Button>
</Col>
</Row>
- <p />
- <Row>
+ <Row className="ds-margin-top">
<h4>Or, you can delete the <b>Conflict
Entry</b></h4>
</Row>
<Row>
diff --git a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
index ca2e354..ffbb5cc 100644
--- a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
@@ -105,6 +105,10 @@ export class ReplMonitor extends React.Component {
this.closeConfirmSwapConflict = this.closeConfirmSwapConflict.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
convertConflict (dn) {
let cmd = ["dsconf", "-j",
"ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
"repl-conflict", "convert", dn, "--new-rdn=" +
this.state.convertRDN];
@@ -752,8 +756,8 @@ export class ReplMonitor extends React.Component {
let conflictsNavTitle = 'Conflicts <font size="1">(' +
(conflictEntries.length + glueEntries.length) + ')</font>';
return (
- <div id="monitor-suffix-page" className="container-fluid
ds-tab-table">
- <TabContainer id="basic-tabs-pf"
onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <div id="monitor-suffix-page"
className="ds-tab-table">
+ <TabContainer className="ds-margin-top-lg"
id="basic-tabs-pf" onSelect={this.handleNavSelect}
activeKey={this.state.activeKey}>
<div>
<Nav bsClass="nav nav-tabs nav-tabs-pf">
<NavItem eventKey={1}>
@@ -777,8 +781,8 @@ export class ReplMonitor extends React.Component {
pokeAgmt={this.pokeAgmt}
viewAgmt={this.showAgmtModal}
/>
- <p />
<Button
+ className="ds-margin-top"
bsStyle="primary"
onClick={this.getLagReportCreds}
title="Display report that shows the lag
time and replication status of each agreement in relationship to its replica"
@@ -907,6 +911,7 @@ ReplMonitor.propTypes = {
reloadAgmts: PropTypes.func,
reloadWinsyncAgmts: PropTypes.func,
reloadConflicts: PropTypes.func,
+ enableTree: PropTypes.func,
};
ReplMonitor.defaultProps = {
@@ -917,6 +922,7 @@ ReplMonitor.defaultProps = {
reloadAgmts: noop,
reloadWinsyncAgmts: noop,
reloadConflicts: noop,
+ enableTree: noop,
};
export default ReplMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/serverMonitor.jsx
b/src/cockpit/389-console/src/lib/monitor/serverMonitor.jsx
index 503b57d..f87fb00 100644
--- a/src/cockpit/389-console/src/lib/monitor/serverMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/serverMonitor.jsx
@@ -14,6 +14,7 @@ import {
Row,
Col,
ControlLabel,
+ Form,
Icon,
noop
} from "patternfly-react";
@@ -27,6 +28,10 @@ export class ServerMonitor extends React.Component {
this.handleNavSelect = this.handleNavSelect.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
handleNavSelect(key) {
this.setState({ activeKey: key });
}
@@ -39,7 +44,7 @@ export class ServerMonitor extends React.Component {
let uptime = get_date_diff(startTime, currTime);
return (
- <div id="monitor-server-page"
className="container-fluid">
+ <div id="monitor-server-page">
<Row>
<Col sm={12} className="ds-word-wrap">
<ControlLabel className="ds-suffix-header">
@@ -52,7 +57,7 @@ export class ServerMonitor extends React.Component {
</Col>
</Row>
- <TabContainer id="server-tabs-pf"
onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <TabContainer className="ds-margin-top-lg"
id="server-tabs-pf" onSelect={this.handleNavSelect}
activeKey={this.state.activeKey}>
<div>
<Nav bsClass="nav nav-tabs nav-tabs-pf">
<NavItem eventKey={1}>
@@ -65,75 +70,123 @@ export class ServerMonitor extends React.Component {
<TabContent>
<TabPane eventKey={1}>
- <div className="ds-container
ds-margin-top-lg">
- <div className="ds-inline">
- <div>
- <label
htmlFor="monitor-serverid" className="ds-label-xsm">Server
Instance</label><input type="text"
- className="ds-input"
id="monitor-serverid" value={"slapd-" + this.props.serverId}
size="50" readOnly />
- </div>
- <div>
- <label htmlFor="monitor-version"
className="ds-label-xsm">Version</label><input
type="text"
- className="ds-input"
id="monitor-version" value={this.props.data.version} size="50"
readOnly />
- </div>
- <div>
- <label
htmlFor="monitor-server-starttime" className="ds-label-xsm">Server
Started</label><input type="text"
- className="ds-input"
id="monitor-server-starttime" value={startDate} size="50" readOnly
/>
- </div>
- <div>
- <label
htmlFor="monitor-server-uptime" className="ds-label-xsm">Server
Uptime</label><input type="text"
- className="ds-input"
id="monitor-server-uptime" value={uptime} size="50" readOnly />
- </div>
- </div>
- </div>
+ <Form horizontal
className="ds-margin-top-lg">
+ <Row>
+ <Col componentClass={ControlLabel} sm={4}>
+ Server Instance
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto" id="monitor-serverid"
value={"slapd-" + this.props.serverId} readOnly />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Version
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto" id="monitor-version"
value={this.props.data.version} readOnly />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Server Started
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto" id="monitor-server-starttime"
value={startDate} readOnly />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Server Uptime
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto" id="monitor-server-uptime" value={uptime}
readOnly />
+ </Col>
+ </Row>
+ </Form>
<hr />
- <div className="ds-container">
- <div className="ds-inline">
- <div>
- <label
htmlFor="monitor-server-threads"
className="ds-monitor-label">Threads</label><input
type="text"
- className="ds-input"
id="monitor-server-threads" value={this.props.data.threads} size="12"
readOnly />
- </div>
- <div>
- <label
htmlFor="monitor-server-totalconnections"
className="ds-monitor-label">Total Connections</label><input
type="text"
- className="ds-input"
id="monitor-server-totalconnections" value={this.props.data.totalconnections}
size="12" readOnly />
- </div>
- <div>
- <label
htmlFor="monitor-server-currentconnections"
className="ds-monitor-label">Current Conections</label><input
type="text"
- className="ds-input"
id="monitor-server-currentconnections"
value={this.props.data.currentconnections} size="12" readOnly />
- </div>
- <div>
- <label
htmlFor="monitor-server-currentconnectionsatmaxthreads"
className="ds-monitor-label">Conns At Max Threads</label><input
type="text"
- className="ds-input"
id="monitor-server-currentconnectionsatmaxthreads"
value={this.props.data.currentconnectionsatmaxthreads} size="12" readOnly />
- </div>
- <div>
- <label
htmlFor="monitor-server-maxthreadsperconnhits"
className="ds-monitor-label">Conns Hit Max Threads</label><input
type="text"
- className="ds-input"
id="monitor-server-maxthreadsperconnhits"
value={this.props.data.maxthreadsperconnhits} size="12" readOnly />
- </div>
- </div>
- <div className="ds-divider" />
- <div className="ds-inline">
- <div>
- <label
htmlFor="monitor-server-readwaiters"
className="ds-monitor-label-med">Threads Waiting To
Read</label><input type="text"
- className="ds-input"
id="monitor-server-readwaiters" value={this.props.data.readwaiters}
size="12" readOnly />
- </div>
- <div>
- <label
htmlFor="monitor-server-opsinitiated"
className="ds-monitor-label-med">Operations Started</label><input
type="text"
- className="ds-input"
id="monitor-server-opsinitiated" value={this.props.data.opsinitiated}
size="12" readOnly />
- </div>
- <div>
- <label
htmlFor="monitor-server-opscompleted"
className="ds-monitor-label-med">Operations Completed</label><input
type="text"
- className="ds-input"
id="monitor-server-opscompleted" value={this.props.data.opscompleted}
size="12" readOnly />
- </div>
- <div>
- <label
htmlFor="monitor-server-entriessent"
className="ds-monitor-label-med">Entries Returned To
Clients</label><input type="text"
- className="ds-input"
id="monitor-server-entriessent" value={this.props.data.entriessent}
size="12" readOnly />
- </div>
- <div>
- <label
htmlFor="monitor-server-bytessent"
className="ds-monitor-label-med">Bytes Sent to Clients</label><input
type="text"
- className="ds-input"
id="monitor-server-bytessent" value={this.props.data.bytessent}
size="12" readOnly />
- </div>
-
- </div>
- </div>
+ <Form horizontal>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Worker Threads
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto" id="monitor-server-threads"
value={this.props.data.threads} readOnly />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Threads Waiting To Read
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto" id="monitor-server-readwaiters"
value={this.props.data.readwaiters} readOnly />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Conns At Max Threads
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto"
id="monitor-server-currentconnectionsatmaxthreads"
value={this.props.data.currentconnectionsatmaxthreads} readOnly />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Conns Exceeded Max Threads
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto" id="monitor-server-maxthreadsperconnhits"
value={this.props.data.maxthreadsperconnhits} readOnly />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Total Connections
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto" id="monitor-server-totalconnections"
value={this.props.data.totalconnections} readOnly />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Current Conections
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto" id="monitor-server-currentconnections"
value={this.props.data.currentconnections} readOnly />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Operations Started
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto" id="monitor-server-opsinitiated"
value={this.props.data.opsinitiated} readOnly />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Operations Completed
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto" id="monitor-server-opscompleted"
value={this.props.data.opscompleted} readOnly />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Entries Returned To Clients
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto" id="monitor-server-entriessent"
value={this.props.data.entriessent} readOnly />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bytes Sent to Clients
+ </Col>
+ <Col sm={8}>
+ <input type="text"
className="ds-input-auto" id="monitor-server-bytessent"
value={this.props.data.bytessent} readOnly />
+ </Col>
+ </Row>
+ </Form>
</TabPane>
<TabPane eventKey={2}>
<ConnectionTable conns={this.props.data.connection}
/>
@@ -149,13 +202,15 @@ export class ServerMonitor extends React.Component {
ServerMonitor.propTypes = {
serverId: PropTypes.string,
data: PropTypes.object,
- reload: PropTypes.func
+ reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
ServerMonitor.defaultProps = {
serverId: "",
data: {},
- reload: noop
+ reload: noop,
+ enableTree: noop,
};
export default ServerMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/snmpMonitor.jsx
b/src/cockpit/389-console/src/lib/monitor/snmpMonitor.jsx
index fd1a4f1..1d3eb5d 100644
--- a/src/cockpit/389-console/src/lib/monitor/snmpMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/snmpMonitor.jsx
@@ -4,15 +4,20 @@ import "../../css/ds.css";
import {
Row,
Col,
+ Form,
Icon,
ControlLabel,
noop
} from "patternfly-react";
export class SNMPMonitor extends React.Component {
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
render() {
return (
- <div className="container-fluid"
id="db-global-page">
+ <div id="snmp-page">
<Row>
<Col sm={12} className="ds-word-wrap">
<ControlLabel className="ds-suffix-header">
@@ -24,185 +29,185 @@ export class SNMPMonitor extends React.Component {
</ControlLabel>
</Col>
</Row>
- <div className="ds-margin-top-med">
+ <Form horizontal className="ds-margin-top-lg">
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>Anonymous Binds</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Anonymous Binds
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.anonymousbinds} size="10" readOnly />
</Col>
- <Col sm={3}>
- <ControlLabel>Referrals</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Referrals
</Col>
- <Col sm={2}>
+ <Col sm={3}>
<input type="text"
value={this.props.data.referrals} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>Unauthenticated
Binds</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Unauthenticated Binds
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.anonymousbinds} size="10" readOnly />
</Col>
- <Col sm={3}>
- <ControlLabel>Returned Referrals</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Returned Referrals
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.referralsreturned} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>Simple Auth Binds</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Simple Auth Binds
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.simpleauthbinds} size="10" readOnly />
</Col>
- <Col sm={3}>
- <ControlLabel>Bind Security
Errors</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Bind Security Errors
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.bindsecurityerrors} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>Strong Auth Binds</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Strong Auth Binds
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.strongauthbinds} size="10" readOnly />
</Col>
- <Col sm={3}>
- <ControlLabel>Security Errors</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Security Errors
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.securityerrors} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>Initiated
Operations</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Initiated Operations
</Col>
<Col sm={2}>
<input type="text" value={this.props.data.inops}
size="10" readOnly />
</Col>
- <Col sm={3}>
- <ControlLabel>Errors</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Errors
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.errors} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>Compare Operations</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Compare Operations
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.compareops} size="10" readOnly />
</Col>
- <Col sm={3}>
- <ControlLabel>Current Connections</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Current Connections
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.connections} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>Add Operations</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Add Operations
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.addentryops} size="10" readOnly />
</Col>
- <Col sm={3}>
- <ControlLabel>Total Connections</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Total Connections
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.connectionseq} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>Delete Operations</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Delete Operations
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.removeentryops} size="10" readOnly />
</Col>
- <Col sm={3}>
- <ControlLabel>Conns in Max
Threads</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Conns in Max Threads
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.connectionsinmaxthreads} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>Modify Operation</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Modify Operation
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.modifyentryops} size="10" readOnly />
</Col>
- <Col sm={3}>
- <ControlLabel>Conns Hit Max
Threads</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Conns Exceeded Max Threads
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.connectionsmaxthreadscount} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>ModRDN Operations</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ ModRDN Operations
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.modifyrdnops} size="10" readOnly />
</Col>
- <Col sm={3}>
- <ControlLabel>Bytes Received</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Bytes Received
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.bytesrecv} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>Search Operations</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Search Operations
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.searchops} size="10" readOnly />
</Col>
- <Col sm={3}>
- <ControlLabel>Bytes Sent</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Bytes Sent
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.bytessent} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>One Level Searches</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ One Level Searches
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.onelevelsearchops} size="10" readOnly />
</Col>
- <Col sm={3}>
- <ControlLabel>Entries Sent</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Entries Sent
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.entriesreturned} size="10" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>Whole Tree Searches</ControlLabel>
+ <Col componentClass={ControlLabel} sm={3}>
+ Whole Tree Searches
</Col>
<Col sm={2}>
<input type="text"
value={this.props.data.wholesubtreesearchops} size="10" readOnly />
</Col>
</Row>
- </div>
+ </Form>
</div>
);
}
@@ -212,12 +217,14 @@ export class SNMPMonitor extends React.Component {
SNMPMonitor.propTypes = {
data: PropTypes.object,
- reload: PropTypes.func
+ reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
SNMPMonitor.defaultProps = {
data: {},
- reload: noop
+ reload: noop,
+ enableTree: noop,
};
export default SNMPMonitor;
diff --git a/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx
b/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx
index f859f57..2174290 100644
--- a/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx
@@ -7,6 +7,7 @@ import {
ControlLabel,
Row,
Col,
+ Form,
Icon,
Nav,
NavItem,
@@ -26,6 +27,10 @@ export class SuffixMonitor extends React.Component {
this.handleNavSelect = this.handleNavSelect.bind(this);
}
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
handleNavSelect(key) {
this.setState({ activeKey: key });
}
@@ -112,7 +117,7 @@ export class SuffixMonitor extends React.Component {
}
return (
- <div id="monitor-suffix-page"
className="container-fluid">
+ <div id="monitor-suffix-page">
<Row>
<Col sm={12} className="ds-word-wrap">
<ControlLabel className="ds-suffix-header">
@@ -124,8 +129,7 @@ export class SuffixMonitor extends React.Component {
</ControlLabel>
</Col>
</Row>
- <p />
- <TabContainer id="basic-tabs-pf"
onSelect={this.handleNavSelect} activeKey={this.state.activeKey}>
+ <TabContainer className="ds-margin-top-lg"
id="basic-tabs-pf" onSelect={this.handleNavSelect}
activeKey={this.state.activeKey}>
<div>
<Nav bsClass="nav nav-tabs nav-tabs-pf">
<NavItem eventKey={1}>
@@ -159,6 +163,8 @@ export class SuffixMonitor extends React.Component {
/>
<b>Entry Cache Hit Ratio</b>
</div>
+ <div className="ds-divider" />
+ <div className="ds-divider" />
<div className="ds-chart-right"
title="How much of the allocated cache space is used (max size vs current size). If
the chart is RED then you should to increase the max cache size because the cache hit
ratio is below 90%">
<PieChart
id="monitor-entry-util-pie"
@@ -184,6 +190,7 @@ export class SuffixMonitor extends React.Component {
}}
title={{type: 'pie'}}
legend={{show: true, position:
'right'}}
+ unloadBeforeLoad
/>
<b>Entry Cache Utilization</b>
<div>
@@ -191,80 +198,65 @@ export class SuffixMonitor extends React.Component {
</div>
</div>
</div>
- <p />
<hr />
- <div>
+ <Form horizontal>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Hit Ratio
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ Entry Cache Hit Ratio
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.entrycachehitratio} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Tries
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ Entry Cache Tries
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.entrycachetries} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Hits
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ Entry Cache Hits
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.entrycachehits} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Max Size
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ Entry Cache Max Size
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.maxentrycachesize} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Current Size
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ Entry Cache Current Size
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.currententrycachesize} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Max Entries
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ Entry Cache Max Entries
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.maxentrycachecount} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Entry Cache Count
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ Entry Cache Count
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.currententrycachecount} size="35" readOnly />
</Col>
</Row>
- </div>
+ </Form>
</div>
</TabPane>
@@ -289,6 +281,8 @@ export class SuffixMonitor extends React.Component {
/>
<b
className="ds-left-margin">DN Cache Hit Ratio</b>
</div>
+ <div className="ds-divider" />
+ <div className="ds-divider" />
<div className="ds-chart-right"
title="How much of the allocated cache space is used (max size vs current size). If
the chart is RED then you should to increase the max cache size because the cache hit
ratio is below 90%">
<PieChart
id="monitor-entry-util-pie"
@@ -314,6 +308,7 @@ export class SuffixMonitor extends React.Component {
}}
title={{type: 'pie'}}
legend={{show: true, position:
'right'}}
+ unloadBeforeLoad
/>
<div
className="ds-left-margin">
<b>DN Cache Utilization</b>
@@ -324,78 +319,64 @@ export class SuffixMonitor extends React.Component {
</div>
</div>
<hr />
- <div>
+ <Form horizontal>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Hit Ratio
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ DN Cache Hit Ratio
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.dncachehitratio} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Tries
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ DN Cache Tries
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.dncachetries} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Hits
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ DN Cache Hits
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.dncachehits} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Max Size
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ DN Cache Max Size
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.maxdncachesize} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Current Size
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ DN Cache Current Size
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.currentdncachesize} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Max Count
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ DN Cache Max Count
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.maxdncachecount} size="35" readOnly />
</Col>
</Row>
<Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- DN Cache Current Count
- </ControlLabel>
+ <Col componentClass={ControlLabel}
sm={3}>
+ DN Cache Current Count
</Col>
<Col sm={3}>
<input type="text"
value={this.props.data.currentdncachecount} size="35" readOnly />
</Col>
</Row>
- </div>
+ </Form>
</div>
</TabPane>
</TabContent>
@@ -411,6 +392,7 @@ SuffixMonitor.propTypes = {
data: PropTypes.object,
bename: PropTypes.string,
reload: PropTypes.func,
+ enableTree: PropTypes.func,
};
SuffixMonitor.defaultProps = {
@@ -418,6 +400,7 @@ SuffixMonitor.defaultProps = {
data: {},
bename: "",
reload: noop,
+ enableTree: noop,
};
export default SuffixMonitor;
diff --git a/src/cockpit/389-console/src/lib/notifications.jsx
b/src/cockpit/389-console/src/lib/notifications.jsx
index ad0b87f..990cdc7 100644
--- a/src/cockpit/389-console/src/lib/notifications.jsx
+++ b/src/cockpit/389-console/src/lib/notifications.jsx
@@ -1,8 +1,16 @@
import React from "react";
import PropTypes from "prop-types";
import {
+ Button,
+ Checkbox,
+ Col,
+ Form,
Icon,
MessageDialog,
+ Modal,
+ noop,
+ Row,
+ Spinner,
TimedToastNotification,
ToastNotificationList
} from "patternfly-react";
@@ -103,4 +111,119 @@ class ConfirmPopup extends React.Component {
}
}
+export class DoubleConfirmModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ actionHandler,
+ checked,
+ spinning,
+ item,
+ mTitle,
+ mMsg,
+ mSpinningMsg,
+ mBtnName,
+ } = this.props;
+ let spinner = "";
+ let saveDisabled = true;
+
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md"
/>{mSpinningMsg}
+ </div>
+ </Row>;
+ saveDisabled = true;
+ }
+ if (checked) {
+ saveDisabled = false;
+ }
+
+ 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>
+ {mTitle}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <h4>{mMsg}</h4>
+ <h5 className="ds-center
ds-margin-top-xlg"><b>{item}</b></h5>
+ <Row className="ds-margin-top-xlg">
+ <Col sm={12} className="ds-center">
+ <Checkbox
+ id="modalChecked"
+ defaultChecked={checked}
+ onChange={handleChange}
+
+
<b>Yes</b>, I am sure.
+ </Checkbox>
+ </Col>
+ </Row>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={actionHandler}
+ disabled={saveDisabled}
+
+
{mBtnName}
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+DoubleConfirmModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ actionHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ item: PropTypes.string,
+ checked: PropTypes.bool,
+ mTitle: PropTypes.string,
+ mMsg: PropTypes.string,
+ mSpinningMsg: PropTypes.string,
+ mBtnName: PropTypes.string,
+};
+
+DoubleConfirmModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ actionHandler: noop,
+ spinning: false,
+ item: "",
+ checked: false,
+ mTitle: "",
+ mMsg: "",
+ mSpinningMsg: "",
+ mBtnName: "",
+};
+
export { NotificationController, ConfirmPopup };
diff --git a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
index 3225f5a..f2bc245 100644
--- a/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/accountPolicy.jsx
@@ -639,7 +639,7 @@ class AccountPolicy extends React.Component {
toggleLoadingHandler={this.props.toggleLoadingHandler}
<Row>
- <Col sm={6}>
+ <Col sm={10}>
<Form horizontal>
<FormGroup key="configArea"
controlId="configArea">
<Col
@@ -649,16 +649,15 @@ class AccountPolicy extends React.Component {
Shared Config Entry
</Col>
- <Col sm={6}>
+ <Col sm={7}>
<FormControl
type="text"
value={configArea}
onChange={this.handleFieldChange}
/>
</Col>
- <Col sm={3}>
+ <Col sm={2}>
<Button
- bsSize="large"
bsStyle="primary"
onClick={this.openModal}
diff --git
a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
index 2a572ac..cc9ed22 100644
--- a/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/attributeUniqueness.jsx
@@ -680,7 +680,7 @@ class AttributeUniqueness extends React.Component {
toggleLoadingHandler={this.props.toggleLoadingHandler}
<Row>
- <Col sm={9}>
+ <Col sm={12}>
<AttrUniqConfigTable
rows={this.state.configRows}
editConfig={this.showEditConfigModal}
diff --git a/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
b/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
index b5079ea..aea83f6 100644
--- a/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/autoMembership.jsx
@@ -1012,7 +1012,7 @@ class AutoMembership extends React.Component {
toggleLoadingHandler={this.props.toggleLoadingHandler}
<Row>
- <Col sm={9}>
+ <Col sm={12}>
<AutoMembershipDefinitionTable
rows={this.state.definitionRows}
editConfig={this.showEditDefinitionModal}
diff --git a/src/cockpit/389-console/src/lib/plugins/dna.jsx
b/src/cockpit/389-console/src/lib/plugins/dna.jsx
index 185ca1f..8e05995 100644
--- a/src/cockpit/389-console/src/lib/plugins/dna.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/dna.jsx
@@ -908,7 +908,6 @@ class DNA extends React.Component {
</Col>
<Col sm={3}>
<Button
- bsSize="large"
bsStyle="primary"
onClick={this.showConfirmSharedSave}
@@ -1120,7 +1119,7 @@ class DNA
extends React.Component {
toggleLoadingHandler={this.props.toggleLoadingHandler}
<Row>
- <Col sm={9}>
+ <Col sm={12}>
<DNATable
rows={this.state.configRows}
editConfig={this.showEditConfigModal}
diff --git a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
index a36ac21..eb6e425 100644
--- a/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/linkedAttributes.jsx
@@ -461,7 +461,7 @@ class LinkedAttributes extends React.Component {
toggleLoadingHandler={this.props.toggleLoadingHandler}
<Row>
- <Col sm={9}>
+ <Col sm={12}>
<LinkedAttributesTable
rows={this.state.configRows}
editConfig={this.showEditConfigModal}
diff --git a/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
index 5433535..3c87169 100644
--- a/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/managedEntries.jsx
@@ -672,7 +672,6 @@ class ManagedEntries extends React.Component {
</Col>
<Col sm={3}>
<Button
- bsSize="large"
bsStyle="primary"
onClick={this.openTemplateModal}
@@ -881,7 +880,7 @@ class
ManagedEntries extends React.Component {
/>
</Col>
</FormGroup>
- <Col sm={9}>
+ <Col sm={12}>
<ManagedEntriesTable
rows={configRows}
editConfig={this.showEditConfigModal}
diff --git a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
index b816944..dba8fba 100644
--- a/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/memberOf.jsx
@@ -22,6 +22,7 @@ import "../../css/ds.css";
class MemberOf extends React.Component {
componentWillMount(prevProps) {
this.getObjectClasses();
+ this.getAttributes();
this.updateFields();
}
@@ -35,6 +36,7 @@ class MemberOf extends React.Component {
super(props);
this.getObjectClasses = this.getObjectClasses.bind(this);
+ this.getAttributes = this.getAttributes.bind(this);
this.updateFields = this.updateFields.bind(this);
this.handleFieldChange = this.handleFieldChange.bind(this);
this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
@@ -49,6 +51,7 @@ class MemberOf extends React.Component {
this.state = {
objectClasses: [],
+ attributeTypes: [],
memberOfAttr: [],
memberOfGroupAttr: [],
@@ -135,6 +138,7 @@ class MemberOf extends React.Component {
openModal() {
this.getObjectClasses();
+ this.getAttributes();
if (!this.state.memberOfConfigEntry) {
this.setState({
configEntryModalShow: true,
@@ -502,9 +506,41 @@ class MemberOf extends React.Component {
});
}
+ getAttributes() {
+ const oc_cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "schema",
+ "attributetypes",
+ "list"
+ ];
+ log_cmd("getAttributes", "Get getAttributes", oc_cmd);
+ cockpit
+ .spawn(oc_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const atContent = JSON.parse(content);
+ let attrs = [];
+ for (let content of atContent["items"]) {
+ attrs.push({
+ id: content.name,
+ label: content.name
+ });
+ }
+ this.setState({
+ attributeTypes: attrs
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification("error", `Failed to get
attributes - ${errMsg.desc}`);
+ });
+ }
+
render() {
const {
objectClasses,
+ attributeTypes,
memberOfAttr,
memberOfGroupAttr,
memberOfEntryScope,
@@ -725,13 +761,8 @@ class MemberOf extends React.Component {
});
}}
selected={configGroupAttr}
- newSelectionPrefix="Add a group
member: "
- options={[
- {
- id: "member",
- label: "member"
- }
- ]}
+ newSelectionPrefix="Add a group
member attribute: "
+ options={attributeTypes}
placeholder="Type a member group
attribute..."
/>
</Col>
@@ -870,7 +901,7 @@ class MemberOf extends React.Component {
toggleLoadingHandler={this.props.toggleLoadingHandler}
<Row>
- <Col sm={9}>
+ <Col sm={12}>
<Form horizontal>
<FormGroup
key="memberOfAttr"
@@ -884,7 +915,7 @@ class MemberOf extends React.Component {
Attribute
</Col>
- <Col sm={9}>
+ <Col sm={8}>
<Typeahead
allowNew
multiple
@@ -894,7 +925,7 @@ class MemberOf extends React.Component {
});
}}
selected={memberOfAttr}
- newSelectionPrefix="Add a member:
"
+ newSelectionPrefix="Add a member
attrbiute: "
options={[
{
id: "member",
@@ -925,7 +956,7 @@ class MemberOf extends React.Component {
Group Attribute
</Col>
- <Col sm={9}>
+ <Col sm={8}>
<Typeahead
allowNew
multiple
@@ -935,25 +966,8 @@ class MemberOf extends React.Component {
});
}}
selected={memberOfGroupAttr}
- newSelectionPrefix="Add a group member:
"
- options={[
- {
- id: "groupOfNames",
- label: "groupOfNames"
- },
- {
- id: "groupOfURLs",
- label: "groupOfURLs"
- },
- {
- id: "groupOfUniqueNames",
- label:
"groupOfUniqueNames"
- },
- {
- id: "groupOfCertificates",
- label:
"groupOfCertificates"
- }
- ]}
+ newSelectionPrefix="Add a group member
attribute: "
+ options={attributeTypes}
placeholder="Type a member group
attribute..."
/>
</Col>
@@ -962,7 +976,7 @@ class MemberOf extends React.Component {
</Col>
</Row>
<Row>
- <Col sm={9}>
+ <Col sm={12}>
<Form horizontal>
<FormGroup
key="memberOfEntryScope"
@@ -1028,7 +1042,7 @@ class MemberOf extends React.Component {
</Col>
</Row>
<Row>
- <Col sm={9}>
+ <Col sm={12}>
<Form horizontal>
<FormGroup
key="memberOfConfigEntry"
@@ -1050,7 +1064,6 @@ class MemberOf extends React.Component {
</Col>
<Col sm={3}>
<Button
- bsSize="large"
bsStyle="primary"
onClick={this.openModal}
@@ -1062,7 +1075,7 @@ class MemberOf
extends React.Component {
</Col>
</Row>
<Row>
- <Col sm={9}>
+ <Col sm={12}>
<Form horizontal>
<FormGroup controlId="memberOfAutoAddOC"
disabled={false}>
<Col
@@ -1071,7 +1084,7 @@ class MemberOf extends React.Component {
<ControlLabel>Auto Add OC</ControlLabel>
</Col>
- <Col sm={9}>
+ <Col sm={8}>
<Typeahead
allowNew
onChange={value => {
@@ -1090,9 +1103,8 @@ class MemberOf extends React.Component {
</Col>
</Row>
<Row>
- <Col sm={9}>
+ <Col sm={12}>
<Button
- bsSize="large"
bsStyle="primary"
onClick={this.toggleFixupModal}
diff --git
a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
index 5dc1ab6..4bda5ac 100644
--- a/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/passthroughAuthentication.jsx
@@ -1135,7 +1135,7 @@ class PassthroughAuthentication extends React.Component {
toggleLoadingHandler={this.props.toggleLoadingHandler}
<Row>
- <Col sm={9}>
+ <Col sm={12}>
<PassthroughAuthURLsTable
rows={urlRows}
editConfig={this.showEditURLModal}
@@ -1151,7 +1151,7 @@ class PassthroughAuthentication extends React.Component {
</Col>
</Row>
<Row>
- <Col sm={9}>
+ <Col sm={12}>
<PassthroughAuthConfigsTable
rows={pamConfigRows}
editConfig={this.showEditPAMConfigModal}
diff --git a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
index fe6b962..966c60f 100644
--- a/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/pluginBasicConfig.jsx
@@ -186,7 +186,7 @@ class PluginBasicConfig extends React.Component {
<div>
<Form inline>
<Row>
- <Col sm={6}>
+ <Col sm={6} className="ds-margin-top">
<h3>
<ControlLabel
className="ds-plugin-tab-header">
{this.props.pluginName}
@@ -195,7 +195,7 @@ class PluginBasicConfig extends React.Component {
</Col>
{this.props.removeSwitch || (
- <Col smOffset={1} sm={3}>
+ <Col smOffset={1} sm={4}
className="ds-margin-top">
<FormGroup key="switchPluginStatus"
controlId="switchPluginStatus">
<ControlLabel className="toolbar-pf-find
ds-float-left ds-right-indent">
Status
@@ -216,14 +216,16 @@ class PluginBasicConfig extends React.Component {
)}
</Row>
</Form>
- {this.props.children}
+ <div className="ds-margin-top">
+ {this.props.children}
+ </div>
<CustomCollapse>
<Row>
- <Col sm={4}>
+ <Col sm={12}>
<Form horizontal>
{Object.entries(modalFieldsCol1).map(([id, value]) =>
(
<FormGroup key={id} controlId={id}
disabled={false}>
- <Col componentClass={ControlLabel} sm={6}>
+ <Col componentClass={ControlLabel} sm={3}>
{this.props.memberOfAttr} Plugin{"
"}
{id.replace("currentPlugin",
"")}
</Col>
@@ -241,7 +243,7 @@ class PluginBasicConfig extends React.Component {
controlId="currentPluginDependsOnType"
disabled={false}
-
<Col componentClass={ControlLabel} sm={6}>
+ <Col componentClass={ControlLabel} sm={3}>
Plugin Depends On Type
</Col>
<Col sm={6}>
@@ -257,7 +259,7 @@ class PluginBasicConfig extends React.Component {
controlId="currentPluginDependsOnNamed"
disabled={false}
-
<Col componentClass={ControlLabel} sm={6}>
+ <Col componentClass={ControlLabel} sm={3}>
Plugin Depends On Named
</Col>
<Col sm={6}>
@@ -268,16 +270,12 @@ class PluginBasicConfig extends React.Component {
/>
</Col>
</FormGroup>
- </Form>
- </Col>
- <Col sm={4}>
- <Form horizontal>
{Object.entries(modalFieldsCol2).map(([id, value]) =>
(
<FormGroup key={id} controlId={id}
disabled={false}>
- <Col componentClass={ControlLabel} sm={5}>
+ <Col componentClass={ControlLabel} sm={3}>
Plugin {id.replace("currentPlugin",
"")}
</Col>
- <Col sm={7}>
+ <Col sm={6}>
<FormControl
type="text"
value={value}
@@ -290,10 +288,9 @@ class PluginBasicConfig extends React.Component {
</Col>
</Row>
</CustomCollapse>
- <Row>
- <Col smOffset={7} sm={1}>
+ <Row className="ds-margin-top-lg">
+ <Col sm={1}>
<Button
- bsSize="large"
bsStyle="primary"
onClick={() =>
this.props.savePluginHandler({
diff --git a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
index d25e32e..af83515 100644
--- a/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/referentialIntegrity.jsx
@@ -627,7 +627,7 @@ class ReferentialIntegrity extends React.Component {
<FormGroup key="updateDelay"
controlId="updateDelay">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Sets the update interval. Special
values: 0 - The check is performed immediately, -1 - No check is performed
(referint-update-delay)"
Update Delay
@@ -643,7 +643,7 @@ class ReferentialIntegrity extends React.Component {
<FormGroup key="membershipAttr"
controlId="membershipAttr">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Specifies attributes to check for and
update (referint-membership-attr)"
Membership Attribute
@@ -667,7 +667,7 @@ class ReferentialIntegrity extends React.Component {
<FormGroup key="entryScope"
controlId="entryScope">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Defines the subtree in which the
plug-in looks for the delete or rename operations of a user entry
(nsslapd-pluginEntryScope)"
Entry Scope
@@ -683,7 +683,7 @@ class ReferentialIntegrity extends React.Component {
<FormGroup key="excludeEntryScope"
controlId="excludeEntryScope">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Defines the subtree in which the
plug-in ignores any operations for deleting or renaming a user
(nsslapd-pluginExcludeEntryScope)"
Exclude Entry Scope
@@ -699,7 +699,7 @@ class ReferentialIntegrity extends React.Component {
<FormGroup key="containerScope"
controlId="containerScope">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Specifies which branch the plug-in
searches for the groups to which the user belongs. It only updates groups that are under
the specified container branch, and leaves all other groups not updated
(nsslapd-pluginContainerScope)"
Container Scope
@@ -715,7 +715,7 @@ class ReferentialIntegrity extends React.Component {
<FormGroup key="logFile"
controlId="logFile">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title={`Specifies a path to the Referential
integrity logfile. For example: /var/log/dirsrv/slapd-${
this.props.serverId
}/referint`}
@@ -736,7 +736,7 @@ class ReferentialIntegrity extends React.Component {
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="The value to set as
nsslapd-pluginConfigArea"
Shared Config Entry
@@ -748,9 +748,8 @@ class ReferentialIntegrity extends React.Component {
onChange={this.handleFieldChange}
/>
</Col>
- <Col sm={3}>
+ <Col sm={2}>
<Button
- bsSize="large"
bsStyle="primary"
onClick={this.openModal}
diff --git
a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
index 237d9aa..a7f35c7 100644
--- a/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/retroChangelog.jsx
@@ -171,7 +171,7 @@ class RetroChangelog extends React.Component {
<FormGroup key="attribute"
controlId="attribute">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Specifies another Directory Server
attribute which must be included in the retro changelog entries (nsslapd-attribute)"
Attribute
@@ -194,7 +194,7 @@ class RetroChangelog extends React.Component {
<FormGroup key="directory"
controlId="directory">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Specifies the name of the directory
in which the changelog database is created the first time the plug-in is run"
Directory
@@ -210,7 +210,7 @@ class RetroChangelog extends React.Component {
<FormGroup key="maxAge"
controlId="maxAge">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="This attribute specifies the maximum
age of any entry in the changelog (nsslapd-changelogmaxage)"
Max Age
@@ -226,7 +226,7 @@ class RetroChangelog extends React.Component {
<FormGroup key="excludeSuffix"
controlId="excludeSuffix">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="This attribute specifies the suffix
which will be excluded from the scope of the plugin (nsslapd-exclude-suffix)"
Exclude Suffix
diff --git a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
index bdd8483..9064adf 100644
--- a/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/rootDNAccessControl.jsx
@@ -177,7 +177,7 @@ class RootDNAccessControl extends React.Component {
<FormGroup key="allowHost"
controlId="allowHost">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Sets what hosts, by fully-qualified
domain name, the root user is allowed to use to access the Directory Server. Any hosts not
listed are implicitly denied (rootdn-allow-host)"
Allow Host
@@ -201,7 +201,7 @@ class RootDNAccessControl extends React.Component {
<FormGroup key="denyHost"
controlId="denyHost">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Sets what hosts, by fully-qualified
domain name, the root user is not allowed to use to access the Directory Server Any hosts
not listed are implicitly allowed (rootdn-deny-host). If an host address is listed in both
the rootdn-allow-host and rootdn-deny-host attributes, it is denied access."
Deny Host
@@ -225,7 +225,7 @@ class RootDNAccessControl extends React.Component {
<FormGroup key="allowIP"
controlId="allowIP">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Sets what IP addresses, either IPv4
or IPv6, for machines the root user is allowed to use to access the Directory Server Any
IP addresses not listed are implicitly denied (rootdn-allow-ip)"
Allow IP address
@@ -249,7 +249,7 @@ class RootDNAccessControl extends React.Component {
<FormGroup key="denyIP"
controlId="denyIP">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Sets what IP addresses, either IPv4
or IPv6, for machines the root user is not allowed to use to access the Directory Server.
Any IP addresses not listed are implicitly allowed (rootdn-deny-ip) If an IP address is
listed in both the rootdn-allow-ip and rootdn-deny-ip attributes, it is denied
access."
Deny IP address
@@ -273,7 +273,7 @@ class RootDNAccessControl extends React.Component {
<FormGroup key="openTime"
controlId="openTime">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Sets part of a time period or range
when the root user is allowed to access the Directory Server. This sets when the
time-based access begins (rootdn-open-time)"
Open Time
@@ -289,7 +289,7 @@ class RootDNAccessControl extends React.Component {
<FormGroup key="closeTime"
controlId="closeTime">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Sets part of a time period or range
when the root user is allowed to access the Directory Server. This sets when the
time-based access ends (rootdn-close-time)"
Close Time
@@ -305,7 +305,7 @@ class RootDNAccessControl extends React.Component {
<FormGroup key="daysAllowed"
controlId="daysAllowed">
<Col
componentClass={ControlLabel}
- sm={2}
+ sm={3}
title="Gives a comma-separated list of what
days the root user is allowed to use to access the Directory Server. Any days listed are
implicitly denied (rootdn-days-allowed)"
Days Allowed
diff --git a/src/cockpit/389-console/src/lib/plugins/usn.jsx
b/src/cockpit/389-console/src/lib/plugins/usn.jsx
index 08d7beb..e587755 100644
--- a/src/cockpit/389-console/src/lib/plugins/usn.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/usn.jsx
@@ -318,7 +318,6 @@ class USN extends React.Component {
<Col sm={9}>
<Button
className="ds-margin-top"
- bsSize="large"
bsStyle="primary"
onClick={this.toggleCleanupModal}
diff --git
a/src/cockpit/389-console/src/lib/plugins/winsync.jsx
b/src/cockpit/389-console/src/lib/plugins/winsync.jsx
index 39be7a9..0e2ff13 100644
--- a/src/cockpit/389-console/src/lib/plugins/winsync.jsx
+++ b/src/cockpit/389-console/src/lib/plugins/winsync.jsx
@@ -1,7 +1,21 @@
+import cockpit from "cockpit";
import React from "react";
-import { Row, Col, Form, noop, FormGroup, Checkbox, ControlLabel } from
"patternfly-react";
+import {
+ Row,
+ Col,
+ Icon,
+ Modal,
+ noop,
+ Form,
+ FormControl,
+ FormGroup,
+ Checkbox,
+ Button,
+ ControlLabel
+} from "patternfly-react";
import PropTypes from "prop-types";
import PluginBasicConfig from "./pluginBasicConfig.jsx";
+import { log_cmd } from "../tools.jsx";
import "../../css/ds.css";
class WinSync extends React.Component {
@@ -19,23 +33,93 @@ class WinSync extends React.Component {
super(props);
this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
+ this.handleFieldChange = this.handleFieldChange.bind(this);
this.updateFields = this.updateFields.bind(this);
+ this.runFixup = this.runFixup.bind(this);
+ this.toggleFixupModal = this.toggleFixupModal.bind(this);
this.state = {
posixWinsyncCreateMemberOfTask: false,
posixWinsyncLowerCaseUID: false,
posixWinsyncMapMemberUID: false,
posixWinsyncMapNestedGrouping: false,
- posixWinsyncMsSFUSchema: false
+ posixWinsyncMsSFUSchema: false,
+
+ fixupModalShow: false,
+ fixupDN: "",
+ fixupFilter: ""
};
}
+ toggleFixupModal() {
+ this.setState(prevState => ({
+ fixupModalShow: !prevState.fixupModalShow,
+ fixupDN: "",
+ fixupFilter: ""
+ }));
+ }
+
+ runFixup() {
+ if (!this.state.fixupDN) {
+ this.props.addNotification("warning", "Fixup DN is
required.");
+ } else {
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId +
".socket",
+ "plugin",
+ "posix-winsync",
+ "fixup",
+ this.state.fixupDN
+ ];
+
+ if (this.state.fixupFilter) {
+ cmd = [...cmd, "--filter", this.state.fixupFilter];
+ }
+
+ this.props.toggleLoadingHandler();
+ log_cmd("runFixup", "Run Member UID task", cmd);
+ cockpit
+ .spawn(cmd, {
+ superuser: true,
+ err: "message"
+ })
+ .done(content => {
+ this.props.addNotification(
+ "success",
+ `Fixup task for ${this.state.fixupDN} was successfull`
+ );
+ this.props.toggleLoadingHandler();
+ this.setState({
+ fixupModalShow: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Fixup task for ${this.state.fixupDN} has failed
${errMsg.desc}`
+ );
+ this.props.toggleLoadingHandler();
+ this.setState({
+ fixupModalShow: false
+ });
+ });
+ }
+ }
+
handleCheckboxChange(e) {
this.setState({
[e.target.id]: e.target.checked
});
}
+ handleFieldChange(e) {
+ this.setState({
+ [e.target.id]: e.target.value
+ });
+ }
+
updateFields() {
if (this.props.rows.length > 0) {
const pluginRow = this.props.rows.find(row => row.cn[0] === "Posix
Winsync API");
@@ -71,7 +155,10 @@ class WinSync extends React.Component {
posixWinsyncLowerCaseUID,
posixWinsyncMapMemberUID,
posixWinsyncMapNestedGrouping,
- posixWinsyncMsSFUSchema
+ posixWinsyncMsSFUSchema,
+ fixupModalShow,
+ fixupDN,
+ fixupFilter
} = this.state;
let specificPluginCMD = [
@@ -94,6 +181,69 @@ class WinSync extends React.Component {
];
return (
<div>
+ <Modal show={fixupModalShow} onHide={this.toggleFixupModal}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={this.toggleFixupModal}
+ aria-hidden="true"
+ aria-label="Close"
+
+ <Icon type="pf"
name="close" />
+ </button>
+ <Modal.Title>MemberOf Task</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup controlId="fixupDN"
key="fixupDN">
+ <Col sm={3}>
+ <ControlLabel title="Base DN that
contains entries to fix up">
+ Base DN
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={fixupDN}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="fixupFilter"
key="fixupFilter">
+ <Col sm={3}>
+ <ControlLabel title="Filter for
entries to fix up. If omitted, all entries with objectclass inetuser/inetadmin/nsmemberof
under the specified base will have their memberOf attribute regenerated.">
+ Filter DN
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={fixupFilter}
+ onChange={this.handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={this.toggleFixupModal}
+
+ Cancel
+ </Button>
+ <Button bsStyle="primary"
onClick={this.runFixup}>
+ Run
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
<PluginBasicConfig
rows={this.props.rows}
serverId={this.props.serverId}
@@ -107,7 +257,7 @@ class WinSync extends React.Component {
toggleLoadingHandler={this.props.toggleLoadingHandler}
<Row>
- <Col sm={9}>
+ <Col sm={12}>
<Form horizontal>
<FormGroup
key="posixWinsyncCreateMemberOfTask"
@@ -115,7 +265,7 @@ class WinSync extends React.Component {
<Col
componentClass={ControlLabel}
- sm={3}
+ sm={6}
title="Sets whether to run the memberOf
fix-up task immediately after a sync run in order to update group memberships for synced
users"
Create MemberOf Task
@@ -134,7 +284,7 @@ class WinSync extends React.Component {
<Col
componentClass={ControlLabel}
- sm={3}
+ sm={6}
title="Sets whether to store (and, if
necessary, convert) the UID value in the memberUID attribute in lower case"
Lower Case UID
@@ -153,7 +303,7 @@ class WinSync extends React.Component {
<Col
componentClass={ControlLabel}
- sm={3}
+ sm={6}
title="Sets whether to map the memberUID
attribute in an Active Directory group to the uniqueMember attribute in a Directory Server
group"
Map Member UID
@@ -173,7 +323,7 @@ class WinSync extends React.Component {
<Col
title="Manages if nested groups are updated
when memberUID attributes in an Active Directory POSIX group change"
componentClass={ControlLabel}
- sm={3}
+ sm={6}
Map Nested Grouping
</Col>
@@ -191,7 +341,7 @@ class WinSync extends React.Component {
<Col
componentClass={ControlLabel}
- sm={3}
+ sm={6}
title="Sets whether to the older Microsoft
System Services for Unix 3.0 (msSFU30) schema when syncing Posix attributes from Active
Directory"
Microsoft System Services for Unix 3.0 (msSFU30) schema
@@ -207,6 +357,17 @@ class WinSync extends React.Component {
</Form>
</Col>
</Row>
+ <Row>
+ <Col sm={12}>
+ <Button
+ bsStyle="primary"
+ onClick={this.toggleFixupModal}
+ title="Corrects mismatched member and uniquemember
values"
+
+ Run MemberOf Task
+ </Button>
+ </Col>
+ </Row>
</PluginBasicConfig>
</div>
);
diff --git a/src/cockpit/389-console/src/lib/replication/replAgmts.jsx
b/src/cockpit/389-console/src/lib/replication/replAgmts.jsx
new file mode 100644
index 0000000..e3c4781
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/replAgmts.jsx
@@ -0,0 +1,1150 @@
+import cockpit from "cockpit";
+import React from "react";
+import { ConfirmPopup, DoubleConfirmModal } from "../notifications.jsx";
+import { ReplAgmtTable } from "./replTables.jsx";
+import { ReplAgmtModal } from "./replModals.jsx";
+import { log_cmd, valid_dn, valid_port } from "../tools.jsx";
+import PropTypes from "prop-types";
+import {
+ Button,
+ noop,
+} from "patternfly-react";
+
+export class ReplAgmts extends React.Component {
+ _mounted = false;
+ constructor(props) {
+ super(props);
+ this.state = {
+ showCreateAgmtModal: false,
+ showEditAgmtModal: false,
+ showConfirmDeleteAgmt: false,
+ showConfirmInitAgmt: false,
+ showConfirmEnableAgmt: false,
+ showConfirmDisableAgmt: false,
+ errObj: {},
+ modalMsg: "",
+ modalScheduleMsg: "",
+ savingAgmt: false,
+ mounted: false,
+ // Create agreement
+ agmtName: "",
+ agmtHost: "",
+ agmtPort: "",
+ agmtProtocol: "LDAP",
+ agmtBindMethod: "SIMPLE",
+ agmtBindDN: "",
+ agmtBindPW: "",
+ agmtBindPWConfirm: "",
+ agmtStripAttrs: [],
+ agmtFracAttrs: [],
+ agmtFracInitAttrs: [],
+ agmtSync: true,
+ agmtSyncMon: true,
+ agmtSyncTue: true,
+ agmtSyncWed: true,
+ agmtSyncThu: true,
+ agmtSyncFri: true,
+ agmtSyncSat: true,
+ agmtSyncSun: true,
+ agmtStartTime: "0",
+ agmtEndTime: "0",
+ agmtInit: "noinit",
+ agmtSaveOK: false,
+ modalChecked: false,
+ modalSpinning: false,
+ // Init agmt
+ agmtInitCounter: 0,
+ agmtInitIntervals: [],
+ };
+ this.showCreateAgmtModal = this.showCreateAgmtModal.bind(this);
+ this.closeCreateAgmtModal = this.closeCreateAgmtModal.bind(this);
+ this.closeEditAgmtModal = this.closeEditAgmtModal.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleModalChange = this.handleModalChange.bind(this);
+ this.handleTAFracInitAttrChange = this.handleTAFracInitAttrChange.bind(this);
+ this.handleTAFracAttrChange = this.handleTAFracAttrChange.bind(this);
+ this.handleTAStripAttrChange = this.handleTAStripAttrChange.bind(this);
+ this.handleTAFracInitAttrChangeEdit =
this.handleTAFracInitAttrChangeEdit.bind(this);
+ this.handleTAFracAttrChangeEdit = this.handleTAFracAttrChangeEdit.bind(this);
+ this.handleTAStripAttrChangeEdit = this.handleTAStripAttrChangeEdit.bind(this);
+ this.createAgmt = this.createAgmt.bind(this);
+ this.showEditAgmt = this.showEditAgmt.bind(this);
+ this.saveAgmt = this.saveAgmt.bind(this);
+ this.pokeAgmt = this.pokeAgmt.bind(this);
+ this.initAgmt = this.initAgmt.bind(this);
+ this.enableAgmt = this.enableAgmt.bind(this);
+ this.disableAgmt = this.disableAgmt.bind(this);
+ this.deleteAgmt = this.deleteAgmt.bind(this);
+ this.showConfirmDeleteAgmt = this.showConfirmDeleteAgmt.bind(this);
+ this.closeConfirmDeleteAgmt = this.closeConfirmDeleteAgmt.bind(this);
+ this.showConfirmInitAgmt = this.showConfirmInitAgmt.bind(this);
+ this.closeConfirmInitAgmt = this.closeConfirmInitAgmt.bind(this);
+ this.confirmToggle = this.confirmToggle.bind(this);
+ this.closeConfirmEnableAgmt = this.closeConfirmEnableAgmt.bind(this);
+ this.closeConfirmDisableAgmt = this.closeConfirmDisableAgmt.bind(this);
+ this.watchAgmtInit = this.watchAgmtInit.bind(this);
+ }
+
+ componentDidMount () {
+ this._mounted = true;
+ }
+
+ componentWillUnmount () {
+ this._mounted = false;
+ }
+
+ listEqual(old_values, new_values) {
+ if (old_values.length != new_values.length) {
+ return false;
+ }
+ for (let i = old_values.length; i--;) {
+ if (old_values[i] != new_values[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ handleModalChange (e) {
+ this.setState({
+ [e.target.id]: e.target.checked,
+ });
+ }
+
+ handleChange (e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked :
e.target.value;
+ let time_val = "";
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ let all_good = true;
+ let modal_msg = "";
+ let modal_schedule_msg = "";
+ let edit = false;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ if (e.target.name == "agmt-modal-edit") {
+ let orig_attr = "_" + e.target.id;
+ let attr = e.target.id;
+ all_good = false;
+ if ((attr != 'agmtHost' && this.state.agmtHost !=
this.state._agmtHost) ||
+ (attr != 'agmtPort' && this.state.agmtPort !=
this.state._agmtPort) ||
+ (attr != 'agmtBindDN' && this.state.agmtBindDN !=
this.state._agmtBindDN) ||
+ (attr != 'agmtBindMethod' && this.state.agmtBindMethod !=
this.state._agmtBindMethod) ||
+ (attr != 'agmtProtocol' && this.state.agmtProtocol !=
this.state._agmtProtocol) ||
+ (attr != 'agmtSync' && this.state.agmtSync !=
this.state._agmtSync) ||
+ (attr != 'agmtStripAttrs' &&
!this.listEqual(this.state.agmtStripAttrs, this.state._agmtStripAttrs)) ||
+ (attr != 'agmtFracAttrs' &&
!this.listEqual(this.state.agmtFracAttrs, this.state._agmtFracAttrs)) ||
+ (attr != 'agmtFracInitAttrs' &&
!this.listEqual(this.state.agmtFracInitAttrs, this.state._agmtFracInitAttrs))) {
+ all_good = true;
+ }
+ if (!this.state._agmtSync) {
+ if ((attr != 'agmtSyncMon' && this.state.agmtSyncMon !=
this.state._agmtSyncMon) ||
+ (attr != 'agmtSyncTue' && this.state.agmtSyncTue !=
this.state._agmtSyncTue) ||
+ (attr != 'agmtSyncWed' && this.state.agmtSyncWed !=
this.state._agmtSyncWed) ||
+ (attr != 'agmtSyncThu' && this.state.agmtSyncThu !=
this.state._agmtSyncThu) ||
+ (attr != 'agmtSyncFri' && this.state.agmtSyncFri !=
this.state._agmtSyncFri) ||
+ (attr != 'agmtSyncSat' && this.state.agmtSyncSat !=
this.state._agmtSyncSat) ||
+ (attr != 'agmtSyncSun' && this.state.agmtSyncSun !=
this.state._agmtSyncSun)) {
+ all_good = true;
+ }
+ }
+ if (attr != 'agmtStripAttrs' &&
+ attr != 'agmtFracAttrs' &&
+ attr != 'agmtFracInitAttrs' &&
+ value != this.state[orig_attr]) {
+ all_good = true;
+ } else if ((attr == 'agmtStripAttrs' &&
!this.listEqual(value, this.state._agmtStripAttrs)) ||
+ (attr == 'agmtFracAttrs' && !this.listEqual(value,
this.state._agmtFracAttrs)) ||
+ (attr == 'agmtFracInitAttrs' &&
!this.listEqual(value, this.state._agmtFracInitAttrs))) {
+ all_good = true;
+ }
+ }
+
+ if (e.target.type == "time") {
+ // Strip out the colon from the time
+ time_val = value.replace(':', '');
+ }
+
+ if (e.target.name.startsWith("agmt-modal")) {
+ // Validate modal settings "live"
+ if (e.target.id == 'agmtName') {
+ if (value == "") {
+ all_good = false;
+ }
+ } else if (this.state.agmtName == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtHost') {
+ if (value == "") {
+ all_good = false;
+ }
+ } else if (this.state.agmtHost == "") {
+ all_good = false;
+ } else if (edit && value == this.state._agmtHost) {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtPort') {
+ if (value == "") {
+ all_good = false;
+ } else if (!valid_port(value)) {
+ all_good = false;
+ errObj['agmtPort'] = true;
+ modal_msg = "Invalid Consumer Port number";
+ }
+ } else if (this.state.agmtPort == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtBindDN') {
+ if (value == "") {
+ all_good = false;
+ }
+ if (!valid_dn(value)) {
+ errObj['agmtBindDN'] = true;
+ all_good = false;
+ modal_msg = "Invalid DN for Bind DN";
+ }
+ } else if (this.state.agmtBindDN == "") {
+ all_good = false;
+ } else if (!valid_dn(this.state.agmtBindDN)) {
+ modal_msg = "Invalid DN for Bind DN";
+ errObj['agmtBindDN'] = true;
+ all_good = false;
+ }
+ if (e.target.id == 'agmtBindPW') {
+ if (value == "") {
+ all_good = false;
+ } else if (value != this.state.agmtBindPWConfirm) {
+ modal_msg = "Passwords Do Not Match";
+ errObj['agmtBindPW'] = true;
+ errObj['agmtBindPWConfirm'] = true;
+ all_good = false;
+ } else {
+ errObj['agmtBindPW'] = false;
+ errObj['agmtBindPWConfirm'] = false;
+ }
+ } else if (this.state.agmtBindPW == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtBindPWConfirm') {
+ if (value == "") {
+ all_good = false;
+ } else if (value != this.state.agmtBindPW) {
+ modal_msg = "Passwords Do Not Match";
+ errObj['agmtBindPW'] = true;
+ errObj['agmtBindPWConfirm'] = true;
+ all_good = false;
+ } else {
+ errObj['agmtBindPW'] = false;
+ errObj['agmtBindPWConfirm'] = false;
+ }
+ } else if (this.state.agmtBindPWConfirm == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtSync') {
+ if (!value) {
+ if (this.state.agmtStartTime >= this.state.agmtEndTime) {
+ modal_schedule_msg = "Schedule start time is greater than or
equal to the end time";
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ }
+ }
+ } else if (!this.state.agmtSync) {
+ // Check the days first
+ let have_days = false;
+ let days = ["agmtSyncSun", "agmtSyncMon",
"agmtSyncTue", "agmtSyncWed",
+ "agmtSyncThu", "agmtSyncFri",
"agmtSyncSat"];
+ for (let day of days) {
+ if ((e.target.id != day && this.state[day]) || (e.target.id
== day && value)) {
+ have_days = true;
+ break;
+ }
+ }
+ if (!have_days) {
+ modal_schedule_msg = "You must select at least one day for
replication";
+ all_good = false;
+ } else if (e.target.id == 'agmtStartTime') {
+ if (time_val == "") {
+ all_good = false;
+ errObj['agmtStartTime'] = true;
+ modal_schedule_msg = "Schedule start time must be
set";
+ } else if (time_val >=
this.state.agmtEndTime.replace(":", "")) {
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ modal_schedule_msg = "Schedule start time is greater than or
equal to the end time";
+ } else {
+ // All good, reset form
+ modal_schedule_msg = "";
+ errObj['agmtStartTime'] = false;
+ errObj['agmtEndTime'] = false;
+ }
+ } else if (e.target.id == 'agmtEndTime') {
+ if (time_val == "") {
+ errObj['agmtEndTime'] = true;
+ all_good = false;
+ } else if (this.state.agmtStartTime.replace(":",
"") >= time_val) {
+ modal_schedule_msg = "Schedule start time is greater than or
equal to the end time";
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ } else {
+ // All good, reset form
+ modal_schedule_msg = "";
+ errObj['agmtStartTime'] = false;
+ errObj['agmtEndTime'] = false;
+ }
+ } else if (this.state.agmtStartTime >= this.state.agmtEndTime) {
+ modal_schedule_msg = "Schedule start time is greater than or
equal to the end time";
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ }
+ }
+ // End of agmt modal live validation
+ }
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj,
+ agmtSaveOK: all_good,
+ modalMsg: modal_msg,
+ modalScheduleMsg: modal_schedule_msg,
+ });
+ }
+
+ handleTAStripAttrChangeEdit (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal-edit',
+ id: 'agmtStripAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ handleTAFracAttrChangeEdit (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal-edit',
+ id: 'agmtFracAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ handleTAFracInitAttrChangeEdit (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal-edit',
+ id: 'agmtFracInitAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ handleTAStripAttrChange (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal',
+ id: 'agmtStripAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ handleTAFracAttrChange (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal',
+ id: 'agmtFracAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ handleTAFracInitAttrChange (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal',
+ id: 'agmtFracInitAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ showConfirmDeleteAgmt (agmtName) {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmDeleteAgmt: true,
+ modalChecked: false,
+ modalSpinning: false,
+ });
+ }
+
+ closeConfirmDeleteAgmt () {
+ this.setState({
+ showConfirmDeleteAgmt: false,
+ modalSpinning: false,
+ modalChecked: false,
+ });
+ }
+
+ showConfirmInitAgmt (agmtName) {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmInitAgmt: true,
+ modalChecked: false,
+ modalSpinning: false,
+ });
+ }
+
+ closeConfirmInitAgmt () {
+ this.setState({
+ showConfirmInitAgmt: false,
+ modalSpinning: false,
+ modalChecked: false,
+ });
+ }
+
+ showCreateAgmtModal () {
+ this.setState({
+ showCreateAgmtModal: true,
+ agmtName: "",
+ agmtHost: "",
+ agmtPort: "",
+ agmtProtocol: "LDAP",
+ agmtBindMethod: "SIMPLE",
+ agmtBindDN: "",
+ agmtBindPW: "",
+ agmtBindPWConfirm: "",
+ agmtStripAttrs: [],
+ agmtFracAttrs: [],
+ agmtFracInitAttrs: [],
+ agmtSync: true,
+ agmtSyncMon: true,
+ agmtSyncTue: true,
+ agmtSyncWed: true,
+ agmtSyncThu: true,
+ agmtSyncFri: true,
+ agmtSyncSat: true,
+ agmtSyncSun: true,
+ agmtStartTime: "00:00",
+ agmtEndTime: "23:59",
+ agmtInit: "noinit",
+ agmtSaveOK: false,
+ modalScheduleMsg: "",
+ errObj: {
+ // Marks all the fields as required
+ agmtName: true,
+ agmtHost: true,
+ agmtPort: true,
+ agmtBindDN: true,
+ agmtBindPW: true,
+ agmtBindPWConfirm: true,
+ agmtStartTime: false,
+ agmtEndTime: false,
+ }
+ });
+ }
+
+ closeCreateAgmtModal () {
+ this.setState({
+ showCreateAgmtModal: false
+ });
+ }
+
+ closeEditAgmtModal () {
+ this.setState({
+ showEditAgmtModal: false
+ });
+ }
+
+ showEditAgmt (agmtName) {
+ // Search for the agmt to get all the details
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' +
this.props.serverId + '.socket',
+ 'repl-agmt', 'get', agmtName, '--suffix=' +
this.props.suffix,
+ ];
+
+ log_cmd('showEditAgmt', 'Edit replication agreement', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let agmtName = "";
+ let agmtHost = "";
+ let agmtPort = "";
+ let agmtProtocol = "";
+ let agmtBindMethod = "";
+ let agmtBindDN = "";
+ let agmtBindPW = "";
+ let agmtBindPWConfirm = "";
+ let agmtStripAttrs = [];
+ let agmtFracAttrs = [];
+ let agmtFracInitAttrs = [];
+ let agmtSync = true;
+ let agmtSyncMon = false;
+ let agmtSyncTue = false;
+ let agmtSyncWed = false;
+ let agmtSyncThu = false;
+ let agmtSyncFri = false;
+ let agmtSyncSat = false;
+ let agmtSyncSun = false;
+ let agmtStartTime = "";
+ let agmtEndTime = "";
+ for (let attr in config['attrs']) {
+ let val = config['attrs'][attr][0];
+ if (attr == "cn") {
+ agmtName = val;
+ }
+ if (attr == "nsds5replicahost") {
+ agmtHost = val;
+ }
+ if (attr == "nsds5replicaport") {
+ agmtPort = val;
+ }
+ if (attr == "nsds5replicatransportinfo") {
+ agmtProtocol = val;
+ }
+ if (attr == "nsds5replicabindmethod") {
+ agmtBindMethod = val.toUpperCase();
+ }
+ if (attr == "nsds5replicabinddn") {
+ agmtBindDN = val;
+ }
+ if (attr == "nsds5replicacredentials") {
+ agmtBindPW = val;
+ agmtBindPWConfirm = val;
+ }
+ if (attr == "nsds5replicatedattributelist") {
+ let attrs = val.replace("(objectclass=*) $
EXCLUDE", "").trim();
+ agmtFracAttrs = attrs.split(' ');
+ }
+ if (attr == "nsds5replicatedattributelisttotal") {
+ let attrs = val.replace("(objectclass=*) $
EXCLUDE", "").trim();
+ agmtFracInitAttrs = attrs.split(' ');
+ }
+ if (attr == "nsds5replicastripattrs") {
+ agmtStripAttrs = val.split(' ');
+ }
+ if (attr == "nsds5replicaupdateschedule") {
+ agmtSync = false;
+ // Parse schedule
+ let parts = val.split(' ');
+ let times = parts[0].split('-');
+ let days = parts[1];
+
+ // Do the times
+ agmtStartTime = times[0].substring(0, 2) + ":" +
times[0].substring(2, 4);
+ agmtEndTime = times[1].substring(0, 2) + ":" +
times[1].substring(2, 4);
+
+ // Do the days
+ if (days.includes("0")) {
+ agmtSyncSun = true;
+ }
+ if (days.includes("1")) {
+ agmtSyncMon = true;
+ }
+ if (days.includes("2")) {
+ agmtSyncTue = true;
+ }
+ if (days.includes("3")) {
+ agmtSyncWed = true;
+ }
+ if (days.includes("4")) {
+ agmtSyncThu = true;
+ }
+ if (days.includes("5")) {
+ agmtSyncFri = true;
+ }
+ if (days.includes("6")) {
+ agmtSyncSat = true;
+ }
+ }
+ }
+ if (this._mounted) {
+ this.setState({
+ showEditAgmtModal: true,
+ agmtName: agmtName,
+ agmtHost: agmtHost,
+ agmtPort: agmtPort,
+ agmtProtocol: agmtProtocol,
+ agmtBindMethod: agmtBindMethod,
+ agmtBindDN: agmtBindDN,
+ agmtBindPW: agmtBindPW,
+ agmtBindPWConfirm: agmtBindPWConfirm,
+ agmtStripAttrs: agmtStripAttrs,
+ agmtFracAttrs: agmtFracAttrs,
+ agmtFracInitAttrs: agmtFracInitAttrs,
+ agmtSync: agmtSync,
+ agmtSyncMon: agmtSyncMon,
+ agmtSyncTue: agmtSyncTue,
+ agmtSyncWed: agmtSyncWed,
+ agmtSyncThu: agmtSyncThu,
+ agmtSyncFri: agmtSyncFri,
+ agmtSyncSat: agmtSyncSat,
+ agmtSyncSun: agmtSyncSun,
+ agmtStartTime: agmtStartTime,
+ agmtEndTime: agmtEndTime,
+ agmtSaveOK: false,
+ // Record original values before editing
+ _agmtName: agmtName,
+ _agmtHost: agmtHost,
+ _agmtPort: agmtPort,
+ _agmtProtocol: agmtProtocol,
+ _agmtBindMethod: agmtBindMethod,
+ _agmtBindDN: agmtBindDN,
+ _agmtBindPW: agmtBindPW,
+ _agmtBindPWConfirm: agmtBindPWConfirm,
+ _agmtStripAttrs: agmtStripAttrs,
+ _agmtFracAttrs: agmtFracAttrs,
+ _agmtFracInitAttrs: agmtFracInitAttrs,
+ _agmtSync: agmtSync,
+ _agmtSyncMon: agmtSyncMon,
+ _agmtSyncTue: agmtSyncTue,
+ _agmtSyncWed: agmtSyncWed,
+ _agmtSyncThu: agmtSyncThu,
+ _agmtSyncFri: agmtSyncFri,
+ _agmtSyncSat: agmtSyncSat,
+ _agmtSyncSun: agmtSyncSun,
+ _agmtStartTime: agmtStartTime,
+ _agmtEndTime: agmtEndTime,
+ });
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to get agreement information for: "${agmtName}"
- ${errMsg.desc}`
+ );
+ });
+ }
+
+ saveAgmt () {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' +
this.props.serverId + '.socket',
+ 'repl-agmt', 'set', this.state.agmtName, '--suffix='
+ this.props.suffix,
+ ];
+
+ // Handle Schedule
+ if (!this.state.agmtSync) {
+ let agmt_days = "";
+ if (this.state.agmtSyncSun) {
+ agmt_days += "0";
+ }
+ if (this.state.agmtSyncMon) {
+ agmt_days += "1";
+ }
+ if (this.state.agmtSyncTue) {
+ agmt_days += "2";
+ }
+ if (this.state.agmtSyncWed) {
+ agmt_days += "3";
+ }
+ if (this.state.agmtSyncThu) {
+ agmt_days += "4";
+ }
+ if (this.state.agmtSyncFri) {
+ agmt_days += "5";
+ }
+ if (this.state.agmtSyncSat) {
+ agmt_days += "6";
+ }
+ cmd.push('--schedule=' +
this.state.agmtStartTime.replace(':', '') + "-" +
this.state.agmtEndTime.replace(':', '') + " " + agmt_days);
+ } else if (this.state.agmtSync != this.state._agmtSync &&
this.state.agmtSync) {
+ // We disabled custom scheduleRow
+ cmd.push('--schedule=');
+ }
+ if (this.state.agmtBindMethod != this.state._agmtBindMethod) {
+ cmd.push('--bind-method=' + this.state.agmtBindMethod);
+ }
+ if (this.state.agmtProtocol != this.state._agmtProtocol) {
+ cmd.push('--conn-protocol=' + this.state.agmtProtocol);
+ }
+ if (this.state.agmtBindPW != this.state._agmtBindPW) {
+ cmd.push('--bind-passwd=' + this.state.agmtBindPW);
+ }
+ if (this.state.agmtBindDN != this.state._agmtBindDN) {
+ cmd.push('--bind-passwd=' + this.state.agmtBindDN);
+ }
+ if (this.state.agmtFracAttrs != this.state._agmtFracAttrs) {
+ cmd.push('--frac-list=' + this.state.agmtFracAttrs.join('
'));
+ }
+ if (this.state.agmtFracInitAttrs != this.state._agmtFracInitAttrs) {
+ cmd.push('--frac-list-total=' +
this.state.agmtFracInitAttrs.join(' '));
+ }
+ if (this.state.agmtStripAttrs != this.state._agmtStripAttrs) {
+ cmd.push('--strip-list=' + this.state.agmtStripAttrs.join('
'));
+ }
+ if (this.state.agmtHost != this.state._agmtHost) {
+ cmd.push('--host=' + this.state.agmtHost);
+ }
+ if (this.state.agmtPort != this.state._agmtPort) {
+ cmd.push('--port=' + this.state.agmtPort);
+ }
+
+ this.setState({
+ savingAgmt: true
+ });
+ log_cmd('saveAgmt', 'update replication agreement', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ if (this._mounted) {
+ this.setState({
+ savingAgmt: false,
+ showEditAgmtModal: false,
+ });
+ }
+ this.props.addNotification(
+ 'success',
+ 'Successfully updated replication agreement'
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to update replication agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ savingAgmt: false
+ });
+ });
+ }
+
+ pokeAgmt (agmtName) {
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'poke', agmtName, '--suffix=' +
this.props.suffix];
+ log_cmd('pokeAgmt', 'send updates now', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, "err": "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully poked replication agreement'
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ 'error',
+ `Failed to poke replication agreement - ${errMsg.desc}`
+ );
+ });
+ }
+
+ initAgmt (agmtName) {
+ let init_cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'init', '--suffix=' + this.props.suffix,
this.state.agmtName];
+ log_cmd('initAgmt', 'Initialize agreement', init_cmd);
+ cockpit
+ .spawn(init_cmd, { superuser: true, "err": "message"
})
+ .done(content => {
+ var agmtIntervalCount = this.state.agmtInitCounter + 1;
+ var intervals = this.state.agmtInitIntervals;
+ this.props.reload(this.props.suffix);
+ intervals[agmtIntervalCount] = setInterval(this.watchAgmtInit, 2000,
this.state.agmtName, agmtIntervalCount);
+ if (this._mounted) {
+ this.setState({
+ agmtInitCounter: agmtIntervalCount,
+ agmtInitIntervals: intervals,
+ showConfirmInitAgmt: false
+ });
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ 'error',
+ `Failed to initialize replication agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ showConfirmInitAgmt: false
+ });
+ });
+ }
+
+ confirmToggle (agmtName, state) {
+ if (state == 'Enabled') {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmDisableAgmt: true
+ });
+ } else {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmEnableAgmt: true
+ });
+ }
+ }
+
+ closeConfirmEnableAgmt () {
+ this.setState({
+ showConfirmEnableAgmt: false
+ });
+ }
+
+ closeConfirmDisableAgmt () {
+ this.setState({
+ showConfirmDisableAgmt: false
+ });
+ }
+
+ enableAgmt (agmtName) {
+ // Enable/disable agmt
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'enable', agmtName, '--suffix=' +
this.props.suffix];
+ log_cmd('enableAgmt', 'enable agmt', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully enabled replication agreement');
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to enabled replication agreement - ${errMsg.desc}`
+ );
+ });
+ }
+
+ disableAgmt (agmtName) {
+ // Enable/disable agmt
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'disable', agmtName, '--suffix=' +
this.props.suffix];
+ log_cmd('disableAgmt', 'Disable agmt', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully disabled replication agreement');
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to disable replication agreement - ${errMsg.desc}`
+ );
+ });
+ }
+
+ deleteAgmt () {
+ this.setState({
+ deleteSpinning: true
+ });
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'delete', '--suffix=' +
this.props.suffix, this.state.agmtName];
+ log_cmd('deleteAgmt', 'Delete agmt', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully deleted replication agreement');
+ this.setState({
+ showDeleteConfirm: false,
+ deleteSpinning: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to delete replication agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ showDeleteConfirm: false,
+ deleteSpinning: false
+ });
+ });
+ }
+
+ createAgmt () {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' +
this.props.serverId + '.socket',
+ 'repl-agmt', 'create', this.state.agmtName,
'--suffix=' + this.props.suffix,
+ '--host=' + this.state.agmtHost, '--port=' +
this.state.agmtPort,
+ '--bind-method=' + this.state.agmtBindMethod,
'--conn-protocol=' + this.state.agmtProtocol,
+ '--bind-dn=' + this.state.agmtBindDN, '--bind-passwd=' +
this.state.agmtBindPW
+ ];
+
+ // Handle Schedule
+ if (!this.state.agmtSync) {
+ let agmt_days = "";
+ if (this.state.agmtSyncSun) {
+ agmt_days += "0";
+ }
+ if (this.state.agmtSyncMon) {
+ agmt_days += "1";
+ }
+ if (this.state.agmtSyncTue) {
+ agmt_days += "2";
+ }
+ if (this.state.agmtSyncWed) {
+ agmt_days += "3";
+ }
+ if (this.state.agmtSyncThu) {
+ agmt_days += "4";
+ }
+ if (this.state.agmtSyncFri) {
+ agmt_days += "5";
+ }
+ if (this.state.agmtSyncSat) {
+ agmt_days += "6";
+ }
+ cmd.push('--schedule=' +
this.state.agmtStartTime.replace(':', '') + "-" +
this.state.agmtEndTime.replace(':', '') + " " + agmt_days);
+ }
+
+ // Handle fractional and stripped attributes
+ if (this.state.agmtFracAttrs.length > 0) {
+ cmd.push('--frac-list=' + this.state.agmtFracAttrs.join('
'));
+ }
+ if (this.state.agmtFracInitAttrs.length > 0) {
+ cmd.push('--frac-list-total=' + this.state.agmtFracAttrs.join('
'));
+ }
+ if (this.state.agmtStripAttrs.length > 0) {
+ cmd.push('--strip-list=' + this.state.agmtStripAttrs.join('
'));
+ }
+
+ this.setState({
+ savingAgmt: true
+ });
+ log_cmd('createAgmt', 'Create replication agreement', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ if (this._mounted) {
+ this.setState({
+ savingAgmt: false,
+ showCreateAgmtModal: false,
+ });
+ }
+ this.props.addNotification(
+ 'success',
+ 'Successfully created replication agreement'
+ );
+ if (this.state.agmtInit == 'online-init') {
+ this.initAgmt(this.state.agmtName);
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to create replication agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ savingAgmt: false
+ });
+ });
+ }
+
+ watchAgmtInit(agmtName, idx) {
+ // Watch the init, then clear the interval index
+ let status_cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-agmt', 'init-status', '--suffix=' +
this.props.suffix, agmtName];
+ log_cmd('watchAgmtInit', 'Get initialization status for agmt',
status_cmd);
+ cockpit
+ .spawn(status_cmd, {superuser: true, "err":
"message"})
+ .done(data => {
+ let init_status = JSON.parse(data);
+ if (init_status.startsWith('Agreement successfully
initialized') ||
+ init_status.startsWith('Agreement initialization
failed')) {
+ // Either way we're done, stop watching the status
+ clearInterval(this.state.agmtInitIntervals[idx]);
+ }
+ this.props.reload(this.props.suffix);
+ });
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-right">
+ <ReplAgmtTable
+ rows={this.props.rows}
+ edit={this.showEditAgmt}
+ poke={this.pokeAgmt}
+ init={this.showConfirmInitAgmt}
+ enable={this.confirmToggle}
+ delete={this.showConfirmDeleteAgmt}
+ />
+ <div className="ds-margin-top ds-container ds-inline">
+ <Button
+ bsStyle="primary"
+ onClick={this.showCreateAgmtModal}
+
+ Create
Agreement
+ </Button>
+ <Button
+ className="ds-left-margin"
+ bsStyle="default"
+ onClick={() => {
+ this.props.reload(this.props.suffix);
+ }}
+
+ Refresh
Agreements
+ </Button>
+ </div>
+ <ReplAgmtModal
+ showModal={this.state.showCreateAgmtModal}
+ closeHandler={this.closeCreateAgmtModal}
+ handleChange={this.handleChange}
+ handleStripChange={this.handleTAStripAttrChange}
+ handleFracChange={this.handleTAFracAttrChange}
+ handleFracInitChange={this.handleTAFracInitAttrChange}
+ saveHandler={this.createAgmt}
+ spinning={this.state.savingAgmt}
+ agmtName={this.state.agmtName}
+ agmtHost={this.state.agmtHost}
+ agmtPort={this.state.agmtPort}
+ agmtBindDN={this.state.agmtBindDN}
+ agmtBindPW={this.state.agmtBindPW}
+ agmtBindPWConfirm={this.state.agmtBindPWConfirm}
+ agmtProtocol={this.state.agmtProtocol}
+ agmtBindMethod={this.state.agmtBindMethod}
+ agmtStripAttrs={this.state.agmtStripAttrs}
+ agmtFracAttrs={this.state.agmtFracAttrs}
+ agmtFracInitAttrs={this.state.agmtFracInitAttrs}
+ agmtSync={this.state.agmtSync}
+ agmtSyncMon={this.state.agmtSyncMon}
+ agmtSyncTue={this.state.agmtSyncTue}
+ agmtSyncWed={this.state.agmtSyncWed}
+ agmtSyncThu={this.state.agmtSyncThu}
+ agmtSyncFri={this.state.agmtSyncFri}
+ agmtSyncSat={this.state.agmtSyncSat}
+ agmtSyncSun={this.state.agmtSyncSun}
+ agmtStartTime={this.state.agmtStartTime}
+ agmtEndTime={this.state.agmtEndTime}
+ availAttrs={this.props.attrs}
+ error={this.state.errObj}
+ errorMsg={this.state.modalMsg}
+ errorScheduleMsg={this.state.modalScheduleMsg}
+ saveOK={this.state.agmtSaveOK}
+ />
+ <ReplAgmtModal
+ showModal={this.state.showEditAgmtModal}
+ closeHandler={this.closeEditAgmtModal}
+ handleChange={this.handleChange}
+ handleStripChange={this.handleTAStripAttrChangeEdit}
+ handleFracChange={this.handleTAFracAttrChangeEdit}
+ handleFracInitChange={this.handleTAFracInitAttrChangeEdit}
+ saveHandler={this.saveAgmt}
+ spinning={this.state.savingAgmt}
+ agmtName={this.state.agmtName}
+ agmtHost={this.state.agmtHost}
+ agmtPort={this.state.agmtPort}
+ agmtBindDN={this.state.agmtBindDN}
+ agmtBindPW={this.state.agmtBindPW}
+ agmtBindPWConfirm={this.state.agmtBindPWConfirm}
+ agmtProtocol={this.state.agmtProtocol}
+ agmtBindMethod={this.state.agmtBindMethod}
+ agmtStripAttrs={this.state.agmtStripAttrs}
+ agmtFracAttrs={this.state.agmtFracAttrs}
+ agmtFracInitAttrs={this.state.agmtFracInitAttrs}
+ agmtSync={this.state.agmtSync}
+ agmtSyncMon={this.state.agmtSyncMon}
+ agmtSyncTue={this.state.agmtSyncTue}
+ agmtSyncWed={this.state.agmtSyncWed}
+ agmtSyncThu={this.state.agmtSyncThu}
+ agmtSyncFri={this.state.agmtSyncFri}
+ agmtSyncSat={this.state.agmtSyncSat}
+ agmtSyncSun={this.state.agmtSyncSun}
+ agmtStartTime={this.state.agmtStartTime}
+ agmtEndTime={this.state.agmtEndTime}
+ availAttrs={this.props.attrs}
+ error={this.state.errObj}
+ errorMsg={this.state.modalMsg}
+ errorScheduleMsg={this.state.modalScheduleMsg}
+ saveOK={this.state.agmtSaveOK}
+ edit
+ />
+ <DoubleConfirmModal
+ showModal={this.state.showConfirmDeleteAgmt}
+ closeHandler={this.closeConfirmDeleteAgmt}
+ handleChange={this.handleModalChange}
+ actionHandler={this.deleteAgmt}
+ spinning={this.state.modalSpinning}
+ item={this.state.agmtName}
+ checked={this.state.modalChecked}
+ mTitle="Delete Replication Agreement"
+ mMsg="Are you sure you want to delete this replication
agreement?"
+ mSpinningMsg="Deleting Replication Agreement ..."
+ mBtnName="Delete Agreement"
+ />
+ <DoubleConfirmModal
+ showModal={this.state.showConfirmInitAgmt}
+ closeHandler={this.closeConfirmInitAgmt}
+ handleChange={this.handleModalChange}
+ actionHandler={this.initAgmt}
+ spinning={this.state.modalSpinning}
+ item={this.state.agmtName}
+ checked={this.state.modalChecked}
+ mTitle="Initialize Replication Agreement"
+ mMsg="Are you sure you want to initialize this replication
agreement?"
+ mSpinningMsg="Initializing Replication Agreement ..."
+ mBtnName="Initialize Agreement"
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmEnableAgmt}
+ closeHandler={this.closeConfirmEnableAgmt}
+ actionFunc={this.enableAgmt}
+ actionParam={this.state.agmtName}
+ msg="Are you sure you want to enable this replication
agreement?"
+ msgContent={this.state.agmtName}
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmDisableAgmt}
+ closeHandler={this.closeConfirmDisableAgmt}
+ actionFunc={this.disableAgmt}
+ actionParam={this.state.agmtName}
+ msg="Are you sure you want to disable this replication
agreement?"
+ msgContent={this.state.agmtName}
+ />
+ </div>
+ );
+ }
+}
+
+ReplAgmts.propTypes = {
+ suffix: PropTypes.string,
+ serverId: PropTypes.string,
+ rows: PropTypes.array,
+ addNotification: PropTypes.func,
+ attrs: PropTypes.array,
+};
+
+ReplAgmts.defaultProps = {
+ serverId: "",
+ suffix: "",
+ rows: [],
+ addNotification: noop,
+ attrs: [],
+};
diff --git a/src/cockpit/389-console/src/lib/replication/replChangelog.jsx
b/src/cockpit/389-console/src/lib/replication/replChangelog.jsx
new file mode 100644
index 0000000..1386393
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/replChangelog.jsx
@@ -0,0 +1,395 @@
+import cockpit from "cockpit";
+import React from "react";
+import "../../css/ds.css";
+import PropTypes from "prop-types";
+import { ConfirmPopup } from "../notifications.jsx";
+import { log_cmd } from "../tools.jsx";
+import {
+ noop,
+ Row,
+ Button,
+ Col,
+ ControlLabel,
+ Checkbox,
+ Form,
+ Icon,
+ Spinner,
+} from "patternfly-react";
+
+export class Changelog extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ loading: false,
+ errObj: {},
+ showConfirmDelete: false,
+ // Changelog settings
+ clDir: this.props.clDir,
+ clMaxEntries: this.props.clMaxEntries,
+ clMaxAge: this.props.clMaxAge,
+ clCompactInt: this.props.clCompactInt,
+ clTrimInt: this.props.clTrimInt,
+ clEncrypt: this.props.clEncrypt,
+ // Preserve original settings
+ _clDir: this.props.clDir,
+ _clMaxEntries: this.props.clMaxEntries,
+ _clMaxAge: this.props.clMaxAge,
+ _clCompactInt: this.props.clCompactInt,
+ _clTrimInt: this.props.clTrimInt,
+ _clEncrypt: this.props.clEncrypt,
+ };
+
+ this.handleChange = this.handleChange.bind(this);
+ this.saveSettings = this.saveSettings.bind(this);
+ this.createChangelog = this.createChangelog.bind(this);
+ this.confirmChangelogDelete = this.confirmChangelogDelete.bind(this);
+ this.closeConfirmDelete = this.closeConfirmDelete.bind(this);
+ this.deleteChangelog = this.deleteChangelog.bind(this);
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ createChangelog () {
+ this.setState({
+ saving: true
+ });
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' +
this.props.serverId + '.socket',
+ 'replication', 'create-changelog'
+ ];
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ "Successfully created replication changelog"
+ );
+ this.setState({
+ saving: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reload();
+ this.setState({
+ saving: false
+ });
+ let msg = errMsg.desc;
+ if ('info' in errMsg) {
+ msg = errMsg.desc + " - " + errMsg.info;
+ }
+ this.props.addNotification(
+ "error",
+ `Error creating changelog - ${msg}`
+ );
+ });
+ }
+
+ confirmChangelogDelete () {
+ this.setState({
+ showConfirmDelete: true
+ });
+ }
+
+ closeConfirmDelete () {
+ this.setState({
+ showConfirmDelete: false
+ });
+ }
+
+ deleteChangelog () {
+ this.setState({
+ saving: true
+ });
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'replication', 'delete-changelog'
+ ];
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ "Successfully deleted replication changelog"
+ );
+ this.setState({
+ saving: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reload();
+ this.setState({
+ saving: false
+ });
+ let msg = errMsg.desc;
+ if ('info' in errMsg) {
+ msg = errMsg.desc + " - " + errMsg.info;
+ }
+ this.props.addNotification(
+ "error",
+ `Error deleting changelog - ${msg}`
+ );
+ });
+ }
+
+ saveSettings () {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' +
this.props.serverId + '.socket',
+ 'replication', 'set-changelog'
+ ];
+ if (this.state.clDir != this.state._clDir) {
+ if (this.state.clDir == "") {
+ // Changelog directory can not be empty
+ let errObj = this.state.errObj;
+ errObj["clDir"] = true;
+ this.setState({
+ errObj: errObj
+ });
+ return;
+ }
+ cmd.push("--cl-dir =" + this.state.clDir);
+ }
+ if (this.state.clMaxEntries != this.state._clMaxEntries) {
+ cmd.push("--max-entries=" + this.state.clMaxEntries);
+ }
+ if (this.state.clMaxAge != this.state._clMaxAge) {
+ cmd.push("--max-age=" + this.state.clMaxAge);
+ }
+ if (this.state.clCompactInt != this.state._clCompactInt) {
+ cmd.push("--compact-interval=" + this.state.clCompactInt);
+ }
+ if (this.state.clTrimInt != this.state._clTrimInt) {
+ cmd.push("--trim-interval=" + this.state.clTrimInt);
+ }
+ if (this.state.clEncrypt != this.state._clEncrypt) {
+ // TODO - Not implemented in dsconf yet
+ // cmd.push("--encrypt=" + this.state.clEncrypt);
+ }
+ if (cmd.length > 5) {
+ this.setState({
+ // Start the spinner
+ saving: true
+ });
+ log_cmd("saveSettings", "Applying replication changelog
changes", cmd);
+ let msg = "Successfully updated changelog configuration.";
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ msg
+ );
+ this.setState({
+ saving: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reload();
+ this.setState({
+ saving: false
+ });
+ let msg = errMsg.desc;
+ if ('info' in errMsg) {
+ msg = errMsg.desc + " - " + errMsg.info;
+ }
+ this.props.addNotification(
+ "error",
+ `Error updating changelog configuration - ${msg}`
+ );
+ });
+ }
+ }
+
+ handleChange(e) {
+ const value = e.target.type === 'checkbox' ? e.target.checked :
e.target.value;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (e.target.id == "clDir" && value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj
+ });
+ }
+
+ render() {
+ let clPage;
+ if (this.state._clDir == "") {
+ // No changelog, only show clDir and Create button
+ clPage =
+ <div>
+ <Row>
+ <Col sm={12} className="ds-word-wrap">
+ <ControlLabel className="ds-suffix-header">
+ Replication Changelog
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh"
title="Refresh changelog settings"
+ onClick={this.props.reload}
+ />
+ </ControlLabel>
+ </Col>
+ </Row>
+ <hr />
+ <div className="ds-margin-top-med ds-center">
+ <p>There is no Replication Changelog</p>
+ <Row className="ds-margin-top-lg" title="Create
the replication changelog">
+ <Button
+ bsStyle="primary"
+ onClick={this.createChangelog}
+
+ Create Changelog
+ </Button>
+ </Row>
+ </div>
+ </div>;
+ } else if (this.state.saving) {
+ clPage =
+ <div className="ds-margin-top ds-loading-spinner
ds-center">
+ <h4>Saving changelog configuration ...</h4>
+ <Spinner className="ds-margin-top-lg" loading
size="md" />
+ </div>;
+ } else if (this.props.loading) {
+ clPage =
+ <div className="ds-loading-spinner ds-center">
+ <h4>Loading changelog configuration ...</h4>
+ <Spinner className="ds-margin-top-lg" loading
size="md" />
+ </div>;
+ } else {
+ clPage =
+ <div>
+ <Row>
+ <Col sm={5} className="ds-word-wrap">
+ <h4>
+ Replication Changelog
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh"
title="Refresh changelog settings"
+ onClick={this.props.reload}
+ />
+ </h4>
+ </Col>
+ <Col sm={7}>
+ <Button
+ className="ds-float-right"
+ bsStyle="danger"
+ onClick={this.confirmChangelogDelete}
+
+ Delete Changelog
+ </Button>
+ </Col>
+ </Row>
+ <Form horizontal>
+ <hr />
+ <Row className="ds-margin-top" title="The
filesystem location of the replication changelog database">
+ <Col componentClass={ControlLabel} sm={4}>
+ Changelog Directory
+ </Col>
+ <Col sm={8}>
+ <input value={this.state.clDir} id="clDir"
onChange={this.handleChange} className={this.state.errObj.clDir ?
"ds-input-auto-bad" : "ds-input-auto"} />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Changelog
trimming parameter. Set the maximum number of changelog entries allowed in the database
(nsslapd-changelogmaxentries).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Changelog Maximum Entries
+ </Col>
+ <Col sm={8}>
+ <input value={this.state.clMaxEntries}
id="clMaxEntries" onChange={this.handleChange}
className="ds-input-auto" />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Changelog
trimming parameter. This set the maximum age of a changelog entry. It is recommended to
use the same value as the Replication Purge Delay. (nsslapd-changelogmaxage).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Changelog Maximum Age
+ </Col>
+ <Col sm={8}>
+ <input value={this.state.clMaxAge}
id="clMaxAge" onChange={this.handleChange} className="ds-input-auto"
/>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The
changelog trimming interval. Set how often the changelog checks if there are entries that
can be purged from the changelog based on the trimming parameters
(nsslapd-changelogtrim-interval).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Changelog Trimming Interval
+ </Col>
+ <Col sm={8}>
+ <input value={this.state.clTrimInt}
id="clTrimInt" onChange={this.handleChange} className="ds-input-auto"
/>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The
changelog compaction interval. Set how often the changelog will compact itself, meaning
remove empty/trimmed database slots. The default is 30 days.
(nsslapd-changelogcompactdb-interval).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Changelog Compaction Interval
+ </Col>
+ <Col sm={8}>
+ <input value={this.state.clCompactInt}
id="clCompactInt" onChange={this.handleChange}
className="ds-input-auto" />
+ </Col>
+ </Row>
+ <Row hidden className="ds-margin-top"
title="TLS must first be enabled in the server for change encryption to work. Please
consult Administration Guide for more details.">
+ <Col componentClass={ControlLabel} sm={4}>
+ Changelog Encryption
+ </Col>
+ <Col sm={2}>
+ <Checkbox
+ id="clEncrypt"
+ checked={this.state.clEncrypt}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg">
+ <Col sm={2}>
+ <Button
+ bsStyle="primary"
+ onClick={this.saveSettings}
+
+
Save
+ </Button>
+ </Col>
+ </Row>
+ </Form>
+ </div>;
+ }
+
+ return (
+ <div>
+ {clPage}
+ <ConfirmPopup
+ showModal={this.state.showConfirmDelete}
+ closeHandler={this.closeConfirmDelete}
+ actionFunc={this.deleteChangelog}
+ msg="Are you sure you want to delete the changelog?"
+ msgContent="This will invalidate all replication agreements, and
they will need to be reinitialized."
+ />
+ </div>
+ );
+ }
+}
+
+Changelog.propTypes = {
+ serverId: PropTypes.string,
+ clDir: PropTypes.string,
+ clMaxEntries: PropTypes.string,
+ clMaxAge: PropTypes.string,
+ clCompactInt: PropTypes.string,
+ clTrimInt: PropTypes.string,
+ clEncrypt: PropTypes.bool,
+ addNotification: PropTypes.func,
+ reload: PropTypes.func,
+ enableTree: PropTypes.func,
+};
+
+Changelog.defaultProps = {
+ serverId: "",
+ clDir: "",
+ clMaxEntries: "",
+ clMaxAge: "",
+ clCompactInt: "",
+ clTrimInt: "",
+ clEncrypt: false,
+ addNotification: noop,
+ reload: noop,
+ enableTree: noop,
+};
diff --git a/src/cockpit/389-console/src/lib/replication/replConfig.jsx
b/src/cockpit/389-console/src/lib/replication/replConfig.jsx
new file mode 100644
index 0000000..2bec513
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/replConfig.jsx
@@ -0,0 +1,640 @@
+import cockpit from "cockpit";
+import React from "react";
+import { log_cmd, valid_dn } from "../tools.jsx";
+import { ConfirmPopup } from "../notifications.jsx";
+import CustomCollapse from "../customCollapse.jsx";
+import { ManagerTable } from "./replTables.jsx";
+import { AddManagerModal, ChangeReplRoleModal } from "./replModals.jsx";
+import {
+ Button,
+ Row,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Spinner,
+ // noop,
+} from "patternfly-react";
+// import PropTypes from "prop-types";
+import "../../css/ds.css";
+
+export class ReplConfig extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ saving: false,
+ showConfirmManagerDelete: false,
+ showAddManagerModal: false,
+ showPromoteDemoteModal: false,
+ addManagerSpinning: false,
+ roleChangeSpinning: false,
+ manager: "cn=replication manager,cn=config",
+ manager_passwd: "",
+ manager_passwd_confirm: "",
+ newRole: this.props.role == "Master" ? "Hub" :
"Master",
+ newRID: "1",
+ modalChecked: false,
+ errObj: {},
+ // Config Settings
+ nsds5replicabinddn: this.props.data['nsds5replicabinddn'],
+ nsds5replicabinddngroup: this.props.data['nsds5replicabinddngroup'],
+ nsds5replicabinddngroupcheckinterval:
this.props.data['nsds5replicabinddngroupcheckinterval'],
+ nsds5replicareleasetimeout:
this.props.data['nsds5replicareleasetimeout'],
+ nsds5replicapurgedelay: this.props.data['nsds5replicapurgedelay'],
+ nsds5replicatombstonepurgeinterval:
this.props.data['nsds5replicatombstonepurgeinterval'],
+ nsds5replicaprecisetombstonepurging:
this.props.data['nsds5replicaprecisetombstonepurging'],
+ nsds5replicaprotocoltimeout:
this.props.data['nsds5replicaprotocoltimeout'],
+ nsds5replicabackoffmin: this.props.data['nsds5replicabackoffmin'],
+ nsds5replicabackoffmax: this.props.data['nsds5replicabackoffmax'],
+ // Original settings
+ _nsds5replicabinddn: this.props.data['nsds5replicabinddn'],
+ _nsds5replicabinddngroup:
this.props.data['nsds5replicabinddngroup'],
+ _nsds5replicabinddngroupcheckinterval:
this.props.data['nsds5replicabinddngroupcheckinterval'],
+ _nsds5replicareleasetimeout:
this.props.data['nsds5replicareleasetimeout'],
+ _nsds5replicapurgedelay: this.props.data['nsds5replicapurgedelay'],
+ _nsds5replicatombstonepurgeinterval:
this.props.data['nsds5replicatombstonepurgeinterval'],
+ _nsds5replicaprecisetombstonepurging:
this.props.data['nsds5replicaprecisetombstonepurging'],
+ _nsds5replicaprotocoltimeout:
this.props.data['nsds5replicaprotocoltimeout'],
+ _nsds5replicabackoffmin: this.props.data['nsds5replicabackoffmin'],
+ _nsds5replicabackoffmax: this.props.data['nsds5replicabackoffmax'],
+
+ };
+
+ this.confirmManagerDelete = this.confirmManagerDelete.bind(this);
+ this.closeConfirmManagerDelete = this.closeConfirmManagerDelete.bind(this);
+ this.deleteManager = this.deleteManager.bind(this);
+ this.showAddManager = this.showAddManager.bind(this);
+ this.closeAddManagerModal = this.closeAddManagerModal.bind(this);
+ this.addManager = this.addManager.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleManagerChange = this.handleManagerChange.bind(this);
+ this.showPromoteDemoteModal = this.showPromoteDemoteModal.bind(this);
+ this.closePromoteDemoteModal = this.closePromoteDemoteModal.bind(this);
+ this.doRoleChange = this.doRoleChange.bind(this);
+ this.saveConfig = this.saveConfig.bind(this);
+ }
+
+ doRoleChange (changeType) {
+ let action = "demote";
+ if (changeType == "Promoting") {
+ action = "promote";
+ }
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
'replication', action,
+ '--suffix=' + this.props.suffix, "--newrole=" +
this.state.newRole];
+ if (this.state.newRole == "Master") {
+ let ridNum = parseInt(this.state.newRID, 10);
+ if (ridNum < 1 || ridNum >= 65535) {
+ this.props.addNotification(
+ "error",
+ "A Master replica requires a unique numerical identifier.
Please enter an ID between 1 and 65534"
+ );
+ return;
+ }
+ cmd.push("--replica-id=" + this.state.newRID);
+ }
+ this.setState({
+ roleChangeSpinning: true
+ });
+ log_cmd('doRoleChange', 'change replica role', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload();
+ this.props.addNotification(
+ "success",
+ `Successfully ${action}d replica to a ${this.state.newRole}`
+ );
+ this.setState({
+ roleChangeSpinning: false,
+ showPromoteDemoteModal: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reload();
+ this.props.addNotification(
+ "error",
+ `Failed to ${action} replica - ${errMsg.desc}`
+ );
+ this.setState({
+ roleChangeSpinning: false,
+ showPromoteDemoteModal: false
+ });
+ });
+ }
+
+ closePromoteDemoteModal () {
+ this.setState({
+ showPromoteDemoteModal: false
+ });
+ }
+
+ showPromoteDemoteModal () {
+ this.setState({
+ showPromoteDemoteModal: true,
+ modalChecked: false,
+ });
+ }
+
+ closeAddManagerModal () {
+ this.setState({
+ showAddManagerModal: false
+ });
+ }
+
+ showAddManager () {
+ this.setState({
+ showAddManagerModal: true,
+ manager: "cn=replication manager,cn=config",
+ manager_passwd: "",
+ manager_passwd_confirm: "",
+ errObj: {
+ manager_passwd: true,
+ manager_passwd_confirm: true,
+ }
+ });
+ }
+
+ addManager () {
+ // Validate DN
+ if (!valid_dn(this.state.manager)) {
+ this.props.addNotification(
+ "error",
+ `Invalid DN for the Replication Manager: ${this.state.manager}`
+ );
+ return;
+ }
+
+ if (this.state.manager_passwd == "" ||
this.state.manager_passwd_confirm == "") {
+ this.props.addNotification(
+ "error", "You must provide a password for the Replication
Manager"
+ );
+ return;
+ }
+ if (this.state.manager_passwd != this.state.manager_passwd_confirm) {
+ this.props.addNotification(
+ "error", "Passwords do not match"
+ );
+ return;
+ }
+
+ this.setState({
+ addManagerSpinning: true
+ });
+
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-"
+ this.props.serverId + ".socket",
+ "replication", "create-manager", "--suffix=" +
this.props.suffix, "--name=" + this.state.manager,
+ "--passwd=" + this.state.manager_passwd
+ ];
+
+ log_cmd("addManager", "Adding Replication Manager", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reloadConfig(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ `Successfully added Replication Manager`
+ );
+ this.setState({
+ addManagerSpinning: false,
+ showAddManagerModal: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reloadConfig(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Failure adding Replication Manager - ${errMsg.desc}`
+ );
+ this.setState({
+ addManagerSpinning: false,
+ showAddManagerModal: false
+ });
+ });
+ }
+
+ handleChange(e) {
+ let 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
+ });
+ }
+
+ handleManagerChange(e) {
+ let value = e.target.value;
+ let attr = e.target.id;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ if (value == "") {
+ valueErr = true;
+ }
+ // Handle password chnages
+ if (attr == "manager_passwd") {
+ if (value != this.state.manager_passwd_confirm) {
+ // No match
+ valueErr = true;
+ } else {
+ errObj[attr] = false;
+ errObj['manager_passwd_confirm'] = false;
+ }
+ } else if (attr == "manager_passwd_confirm") {
+ if (value != this.state.manager_passwd) {
+ // No match
+ valueErr = true;
+ } else {
+ errObj[attr] = false;
+ errObj['manager_passwd'] = false;
+ }
+ }
+
+ errObj[attr] = valueErr;
+ this.setState({
+ [attr]: value,
+ errObj: errObj
+ });
+ }
+
+ confirmManagerDelete (item) {
+ this.setState({
+ showConfirmManagerDelete: true,
+ manager: item.name,
+ });
+ }
+
+ closeConfirmManagerDelete () {
+ this.setState({
+ showConfirmManagerDelete: false,
+ manager: "",
+ });
+ }
+
+ deleteManager (dn) {
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-"
+ this.props.serverId + ".socket",
+ "replication", "delete-manager", "--suffix=" +
this.props.suffix, "--name=" + dn
+ ];
+ log_cmd("deleteManager", "Deleting Replication Manager",
cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reloadConfig(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ `Successfully removed Replication Manager`
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reloadConfig(this.props.suffix);
+ this.props.addNotification(
+ "error",
+ `Failure removing Replication Manager - ${errMsg.desc}`
+ );
+ });
+ }
+
+ saveConfig () {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' +
this.props.serverId + '.socket',
+ 'replication', 'set', '--suffix=' +
this.props.suffix
+ ];
+
+ if (this.state.nsds5replicabackoffmax != this.state._nsds5replicabackoffmax) {
+ cmd.push("--repl-backoff-max=" +
this.state.nsds5replicabackoffmax);
+ }
+ if (this.state.nsds5replicabackoffmin != this.state._nsds5replicabackoffmin) {
+ cmd.push("--repl-backoff-min=" +
this.state.nsds5replicabackoffmin);
+ }
+
+ if (this.state.nsds5replicaprotocoltimeout !=
this.state._nsds5replicaprotocoltimeout) {
+ cmd.push("--repl-protocol-timeout=" +
this.state.nsds5replicaprotocoltimeout);
+ }
+ if (this.state.nsds5replicaprecisetombstonepurging !=
this.state._nsds5replicaprecisetombstonepurging) {
+ if (this.state.nsds5replicaprecisetombstonepurging) {
+ cmd.push("--repl-fast-tombstone-purging=on");
+ } else {
+ cmd.push("--repl-fast-tombstone-purging=off");
+ }
+ }
+ if (this.state.nsds5replicatombstonepurgeinterval !=
this.state._nsds5replicatombstonepurgeinterval) {
+ cmd.push("--repl-tombstone-purge-interval=" +
this.state.nsds5replicatombstonepurgeinterval);
+ }
+ if (this.state.nsds5replicabinddngroup != this.state._nsds5replicabinddngroup) {
+ cmd.push("--repl-bind-group=" +
this.state.nsds5replicabinddngroup);
+ }
+ if (this.state.nsds5replicabinddngroupcheckinterval !=
this.state._nsds5replicabinddngroupcheckinterval) {
+ cmd.push("--repl-bind-group-interval=" +
this.state.nsds5replicabinddngroupcheckinterval);
+ }
+ if (this.state.nsds5replicareleasetimeout !=
this.state._nsds5replicareleasetimeout) {
+ cmd.push("--repl-release-timeout=" +
this.state.nsds5replicareleasetimeout);
+ }
+ if (this.state.nsds5replicapurgedelay != this.state._nsds5replicapurgedelay) {
+ cmd.push("--repl-purge-delay=" +
this.state.nsds5replicapurgedelay);
+ }
+ if (cmd.length > 6) {
+ this.setState({
+ // Start the spinner
+ saving: true
+ });
+ log_cmd("saveConfig", "Applying replication changes",
cmd);
+ let msg = "Successfully updated replication configuration.";
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reloadConfig(this.props.suffix);
+ this.props.addNotification(
+ "success",
+ msg
+ );
+ this.setState({
+ saving: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.reloadConfig(this.props.suffix);
+ this.setState({
+ saving: false
+ });
+ let msg = errMsg.desc;
+ if ('info' in errMsg) {
+ msg = errMsg.desc + " - " + errMsg.info;
+ }
+ this.props.addNotification(
+ "error",
+ `Error updating replication configuration - ${msg}`
+ );
+ });
+ }
+ }
+
+ render() {
+ let content = "";
+ let roleButton = "";
+ let manager_rows = [];
+ for (let row of this.props.data.nsds5replicabinddn) {
+ manager_rows.push({'name': row});
+ }
+
+ if (this.props.role == "Master") {
+ roleButton =
+ <Button
+ bsStyle="primary"
+ onClick={this.showPromoteDemoteModal}
+ title="Demote this Master replica to a Hub or Consumer"
+ className="ds-inline-btn"
+
+ Demote
+ </Button>;
+ } else if (this.props.role == "Hub") {
+ roleButton =
+ <Button
+ bsStyle="primary"
+ onClick={this.showPromoteDemoteModal}
+ title="Promote or Demote this Hub replica to a Master or
Consumer"
+ className="ds-inline-btn"
+
+ Promote/Demote
+ </Button>;
+ } else {
+ // Consumer
+ roleButton =
+ <Button
+ bsStyle="primary"
+ onClick={this.showPromoteDemoteModal}
+ title="Promte this Consumer replica to a Master or Hub"
+ className="ds-inline-btn"
+
+ Promote
+ </Button>;
+ }
+
+ if (this.state.saving) {
+ content =
+ <div className="ds-margin-top-xxlg ds-loading-spinner-tree
ds-center">
+ <h4>Saving replication configuration ...</h4>
+ <Spinner loading size="md" />
+ </div>;
+ } else {
+ content =
+ <div className="ds-margin-top-xxlg ds-left-margin">
+ <Form horizontal>
+ <Row className="ds-margin-top-xlg">
+ <Col sm={2}>
+ <ControlLabel>
+ Replica Role
+ </ControlLabel>
+ </Col>
+ <Col sm={6}>
+ <input type="text"
defaultValue={this.props.role} size="10" disabled />{roleButton}
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col sm={2}>
+ <ControlLabel>
+ Replica ID
+ </ControlLabel>
+ </Col>
+ <Col sm={4}>
+ <input type="text"
defaultValue={this.props.data.nsds5replicaid} size="10" disabled />
+ </Col>
+ </Row>
+ <hr />
+ <Row className="ds-margin-top">
+ <Col sm={9}>
+ <ManagerTable
+ rows={manager_rows}
+ confirmDelete={this.confirmManagerDelete}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col sm={4}>
+ <Button
+ bsStyle="primary"
+ onClick={this.showAddManager}
+
+
Add Replication Manager
+ </Button>
+ </Col>
+ </Row>
+ <CustomCollapse>
+ <div className="ds-margin-top">
+ <div className="ds-margin-left">
+ <Row className="ds-margin-top-lg"
title="The DN of the replication manager">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind DN Group
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsds5replicabinddngroup"
+ type="text"
+
defaultValue={this.state.nsds5replicabinddngroup}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="The interval to check for any changes in the group memebrship specified in the
Bind DN Group and automatically rebuilds the list for the replication managers
accordingly. (nsds5replicabinddngroupcheckinterval).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind DN Group Check Interval
+ </Col>
+ <Col sm={6}>
+ <FormControl
+
id="nsds5replicabinddngroupcheckinterval"
+ type="text"
+
defaultValue={this.state.nsds5replicabinddngroupcheckinterval}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="This controls the maximum age of deleted entries (tombstone entries), and
entry state information. (nsds5replicapurgedelay).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Purge Delay
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsds5replicapurgedelay"
+ type="text"
+
defaultValue={this.state.nsds5replicapurgedelay}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="This attribute specifies the time interval in seconds between purge operation
cycles. (nsds5replicatombstonepurgeinterval).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Tombstone Purge Interval
+ </Col>
+ <Col sm={6}>
+ <FormControl
+
id="nsds5replicatombstonepurgeinterval"
+ type="text"
+
defaultValue={this.state.nsds5replicatombstonepurgeinterval}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="A time limit (in seconds) that tells a replication session to yield if other
replicas are trying to acquire this one (nsds5replicareleasetimeout).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replica Release Timeout
+ </Col>
+ <Col sm={6}>
+ <FormControl
+
id="nsds5replicareleasetimeout"
+ type="text"
+
defaultValue={this.state.nsds5replicareleasetimeout}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="A timeout on how long to wait before stopping a replication session when the
server is being stopped, replication is being disabled, or when removing a replication
agreement. (nsds5replicaprotocoltimeout).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication Timeout
+ </Col>
+ <Col sm={6}>
+ <FormControl
+
id="nsds5replicaprotocoltimeout"
+ type="text"
+
defaultValue={this.state.nsds5replicaprotocoltimeout}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="This is the minimum amount of time in seconds that a replication will go into
a backoff state (nsds5replicabackoffmin).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Back Off Minimum
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsds5replicabackoffmin"
+ type="text"
+
defaultValue={this.state.nsds5replicabackoffmin}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="This is the maximum amount of time in seconds that a replication will go into
a backoff state (nsds5replicabackoffmax).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Back Off Maximum
+ </Col>
+ <Col sm={6}>
+ <FormControl
+ id="nsds5replicabackoffmax"
+ type="text"
+
defaultValue={this.state.nsds5replicabackoffmax}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="Enables faster tombstone purging
(nsds5replicaprecisetombstonepurging)">
+ <Col componentClass={ControlLabel} sm={4}>
+ Fast Tombstone Purging
+ </Col>
+ <Col sm={6}>
+ <Checkbox
+
id="nsds5replicaprecisetombstonepurging"
+
defaultChecked={this.props.data.nsds5replicaprecisetombstonepurging}
+ onChange={this.handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg">
+ <Col componentClass={ControlLabel} sm={4}>
+ <Button
+ bsStyle="primary"
+ onClick={this.saveConfig}
+
+
Save Configuration
+ </Button>
+ </Col>
+ </Row>
+ </div>
+ </div>
+ </CustomCollapse>
+ </Form>
+ <ConfirmPopup
+ showModal={this.state.showConfirmManagerDelete}
+ closeHandler={this.closeConfirmManagerDelete}
+ actionFunc={this.deleteManager}
+ actionParam={this.state.manager}
+ msg="Are you sure you want to remove this Replication
Manager?"
+ msgContent={this.state.manager}
+ />
+ <AddManagerModal
+ showModal={this.state.showAddManagerModal}
+ closeHandler={this.closeAddManagerModal}
+ handleChange={this.handleManagerChange}
+ saveHandler={this.addManager}
+ spinning={this.state.addManagerSpinning}
+ error={this.state.errObj}
+ />
+ <ChangeReplRoleModal
+ showModal={this.state.showPromoteDemoteModal}
+ closeHandler={this.closePromoteDemoteModal}
+ handleChange={this.handleChange}
+ saveHandler={this.doRoleChange}
+ spinning={this.state.roleChangeSpinning}
+ role={this.props.role}
+ newRole={this.state.newRole}
+ checked={this.state.modalChecked}
+ />
+ </div>;
+ }
+
+ return (
+ <div>
+ {content}
+ </div>
+ );
+ }
+}
diff --git a/src/cockpit/389-console/src/lib/replication/replModals.jsx
b/src/cockpit/389-console/src/lib/replication/replModals.jsx
new file mode 100644
index 0000000..0e7a0d9
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/replModals.jsx
@@ -0,0 +1,1684 @@
+import React from "react";
+import {
+ Button,
+ Row,
+ Checkbox,
+ Col,
+ ControlLabel,
+ Form,
+ FormControl,
+ Icon,
+ Modal,
+ noop,
+ Spinner,
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import CustomCollapse from "../customCollapse.jsx";
+import { Typeahead } from "react-bootstrap-typeahead";
+import "../../css/ds.css";
+
+export class WinsyncAgmtModal extends React.Component {
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ saveHandler,
+ handleChange,
+ handleFracChange,
+ spinning,
+ agmtName,
+ agmtHost,
+ agmtPort,
+ agmtProtocol,
+ agmtBindDN,
+ agmtBindPW,
+ agmtBindPWConfirm,
+ agmtFracAttrs,
+ agmtSync,
+ agmtSyncMon,
+ agmtSyncTue,
+ agmtSyncWed,
+ agmtSyncThu,
+ agmtSyncFri,
+ agmtSyncSat,
+ agmtSyncSun,
+ agmtStartTime,
+ agmtEndTime,
+ agmtSyncGroups,
+ agmtSyncUsers,
+ agmtWinDomain,
+ agmtWinSubtree,
+ agmtDSSubtree,
+ agmtOneWaySync, // "both", "toWindows",
"fromWindows"
+ agmtSyncInterval,
+ availAttrs,
+ error,
+ errorMsg,
+ errorScheduleMsg,
+ } = this.props;
+ let spinner = "";
+ let saveDisabled = !this.props.saveOK;
+ let title = "Create";
+ let initRow = "";
+ let errMsgClass = "ds-center ds-modal-error";
+ let errMsg = errorMsg;
+ let name = "agmt-modal";
+
+ if (this.props.edit) {
+ title = "Edit";
+ name = "agmt-modal-edit";
+ } else {
+ initRow =
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Consumer Initialization
+ </Col>
+ <Col sm={8}>
+ <select className="btn btn-default dropdown"
id="agmtInit" name={name} onChange={handleChange}>
+ <option value="noinit">Do Not
Initialize</option>
+ <option value="online-init">Do Online
Initialization</option>
+ </select>
+ </Col>
+ </Row>;
+ }
+
+ if (errMsg == "") {
+ // To keep the modal nice and stable during input validation
+ // We need text that is invisible to keep the modal input from
+ // jumping around
+ errMsgClass = "ds-center ds-clear-text";
+ errMsg = "No errors";
+ }
+
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md" />Creating
winsync agreement ...
+ </div>
+ </Row>;
+ }
+
+ let scheduleRow =
+ <div className="ds-left-indent-md">
+ <Row className="ds-margin-top-lg">
+ <Col sm={12}>
+ <i>Custom Synchronization Schedule</i>
+ </Col>
+ </Row>
+ <hr />
+ <div className="ds-indent">
+ <Row>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncMon"
+ onChange={handleChange}
+ name={name}
+ title="Monday"
+ defaultChecked={agmtSyncMon}
+
+ Mon
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncWed"
+ onChange={handleChange}
+ title="Wednesday"
+ name={name}
+ defaultChecked={agmtSyncWed}
+
+ Wed
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncFri"
+ onChange={handleChange}
+ title="Friday"
+ name={name}
+ defaultChecked={agmtSyncFri}
+
+ Fri
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncSun"
+ onChange={handleChange}
+ title="Sunday"
+ name={name}
+ defaultChecked={agmtSyncSun}
+
+ Sun
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncTue"
+ onChange={handleChange}
+ title="Tuesday"
+ name={name}
+ defaultChecked={agmtSyncTue}
+
+ Tue
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncThu"
+ onChange={handleChange}
+ title="Thursday"
+ name={name}
+ defaultChecked={agmtSyncThu}
+
+ Thu
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncSat"
+ onChange={handleChange}
+ title="Saturday"
+ name={name}
+ defaultChecked={agmtSyncSat}
+
+ Sat
+ </Checkbox>
+ </Col>
+ </Row>
+ </div>
+ <Row className="ds-margin-top">
+ <Col sm={10}>
+ <p
className="ds-modal-error">{errorScheduleMsg}</p>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Time to start
initiating replication sessions">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication Start Time
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtStartTime"
+ type="time"
+ name={name}
+ className={error.agmtStartTime ? "ds-input-bad" :
""}
+ onChange={handleChange}
+ defaultValue={agmtStartTime}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Time to
initiating replication sessions">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication End Time
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtEndTime"
+ type="time"
+ name={name}
+ className={error.agmtEndTime ? "ds-input-bad" :
""}
+ onChange={handleChange}
+ defaultValue={agmtEndTime}
+ />
+ </Col>
+ </Row>
+ </div>;
+
+ if (agmtSync) {
+ scheduleRow = "";
+ }
+ 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>
+ {title} Winsync Agreement
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <Row className="ds-margin-top">
+ <Col sm={10}>
+ <p className={errMsgClass}>{errMsg}</p>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Agreement Name
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtName"
+ type="text"
+ name={name}
+ className={error.agmtName ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtName}
+ disabled={this.props.edit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Windows AD Host
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtHost"
+ type="text"
+ name={name}
+ className={error.agmtHost ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtHost}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Windows AD Port
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtPort"
+ type="text"
+ name={name}
+ className={error.agmtPort ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtPort}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind DN
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtBindDN"
+ type="text"
+ name={name}
+ className={error.agmtBindDN ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ autoComplete="false"
+ defaultValue={agmtBindDN}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtBindPW"
+ type="password"
+ name={name}
+ className={error.agmtBindPW ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ autoComplete="new-password"
+ defaultValue={agmtBindPW}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Confirm Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtBindPWConfirm"
+ type="password"
+ name={name}
+ className={error.agmtBindPWConfirm ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ autoComplete="new-password"
+ defaultValue={agmtBindPWConfirm}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Windows Domain Name
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="agmtWinDomain"
+ name={name}
+ className={error.agmtWinDomain ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtWinDomain}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The
Active Directory subtree to synchronize">
+ <Col componentClass={ControlLabel} sm={4}>
+ Windows Subtree
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="agmtWinSubtree"
+ name={name}
+ className={error.agmtWinSubtree ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtWinSubtree}
+ placeholder="e.g.
cn=Users,dc=domain,dc=com"
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="Directory Server subtree to synchronize">
+ <Col componentClass={ControlLabel} sm={4}>
+ DS Subtree
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="agmtDSSubtree"
+ name={name}
+ className={error.agmtDSSubtree ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtDSSubtree}
+ placeholder="e.g.
ou=People,dc=domain,dc=com"
+ />
+ </Col>
+ </Row>
+ {initRow}
+ <CustomCollapse
+ className="ds-margin-top"
+ textOpened="Hide Advanced Settings"
+ textClosed="Show Advanced Setting"
+
+ <div
className="ds-margin-left">
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Connection Protocol
+ </Col>
+ <Col sm={8}>
+ <select className="btn btn-default
dropdown" id="agmtProtocol" defaultValue={agmtProtocol} name={name}
onChange={handleChange}>
+ <option>LDAPS</option>
+ <option title="Currently not
recommended">StartTLS</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Synchronization Direction
+ </Col>
+ <Col sm={8}>
+ <select className="btn btn-default
dropdown" defaultValue={agmtOneWaySync} id="agmtOneWaySync" name={name}
onChange={handleChange}>
+ <option title="Synchronization in
both directions (default behavior).">both</option>
+ <option title="Only synchronize
Directory Server updates to Windows.">toWindows</option>
+ <option title="Only synchronize
Windows updates to Directory Server.">fromWindows</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="The interval to check for updates on Windows. Default is 300
seconds">
+ <Col componentClass={ControlLabel} sm={4}>
+ Synchronization Interval
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="agmtSyncInterval"
+ name={name}
+ className={error.agmtSyncInterval ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtSyncInterval}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="Attribute to exclude from replication">
+ <Col componentClass={ControlLabel} sm={4}>
+ Exclude Attributes
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ multiple
+ onChange={handleFracChange}
+ selected={agmtFracAttrs}
+ options={availAttrs}
+ name={name}
+ newSelectionPrefix="Add a attribute:
"
+ placeholder="Start typing an
attribute..."
+ id="agmtFracAttrs"
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-med">
+ <Col>
+ <Checkbox
+ id="agmtSyncGroups"
+ onChange={handleChange}
+ name={name}
+ defaultChecked={agmtSyncGroups}
+
+
Synchronize New Windows Groups
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col>
+ <Checkbox
+ id="agmtSyncUsers"
+ onChange={handleChange}
+ name={name}
+ defaultChecked={agmtSyncUsers}
+
+
Synchronize New Windows Users
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col>
+ <Checkbox
+ id="agmtSync"
+ defaultChecked={agmtSync}
+ onChange={handleChange}
+ name={name}
+ title="Always keep replication in
synchronization, or use a specific schedule by unchecking the box."
+
+
Keep Replication In Constant Synchronization
+ </Checkbox>
+ </Col>
+ </Row>
+ {scheduleRow}
+ </div>
+ </CustomCollapse>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ disabled={saveDisabled}
+
+ Save
Agreement
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+export class ReplAgmtModal extends React.Component {
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ saveHandler,
+ handleChange,
+ handleStripChange,
+ handleFracChange,
+ handleFracInitChange,
+ spinning,
+ agmtName,
+ agmtHost,
+ agmtPort,
+ agmtProtocol,
+ agmtBindMethod,
+ agmtBindDN,
+ agmtBindPW,
+ agmtBindPWConfirm,
+ agmtStripAttrs,
+ agmtFracAttrs,
+ agmtFracInitAttrs,
+ agmtSync,
+ agmtSyncMon,
+ agmtSyncTue,
+ agmtSyncWed,
+ agmtSyncThu,
+ agmtSyncFri,
+ agmtSyncSat,
+ agmtSyncSun,
+ agmtStartTime,
+ agmtEndTime,
+ availAttrs,
+ error,
+ errorMsg,
+ errorScheduleMsg,
+ } = this.props;
+ let spinner = "";
+ let saveDisabled = !this.props.saveOK;
+ let title = "Create";
+ let initRow = "";
+ let errMsgClass = "ds-center ds-modal-error";
+ let errMsg = errorMsg;
+ let name = "agmt-modal";
+
+ if (this.props.edit) {
+ title = "Edit";
+ name = "agmt-modal-edit";
+ } else {
+ initRow =
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Consumer Initialization
+ </Col>
+ <Col sm={8}>
+ <select className="btn btn-default dropdown"
id="agmtInit" name={name} onChange={handleChange}>
+ <option value="noinit">Do Not
Initialize</option>
+ <option value="online-init">Do Online
Initialization</option>
+ </select>
+ </Col>
+ </Row>;
+ }
+
+ if (errMsg == "") {
+ // To keep the modal nice and stable during input validation
+ // We need text that is invisible to keep the modal input from
+ // jumping around
+ errMsgClass = "ds-center ds-clear-text";
+ errMsg = "No errors";
+ }
+
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md" />Creating
replication agreement ...
+ </div>
+ </Row>;
+ }
+
+ let scheduleRow =
+ <div className="ds-left-indent-md">
+ <Row className="ds-margin-top-lg">
+ <Col sm={12}>
+ <i>Custom Synchronization Schedule</i>
+ </Col>
+ </Row>
+ <hr />
+ <div className="ds-indent">
+ <Row>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncMon"
+ onChange={handleChange}
+ name={name}
+ title="Monday"
+ defaultChecked={agmtSyncMon}
+
+ Mon
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncWed"
+ onChange={handleChange}
+ title="Wednesday"
+ name={name}
+ defaultChecked={agmtSyncWed}
+
+ Wed
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncFri"
+ onChange={handleChange}
+ title="Friday"
+ name={name}
+ defaultChecked={agmtSyncFri}
+
+ Fri
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncSun"
+ onChange={handleChange}
+ title="Sunday"
+ name={name}
+ defaultChecked={agmtSyncSun}
+
+ Sun
+ </Checkbox>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncTue"
+ onChange={handleChange}
+ title="Tuesday"
+ name={name}
+ defaultChecked={agmtSyncTue}
+
+ Tue
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncThu"
+ onChange={handleChange}
+ title="Thursday"
+ name={name}
+ defaultChecked={agmtSyncThu}
+
+ Thu
+ </Checkbox>
+ </Col>
+ <Col sm={3}>
+ <Checkbox
+ id="agmtSyncSat"
+ onChange={handleChange}
+ title="Saturday"
+ name={name}
+ defaultChecked={agmtSyncSat}
+
+ Sat
+ </Checkbox>
+ </Col>
+ </Row>
+ </div>
+ <Row className="ds-margin-top">
+ <Col sm={10}>
+ <p
className="ds-modal-error">{errorScheduleMsg}</p>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Time to start
initiating replication sessions">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication Start Time
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtStartTime"
+ type="time"
+ name={name}
+ className={error.agmtStartTime ? "ds-input-bad" :
""}
+ onChange={handleChange}
+ defaultValue={agmtStartTime}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="Time to
initiating replication sessions">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication End Time
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtEndTime"
+ type="time"
+ name={name}
+ className={error.agmtEndTime ? "ds-input-bad" :
""}
+ onChange={handleChange}
+ defaultValue={agmtEndTime}
+ /> </Col>
+ </Row>
+ </div>;
+
+ if (agmtSync) {
+ scheduleRow = "";
+ }
+ 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>
+ {title} Replication Agreement
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <Row className="ds-margin-top">
+ <Col sm={10}>
+ <p className={errMsgClass}>{errMsg}</p>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Agreement Name
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtName"
+ type="text"
+ name={name}
+ className={error.agmtName ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtName}
+ disabled={this.props.edit}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Consumer Host
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtHost"
+ type="text"
+ name={name}
+ className={error.agmtHost ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtHost}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Consumer Port
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtPort"
+ type="text"
+ name={name}
+ className={error.agmtPort ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ defaultValue={agmtPort}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind DN
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtBindDN"
+ type="text"
+ name={name}
+ className={error.agmtBindDN ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ autoComplete="false"
+ defaultValue={agmtBindDN}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtBindPW"
+ type="password"
+ name={name}
+ className={error.agmtBindPW ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ autoComplete="new-password"
+ defaultValue={agmtBindPW}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Confirm Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ id="agmtBindPWConfirm"
+ type="password"
+ name={name}
+ className={error.agmtBindPWConfirm ?
"ds-input-bad" : ""}
+ onChange={handleChange}
+ autoComplete="new-password"
+ defaultValue={agmtBindPWConfirm}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Connection Protocol
+ </Col>
+ <Col sm={8}>
+ <select className="btn btn-default
dropdown" id="agmtProtocol" defaultValue={agmtProtocol} name={name}
onChange={handleChange}>
+ <option>LDAP</option>
+ <option>LDAPS</option>
+ <option title="Currently not
recommended">StartTLS</option>
+ </select>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top">
+ <Col componentClass={ControlLabel} sm={4}>
+ Authentication Method
+ </Col>
+ <Col sm={8}>
+ <select className="btn btn-default
dropdown" defaultValue={agmtBindMethod} id="agmtBindMethod" name={name}
onChange={handleChange}>
+ <option title="Use bind DN and
password">SIMPLE</option>
+ <option title="Use SSL Client
Certificate">SSLCLIENTAUTH</option>
+ <option title="Use SASL
Digest-MD5">SASL/DIGEST-MD5</option>
+ <option title="Use SASL
GSSAPI">SASL/GSSAPI</option>
+ </select>
+ </Col>
+ </Row>
+ {initRow}
+ <CustomCollapse
+ className="ds-margin-top"
+ textOpened="Hide Advanced Settings"
+ textClosed="Show Advanced Settings"
+
+ <div
className="ds-margin-left">
+ <Row className="ds-margin-top"
title="Attribute to exclude from replication">
+ <Col componentClass={ControlLabel} sm={4}>
+ Exclude Attributes
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ multiple
+ onChange={handleFracChange}
+ selected={agmtFracAttrs}
+ options={availAttrs}
+ name={name}
+ newSelectionPrefix="Add a attribute:
"
+ placeholder="Start typing an
attribute..."
+ id="agmtFracAttrs"
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="Attribute to exclude from replica Initializations">
+ <Col componentClass={ControlLabel} sm={4}>
+ Exclude Init Attributes
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ multiple
+ onChange={handleFracInitChange}
+ selected={agmtFracInitAttrs}
+ options={availAttrs}
+ name={name}
+ newSelectionPrefix="Add a attribute:
"
+ placeholder="Start typing an
attribute..."
+ id="agmtFracInitAttrs"
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="Attributes to strip from a replication update">
+ <Col componentClass={ControlLabel} sm={4}>
+ Strip Attributes
+ </Col>
+ <Col sm={8}>
+ <Typeahead
+ multiple
+ onChange={handleStripChange}
+ selected={agmtStripAttrs}
+ options={availAttrs}
+ name={name}
+ newSelectionPrefix="Add a attribute:
"
+ placeholder="Start typing an
attribute..."
+ id="agmtStripAttrs"
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-med">
+ <Col sm={8}>
+ <Checkbox
+ id="agmtSync"
+ defaultChecked={agmtSync}
+ onChange={handleChange}
+ name={name}
+ title="Always keep replication in
synchronization, or use a specific schedule by unchecking the box."
+
+
Keep Replication In Constant Synchronization
+ </Checkbox>
+ </Col>
+ </Row>
+ {scheduleRow}
+ </div>
+ </CustomCollapse>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ disabled={saveDisabled}
+
+ Save
Agreement
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+export class ChangeReplRoleModal extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ showConfirmPromote: false,
+ showConfirmDemote: false,
+ };
+ }
+
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ role,
+ spinning,
+ checked,
+ } = this.props;
+ let spinner = "";
+ let changeType = "";
+ let roleOptions = [];
+ let ridRow = "";
+ let newRole = this.props.newRole;
+ let saveDisabled = !checked;
+
+ // Set the change type
+ if (role == "Master") {
+ changeType = "Demoting";
+ roleOptions = ["Hub", "Consumer"];
+ } else if (role == "Consumer") {
+ changeType = "Promoting";
+ roleOptions = ["Master", "Hub"];
+ } else {
+ // Hub
+ if (newRole == "Master") {
+ changeType = "Promoting";
+ } else {
+ changeType = "Demoting";
+ }
+ roleOptions = ["Master", "Consumer"];
+ }
+ if (newRole == "Master") {
+ ridRow =
+ <Row className="ds-margin-top-lg" title="Master Replica
Identifier. This must be unique across all the Master replicas in your
environment">
+ <Col componentClass={ControlLabel} sm={2}>
+ Replica ID
+ </Col>
+ <Col sm={4}>
+ <input id="newRID" type="number"
min="1" max="65534"
+ onChange={handleChange} defaultValue="1"
size="10"
+ />
+ </Col>
+ </Row>;
+ }
+
+ let selectOptions = roleOptions.map((role) =>
+ <option key={role} value={role}>{role}</option>
+ );
+
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md" />{changeType}
replica ...
+ </div>
+ </Row>;
+ saveDisabled = true;
+ }
+
+ 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>
+ Change Replica Role
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <h4>Please choose the new replication role you would
like for this suffix</h4>
+ <Row className="ds-margin-top-lg">
+ <Col componentClass={ControlLabel} sm={2}>
+ New Role
+ </Col>
+ <Col sm={4}>
+ <select id="newRole"
onChange={handleChange}>
+ {selectOptions}
+ </select>
+ </Col>
+ </Row>
+ {ridRow}
+ <Row className="ds-margin-top-xlg">
+ <Col sm={12} className="ds-center">
+ <Checkbox
+ id="modalChecked"
+ defaultChecked={checked}
+ onChange={handleChange}
+
+
<b>Yes</b>, I am sure.
+ </Checkbox>
+ </Col>
+ </Row>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={() => {
+ saveHandler(changeType);
+ }}
+ disabled={saveDisabled}
+
+ Change
Role
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+export class AddManagerModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ spinning,
+ error
+ } = this.props;
+ let spinner = "";
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md" />Adding
Replication Manager...
+ </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>
+ Add Replication Manager
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <p>Create a Replication Manager entry, and add it to
the replication configuration for this suffix. If the entry already exists it will be
overwritten with the new credentials.</p>
+ <Row className="ds-margin-top" title="The
DN of the replication manager">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication Manager DN
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="manager"
+ defaultValue="cn=replication
manager,cn=config"
+ className={error.manager ?
"ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="Replication Manager password">
+ <Col componentClass={ControlLabel} sm={4}>
+ Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="password"
+ id="manager_passwd"
+ className={error.manager_passwd ?
"ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="Replication Manager password">
+ <Col componentClass={ControlLabel} sm={4}>
+ Confirm Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="password"
+ id="manager_passwd_confirm"
+ className={error.manager_passwd_confirm ?
"ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+
+ Add
Replication Manager
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+export class EnableReplModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ spinning,
+ role,
+ error
+ } = this.props;
+ let spinner = "";
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="md" />Enabling
Replication ...
+ </div>
+ </Row>;
+ }
+
+ let replicaIDRow = "";
+ if (role == "Master") {
+ replicaIDRow =
+ <Row className="ds-margin-top">
+ <Col sm={3} componentClass={ControlLabel}>
+ Replica ID
+ </Col>
+ <Col sm={9}>
+ <input id="enableRID" type="number"
min="1" max="65534"
+ onChange={handleChange} defaultValue="1"
size="10"
+ />
+ </Col>
+ </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>
+ Enable Replication
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <p>
+ Choose the replication role for this suffix. If it
+ is a Master replica then you must pick a unique ID
+ to identify it among the other Master replicas in your
+ environment. You can optionally define the
authentication
+ information for this replicated suffix, either a Bind DN
and
+ Password, or a Group DN. The replication changelog will
+ also automatically be created if it does not exist.
+ </p>
+ <hr />
+ <Row className="ds-margin-top-lg">
+ <Col sm={3} componentClass={ControlLabel}>
+ Replication Role
+ </Col>
+ <Col sm={9}>
+ <select className="btn btn-default
dropdown" id="enableRole" defaultValue="Master"
onChange={handleChange}>
+ <option>Master</option>
+ <option>Hub</option>
+ <option>Consumer</option>
+ </select>
+ </Col>
+ </Row>
+ {replicaIDRow}
+ <hr />
+ <Row className="ds-margin-top" title="The
DN of the replication manager. If you supply a password the entry will be created in the
server (it will also overwrite the entry is it already exists).">
+ <Col componentClass={ControlLabel} sm={4}>
+ Replication Manager DN
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="enableBindDN"
+ defaultValue="cn=replication
manager,cn=config"
+ className={error.enableBindDN ?
"ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="Replication Manager password">
+ <Col componentClass={ControlLabel} sm={4}>
+ Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="password"
+ id="enableBindPW"
+ className={error.enableBindPW ?
"ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top"
title="Confirm the Replication Manager password">
+ <Col componentClass={ControlLabel} sm={4}>
+ Confirm Password
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="password"
+ id="enableBindPWConfirm"
+ className={error.enableBindPWConfirm ?
"ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ <Row className="ds-margin-top" title="The
DN of a group that contains users that can perform replication updates">
+ <Col componentClass={ControlLabel} sm={4}>
+ Bind Group DN
+ </Col>
+ <Col sm={8}>
+ <FormControl
+ type="text"
+ id="enableBindGroupDN"
+ className={error.enableBindGroupDN ?
"ds-input-auto-bad" : "ds-input-auto"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+
+ Enable
Replication
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+export class ExportModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ saveHandler,
+ spinning,
+ saveOK
+ } = this.props;
+ let spinner = "";
+ if (spinning) {
+ spinner =
+ <Row>
+ <div className="ds-margin-top ds-modal-spinner">
+ <Spinner loading inline size="lg" />Exporting
database... <font size="1">(You can safely close this
window)</font>
+ </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>
+ Create Replication Initialization LDIF File
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <Row>
+ <Col sm={11}
className="ds-left-indent-md">
+ <p>Enter the name of the LDIF file, do not use
a path as the file will only be written to the server's LDIF directory</p>
+ </Col>
+ </Row>
+ <Row className="ds-margin-top-lg"
title="Name of the exported LDIF file">
+ <Col sm={3}>
+ <b>LDIF Name</b>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ id="ldifLocation"
+ className={saveOK ? "" :
"ds-input-bad"}
+ onChange={handleChange}
+ />
+ </Col>
+ </Row>
+ {spinner}
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button
+ bsStyle="default"
+ className="btn-cancel"
+ onClick={closeHandler}
+
+ Cancel
+ </Button>
+ <Button
+ bsStyle="primary"
+ onClick={saveHandler}
+ disabled={!saveOK}
+
+ Export
Replica
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+EnableReplModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ error: PropTypes.object,
+};
+
+EnableReplModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ spinning: false,
+ error: {},
+};
+
+AddManagerModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ error: PropTypes.object,
+};
+
+AddManagerModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ spinning: false,
+ error: {},
+};
+
+ChangeReplRoleModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ role: PropTypes.string,
+ newRole: PropTypes.string,
+};
+
+ChangeReplRoleModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ spinning: false,
+ role: "",
+ newRole: "",
+};
+
+ReplAgmtModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleStripChange: PropTypes.func,
+ handleFracChange: PropTypes.func,
+ handleFracInitChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ availAttrs: PropTypes.array,
+ agmtName: PropTypes.string,
+ agmtHost: PropTypes.string,
+ agmtPort: PropTypes.string,
+ agmtProtocol: PropTypes.string,
+ agmtBindMethod: PropTypes.string,
+ agmtBindDN: PropTypes.string,
+ agmtBindPW: PropTypes.string,
+ agmtBindPWConfirm: PropTypes.string,
+ agmtStripAttrs: PropTypes.array,
+ agmtFracAttrs: PropTypes.array,
+ agmtFracInitAttrs: PropTypes.array,
+ agmtSync: PropTypes.bool,
+ agmtSyncMon: PropTypes.bool,
+ agmtSyncTue: PropTypes.bool,
+ agmtSyncWed: PropTypes.bool,
+ agmtSyncThu: PropTypes.bool,
+ agmtSyncFri: PropTypes.bool,
+ agmtSyncSat: PropTypes.bool,
+ agmtSyncSun: PropTypes.bool,
+ agmtStartTime: PropTypes.string,
+ agmtEndTime: PropTypes.string,
+ saveOK: PropTypes.bool,
+ error: PropTypes.object,
+ errorMsg: PropTypes.string,
+ edit: PropTypes.bool,
+};
+
+ReplAgmtModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ handleStripChange: noop,
+ handleFracChange: noop,
+ handleFracInitChange: noop,
+ saveHandler: noop,
+ spinning: false,
+ availAttrs: [],
+ agmtName: "",
+ agmtHost: "",
+ agmtPort: "",
+ agmtProtocol: "LDAP",
+ agmtBindMethod: "SIMPLE",
+ agmtBindDN: "",
+ agmtBindPW: "",
+ agmtBindPWConfirm: "",
+ agmtStripAttrs: [],
+ agmtFracAttrs: [],
+ agmtFracInitAttrs: [],
+ agmtSync: true,
+ agmtSyncMon: false,
+ agmtSyncTue: false,
+ agmtSyncWed: false,
+ agmtSyncThu: false,
+ agmtSyncFri: false,
+ agmtSyncSat: false,
+ agmtSyncSun: false,
+ agmtStartTime: "00:00",
+ agmtEndTime: "23:59",
+ saveOK: false,
+ error: {},
+ errorMsg: "",
+ edit: false,
+};
+
+WinsyncAgmtModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ handleFracChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ spinning: PropTypes.bool,
+ availAttrs: PropTypes.array,
+ agmtName: PropTypes.string,
+ agmtHost: PropTypes.string,
+ agmtPort: PropTypes.string,
+ agmtProtocol: PropTypes.string,
+ agmtBindDN: PropTypes.string,
+ agmtBindPW: PropTypes.string,
+ agmtBindPWConfirm: PropTypes.string,
+ agmtFracAttrs: PropTypes.array,
+ agmtSync: PropTypes.bool,
+ agmtSyncMon: PropTypes.bool,
+ agmtSyncTue: PropTypes.bool,
+ agmtSyncWed: PropTypes.bool,
+ agmtSyncThu: PropTypes.bool,
+ agmtSyncFri: PropTypes.bool,
+ agmtSyncSat: PropTypes.bool,
+ agmtSyncSun: PropTypes.bool,
+ agmtStartTime: PropTypes.string,
+ agmtEndTime: PropTypes.string,
+ agmtSyncGroups: PropTypes.bool,
+ agmtSyncUsers: PropTypes.bool,
+ agmtWinDomain: PropTypes.string,
+ agmtWinSubtree: PropTypes.string,
+ agmtDSSubtree: PropTypes.string,
+ agmtOneWaySync: PropTypes.string,
+ agmtSyncInterval: PropTypes.string,
+ saveOK: PropTypes.bool,
+ error: PropTypes.object,
+ errorMsg: PropTypes.string,
+ edit: PropTypes.bool,
+};
+
+WinsyncAgmtModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ handleFracChange: noop,
+ saveHandler: noop,
+ spinning: false,
+ availAttrs: [],
+ agmtName: "",
+ agmtHost: "",
+ agmtPort: "",
+ agmtProtocol: "LDAPS",
+ agmtBindDN: "",
+ agmtBindPW: "",
+ agmtBindPWConfirm: "",
+ agmtFracAttrs: [],
+ agmtSync: true,
+ agmtSyncMon: false,
+ agmtSyncTue: false,
+ agmtSyncWed: false,
+ agmtSyncThu: false,
+ agmtSyncFri: false,
+ agmtSyncSat: false,
+ agmtSyncSun: false,
+ agmtStartTime: "00:00",
+ agmtEndTime: "23:59",
+ agmtSyncGroups: false,
+ agmtSyncUsers: false,
+ agmtWinDomain: "",
+ agmtWinSubtree: "",
+ agmtDSSubtree: "",
+ agmtOneWaySync: "both", // "both", "toWindows",
"fromWindows"
+ agmtSyncInterval: "",
+ saveOK: false,
+ error: {},
+ errorMsg: "",
+ edit: false,
+};
+
+ExportModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ saveHandler: PropTypes.func,
+ saveOK: PropTypes.bool,
+ spinning: PropTypes.bool
+};
+
+ExportModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ saveHandler: noop,
+ saveOK: false,
+ spinning: false
+};
diff --git a/src/cockpit/389-console/src/lib/replication/replSuffix.jsx
b/src/cockpit/389-console/src/lib/replication/replSuffix.jsx
new file mode 100644
index 0000000..0978c0c
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/replSuffix.jsx
@@ -0,0 +1,456 @@
+import React from "react";
+import cockpit from "cockpit";
+import { ReplConfig } from "./replConfig.jsx";
+import { WinsyncAgmts } from "./winsyncAgmts.jsx";
+import { ReplAgmts } from "./replAgmts.jsx";
+import { ReplRUV } from "./replTasks.jsx";
+import { DoubleConfirmModal } from "../notifications.jsx";
+import { EnableReplModal } from "./replModals.jsx";
+import {
+ Button,
+ Col,
+ ControlLabel,
+ Icon,
+ Nav,
+ NavItem,
+ noop,
+ Row,
+ Spinner,
+ TabContainer,
+ TabContent,
+ TabPane,
+} from "patternfly-react";
+import PropTypes from "prop-types";
+import { log_cmd, valid_dn } from "../tools.jsx";
+import "../../css/ds.css";
+
+export class ReplSuffix extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ loading: false,
+ activeKey: 1,
+ showDisableConfirm: false,
+ replicationEnabled: false,
+ errObj: {},
+ replEnabled: this.props.replicated,
+ // Enable replication settings
+ showEnableReplModal: false,
+ enableRole: "Master",
+ enableRID: "1",
+ enableBindDN: "cn=replication manager,cn=config",
+ enableBindPW: "",
+ enableBindPWConfirm: "",
+ enableBindGroupDN: "",
+ // Disable replication
+ showDisableReplModal: false,
+ disableChecked: false,
+ disableSpinning: false,
+ modalChecked: false,
+ modalSpinning: false,
+ };
+
+ // General bindings
+ this.handleReplChange = this.handleReplChange.bind(this);
+ this.handleEnableChange = this.handleEnableChange.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleNavSelect = this.handleNavSelect.bind(this);
+ this.disableReplication = this.disableReplication.bind(this);
+ this.enableReplication = this.enableReplication.bind(this);
+ this.closeEnableReplModal = this.closeEnableReplModal.bind(this);
+ this.closeDisableReplModal = this.closeDisableReplModal.bind(this);
+ }
+
+ componentDidMount() {
+ this.props.enableTree();
+ }
+
+ handleNavSelect(key) {
+ this.setState({ activeKey: key });
+ }
+
+ handleReplChange() {
+ if (this.props.replicated) {
+ // Disable Replication
+ this.setState({
+ showDisableReplModal: true,
+ modalChecked: false,
+ modalSpinning: false
+ });
+ } else {
+ // Enable replication
+ this.setState({
+ showEnableReplModal: true
+ });
+ }
+ }
+
+ handleChange (e) {
+ let 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
+ });
+ }
+
+ handleEnableChange (e) {
+ let value = e.target.value;
+ let attr = e.target.id;
+ let valueErr = false;
+ let errObj = this.state.errObj;
+
+ if (attr == "enableBindDN" && value != "" &&
!valid_dn(value)) {
+ valueErr = true;
+ }
+ if (attr == "enableBindGroupDN" && value != ""
&& !valid_dn(value)) {
+ valueErr = true;
+ }
+ if (attr == "enableBindPW") {
+ if (value != this.state.enableBindPWConfirm) {
+ valueErr = true;
+ } else {
+ errObj.enableBindPW = false;
+ errObj.enableBindPWConfirm = false;
+ }
+ }
+ if (attr == "enableBindPWConfirm") {
+ if (value != this.state.enableBindPW) {
+ valueErr = true;
+ } else {
+ errObj.enableBindPW = false;
+ errObj.enableBindPWConfirm = false;
+ }
+ }
+ errObj[attr] = valueErr;
+ this.setState({
+ [attr]: value,
+ errObj: errObj
+ });
+ }
+
+ closeEnableReplModal () {
+ this.setState({
+ showEnableReplModal: false,
+ });
+ }
+
+ enableReplication () {
+ // First, Validate
+ if (this.state.enableBindDN != "" &&
!valid_dn(this.state.enableBindDN)) {
+ this.props.addNotification(
+ "error",
+ `The Bind DN is not a valid DN (Distinguished Name)
${this.state.enableBindDN}`
+ );
+ return;
+ }
+ if (this.state.enableBindGroupDN != "" &&
!valid_dn(this.state.enableBindGroupDN)) {
+ this.props.addNotification(
+ "error",
+ `The Group DN is not a valid DN (Distinguished Name)`
+ );
+ return;
+ }
+ if (this.state.enableBindPW != this.state.enableBindPWConfirm) {
+ this.props.addNotification(
+ "error",
+ `The Bind DN passwords do not match`
+ );
+ return;
+ }
+ if (this.state.enableRID != "" && (this.state.enableRID < 1
|| this.state.enableRID > 65534)) {
+ this.props.addNotification(
+ "error",
+ `The Replica ID is not in the valid range of 1 - 65534`
+ );
+ return;
+ }
+
+ // Now enable replication
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' +
this.props.serverId + '.socket',
+ 'replication', 'enable', '--suffix=' +
this.props.suffix,
+ '--role=' + this.state.enableRole
+ ];
+ if (this.state.enableBindDN != "") {
+ cmd.push('--bind-dn=' + this.state.enableBindDN);
+ }
+ if (this.state.enableBindPW != "") {
+ cmd.push('--bind-passwd=' + this.state.enableBindPW);
+ }
+ if (this.state.enableBindGroupDN != "") {
+ cmd.push('--bind-group-dn=' + this.state.enableBindGroupDN);
+ }
+ if (this.state.enableRole == "Master") {
+ cmd.push('--replica-id=' + this.state.enableRID);
+ }
+
+ this.props.disableTree();
+ log_cmd('enableReplication', 'Enable replication', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(1);
+ this.props.addNotification(
+ "success",
+ `Successfully enabled replication for
"${this.props.suffix}"`
+ );
+ })
+ .fail(err => {
+ this.props.reload(1);
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to enable replication for
"${this.props.suffix}" - ${errMsg.desc}`
+ );
+ });
+ }
+
+ closeDisableReplModal () {
+ this.setState({
+ showDisableReplModal: false
+ });
+ }
+
+ disableReplication () {
+ this.props.disableTree();
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
'replication', 'disable', '--suffix=' + this.props.suffix];
+ log_cmd('disableReplication', 'Disable replication', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(1);
+ this.props.addNotification(
+ "success",
+ `Successfully disabled replication for
"${this.props.suffix}"`
+ );
+ })
+ .fail(err => {
+ this.props.reload(1);
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to disable replication for
"${this.props.suffix}" - ${errMsg.desc}`
+ );
+ });
+ }
+
+ //
+ // Render the component
+ //
+ render () {
+ let spinning = "";
+ let spintext = "";
+ let suffixIcon = "tree";
+ if (this.props.replicated) {
+ suffixIcon = "clone";
+ } else {
+ if (this.props.repl == "subsuffix") {
+ suffixIcon = "leaf";
+ }
+ }
+ if (this.props.spinning) {
+ spinning =
+ <Spinner className="ds-margin-top ds-margin-left
ds-inline-spinner" loading inline size="sm" />;
+ spintext =
+ <font
size="2"><i>Refreshing</i></font>;
+ }
+ let suffixClass = "ds-margin-top-xlg";
+ if (this.props.disabled) {
+ suffixClass = "ds-margin-top-xlg ds-disabled";
+ }
+ let replAgmtNavTitle = 'Replication Agreements <font
size="1">(' + this.props.agmtRows.length + ')</font>';
+ let winsyncNavTitle = 'Winsync Agreements <font
size="1">(' + this.props.winsyncRows.length + ')</font>';
+
+ let enabledContent =
+ <div className={suffixClass}>
+ <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:
'Configuration'}} />
+ </NavItem>
+ <NavItem eventKey={2}>
+ <div dangerouslySetInnerHTML={{__html:
replAgmtNavTitle}} />
+ </NavItem>
+ <NavItem eventKey={3}>
+ <div dangerouslySetInnerHTML={{__html:
winsyncNavTitle}} />
+ </NavItem>
+ <NavItem eventKey={4}>
+ <div dangerouslySetInnerHTML={{__html: "RUV's
& Tasks"}} />
+ </NavItem>
+ </Nav>
+ <TabContent>
+ <TabPane eventKey={1}>
+ <ReplConfig
+ suffix={this.props.suffix}
+ role={this.props.role}
+ data={this.props.data}
+ serverId={this.props.serverId}
+ addNotification={this.props.addNotification}
+ reload={this.props.reload}
+ reloadConfig={this.props.reloadConfig}
+ />
+ </TabPane>
+ <TabPane eventKey={2}>
+ <ReplAgmts
+ suffix={this.props.suffix}
+ serverId={this.props.serverId}
+ rows={this.props.agmtRows}
+ addNotification={this.props.addNotification}
+ reload={this.props.reloadAgmts}
+ attrs={this.props.attrs}
+ disableTable={this.props.disableAgmtTable}
+ key={this.props.agmtRows}
+ />
+ </TabPane>
+ <TabPane eventKey={3}>
+ <WinsyncAgmts
+ suffix={this.props.suffix}
+ serverId={this.props.serverId}
+ rows={this.props.winsyncRows}
+ addNotification={this.props.addNotification}
+ reload={this.props.reloadWinsyncAgmts}
+ attrs={this.props.attrs}
+ disableTable={this.props.disableWSAgmtTable}
+ key={this.props.winsyncRows}
+ />
+ </TabPane>
+ <TabPane eventKey={4}>
+ <ReplRUV
+ suffix={this.props.suffix}
+ serverId={this.props.serverId}
+ rows={this.props.ruvRows}
+ addNotification={this.props.addNotification}
+ reload={this.props.reloadRUV}
+ localRID={this.props.data.nsds5replicaid}
+ key={this.props.ruvRows}
+ />
+ </TabPane>
+ </TabContent>
+ </div>
+ </TabContainer>
+ </div>;
+
+ let replActionButton = "";
+ if (this.props.replicated) {
+ replActionButton =
+ <Button
+ bsStyle="danger"
+ onClick={this.handleReplChange}
+ title="Disable replication, and remove all replication
agreements."
+
+ Disable
+ </Button>;
+ } else {
+ enabledContent =
+ <div className="ds-center ds-margin-top-xlg">
+ <h4>
+ Replication is not enabled for this suffix
+ </h4>
+ <Button
+ bsStyle="primary"
+ onClick={this.handleReplChange}
+ className="ds-margin-top-lg"
+
+ Enable
Replication
+ </Button>
+ </div>;
+ }
+
+ return (
+ <div id="suffix-page">
+ <Row>
+ <Col sm={8} className="ds-word-wrap">
+ <ControlLabel
className="ds-suffix-header"><Icon type="fa" name={suffixIcon}
/>
+ {" " + this.props.suffix}
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh"
title="Refresh replication settings for this suffix"
+ onClick={() => {
+ this.props.reload(false);
+ }}
+ />
+ {spinning} {spintext}
+ </ControlLabel>
+ </Col>
+ <Col sm={4}>
+ <Row>
+ <Col className="ds-no-padding ds-container"
componentClass={ControlLabel} sm={12}>
+ {replActionButton}
+ </Col>
+ </Row>
+ </Col>
+ </Row>
+ <p />
+ {enabledContent}
+ <EnableReplModal
+ showModal={this.state.showEnableReplModal}
+ closeHandler={this.closeEnableReplModal}
+ handleChange={this.handleEnableChange}
+ saveHandler={this.enableReplication}
+ spinning={this.state.addManagerSpinning}
+ role={this.state.enableRole}
+ error={this.state.errObj}
+ />
+ <DoubleConfirmModal
+ showModal={this.state.showDisableReplModal}
+ closeHandler={this.closeDisableReplModal}
+ handleChange={this.handleChange}
+ actionHandler={this.disableReplication}
+ spinning={this.state.modalSpinning}
+ item={this.props.suffix}
+ checked={this.state.modalChecked}
+ mTitle="Disable Replication"
+ mMsg="Are you sure you want to disable replication for this
suffix?"
+ mSpinningMsg="Disabling Replication ..."
+ mBtnName="Disable Replication"
+ />
+ </div>
+ );
+ }
+}
+
+ReplSuffix.propTypes = {
+ serverId: PropTypes.string,
+ suffix: PropTypes.string,
+ role: PropTypes.string,
+ addNotification: PropTypes.func,
+ agmtRows: PropTypes.array,
+ winsyncRows: PropTypes.array,
+ ruvRows: PropTypes.array,
+ reloadAgmts: PropTypes.func,
+ reloadRUV: PropTypes.func,
+ reloadConfig: PropTypes.func,
+ reload: PropTypes.func,
+ replicated: PropTypes.bool,
+ attrs: PropTypes.array,
+ enableTree: PropTypes.func,
+ disableTree: PropTypes.func,
+ spinning: PropTypes.bool,
+ disabled: PropTypes.bool,
+};
+
+ReplSuffix.defaultProps = {
+ serverId: "",
+ suffix: "",
+ role: "",
+ addNotification: noop,
+ agmtRows: [],
+ winsyncRows: [],
+ ruvRows: [],
+ reloadAgmts: noop,
+ reloadRUV: noop,
+ reloadConfig: noop,
+ reload: noop,
+ replicated: false,
+ attrs: [],
+ enableTree: noop,
+ disableTree: noop,
+ spinning: false,
+ disabled: false,
+};
diff --git a/src/cockpit/389-console/src/lib/security/securityTables.jsx
b/src/cockpit/389-console/src/lib/replication/replTables.jsx
similarity index 57%
copy from src/cockpit/389-console/src/lib/security/securityTables.jsx
copy to src/cockpit/389-console/src/lib/replication/replTables.jsx
index 6b74a01..332d458 100644
--- a/src/cockpit/389-console/src/lib/security/securityTables.jsx
+++ b/src/cockpit/389-console/src/lib/replication/replTables.jsx
@@ -1,6 +1,6 @@
import React from "react";
import {
- // Button,
+ Button,
DropdownButton,
MenuItem,
actionHeaderCellFormatter,
@@ -8,21 +8,22 @@ import {
tableCellFormatter,
noop
} from "patternfly-react";
-import { DSTable } from "../dsTable.jsx";
+import { DSTable, DSShortTable } from "../dsTable.jsx";
import PropTypes from "prop-types";
import "../../css/ds.css";
-class CertTable extends React.Component {
+class ReplAgmtTable extends React.Component {
constructor(props) {
super(props);
-
this.state = {
- rowKey: "nickname",
+ searchField: "Agreements",
+ fieldsToSearch: ["name"],
+ rowKey: "name",
columns: [
{
- property: "nickname",
+ property: "name",
header: {
- label: "Certificate Name",
+ label: "Agreement Name",
props: {
index: 0,
rowSpan: 1,
@@ -41,9 +42,9 @@ class CertTable extends React.Component {
}
},
{
- property: "subject",
+ property: "host",
header: {
- label: "Subject DN",
+ label: "Replica Hostname",
props: {
index: 1,
rowSpan: 1,
@@ -62,9 +63,9 @@ class CertTable extends React.Component {
}
},
{
- property: "issuer",
+ property: "port",
header: {
- label: "Issued By",
+ label: "Replica Port",
props: {
index: 2,
rowSpan: 1,
@@ -83,9 +84,9 @@ class CertTable extends React.Component {
}
},
{
- property: "flags",
+ property: "state",
header: {
- label: "Trust Flags",
+ label: "State",
props: {
index: 3,
rowSpan: 1,
@@ -104,9 +105,9 @@ class CertTable extends React.Component {
}
},
{
- property: "expires",
+ property: "status",
header: {
- label: "Expiration Date",
+ label: "Last Update Status",
props: {
index: 4,
rowSpan: 1,
@@ -125,38 +126,80 @@ class CertTable extends React.Component {
}
},
{
- property: "action",
+ property: "initstatus",
header: {
- label: "",
+ label: "Last Init Status",
props: {
index: 5,
rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 5
+ },
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 6,
+ rowSpan: 1,
colSpan: 1
},
formatters: [actionHeaderCellFormatter]
},
cell: {
props: {
- index: 5
+ index: 6
},
formatters: [
(value, { rowData }) => {
return [
- <td key={rowData.nickname[0]}>
- <DropdownButton id={rowData.nickname[0]}
- bsStyle="default"
title="Actions">
+ <td key={rowData.name[0]}>
+ <DropdownButton
+ pullRight
+ id={rowData.name[0]}
+ className="ds-action-button"
+ bsStyle="primary"
title="Actions"
+
<MenuItem eventKey="1" onClick={() => {
- this.props.editCert(rowData);
+ this.props.edit(rowData.name[0]);
}}
-
Edit Trust Flags
+ Edit Agreement
</MenuItem>
- <MenuItem divider />
<MenuItem eventKey="2"
onClick={() => {
- this.props.delCert(rowData);
+ this.props.init(rowData.name[0]);
+ }}
+
+
Initialize Agreement
+ </MenuItem>
+ <MenuItem eventKey="3"
onClick={() => {
+ this.props.poke(rowData.name[0]);
}}
+ title="Awaken agreement if it is
sleeping"
-
Delete Certificate
+ Poke Agreement
+ </MenuItem>
+ <MenuItem eventKey="4"
onClick={() => {
+ this.props.enable(rowData.name[0],
rowData.state[0]);
+ }}
+
+
Disable/Enable Agreement
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem eventKey="3"
onClick={() => {
+ this.props.delete(rowData.name[0],
rowData.state[0]);
+ }}
+
+
Delete Agreement
</MenuItem>
</DropdownButton>
</td>
@@ -164,9 +207,10 @@ class CertTable extends React.Component {
}
]
}
- }
- ]
+ },
+ ],
};
+
this.getColumns = this.getColumns.bind(this);
this.getSingleColumn = this.getSingleColumn.bind(this);
}
@@ -176,7 +220,7 @@ class CertTable extends React.Component {
{
property: "msg",
header: {
- label: "Certificates",
+ label: "Agreements",
props: {
index: 0,
rowSpan: 1,
@@ -202,46 +246,38 @@ class CertTable extends React.Component {
}
render() {
- let certRows = [];
- let serverTable;
- for (let cert of this.props.certs) {
- let obj = {
- 'nickname': [cert.attrs['nickname']],
- 'subject': [cert.attrs['subject']],
- 'issuer': [cert.attrs['issuer']],
- 'expires': [cert.attrs['expires']],
- 'flags': [cert.attrs['flags']],
- };
- certRows.push(obj);
- }
-
- if (certRows.length == 0) {
- serverTable = <DSTable
- getColumns={this.getSingleColumn}
- rowKey={"msg"}
- rows={[{msg: "No Certificates"}]}
- key={"nocerts"}
- />;
+ let agmtTable;
+ if (this.props.rows.length == 0) {
+ agmtTable =
+ <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{msg: "No Agreements"}]}
+ />;
} else {
- serverTable = <DSTable
- getColumns={this.getColumns}
- rowKey={this.state.rowKey}
- rows={certRows}
- key={certRows}
- disableLoadingSpinner
- />;
+ agmtTable =
+ <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}
+ />;
}
return (
- <div>
- {serverTable}
+ <div className="ds-margin-top-xlg">
+ {agmtTable}
</div>
+
);
}
}
-// Future -
https://pagure.io/389-ds-base/issue/50491
-class CRLTable extends React.Component {
+class ManagerTable extends React.Component {
constructor(props) {
super(props);
@@ -251,7 +287,7 @@ class CRLTable extends React.Component {
{
property: "name",
header: {
- label: "Issued By",
+ label: "Replication Manager",
props: {
index: 0,
rowSpan: 1,
@@ -270,12 +306,113 @@ class CRLTable extends React.Component {
}
},
{
- property: "effective",
+ property: "actions",
header: {
- label: "Effective Date",
+ label: "",
props: {
index: 1,
rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.name[0]}>
+ <Button
+ bsStyle="default"
+ onClick={() => {
+ this.props.confirmDelete(rowData);
+ }}
+ title="Delete replication manager"
+
+
Delete
+ </Button>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Replication Managers",
+ 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 managerTable;
+ if (this.props.rows.length == 0) {
+ managerTable = <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{msg: "No Replication Managers"}]}
+ />;
+ } else {
+ managerTable = <DSShortTable
+ getColumns={this.getColumns}
+ rowKey={this.state.rowKey}
+ rows={this.props.rows}
+ disableLoadingSpinner
+ />;
+ }
+ return (
+ <div>
+ {managerTable}
+ </div>
+ );
+ }
+}
+
+class RUVTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ rowKey: "rid",
+ columns: [
+ {
+ property: "rid",
+ header: {
+ label: "Replica ID",
+ props: {
+ index: 0,
+ rowSpan: 1,
colSpan: 1,
sort: true
},
@@ -285,17 +422,17 @@ class CRLTable extends React.Component {
},
cell: {
props: {
- index: 1
+ index: 0
},
formatters: [tableCellFormatter]
}
},
{
- property: "nextUpdate",
+ property: "url",
header: {
- label: "Next Updateo",
+ label: "Replica LDAP URL",
props: {
- index: 2,
+ index: 1,
rowSpan: 1,
colSpan: 1,
sort: true
@@ -306,18 +443,17 @@ class CRLTable extends React.Component {
},
cell: {
props: {
- index: 2
+ index: 1
},
formatters: [tableCellFormatter]
}
},
-
{
- property: "type",
+ property: "maxcsn",
header: {
- label: "Type",
+ label: "Max CSN",
props: {
- index: 3,
+ index: 2,
rowSpan: 1,
colSpan: 1,
sort: true
@@ -328,17 +464,17 @@ class CRLTable extends React.Component {
},
cell: {
props: {
- index: 3
+ index: 2
},
formatters: [tableCellFormatter]
}
},
{
- property: "action",
+ property: "actions",
header: {
label: "",
props: {
- index: 4,
+ index: 3,
rowSpan: 1,
colSpan: 1
},
@@ -346,27 +482,21 @@ class CRLTable extends React.Component {
},
cell: {
props: {
- index: 4
+ 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);
+ <td key={rowData.rid[0]}>
+ <Button
+ bsStyle="default"
+ onClick={() => {
+
this.props.confirmDelete(rowData.rid[0]);
}}
-
-
View CRL
- </MenuItem>
- <MenuItem eventKey="2"
onClick={() => {
- this.props.reindexIndex(rowData);
- }}
-
-
Delete CRL
- </MenuItem>
- </DropdownButton>
+ title="Attempt to clean and remove this
Replica ID from this suffix"
+
+
Clean
+ </Button>
</td>
];
}
@@ -376,6 +506,7 @@ class CRLTable extends React.Component {
]
};
this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
}
getSingleColumn () {
@@ -383,7 +514,7 @@ class CRLTable extends React.Component {
{
property: "msg",
header: {
- label: "Certificate Revocation Lists",
+ label: "Remote RUV's",
props: {
index: 0,
rowSpan: 1,
@@ -409,15 +540,15 @@ class CRLTable extends React.Component {
}
render() {
- let crlTable;
+ let ruvTable;
if (this.props.rows.length == 0) {
- crlTable = <DSTable
+ ruvTable = <DSShortTable
getColumns={this.getSingleColumn}
rowKey={"msg"}
- rows={[{msg: "None"}]}
+ rows={[{msg: "No RUV's"}]}
/>;
} else {
- crlTable = <DSTable
+ ruvTable = <DSShortTable
getColumns={this.getColumns}
rowKey={this.state.rowKey}
rows={this.props.rows}
@@ -426,29 +557,52 @@ class CRLTable extends React.Component {
}
return (
<div>
- {crlTable}
+ {ruvTable}
</div>
);
}
}
-// Props and defaults
+ReplAgmtTable.propTypes = {
+ rows: PropTypes.array,
+ edit: PropTypes.func,
+ poke: PropTypes.func,
+ init: PropTypes.func,
+ enable: PropTypes.func,
+ delete: PropTypes.func,
+};
+
+ReplAgmtTable.defaultProps = {
+ rows: [],
+ edit: noop,
+ poke: noop,
+ init: noop,
+ enable: noop,
+ delete: noop,
+};
+
+ManagerTable.propTypes = {
+ rows: PropTypes.array,
+ confirmDelete: PropTypes.func
+};
+
+ManagerTable.defaultProps = {
+ rows: [],
+ confirmDelete: noop
+};
-CertTable.propTypes = {
- // serverId: PropTypes.string,
- certs: PropTypes.array,
- editCert: PropTypes.func,
- delCert: PropTypes.func,
+RUVTable.propTypes = {
+ rows: PropTypes.array,
+ confirmDelete: PropTypes.func
};
-CertTable.defaultProps = {
- // serverId: "",
- certs: [],
- editCert: noop,
- delCert: noop,
+RUVTable.defaultProps = {
+ rows: [],
+ confirmDelete: noop
};
export {
- CertTable,
- CRLTable
+ ReplAgmtTable,
+ ManagerTable,
+ RUVTable,
};
diff --git a/src/cockpit/389-console/src/lib/replication/replTasks.jsx
b/src/cockpit/389-console/src/lib/replication/replTasks.jsx
new file mode 100644
index 0000000..acdc6c8
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/replTasks.jsx
@@ -0,0 +1,314 @@
+import cockpit from "cockpit";
+import React from "react";
+import { log_cmd, bad_file_name } from "../tools.jsx";
+import { RUVTable } from "./replTables.jsx";
+import { ExportModal } from "./replModals.jsx";
+import { DoubleConfirmModal } from "../notifications.jsx";
+import PropTypes from "prop-types";
+import {
+ Button,
+ Col,
+ ControlLabel,
+ Icon,
+ noop,
+ Row,
+} from "patternfly-react";
+
+export class ReplRUV extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ errObj: {},
+ rid: "",
+ localRID: "",
+ ldifLocatio: "",
+ saveOK: false,
+ showConfirmCleanRUV: false,
+ modalChecked: false,
+ modalSpinning: false,
+ };
+ this.showConfirmCleanRUV = this.showConfirmCleanRUV.bind(this);
+ this.closeConfirmCleanRUV = this.closeConfirmCleanRUV.bind(this);
+ this.showConfirmExport = this.showConfirmExport.bind(this);
+ this.closeConfirmExport = this.closeConfirmExport.bind(this);
+ this.handleLDIFChange = this.handleLDIFChange.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.cleanRUV = this.cleanRUV.bind(this);
+ this.doExport = this.doExport.bind(this);
+ }
+
+ showConfirmCleanRUV (rid) {
+ this.setState({
+ rid: rid,
+ showConfirmCleanRUV: true,
+ modalChecked: false,
+ modalSpinning: false,
+ });
+ }
+
+ closeConfirmCleanRUV () {
+ this.setState({
+ showConfirmCleanRUV: false,
+ modalChecked: false,
+ modalSpinning: false,
+ });
+ }
+
+ showConfirmExport () {
+ this.setState({
+ saveOK: false,
+ showConfirmExport: true,
+ });
+ }
+
+ closeConfirmExport () {
+ this.setState({
+ showConfirmExport: false,
+ });
+ }
+
+ cleanRUV () {
+ // Enable/disable agmt
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-tasks', 'cleanallruv', '--replica-id=' +
this.state.rid,
+ '--force-cleaning', '--suffix=' + this.props.suffix];
+
+ log_cmd('cleanRUV', 'Clean the rid', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully started CleanAllRUV task');
+ this.closeConfirmCleanRUV();
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to start CleanAllRUV task - ${errMsg.desc}`
+ );
+ this.closeConfirmCleanRUV();
+ });
+ }
+
+ handleLDIFChange (e) {
+ let value = e.target.value;
+ let saveOK = true;
+ if (value == "" || bad_file_name(value)) {
+ saveOK = false;
+ }
+ this.setState({
+ [e.target.id]: value,
+ saveOK: saveOK
+ });
+ }
+
+ handleChange (e) {
+ this.setState({
+ [e.target.id]: e.target.checked,
+ });
+ }
+
+ doExport () {
+ // Do Export
+ let export_cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-"
+ this.props.serverId + ".socket",
+ "backend", "export", this.props.suffix,
"--ldif=" + this.state.ldifLocation,
+ '--replication', "--encrypted"
+ ];
+
+ this.setState({
+ exportSpinner: true,
+ });
+
+ log_cmd("doExport", "replication do online export",
export_cmd);
+ cockpit
+ .spawn(export_cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.addNotification(
+ "success",
+ `Database export complete`
+ );
+ this.setState({
+ showConfirmExport: false,
+ exportSpinner: false,
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Error exporting database - ${errMsg.desc}`
+ );
+ this.setState({
+ showConfirmExport: false,
+ exportSpinner: false,
+ });
+ });
+ }
+
+ render() {
+ // Strip outthe loca RUV and display it diffent then onmly allow cleaning of
remote rids
+ let remote_rows = [];
+ let localRID = "";
+ let localURL = "";
+ let localCSN = "";
+ let localRawCSN = "";
+ let localMinCSN = "";
+ let localRawMinCSN = "";
+ for (let row of this.props.rows) {
+ if (row.rid == this.props.localRID) {
+ localRID = row.rid;
+ localURL = row.url;
+ localCSN = row.maxcsn;
+ localRawCSN = row.raw_maxcsn;
+ localMinCSN = row.csn;
+ localRawMinCSN = row.raw_csn;
+ } else {
+ remote_rows.push(row);
+ }
+ }
+ let localRUV =
+ <div className="ds-left-indent-md">
+ <Row className="ds-margin-top-med">
+ <Col sm={2}>
+ <ControlLabel>
+ Replica ID
+ </ControlLabel>
+ </Col>
+ <Col sm={10}>
+ <b>{localRID}</b>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={2}>
+ <ControlLabel>
+ LDAP URL
+ </ControlLabel>
+ </Col>
+ <Col sm={10}>
+ <b>{localURL}</b>
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={2}>
+ <ControlLabel>
+ Min CSN
+ </ControlLabel>
+ </Col>
+ <Col sm={10}>
+ <b>{localMinCSN}</b> ({localRawMinCSN})
+ </Col>
+ </Row>
+ <Row>
+ <Col sm={2}>
+ <ControlLabel>
+ Max CSN
+ </ControlLabel>
+ </Col>
+ <Col sm={10}>
+ <b>{localCSN}</b> ({localRawCSN})
+ </Col>
+ </Row>
+ </div>;
+
+ if (localRID == "") {
+ localRUV =
+ <div className="ds-indent">
+ <i>
+ There is no local RUV, the database might not have been
initialized yet.
+ </i>
+ </div>;
+ }
+
+ return (
+ <div className="ds-margin-top-xlg ds-indent">
+ <ControlLabel className="ds-h4">
+ Local RUV
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh" title="Refresh
the RUV for this suffix"
+ onClick={() => {
+ this.props.reload(this.props.suffix);
+ }}
+ />
+ </ControlLabel>
+ {localRUV}
+ <hr />
+ <Row className="ds-margin-top">
+ <Col sm={12} className="ds-word-wrap">
+ <ControlLabel className="ds-h4">
+ Remote RUV's
+ <Icon className="ds-left-margin ds-refresh"
+ type="fa" name="refresh"
title="Refresh the RUV for this suffix"
+ onClick={() => {
+ this.props.reload(this.props.suffix);
+ }}
+ />
+ </ControlLabel>
+ </Col>
+ </Row>
+ <div className="ds-left-indent-md ds-margin-top-lg">
+ <RUVTable
+ rows={remote_rows}
+ confirmDelete={this.showConfirmCleanRUV}
+ />
+ </div>
+ <hr />
+ <h4>Create Initialization LDIF File</h4>
+ <div className="ds-left-indent-md ds-margin-top-lg">
+ <p><i>Export this suffix with the replication metadata
needed to initialize another replica</i></p>
+ <Button
+ bsStyle="primary"
+ onClick={this.showConfirmExport}
+ className="ds-margin-top"
+ title="See Database Tab -> Backups & LDIFs to manage
the new LDIF"
+
+ Export To
LDIF
+ </Button>
+ </div>
+ <DoubleConfirmModal
+ showModal={this.state.showConfirmCleanRUV}
+ closeHandler={this.closeConfirmCleanRUV}
+ handleChange={this.handleChange}
+ actionHandler={this.cleanRUV}
+ spinning={this.state.modalSpinning}
+ item={"Replica ID " + this.state.rid}
+ checked={this.state.modalChecked}
+ mTitle="Remove RUV Element (CleanAllRUV)"
+ mMsg="Are you sure you want to attempt to clean this Replica ID
from the suffix?"
+ mSpinningMsg="Starting cleaning task (CleanAllRUV) ..."
+ mBtnName="Remove RUV Element"
+ />
+ <ExportModal
+ showModal={this.state.showConfirmExport}
+ closeHandler={this.closeConfirmExport}
+ handleChange={this.handleLDIFChange}
+ saveHandler={this.doExport}
+ spinning={this.state.exportSpinner}
+ saveOK={this.state.saveOK}
+ />
+ </div>
+ );
+ }
+}
+
+ReplRUV.propTypes = {
+ suffix: PropTypes.string,
+ serverId: PropTypes.string,
+ rows: PropTypes.array,
+ addNotification: PropTypes.func,
+ localRID: PropTypes.string,
+ reload: PropTypes.func,
+};
+
+ReplRUV.defaultProps = {
+ serverId: "",
+ suffix: "",
+ rows: [],
+ addNotification: noop,
+ localRID: "",
+ reload: noop,
+};
diff --git a/src/cockpit/389-console/src/lib/replication/winsyncAgmts.jsx
b/src/cockpit/389-console/src/lib/replication/winsyncAgmts.jsx
new file mode 100644
index 0000000..9303720
--- /dev/null
+++ b/src/cockpit/389-console/src/lib/replication/winsyncAgmts.jsx
@@ -0,0 +1,1210 @@
+import cockpit from "cockpit";
+import React from "react";
+import { ConfirmPopup, DoubleConfirmModal } from "../notifications.jsx";
+import { ReplAgmtTable } from "./replTables.jsx";
+import { WinsyncAgmtModal } from "./replModals.jsx";
+import { log_cmd, valid_dn, valid_port } from "../tools.jsx";
+import PropTypes from "prop-types";
+import {
+ Button,
+ // Icon,
+ noop,
+} from "patternfly-react";
+
+export class WinsyncAgmts extends React.Component {
+ _mounted = false;
+ constructor(props) {
+ super(props);
+ this.state = {
+ showCreateAgmtModal: false,
+ showEditAgmtModal: false,
+ showConfirmDeleteAgmt: false,
+ showConfirmInitAgmt: false,
+ showConfirmEnableAgmt: false,
+ showConfirmDisableAgmt: false,
+ errObj: {},
+ modalMsg: "",
+ modalScheduleMsg: "",
+ savingAgmt: false,
+ mounted: false,
+ // Create agreement
+ agmtName: "",
+ agmtHost: "",
+ agmtPort: "",
+ agmtProtocol: "LDAPS",
+ agmtBindDN: "",
+ agmtBindPW: "",
+ agmtBindPWConfirm: "",
+ agmtFracAttrs: [],
+ agmtSync: true,
+ agmtSyncMon: true,
+ agmtSyncTue: true,
+ agmtSyncWed: true,
+ agmtSyncThu: true,
+ agmtSyncFri: true,
+ agmtSyncSat: true,
+ agmtSyncSun: true,
+ agmtStartTime: "0",
+ agmtEndTime: "0",
+ agmtInit: "noinit",
+ agmtSaveOK: false,
+ modalChecked: false,
+ modalSpinning: false,
+ // Winsync specific settings
+ agmtSyncGroups: false,
+ agmtSyncUsers: true,
+ agmtWinDomain: "",
+ agmtWinSubtree: "",
+ agmtDSSubtree: "",
+ agmtOneWaySync: "both", // "both", "toWindows",
"fromWindows"
+ agmtSyncInterval: "",
+ // Init agmt
+ agmtInitCounter: 0,
+ agmtInitIntervals: [],
+ };
+ this.showCreateAgmtModal = this.showCreateAgmtModal.bind(this);
+ this.closeCreateAgmtModal = this.closeCreateAgmtModal.bind(this);
+ this.closeEditAgmtModal = this.closeEditAgmtModal.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleModalChange = this.handleModalChange.bind(this);
+ this.handleTAFracAttrChange = this.handleTAFracAttrChange.bind(this);
+ this.handleTAFracAttrChangeEdit = this.handleTAFracAttrChangeEdit.bind(this);
+ this.createAgmt = this.createAgmt.bind(this);
+ this.showEditAgmt = this.showEditAgmt.bind(this);
+ this.saveAgmt = this.saveAgmt.bind(this);
+ this.pokeAgmt = this.pokeAgmt.bind(this);
+ this.initAgmt = this.initAgmt.bind(this);
+ this.enableAgmt = this.enableAgmt.bind(this);
+ this.disableAgmt = this.disableAgmt.bind(this);
+ this.deleteAgmt = this.deleteAgmt.bind(this);
+ this.showConfirmDeleteAgmt = this.showConfirmDeleteAgmt.bind(this);
+ this.closeConfirmDeleteAgmt = this.closeConfirmDeleteAgmt.bind(this);
+ this.showConfirmInitAgmt = this.showConfirmInitAgmt.bind(this);
+ this.closeConfirmInitAgmt = this.closeConfirmInitAgmt.bind(this);
+ this.confirmToggle = this.confirmToggle.bind(this);
+ this.closeConfirmEnableAgmt = this.closeConfirmEnableAgmt.bind(this);
+ this.closeConfirmDisableAgmt = this.closeConfirmDisableAgmt.bind(this);
+ this.watchAgmtInit = this.watchAgmtInit.bind(this);
+ }
+
+ componentDidMount () {
+ this._mounted = true;
+ }
+
+ componentWillUnmount () {
+ this._mounted = false;
+ }
+
+ listEqual(old_values, new_values) {
+ if (old_values.length != new_values.length) {
+ return false;
+ }
+ for (let i = old_values.length; i--;) {
+ if (old_values[i] != new_values[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ handleModalChange (e) {
+ this.setState({
+ [e.target.id]: e.target.checked,
+ });
+ }
+
+ handleChange (e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked :
e.target.value;
+ let time_val = "";
+ let valueErr = false;
+ let errObj = this.state.errObj;
+ let all_good = true;
+ let modal_msg = "";
+ let modal_schedule_msg = "";
+ let edit = false;
+ if (value == "") {
+ valueErr = true;
+ }
+ errObj[e.target.id] = valueErr;
+ if (e.target.name == "agmt-modal-edit") {
+ let orig_attr = "_" + e.target.id;
+ let attr = e.target.id;
+ all_good = false;
+ if ((attr != 'agmtHost' && this.state.agmtHost !=
this.state._agmtHost) ||
+ (attr != 'agmtPort' && this.state.agmtPort !=
this.state._agmtPort) ||
+ (attr != 'agmtBindDN' && this.state.agmtBindDN !=
this.state._agmtBindDN) ||
+ (attr != 'agmtProtocol' && this.state.agmtProtocol !=
this.state._agmtProtocol) ||
+ (attr != 'agmtSync' && this.state.agmtSync !=
this.state._agmtSync) ||
+ (attr != 'agmtSyncGroups' && this.state.agmtSyncGroups !=
this.state._agmtSyncGroups) ||
+ (attr != 'agmtSyncUsers' && this.state.agmtSyncUsers !=
this.state._agmtSyncUsers) ||
+ (attr != 'agmtWinDomain' && this.state.agmtWinDomain !=
this.state._agmtWinDomain) ||
+ (attr != 'agmtWinSubtree' && this.state.agmtWinSubtree !=
this.state._agmtWinSubtree) ||
+ (attr != 'agmtDSSubtree' && this.state.agmtDSSubtree !=
this.state._agmtDSSubtree) ||
+ (attr != 'agmtOneWaySync' && this.state.agmtOneWaySync !=
this.state._agmtOneWaySync) ||
+ (attr != 'agmtSyncInterval' &&
this.state.agmtSyncInterval != this.state._agmtSyncInterval) ||
+ (attr != 'agmtFracAttrs' &&
!this.listEqual(this.state.agmtFracAttrs, this.state._agmtFracAttrs))) {
+ all_good = true;
+ }
+ if (!this.state._agmtSync) {
+ if ((attr != 'agmtSyncMon' && this.state.agmtSyncMon !=
this.state._agmtSyncMon) ||
+ (attr != 'agmtSyncTue' && this.state.agmtSyncTue !=
this.state._agmtSyncTue) ||
+ (attr != 'agmtSyncWed' && this.state.agmtSyncWed !=
this.state._agmtSyncWed) ||
+ (attr != 'agmtSyncThu' && this.state.agmtSyncThu !=
this.state._agmtSyncThu) ||
+ (attr != 'agmtSyncFri' && this.state.agmtSyncFri !=
this.state._agmtSyncFri) ||
+ (attr != 'agmtSyncSat' && this.state.agmtSyncSat !=
this.state._agmtSyncSat) ||
+ (attr != 'agmtSyncSun' && this.state.agmtSyncSun !=
this.state._agmtSyncSun)) {
+ all_good = true;
+ }
+ }
+ if (attr != 'agmtFracAttrs' &&
+ value != this.state[orig_attr]) {
+ all_good = true;
+ } else if (attr == 'agmtFracAttrs' && !this.listEqual(value,
this.state._agmtFracAttrs)) {
+ all_good = true;
+ }
+ }
+
+ if (e.target.type == "time") {
+ // Strip out the colon from the time
+ time_val = value.replace(':', '');
+ }
+
+ if (e.target.name.startsWith("agmt-modal")) {
+ // Validate modal settings "live"
+ if (e.target.id == 'agmtName') {
+ if (value == "") {
+ all_good = false;
+ }
+ } else if (this.state.agmtName == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtHost') {
+ if (value == "") {
+ all_good = false;
+ }
+ } else if (this.state.agmtHost == "") {
+ all_good = false;
+ } else if (edit && value == this.state._agmtHost) {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtPort') {
+ if (value == "") {
+ all_good = false;
+ } else if (!valid_port(value)) {
+ all_good = false;
+ errObj['agmtPort'] = true;
+ modal_msg = "Invalid Consumer Port number";
+ }
+ } else if (this.state.agmtPort == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtBindDN') {
+ if (value == "") {
+ all_good = false;
+ }
+ if (!valid_dn(value)) {
+ errObj['agmtBindDN'] = true;
+ all_good = false;
+ modal_msg = "Invalid DN for Bind DN";
+ }
+ } else if (this.state.agmtBindDN == "") {
+ all_good = false;
+ } else if (!valid_dn(this.state.agmtBindDN)) {
+ modal_msg = "Invalid DN for Bind DN";
+ errObj['agmtBindDN'] = true;
+ all_good = false;
+ }
+ if (e.target.id == 'agmtBindPW') {
+ if (value == "") {
+ all_good = false;
+ } else if (value != this.state.agmtBindPWConfirm) {
+ modal_msg = "Passwords Do Not Match";
+ errObj['agmtBindPW'] = true;
+ errObj['agmtBindPWConfirm'] = true;
+ all_good = false;
+ } else {
+ errObj['agmtBindPW'] = false;
+ errObj['agmtBindPWConfirm'] = false;
+ }
+ } else if (this.state.agmtBindPW == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtBindPWConfirm') {
+ if (value == "") {
+ all_good = false;
+ } else if (value != this.state.agmtBindPW) {
+ modal_msg = "Passwords Do Not Match";
+ errObj['agmtBindPW'] = true;
+ errObj['agmtBindPWConfirm'] = true;
+ all_good = false;
+ } else {
+ errObj['agmtBindPW'] = false;
+ errObj['agmtBindPWConfirm'] = false;
+ }
+ } else if (this.state.agmtBindPWConfirm == "") {
+ all_good = false;
+ }
+ if (e.target.id == 'agmtSync') {
+ if (!value) {
+ if (this.state.agmtStartTime >= this.state.agmtEndTime) {
+ modal_schedule_msg = "Schedule start time is greater than or
equal to the end time";
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ }
+ }
+ } else if (!this.state.agmtSync) {
+ // Check the days first
+ let have_days = false;
+ let days = ["agmtSyncSun", "agmtSyncMon",
"agmtSyncTue", "agmtSyncWed",
+ "agmtSyncThu", "agmtSyncFri",
"agmtSyncSat"];
+ for (let day of days) {
+ if ((e.target.id != day && this.state[day]) || (e.target.id
== day && value)) {
+ have_days = true;
+ break;
+ }
+ }
+ if (!have_days) {
+ modal_schedule_msg = "You must select at least one day for
replication";
+ all_good = false;
+ } else if (e.target.id == 'agmtStartTime') {
+ if (time_val == "") {
+ all_good = false;
+ errObj['agmtStartTime'] = true;
+ } else if (time_val >=
this.state.agmtEndTime.replace(":", "")) {
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ modal_schedule_msg = "Schedule start time is greater than or
equal to the end time";
+ } else {
+ // All good, reset form
+ modal_schedule_msg = "";
+ errObj['agmtStartTime'] = false;
+ errObj['agmtEndTime'] = false;
+ }
+ } else if (e.target.id == 'agmtEndTime') {
+ if (time_val == "") {
+ errObj['agmtEndTime'] = true;
+ all_good = false;
+ } else if (this.state.agmtStartTime.replace(":",
"") >= time_val) {
+ modal_schedule_msg = "Schedule start time is greater than or
equal to the end time";
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ } else {
+ // All good, reset form
+ modal_schedule_msg = "";
+ errObj['agmtStartTime'] = false;
+ errObj['agmtEndTime'] = false;
+ }
+ } else if (this.state.agmtStartTime >= this.state.agmtEndTime) {
+ modal_schedule_msg = "Schedule start time is greater than or
equal to the end time";
+ errObj['agmtStartTime'] = true;
+ all_good = false;
+ }
+ }
+ if (e.target.id == 'agmtSyncGroups') {
+ if (edit && value == this.state._agmtSyncGroups) {
+ all_good = false;
+ }
+ }
+ if (e.target.id == 'agmtSyncUsers') {
+ if (edit && value == this.state._agmtSyncUsers) {
+ all_good = false;
+ }
+ }
+ if (e.target.id == 'agmtWinSubtree') {
+ if (value == "") {
+ all_good = false;
+ } else if (edit && value == this.state._agmtWinSubtree) {
+ all_good = false;
+ }
+ if (!valid_dn(value)) {
+ errObj['agmtWinSubtree'] = true;
+ all_good = false;
+ modal_msg = "Invalid DN for Windows Subtree";
+ }
+ }
+ if (e.target.id == 'agmtDSSubtree') {
+ if (value == "") {
+ all_good = false;
+ } else if (edit && value == this.state._agmtDSSubtree) {
+ all_good = false;
+ }
+ if (!valid_dn(value)) {
+ errObj['agmtDSSubtree'] = true;
+ all_good = false;
+ modal_msg = "Invalid DN for Directory Server Subtree";
+ }
+ }
+ if (e.target.id == 'agmtSyncInterval') {
+ if (value != "" && isNaN(value)) {
+ errObj['agmtSyncInterval'] = true;
+ all_good = false;
+ modal_msg = "Invalid value, value must be a number";
+ } else if (edit && value == this.state._agmtSyncInterval) {
+ all_good = false;
+ }
+ }
+ if (e.target.id == 'agmtOneWaySync') {
+ if (edit && value == this.state._agmtOneWaySync) {
+ all_good = false;
+ }
+ }
+ // End of agmt modal live validation
+ }
+ this.setState({
+ [e.target.id]: value,
+ errObj: errObj,
+ agmtSaveOK: all_good,
+ modalMsg: modal_msg,
+ modalScheduleMsg: modal_schedule_msg,
+ });
+ }
+
+ handleTAFracAttrChangeEdit (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal-edit',
+ id: 'agmtFracAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ handleTAFracAttrChange (values) {
+ // TypeAhead handling
+ let e = {
+ target: {
+ name: 'agmt-modal',
+ id: 'agmtFracAttrs',
+ value: values,
+ type: 'input',
+ }
+ };
+ this.handleChange(e);
+ }
+
+ showConfirmDeleteAgmt (agmtName) {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmDeleteAgmt: true,
+ modalSpinning: false,
+ modalChecked: false,
+ });
+ }
+
+ closeConfirmDeleteAgmt () {
+ this.setState({
+ showConfirmDeleteAgmt: false,
+ modalChecked: false,
+ });
+ }
+
+ showConfirmInitAgmt (agmtName) {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmInitAgmt: true,
+ modalSpinning: false,
+ modalChecked: false,
+ });
+ }
+
+ closeConfirmInitAgmt () {
+ this.setState({
+ showConfirmInitAgmt: false,
+ modalChecked: false,
+ });
+ }
+
+ showCreateAgmtModal () {
+ this.setState({
+ showCreateAgmtModal: true,
+ agmtName: "",
+ agmtHost: "",
+ agmtPort: "",
+ agmtProtocol: "LDAP",
+ agmtBindDN: "",
+ agmtBindPW: "",
+ agmtBindPWConfirm: "",
+ agmtFracAttrs: [],
+ agmtSync: true,
+ agmtSyncMon: true,
+ agmtSyncTue: true,
+ agmtSyncWed: true,
+ agmtSyncThu: true,
+ agmtSyncFri: true,
+ agmtSyncSat: true,
+ agmtSyncSun: true,
+ agmtStartTime: "00:00",
+ agmtEndTime: "23:59",
+ agmtInit: "noinit",
+ agmtSaveOK: false,
+ agmtSyncGroups: false,
+ agmtSyncUsers: true,
+ agmtWinDomain: "",
+ agmtWinSubtree: "",
+ agmtDSSubtree: "",
+ agmtOneWaySync: "both", // "both", "toWindows",
"fromWindows"
+ agmtSyncInterval: "",
+ modalScheduleMsg: "",
+ errObj: {
+ // Marks all the fields as required
+ agmtName: true,
+ agmtHost: true,
+ agmtPort: true,
+ agmtBindDN: true,
+ agmtBindPW: true,
+ agmtBindPWConfirm: true,
+ agmtStartTime: false,
+ agmtEndTime: false,
+ agmtWinDomain: true,
+ agmtWinSubtree: true,
+ agmtDSSubtree: true,
+ }
+ });
+ }
+
+ closeCreateAgmtModal () {
+ this.setState({
+ showCreateAgmtModal: false
+ });
+ }
+
+ closeEditAgmtModal () {
+ this.setState({
+ showEditAgmtModal: false
+ });
+ }
+
+ showEditAgmt (agmtName) {
+ // Search for the agmt to get all the details
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' +
this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'get', agmtName, '--suffix=' +
this.props.suffix,
+ ];
+
+ log_cmd('showEditAgmt', 'Edit winsync agreement', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let agmtName = "";
+ let agmtHost = "";
+ let agmtPort = "";
+ let agmtProtocol = "";
+ let agmtBindDN = "";
+ let agmtBindPW = "";
+ let agmtBindPWConfirm = "";
+ let agmtFracAttrs = [];
+ let agmtSync = true;
+ let agmtSyncMon = false;
+ let agmtSyncTue = false;
+ let agmtSyncWed = false;
+ let agmtSyncThu = false;
+ let agmtSyncFri = false;
+ let agmtSyncSat = false;
+ let agmtSyncSun = false;
+ let agmtStartTime = "";
+ let agmtEndTime = "";
+ let agmtSyncGroups = false;
+ let agmtSyncUsers = false;
+ let agmtWinDomain = "";
+ let agmtWinSubtree = "";
+ let agmtDSSubtree = "";
+ let agmtOneWaySync = "both";
+ let agmtSyncInterval = "";
+
+ for (let attr in config['attrs']) {
+ let val = config['attrs'][attr][0];
+ if (attr == "winsyncinterval") {
+ agmtSyncInterval = val;
+ }
+ if (attr == "onewaysync") {
+ agmtOneWaySync = val;
+ }
+ if (attr == "nsds7directoryreplicasubtree") {
+ agmtDSSubtree = val;
+ }
+ if (attr == "nsds7windowsreplicasubtree") {
+ agmtWinSubtree = val;
+ }
+ if (attr == "nsds7windowsdomain") {
+ agmtWinDomain = val;
+ }
+ if (attr == "nsds7newwinusersyncenabled") {
+ if (val.toLowerCase() == "on") {
+ agmtSyncUsers = true;
+ }
+ }
+ if (attr == "nsds7newwingroupsyncenabled") {
+ if (val.toLowerCase() == "on") {
+ agmtSyncGroups = true;
+ }
+ }
+ if (attr == "cn") {
+ agmtName = val;
+ }
+ if (attr == "nsds5replicahost") {
+ agmtHost = val;
+ }
+ if (attr == "nsds5replicaport") {
+ agmtPort = val;
+ }
+ if (attr == "nsds5replicatransportinfo") {
+ agmtProtocol = val;
+ }
+ if (attr == "nsds5replicabinddn") {
+ agmtBindDN = val;
+ }
+ if (attr == "nsds5replicacredentials") {
+ agmtBindPW = val;
+ agmtBindPWConfirm = val;
+ }
+ if (attr == "nsds5replicatedattributelist") {
+ let attrs = val.replace("(objectclass=*) $
EXCLUDE", "").trim();
+ agmtFracAttrs = attrs.split(' ');
+ }
+ if (attr == "nsds5replicaupdateschedule") {
+ agmtSync = false;
+ // Parse schedule
+ let parts = val.split(' ');
+ let times = parts[0].split('-');
+ let days = parts[1];
+
+ // Do the times
+ agmtStartTime = times[0].substring(0, 2) + ":" +
times[0].substring(2, 4);
+ agmtEndTime = times[1].substring(0, 2) + ":" +
times[1].substring(2, 4);
+
+ // Do the days
+ if (days.includes("0")) {
+ agmtSyncSun = true;
+ }
+ if (days.includes("1")) {
+ agmtSyncMon = true;
+ }
+ if (days.includes("2")) {
+ agmtSyncTue = true;
+ }
+ if (days.includes("3")) {
+ agmtSyncWed = true;
+ }
+ if (days.includes("4")) {
+ agmtSyncThu = true;
+ }
+ if (days.includes("5")) {
+ agmtSyncFri = true;
+ }
+ if (days.includes("6")) {
+ agmtSyncSat = true;
+ }
+ }
+ }
+ if (this._mounted) {
+ this.setState({
+ showEditAgmtModal: true,
+ errObj: {},
+ agmtName: agmtName,
+ agmtHost: agmtHost,
+ agmtPort: agmtPort,
+ agmtProtocol: agmtProtocol,
+ agmtBindDN: agmtBindDN,
+ agmtBindPW: agmtBindPW,
+ agmtBindPWConfirm: agmtBindPWConfirm,
+ agmtFracAttrs: agmtFracAttrs,
+ agmtSync: agmtSync,
+ agmtSyncMon: agmtSyncMon,
+ agmtSyncTue: agmtSyncTue,
+ agmtSyncWed: agmtSyncWed,
+ agmtSyncThu: agmtSyncThu,
+ agmtSyncFri: agmtSyncFri,
+ agmtSyncSat: agmtSyncSat,
+ agmtSyncSun: agmtSyncSun,
+ agmtStartTime: agmtStartTime,
+ agmtEndTime: agmtEndTime,
+ agmtSyncGroups: agmtSyncGroups,
+ agmtSyncUsers: agmtSyncUsers,
+ agmtWinDomain: agmtWinDomain,
+ agmtWinSubtree: agmtWinSubtree,
+ agmtDSSubtree: agmtDSSubtree,
+ agmtOneWaySync: agmtOneWaySync,
+ agmtSyncInterval: agmtSyncInterval,
+ agmtSaveOK: false,
+ // Record original values before editing
+ _agmtName: agmtName,
+ _agmtHost: agmtHost,
+ _agmtPort: agmtPort,
+ _agmtProtocol: agmtProtocol,
+ _agmtBindDN: agmtBindDN,
+ _agmtBindPW: agmtBindPW,
+ _agmtBindPWConfirm: agmtBindPWConfirm,
+ _agmtFracAttrs: agmtFracAttrs,
+ _agmtSync: agmtSync,
+ _agmtSyncMon: agmtSyncMon,
+ _agmtSyncTue: agmtSyncTue,
+ _agmtSyncWed: agmtSyncWed,
+ _agmtSyncThu: agmtSyncThu,
+ _agmtSyncFri: agmtSyncFri,
+ _agmtSyncSat: agmtSyncSat,
+ _agmtSyncSun: agmtSyncSun,
+ _agmtStartTime: agmtStartTime,
+ _agmtEndTime: agmtEndTime,
+ _agmtSyncGroups: agmtSyncGroups,
+ _agmtSyncUsers: agmtSyncUsers,
+ _agmtWinDomain: agmtWinDomain,
+ _agmtWinSubtree: agmtWinSubtree,
+ _agmtDSSubtree: agmtDSSubtree,
+ _agmtOneWaySync: agmtOneWaySync,
+ _agmtSyncInterval: agmtSyncInterval,
+ });
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to get agreement information for: "${agmtName}"
- ${errMsg.desc}`
+ );
+ });
+ }
+
+ saveAgmt () {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' +
this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'set', this.state.agmtName,
'--suffix=' + this.props.suffix,
+ ];
+
+ // Handle Schedule
+ if (!this.state.agmtSync) {
+ let agmt_days = "";
+ if (this.state.agmtSyncSun) {
+ agmt_days += "0";
+ }
+ if (this.state.agmtSyncMon) {
+ agmt_days += "1";
+ }
+ if (this.state.agmtSyncTue) {
+ agmt_days += "2";
+ }
+ if (this.state.agmtSyncWed) {
+ agmt_days += "3";
+ }
+ if (this.state.agmtSyncThu) {
+ agmt_days += "4";
+ }
+ if (this.state.agmtSyncFri) {
+ agmt_days += "5";
+ }
+ if (this.state.agmtSyncSat) {
+ agmt_days += "6";
+ }
+ cmd.push('--schedule=' +
this.state.agmtStartTime.replace(':', '') + "-" +
this.state.agmtEndTime.replace(':', '') + " " + agmt_days);
+ } else if (this.state.agmtSync != this.state._agmtSync &&
this.state.agmtSync) {
+ // We disabled custom scheduleRow
+ cmd.push('--schedule=');
+ }
+ if (this.state.agmtSyncGroups != this.state._agmtSyncGroups) {
+ let val = "off";
+ if (this.state.agmtSyncGroups) {
+ val = "on";
+ }
+ cmd.push('--sync-groups=' + val);
+ }
+ if (this.state.agmtSyncUsers != this.state._agmtSyncUsers) {
+ let val = "off";
+ if (this.state.agmtSyncUsers) {
+ val = "on";
+ }
+ cmd.push('--sync-users=' + val);
+ }
+ if (this.state.agmtWinDomain != this.state._agmtWinDomain) {
+ cmd.push('--win-domain=' + this.state.agmtWinDomain);
+ }
+ if (this.state.agmtWinSubtree != this.state._agmtWinSubtree) {
+ cmd.push('--win-subtree=' + this.state.agmtWinSubtree);
+ }
+ if (this.state.agmtDSSubtree != this.state._agmtDSSubtree) {
+ cmd.push('--ds-subtree=' + this.state.agmtDSSubtree);
+ }
+ if (this.state.agmtOneWaySync != this.state._agmtOneWaySync) {
+ cmd.push('--one-way-sync=' + this.state.agmtOneWaySync);
+ }
+ if (this.state.agmtSyncInterval != this.state._agmtSyncInterval) {
+ cmd.push('--sync-interval=' + this.state.agmtSyncInterval);
+ }
+ if (this.state.agmtProtocol != this.state._agmtProtocol) {
+ cmd.push('--conn-protocol=' + this.state.agmtProtocol);
+ }
+ if (this.state.agmtBindPW != this.state._agmtBindPW) {
+ cmd.push('--bind-passwd=' + this.state.agmtBindPW);
+ }
+ if (this.state.agmtBindDN != this.state._agmtBindDN) {
+ cmd.push('--bind-passwd=' + this.state.agmtBindDN);
+ }
+ if (this.state.agmtFracAttrs != this.state._agmtFracAttrs) {
+ cmd.push('--frac-list=' + this.state.agmtFracAttrs.join('
'));
+ }
+ if (this.state.agmtHost != this.state._agmtHost) {
+ cmd.push('--host=' + this.state.agmtHost);
+ }
+ if (this.state.agmtPort != this.state._agmtPort) {
+ cmd.push('--port=' + this.state.agmtPort);
+ }
+
+ this.setState({
+ savingAgmt: true
+ });
+ log_cmd('saveAgmt', 'update winsync agreement', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ if (this._mounted) {
+ this.setState({
+ savingAgmt: false,
+ showEditAgmtModal: false,
+ });
+ }
+ this.props.addNotification(
+ 'success',
+ 'Successfully updated winsync agreement'
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to update winsync agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ savingAgmt: false
+ });
+ });
+ }
+
+ pokeAgmt (agmtName) {
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'poke', agmtName, '--suffix=' +
this.props.suffix];
+ log_cmd('pokeAgmt', 'send updates now', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, "err": "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully poked winsync agreement'
+ );
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ 'error',
+ `Failed to poke winsync agreement - ${errMsg.desc}`
+ );
+ });
+ }
+
+ initAgmt () {
+ let init_cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'init', '--suffix=' +
this.props.suffix, this.state.agmtName];
+ log_cmd('initAgmt', 'Initialize agreement', init_cmd);
+ cockpit
+ .spawn(init_cmd, { superuser: true, "err": "message"
})
+ .done(content => {
+ var agmtIntervalCount = this.state.agmtInitCounter + 1;
+ var intervals = this.state.agmtInitIntervals;
+ this.props.reload(this.props.suffix);
+ intervals[agmtIntervalCount] = setInterval(this.watchAgmtInit, 2000,
this.state.agmtName, agmtIntervalCount);
+ // This triggers error and does not actually work
+ if (this._mounted) {
+ this.setState({
+ agmtInitCounter: agmtIntervalCount,
+ agmtInitIntervals: intervals,
+ showConfirmInitAgmt: false
+ });
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ 'error',
+ `Failed to initialize winsync agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ showConfirmInitAgmt: false
+ });
+ });
+ }
+
+ confirmToggle (agmtName, state) {
+ if (state == 'Enabled') {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmDisableAgmt: true
+ });
+ } else {
+ this.setState({
+ agmtName: agmtName,
+ showConfirmEnableAgmt: true
+ });
+ }
+ }
+
+ closeConfirmEnableAgmt () {
+ this.setState({
+ showConfirmEnableAgmt: false
+ });
+ }
+
+ closeConfirmDisableAgmt () {
+ this.setState({
+ showConfirmDisableAgmt: false
+ });
+ }
+
+ enableAgmt (agmtName) {
+ // Enable/disable agmt
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'enable', agmtName, '--suffix='
+ this.props.suffix];
+ log_cmd('enableAgmt', 'enable agmt', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully enabled winsync agreement');
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to enabled winsync agreement - ${errMsg.desc}`
+ );
+ });
+ }
+
+ disableAgmt (agmtName) {
+ // Enable/disable agmt
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'disable', agmtName, '--suffix='
+ this.props.suffix];
+ log_cmd('disableAgmt', 'Disable agmt', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully disabled winsync agreement');
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to disable winsync agreement - ${errMsg.desc}`
+ );
+ });
+ }
+
+ deleteAgmt () {
+ this.setState({
+ deleteSpinning: true
+ });
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'delete', '--suffix=' +
this.props.suffix, this.state.agmtName];
+ log_cmd('deleteAgmt', 'Delete agmt', cmd);
+ cockpit
+ .spawn(cmd, {superuser: true, "err": "message"})
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ this.props.addNotification(
+ 'success',
+ 'Successfully deleted winsync agreement');
+ this.setState({
+ showDeleteConfirm: false,
+ deleteSpinning: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to delete winsync agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ showDeleteConfirm: false,
+ deleteSpinning: false
+ });
+ });
+ }
+
+ createAgmt () {
+ let cmd = [
+ 'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' +
this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'create', this.state.agmtName,
'--suffix=' + this.props.suffix,
+ '--host=' + this.state.agmtHost, '--port=' +
this.state.agmtPort,
+ '--conn-protocol=' + this.state.agmtProtocol,
+ '--bind-dn=' + this.state.agmtBindDN, '--bind-passwd=' +
this.state.agmtBindPW,
+ '--ds-subtree=' + this.state.agmtDSSubtree, '--win-subtree='
+ this.state.agmtWinSubtree,
+ '--win-domain=' + this.state.agmtWinDomain, '--one-way-sync='
+ this.state.agmtOneWaySync
+ ];
+
+ // Handle Schedule
+ if (!this.state.agmtSync) {
+ let agmt_days = "";
+ if (this.state.agmtSyncSun) {
+ agmt_days += "0";
+ }
+ if (this.state.agmtSyncMon) {
+ agmt_days += "1";
+ }
+ if (this.state.agmtSyncTue) {
+ agmt_days += "2";
+ }
+ if (this.state.agmtSyncWed) {
+ agmt_days += "3";
+ }
+ if (this.state.agmtSyncThu) {
+ agmt_days += "4";
+ }
+ if (this.state.agmtSyncFri) {
+ agmt_days += "5";
+ }
+ if (this.state.agmtSyncSat) {
+ agmt_days += "6";
+ }
+ cmd.push('--schedule=' +
this.state.agmtStartTime.replace(':', '') + "-" +
this.state.agmtEndTime.replace(':', '') + " " + agmt_days);
+ }
+
+ if (this.state.agmtFracAttrs.length > 0) {
+ cmd.push('--frac-list=' + this.state.agmtFracAttrs.join('
'));
+ }
+ if (this.state.agmtSyncGroups) {
+ cmd.push('--sync-groups=on');
+ }
+ if (this.state.agmtSyncUsers) {
+ cmd.push('--sync-users=on');
+ }
+ if (this.state.agmtSyncInterval != "") {
+ cmd.push('--sync-interval=' + this.state.agmtSyncInterval);
+ }
+
+ this.setState({
+ savingAgmt: true
+ });
+ log_cmd('createAgmt', 'Create winsync agreement', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ this.props.reload(this.props.suffix);
+ if (this._mounted) {
+ this.setState({
+ savingAgmt: false,
+ showCreateAgmtModal: false,
+ });
+ }
+ this.props.addNotification(
+ 'success',
+ 'Successfully created winsync agreement'
+ );
+ if (this.state.agmtInit == 'online-init') {
+ this.initAgmt(this.state.agmtName);
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to create winsync agreement - ${errMsg.desc}`
+ );
+ this.setState({
+ savingAgmt: false
+ });
+ });
+ }
+
+ watchAgmtInit(agmtName, idx) {
+ // Watch the init, then clear the interval index
+ let status_cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'repl-winsync-agmt', 'init-status', '--suffix=' +
this.props.suffix, agmtName];
+ log_cmd('watchAgmtInit', 'Get initialization status for agmt',
status_cmd);
+ cockpit
+ .spawn(status_cmd, {superuser: true, "err":
"message"})
+ .done(data => {
+ let init_status = JSON.parse(data);
+ if (init_status.startsWith('Agreement successfully
initialized') ||
+ init_status.startsWith('Agreement initialization
failed')) {
+ // Either way we're done, stop watching the status
+ clearInterval(this.state.agmtInitIntervals[idx]);
+ }
+ this.props.reload(this.props.suffix);
+ });
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-right">
+ <ReplAgmtTable
+ rows={this.props.rows}
+ edit={this.showEditAgmt}
+ poke={this.pokeAgmt}
+ init={this.showConfirmInitAgmt}
+ enable={this.confirmToggle}
+ delete={this.showConfirmDeleteAgmt}
+ />
+ <div className="ds-margin-top ds-container ds-inline">
+ <Button
+ bsStyle="primary"
+ onClick={this.showCreateAgmtModal}
+
+ Create
Agreement
+ </Button>
+ <Button
+ className="ds-left-margin"
+ bsStyle="default"
+ onClick={() => {
+ this.props.reload(this.props.suffix);
+ }}
+
+ Refresh
Agreements
+ </Button>
+ </div>
+ <WinsyncAgmtModal
+ showModal={this.state.showCreateAgmtModal}
+ closeHandler={this.closeCreateAgmtModal}
+ handleChange={this.handleChange}
+ handleFracChange={this.handleTAFracAttrChange}
+ saveHandler={this.createAgmt}
+ spinning={this.state.savingAgmt}
+ agmtName={this.state.agmtName}
+ agmtHost={this.state.agmtHost}
+ agmtPort={this.state.agmtPort}
+ agmtBindDN={this.state.agmtBindDN}
+ agmtBindPW={this.state.agmtBindPW}
+ agmtBindPWConfirm={this.state.agmtBindPWConfirm}
+ agmtProtocol={this.state.agmtProtocol}
+ agmtFracAttrs={this.state.agmtFracAttrs}
+ agmtSync={this.state.agmtSync}
+ agmtSyncMon={this.state.agmtSyncMon}
+ agmtSyncTue={this.state.agmtSyncTue}
+ agmtSyncWed={this.state.agmtSyncWed}
+ agmtSyncThu={this.state.agmtSyncThu}
+ agmtSyncFri={this.state.agmtSyncFri}
+ agmtSyncSat={this.state.agmtSyncSat}
+ agmtSyncSun={this.state.agmtSyncSun}
+ agmtStartTime={this.state.agmtStartTime}
+ agmtEndTime={this.state.agmtEndTime}
+ agmtSyncGroups={this.state.agmtSyncGroups}
+ agmtSyncUsers={this.state.agmtSyncUsers}
+ agmtWinDomain={this.state.agmtWinDomain}
+ agmtWinSubtree={this.state.agmtWinSubtree}
+ agmtDSSubtree={this.state.agmtDSSubtree}
+ agmtOneWaySync={this.state.agmtOneWaySync}
+ agmtSyncInterval={this.state.agmtSyncInterval}
+ availAttrs={this.props.attrs}
+ error={this.state.errObj}
+ errorMsg={this.state.modalMsg}
+ errorScheduleMsg={this.state.modalScheduleMsg}
+ saveOK={this.state.agmtSaveOK}
+ />
+ <WinsyncAgmtModal
+ showModal={this.state.showEditAgmtModal}
+ closeHandler={this.closeEditAgmtModal}
+ handleChange={this.handleChange}
+ handleFracChange={this.handleTAFracAttrChangeEdit}
+ saveHandler={this.saveAgmt}
+ spinning={this.state.savingAgmt}
+ agmtName={this.state.agmtName}
+ agmtHost={this.state.agmtHost}
+ agmtPort={this.state.agmtPort}
+ agmtBindDN={this.state.agmtBindDN}
+ agmtBindPW={this.state.agmtBindPW}
+ agmtBindPWConfirm={this.state.agmtBindPWConfirm}
+ agmtProtocol={this.state.agmtProtocol}
+ agmtStripAttrs={this.state.agmtStripAttrs}
+ agmtFracAttrs={this.state.agmtFracAttrs}
+ agmtFracInitAttrs={this.state.agmtFracInitAttrs}
+ agmtSync={this.state.agmtSync}
+ agmtSyncMon={this.state.agmtSyncMon}
+ agmtSyncTue={this.state.agmtSyncTue}
+ agmtSyncWed={this.state.agmtSyncWed}
+ agmtSyncThu={this.state.agmtSyncThu}
+ agmtSyncFri={this.state.agmtSyncFri}
+ agmtSyncSat={this.state.agmtSyncSat}
+ agmtSyncSun={this.state.agmtSyncSun}
+ agmtStartTime={this.state.agmtStartTime}
+ agmtEndTime={this.state.agmtEndTime}
+ agmtSyncGroups={this.state.agmtSyncGroups}
+ agmtSyncUsers={this.state.agmtSyncUsers}
+ agmtWinDomain={this.state.agmtWinDomain}
+ agmtWinSubtree={this.state.agmtWinSubtree}
+ agmtDSSubtree={this.state.agmtDSSubtree}
+ agmtOneWaySync={this.state.agmtOneWaySync}
+ agmtSyncInterval={this.state.agmtSyncInterval}
+ availAttrs={this.props.attrs}
+ error={this.state.errObj}
+ errorMsg={this.state.modalMsg}
+ errorScheduleMsg={this.state.modalScheduleMsg}
+ saveOK={this.state.agmtSaveOK}
+ edit
+ />
+ <DoubleConfirmModal
+ showModal={this.state.showConfirmDeleteAgmt}
+ closeHandler={this.closeConfirmDeleteAgmt}
+ handleChange={this.handleModalChange}
+ actionHandler={this.deleteAgmt}
+ spinning={this.state.modalSpinning}
+ item={this.state.agmtName}
+ checked={this.state.modalChecked}
+ mTitle="Delete Winsync Agreement"
+ mMsg="Are you sure you want to delete this winsync
agreement"
+ mSpinningMsg="Deleting Winsync Agreement ..."
+ mBtnName="Delete Agreement"
+ />
+ <DoubleConfirmModal
+ showModal={this.state.showConfirmInitAgmt}
+ closeHandler={this.closeConfirmInitAgmt}
+ handleChange={this.handleModalChange}
+ actionHandler={this.initAgmt}
+ spinning={this.state.modalSpinning}
+ item={this.state.agmtName}
+ checked={this.state.modalChecked}
+ mTitle="Initialize Winsync Agreement"
+ mMsg="Are you sure you want to initialize this winsync
agreement"
+ mSpinningMsg="Initializing Winsync Agreement ..."
+ mBtnName="Initialize Agreement"
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmEnableAgmt}
+ closeHandler={this.closeConfirmEnableAgmt}
+ actionFunc={this.enableAgmt}
+ actionParam={this.state.agmtName}
+ msg="Are you sure you want to enable this winsync
agreement?"
+ msgContent={this.state.agmtName}
+ />
+ <ConfirmPopup
+ showModal={this.state.showConfirmDisableAgmt}
+ closeHandler={this.closeConfirmDisableAgmt}
+ actionFunc={this.disableAgmt}
+ actionParam={this.state.agmtName}
+ msg="Are you sure you want to disable this winsync
agreement?"
+ msgContent={this.state.agmtName}
+ />
+ </div>
+ );
+ }
+}
+
+WinsyncAgmts.propTypes = {
+ suffix: PropTypes.string,
+ serverId: PropTypes.string,
+ rows: PropTypes.array,
+ addNotification: PropTypes.func,
+};
+
+WinsyncAgmts.defaultProps = {
+ serverId: "",
+ suffix: "",
+ rows: [],
+ addNotification: noop,
+};
diff --git a/src/cockpit/389-console/src/lib/security/certificateManagement.jsx
b/src/cockpit/389-console/src/lib/security/certificateManagement.jsx
index d5cd927..5bf091c 100644
--- a/src/cockpit/389-console/src/lib/security/certificateManagement.jsx
+++ b/src/cockpit/389-console/src/lib/security/certificateManagement.jsx
@@ -10,7 +10,7 @@ import {
Spinner,
noop
} from "patternfly-react";
-import { ConfirmPopup } from "../../lib/notifications.jsx";
+import { DoubleConfirmModal } from "../../lib/notifications.jsx";
import {
CertTable
} from "./securityTables.jsx";
@@ -41,11 +41,13 @@ export class CertificateManagement extends React.Component {
isCACert: false,
showConfirmCAChange: false,
loading: false,
+ modalSpinning: false,
+ modalChecked: false,
};
this.handleNavSelect = this.handleNavSelect.bind(this);
this.addCACert = this.addCACert.bind(this);
- this.handleAddChange = this.handleAddChange.bind(this);
+ this.handleChange = this.handleChange.bind(this);
this.addCert = this.addCert.bind(this);
this.showAddModal = this.showAddModal.bind(this);
this.closeAddModal = this.closeAddModal.bind(this);
@@ -217,6 +219,8 @@ export class CertificateManagement extends React.Component {
this.setState({
showConfirmDelete: true,
certName: dataRow.nickname[0],
+ modalSpinning: false,
+ modalChecked: false,
});
}
@@ -295,7 +299,9 @@ export class CertificateManagement extends React.Component {
if (!SSLFlags[0].includes('C') || !SSLFlags[0].includes('T'))
{
// This could remove the CA cert properties, better warn user
this.setState({
- showConfirmCAChange: true
+ showConfirmCAChange: true,
+ modalSpinning: false,
+ modalChecked: false
});
return;
}
@@ -305,7 +311,9 @@ export class CertificateManagement extends React.Component {
closeConfirmCAChange () {
this.setState({
- showConfirmCAChange: false
+ showConfirmCAChange: false,
+ modalSpinning: false,
+ modalChecked: false,
});
}
@@ -354,8 +362,8 @@ export class CertificateManagement extends React.Component {
});
}
- handleAddChange (e) {
- const value = e.target.value;
+ handleChange (e) {
+ const value = e.target.type === 'checkbox' ? e.target.checked :
e.target.value;
let valueErr = false;
let errObj = this.state.errObj;
@@ -418,6 +426,8 @@ export class CertificateManagement extends React.Component {
closeConfirmDelete () {
this.setState({
showConfirmDelete: false,
+ modalSpinning: false,
+ modalChecked: false,
});
}
@@ -567,7 +577,7 @@ export class CertificateManagement extends React.Component {
<SecurityAddCertModal
showModal={this.state.showAddModal}
closeHandler={this.closeAddModal}
- handleChange={this.handleAddChange}
+ handleChange={this.handleChange}
saveHandler={this.addCert}
spinning={this.state.modalSpinner}
error={this.state.errObj}
@@ -575,23 +585,36 @@ export class CertificateManagement extends React.Component {
<SecurityAddCACertModal
showModal={this.state.showAddCAModal}
closeHandler={this.closeAddCAModal}
- handleChange={this.handleAddChange}
+ handleChange={this.handleChange}
saveHandler={this.addCACert}
spinning={this.state.modalSpinner}
error={this.state.errObj}
/>
- <ConfirmPopup
+ <DoubleConfirmModal
showModal={this.state.showConfirmDelete}
closeHandler={this.closeConfirmDelete}
- actionFunc={this.delCert}
- msg="Are you sure you want to delete this certificate?"
- msgContent={this.state.certName}
+ handleChange={this.handleChange}
+ actionHandler={this.delCert}
+ spinning={this.state.modalSpinning}
+ item={this.state.certName}
+ checked={this.state.modalChecked}
+ mTitle="Delete Certificate"
+ mMsg="Are you sure you want to delete this certificate?"
+ mSpinningMsg="Deleting Certificate ..."
+ mBtnName="Delete Certificate"
/>
- <ConfirmPopup
+ <DoubleConfirmModal
showModal={this.state.showConfirmCAChange}
closeHandler={this.closeConfirmCAChange}
- actionFunc={this.doEditCert}
- msg="Removing the 'C' or 'T' flags from the SSL
trust catagory could break all TLS connectivity to and from the server, are you sure you
want to proceed?"
+ handleChange={this.handleChange}
+ actionHandler={this.doEditCert}
+ spinning={this.state.modalSpinning}
+ item={this.state.certName}
+ checked={this.state.modalChecked}
+ mTitle="Warning - "
+ mMsg="Removing the 'C' or 'T' flags from the SSL
trust catagory could break all TLS connectivity to and from the server, are you sure you
want to proceed?"
+ mSpinningMsg="Editing CA Certificate ..."
+ mBtnName="Change Trust Flags"
/>
</div>
);
diff --git a/src/cockpit/389-console/src/lib/security/ciphers.jsx
b/src/cockpit/389-console/src/lib/security/ciphers.jsx
index 4714fcb..c07c0dc 100644
--- a/src/cockpit/389-console/src/lib/security/ciphers.jsx
+++ b/src/cockpit/389-console/src/lib/security/ciphers.jsx
@@ -89,7 +89,7 @@ export class Ciphers extends React.Component {
.done(() => {
this.props.addNotification(
"success",
- `Successfully set cipher preferences. You must restart the
server for these changes to take effect.`
+ `Successfully set cipher preferences. You must restart the
Directory Server for these changes to take effect.`
);
this.setState({
saving: false,
diff --git a/src/cockpit/389-console/src/lib/security/securityTables.jsx
b/src/cockpit/389-console/src/lib/security/securityTables.jsx
index 6b74a01..ddcc717 100644
--- a/src/cockpit/389-console/src/lib/security/securityTables.jsx
+++ b/src/cockpit/389-console/src/lib/security/securityTables.jsx
@@ -144,7 +144,8 @@ class CertTable extends React.Component {
return [
<td key={rowData.nickname[0]}>
<DropdownButton id={rowData.nickname[0]}
- bsStyle="default"
title="Actions">
+ className="ds-action-button"
+ bsStyle="primary"
title="Actions">
<MenuItem eventKey="1"
onClick={() => {
this.props.editCert(rowData);
}}
@@ -353,7 +354,8 @@ class CRLTable extends React.Component {
return [
<td key={rowData.name[0]}>
<DropdownButton id={rowData.name[0]}
- bsStyle="default"
title="Actions">
+ className="ds-action-button"
+ bsStyle="primary"
title="Actions">
<MenuItem eventKey="1"
onClick={() => {
this.props.editIndex(rowData);
}}
diff --git a/src/cockpit/389-console/src/lib/tools.jsx
b/src/cockpit/389-console/src/lib/tools.jsx
index b3e7573..ca402f5 100644
--- a/src/cockpit/389-console/src/lib/tools.jsx
+++ b/src/cockpit/389-console/src/lib/tools.jsx
@@ -65,7 +65,7 @@ export function get_date_string (timestamp) {
let hour = timestamp.substr(8, 2);
let minute = timestamp.substr(10, 2);
let sec = timestamp.substr(12, 2);
- let date = new Date(parseInt(year), parseInt(month), parseInt(day),
+ let date = new Date(parseInt(year), (parseInt(month) - 1), parseInt(day),
parseInt(hour), parseInt(minute), parseInt(sec));
return date.toLocaleString();
}
@@ -103,3 +103,29 @@ export function get_date_diff(start, end) {
return `${days} days, ${hours} hours, ${minutes} minutes, and ${seconds} seconds`;
}
+
+export function bad_file_name(file_name) {
+ // file_name must be a string, and not a location/directory
+ if (file_name.includes("/")) {
+ return true;
+ }
+ return false;
+}
+
+export function valid_port (val) {
+ // Validate value is a number and between 1 and 65535
+ let result = !isNaN(val);
+ if (result) {
+ if (val < 1 || val > 65535) {
+ result = false;
+ }
+ }
+ return result;
+}
+
+export function valid_dn (dn) {
+ // Validate value is a valid DN (sanity validation)
+ let dn_regex = new RegExp("^([A-Za-z]+=.*)");
+ let result = dn_regex.test(dn);
+ return result;
+}
diff --git a/src/cockpit/389-console/src/monitor.jsx
b/src/cockpit/389-console/src/monitor.jsx
index 89d47b0..d9d7807 100644
--- a/src/cockpit/389-console/src/monitor.jsx
+++ b/src/cockpit/389-console/src/monitor.jsx
@@ -39,9 +39,9 @@ export class Monitor extends React.Component {
snmpData: {},
ldbmData: {},
serverData: {},
- showLoading: false,
loadingMsg: "",
notifications: [],
+ disableTree: false,
// Suffix
suffixLoading: false,
serverLoading: false,
@@ -112,9 +112,11 @@ export class Monitor extends React.Component {
this.addNotification = this.addNotification.bind(this);
this.removeNotification = this.removeNotification.bind(this);
this.loadSuffixTree = this.loadSuffixTree.bind(this);
+ this.enableTree = this.enableTree.bind(this);
this.update_tree_nodes = this.update_tree_nodes.bind(this);
this.selectNode = this.selectNode.bind(this);
this.loadMonitorSuffix = this.loadMonitorSuffix.bind(this);
+ this.disableSuffixLoading = this.disableSuffixLoading.bind(this);
this.loadMonitorLDBM = this.loadMonitorLDBM.bind(this);
this.reloadLDBM = this.reloadLDBM.bind(this);
this.loadMonitorSNMP = this.loadMonitorSNMP.bind(this);
@@ -209,6 +211,7 @@ export class Monitor extends React.Component {
icon: "pficon-catalog",
selectable: false,
id: "log-monitor",
+ state: {"expanded": true},
nodes: [
{
text: "Access Log",
@@ -279,8 +282,11 @@ export class Monitor extends React.Component {
}
selectNode(selectedNode) {
+ if (selectedNode.selected) {
+ return;
+ }
this.setState({
- showLoading: true
+ disableTree: true, // Disable the tree to allow node to be fully loaded
});
if (selectedNode.id == "database-monitor" ||
@@ -344,12 +350,7 @@ export class Monitor extends React.Component {
});
} else {
if (selectedNode.id in this.state) {
- // This suffix is already cached, but it might be incomplete...
- if (selectedNode.type == "dblink" &&
this.state.nsaddcount === undefined) {
- this.loadMonitorChaining(selectedNode.id);
- } else if (selectedNode.type != "dblink" &&
this.state.entrycachehitratio === undefined) {
- this.loadMonitorSuffix(selectedNode.id);
- }
+ // This suffix is already cached
this.setState(prevState => {
return {
nodes: this.nodeSelector(prevState.nodes, selectedNode),
@@ -597,6 +598,12 @@ export class Monitor extends React.Component {
});
}
+ disableSuffixLoading () {
+ this.setState({
+ suffixLoading: false
+ });
+ }
+
loadMonitorSuffix(suffix) {
this.setState({
suffixLoading: true
@@ -616,8 +623,7 @@ export class Monitor extends React.Component {
...this.state[suffix],
suffixData: config.attrs,
},
- suffixLoading: false,
- });
+ }, this.disableSuffixLoading);
})
.fail(() => {
// Notification of failure (could only be server down)
@@ -1001,10 +1007,20 @@ export class Monitor extends React.Component {
), this.loadMonitorReplication);
}
+ enableTree () {
+ this.setState({
+ disableTree: false
+ });
+ }
+
render() {
const { nodes } = this.state;
let monitorPage = "";
let monitor_element = "";
+ let disabled = "tree-view-container";
+ if (this.state.disableTree) {
+ disabled = "tree-view-container ds-disabled";
+ }
if (this.state.loaded) {
if (this.state.node_name == "database-monitor" ||
this.state.node_name == "") {
@@ -1012,14 +1028,15 @@ export class Monitor extends React.Component {
monitor_element =
<div className="ds-loading-spinner ds-center">
<p />
- <h4><b>Loading database monitor information
...</b></h4>
- <Spinner loading size="md" />
+ <h4>Loading database monitor information
...</h4>
+ <Spinner className="ds-margin-top-lg" loading
size="md" />
</div>;
} else {
monitor_element =
<DatabaseMonitor
data={this.state.ldbmData}
reload={this.reloadLDBM}
+ enableTree={this.enableTree}
/>;
}
} else if (this.state.node_name == "server-monitor") {
@@ -1027,8 +1044,8 @@ export class Monitor extends React.Component {
monitor_element =
<div className="ds-loading-spinner ds-center">
<p />
- <h4><b>Loading server monitor information
...</b></h4>
- <Spinner loading size="md" />
+ <h4>Loading server monitor information ...</h4>
+ <Spinner className="ds-margin-top-lg" loading
size="md" />
</div>;
} else {
monitor_element =
@@ -1036,6 +1053,7 @@ export class Monitor extends React.Component {
data={this.state.serverData}
reload={this.reloadServer}
serverId={this.props.serverId}
+ enableTree={this.enableTree}
/>;
}
} else if (this.state.node_name == "snmp-monitor") {
@@ -1043,14 +1061,15 @@ export class Monitor extends React.Component {
monitor_element =
<div className="ds-loading-spinner ds-center">
<p />
- <h4><b>Loading SNMP monitor information
...</b></h4>
- <Spinner loading size="md" />
+ <h4>Loading SNMP monitor information ...</h4>
+ <Spinner className="ds-margin-top-lg" loading
size="md" />
</div>;
} else {
monitor_element =
<SNMPMonitor
data={this.state.snmpData}
reload={this.reloadSNMP}
+ enableTree={this.enableTree}
/>;
}
} else if (this.state.node_name == "access-log-monitor") {
@@ -1063,6 +1082,7 @@ export class Monitor extends React.Component {
refreshing={this.state.accessRefreshing}
handleRefresh={this.accessRefreshCont}
lines={this.state.accessLines}
+ enableTree={this.enableTree}
/>;
} else if (this.state.node_name == "audit-log-monitor") {
monitor_element =
@@ -1074,6 +1094,7 @@ export class Monitor extends React.Component {
refreshing={this.state.auditRefreshing}
handleRefresh={this.auditRefreshCont}
lines={this.state.auditLines}
+ enableTree={this.enableTree}
/>;
} else if (this.state.node_name == "auditfail-log-monitor") {
monitor_element =
@@ -1085,6 +1106,7 @@ export class Monitor extends React.Component {
refreshing={this.state.auditfailRefreshing}
handleRefresh={this.auditFailRefreshCont}
lines={this.state.auditfailLines}
+ enableTree={this.enableTree}
/>;
} else if (this.state.node_name == "error-log-monitor") {
monitor_element =
@@ -1097,6 +1119,7 @@ export class Monitor extends React.Component {
handleRefresh={this.errorRefreshCont}
handleSevLevel={this.handleSevChange}
lines={this.state.errorLines}
+ enableTree={this.enableTree}
/>;
} else if (this.state.node_name == "replication-monitor") {
if (this.state.replLoading) {
@@ -1104,7 +1127,7 @@ export class Monitor extends React.Component {
<div className="ds-loading-spinner ds-center">
<p />
<h4>Loading replication monitor information
...</h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top-lg" loading
size="md" />
</div>;
} else {
if (this.state.replicatedSuffixes.length < 1) {
@@ -1139,6 +1162,7 @@ export class Monitor extends React.Component {
reloadAgmts={this.reloadReplAgmts}
reloadWinsyncAgmts={this.reloadReplWinsyncAgmts}
reloadConflicts={this.loadConflicts}
+ enableTree={this.enableTree}
key={this.state.replSuffix}
/>
</div>
@@ -1152,14 +1176,14 @@ export class Monitor extends React.Component {
<div className="ds-loading-spinner ds-center">
<p />
<h4>Loading suffix monitor information for
<b>{this.state.node_text} ...</b></h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top-lg" loading
size="md" />
</div>;
} else if (this.state.chainingLoading) {
monitor_element =
<div className="ds-loading-spinner ds-center">
<p />
<h4>Loading chaining monitor information for
<b>{this.state.node_text} ...</b></h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top-lg" loading
size="md" />
</div>;
} else {
if (this.state.node_type == "dblink") {
@@ -1169,6 +1193,7 @@ export class Monitor extends React.Component {
bename={this.state.bename}
reload={this.loadMonitorChaining}
data={this.state[this.state.node_text].chainingData}
+ enableTree={this.enableTree}
key={this.state.node_text}
/>;
} else {
@@ -1179,6 +1204,7 @@ export class Monitor extends React.Component {
bename={this.state.bename}
reload={this.loadMonitorSuffix}
data={this.state[this.state.node_text].suffixData}
+ enableTree={this.enableTree}
key={this.state.node_text}
/>;
}
@@ -1193,13 +1219,14 @@ export class Monitor extends React.Component {
<div className="ds-container">
<div>
<div className="ds-tree">
- <div className="tree-view-container"
id="db-tree"
+ <div className={disabled} id="monitor-tree"
style={treeViewContainerStyles}>
<TreeView
nodes={nodes}
highlightOnHover
highlightOnSelect
selectNode={this.selectNode}
+ key={this.state.node_text}
/>
</div>
</div>
@@ -1214,7 +1241,7 @@ export class Monitor extends React.Component {
<div className="ds-loading-spinner ds-center">
<p />
<h4>Loading monitor information ...</h4>
- <Spinner loading size="md" />
+ <Spinner className="ds-margin-top-lg" loading
size="md" />
</div>;
}
diff --git a/src/cockpit/389-console/src/plugins.jsx
b/src/cockpit/389-console/src/plugins.jsx
index 0523614..342a19c 100644
--- a/src/cockpit/389-console/src/plugins.jsx
+++ b/src/cockpit/389-console/src/plugins.jsx
@@ -493,10 +493,7 @@ export class Plugins extends React.Component {
removeNotificationAction={this.removeNotification}
/>
<Row className="clearfix">
- <Col sm={1}>
- <h2>Plugins</h2>
- </Col>
- <Col sm={10}>
+ <Col sm={12}>
<Spinner
className="ds-float-left ds-plugin-spinner"
loading={this.state.loading}
@@ -504,13 +501,12 @@ export class Plugins extends React.Component {
/>
</Col>
</Row>
- <hr />
<Tab.Container
id="left-tabs-example"
defaultActiveKey={Object.keys(selectPlugins)[0]}
<Row
className="clearfix">
- <Col sm={2}>
+ <Col sm={3}>
<Nav bsStyle="pills" stacked>
{Object.entries(selectPlugins).map(([id, item]) => (
<NavItem key={id} eventKey={id}>
@@ -519,7 +515,7 @@ export class Plugins extends React.Component {
))}
</Nav>
</Col>
- <Col sm={10}>
+ <Col sm={9}>
<Tab.Content animation={false}>
{Object.entries(selectPlugins).map(([id, item]) => (
<Tab.Pane key={id} eventKey={id}>
diff --git a/src/cockpit/389-console/src/replication.jsx
b/src/cockpit/389-console/src/replication.jsx
new file mode 100644
index 0000000..7663129
--- /dev/null
+++ b/src/cockpit/389-console/src/replication.jsx
@@ -0,0 +1,1148 @@
+import cockpit from "cockpit";
+import React from "react";
+import { NotificationController } from "./lib/notifications.jsx";
+import { log_cmd } from "./lib/tools.jsx";
+import { ReplSuffix } from "./lib/replication/replSuffix.jsx";
+import { Changelog } from "./lib/replication/replChangelog.jsx";
+import { TreeView, Spinner } from "patternfly-react";
+import PropTypes from "prop-types";
+import "./css/ds.css";
+
+const treeViewContainerStyles = {
+ width: '295px',
+};
+
+export class Replication extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ notifications: [],
+ errObj: {},
+ nodes: [],
+ node_name: "",
+ node_text: "",
+ node_type: "",
+ node_replicated: false,
+ disableTree: true,
+
+ // Changelog
+ clLoading: false,
+ clDir: "",
+ clMaxEntries: "",
+ clMaxAge: "",
+ clCompactInt: "",
+ clTrimInt: "",
+ clEncrypt: false,
+
+ // Suffix
+ suffixLoading: false,
+ attributes: [],
+ role: "",
+ rid: "0",
+ bindDNs: [],
+ bindDNGroup: "",
+ agmtRows: [],
+ winsyncRows: [],
+ ruvRows: [],
+ suffixSpinning: false,
+ disabled: false,
+ suffixKey: 0,
+
+ showDisableConfirm: false,
+ 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.disableTree = this.disableTree.bind(this);
+ this.enableTree = this.enableTree.bind(this);
+
+ this.reloadConfig = this.reloadConfig.bind(this);
+ this.reloadAgmts = this.reloadAgmts.bind(this);
+ this.reloadWinsyncAgmts = this.reloadWinsyncAgmts.bind(this);
+ this.reloadRUV = this.reloadRUV.bind(this);
+ this.loadAttrs = this.loadAttrs.bind(this);
+ this.loadReplication = this.loadReplication.bind(this);
+ this.loadReplSuffix = this.loadReplSuffix.bind(this);
+ this.reloadChangelog = this.reloadChangelog.bind(this);
+ this.loadSuffixTree = this.loadSuffixTree.bind(this);
+ }
+
+ componentWillMount () {
+ this.loadReplication();
+ if (!this.state.loaded) {
+ this.loadAttrs();
+ }
+ }
+
+ componentDidMount() {
+ // this.loadSuffixTree(false);
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props.serverId !== prevProps.serverId) {
+ this.loadSuffixTree(true);
+ }
+ }
+
+ loadReplication () {
+ // Load the changelog, and build the suffix tree
+ this.setState({
+ loaded: false
+ });
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
'replication', 'get-changelog'];
+ log_cmd("loadReplication", "Load the replication info",
cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let clDir = "";
+ let clMaxEntries = "";
+ let clMaxAge = "";
+ let clCompactInt = "";
+ let clTrimInt = "";
+ let clEncrypt = false;
+ for (let attr in config['attrs']) {
+ let val = config['attrs'][attr][0];
+ if (attr == "nsslapd-changelogdir") {
+ clDir = val;
+ }
+ if (attr == "nsslapd-changelogmaxentries") {
+ clMaxEntries = val;
+ }
+ if (attr == "nsslapd-changelogmaxage") {
+ clMaxAge = val;
+ }
+ if (attr == "nsslapd-changelogcompactdb-interval") {
+ clCompactInt = val;
+ }
+ if (attr == "nsslapd-changelogtrim-interval") {
+ clTrimInt = val;
+ }
+ if (attr == "nsslapd-encryptionalgorithm") {
+ clEncrypt = true;
+ }
+ }
+ this.setState({
+ clDir: clDir,
+ clMaxEntries: clMaxEntries,
+ clMaxAge: clMaxAge,
+ clCompactInt: clCompactInt,
+ clTrimInt: clTrimInt,
+ clEncrypt: clEncrypt,
+ }, this.loadSuffixTree(true));
+ })
+ .fail(() => {
+ this.setState({
+ clDir: "",
+ clMaxEntries: "",
+ clMaxAge: "",
+ clCompactInt: "",
+ clTrimInt: "",
+ clEncrypt: false,
+ }, this.loadSuffixTree(true));
+ });
+ }
+
+ reloadChangelog () {
+ // Refresh the changelog
+ this.setState({
+ clLoading: true
+ });
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
'replication', 'get-changelog'];
+ log_cmd("reloadChangelog", "Reload the changelog", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let clDir = "";
+ let clMaxEntries = "";
+ let clMaxAge = "";
+ let clCompactInt = "";
+ let clTrimInt = "";
+ let clEncrypt = false;
+ for (let attr in config['attrs']) {
+ let val = config['attrs'][attr][0];
+ if (attr == "nsslapd-changelogdir") {
+ clDir = val;
+ }
+ if (attr == "nsslapd-changelogmaxentries") {
+ clMaxEntries = val;
+ }
+ if (attr == "nsslapd-changelogmaxage") {
+ clMaxAge = val;
+ }
+ if (attr == "nsslapd-changelogcompactdb-interval") {
+ clCompactInt = val;
+ }
+ if (attr == "nsslapd-changelogtrim-interval") {
+ clTrimInt = val;
+ }
+ if (attr == "nsslapd-encryptionalgorithm") {
+ clEncrypt = true;
+ }
+ }
+ this.setState({
+ clDir: clDir,
+ clMaxEntries: clMaxEntries,
+ clMaxAge: clMaxAge,
+ clCompactInt: clCompactInt,
+ clTrimInt: clTrimInt,
+ clEncrypt: clEncrypt,
+ clLoading: false
+ });
+ })
+ .fail(() => {
+ this.setState({
+ clDir: "",
+ clMaxEntries: "",
+ clMaxAge: "",
+ clCompactInt: "",
+ clTrimInt: "",
+ clEncrypt: false,
+ clLoading: false
+ });
+ });
+ }
+
+ processBranch(treeBranch) {
+ if (treeBranch.length == 0) {
+ return;
+ }
+ for (let sub in treeBranch) {
+ if (!treeBranch[sub].type.endsWith("suffix")) {
+ // Not a suffix, skip it
+ treeBranch.splice(sub, 1);
+ continue;
+ } else if (treeBranch[sub].replicated) {
+ treeBranch[sub].icon = "fa fa-clone";
+ treeBranch[sub].replicated = true;
+ }
+ this.processBranch(treeBranch[sub].nodes);
+ }
+ }
+
+ loadSuffixTree(fullReset) {
+ const cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-"
+ this.props.serverId + ".socket",
+ "backend", "get-tree",
+ ];
+ log_cmd("loadSuffixTree", "Start building the suffix tree",
cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let treeData = [];
+ if (content != "") {
+ treeData = JSON.parse(content);
+ }
+ let basicData = [
+ {
+ text: "Replication Changelog",
+ selectable: true,
+ selected: false,
+ icon: "pficon-catalog",
+ id: "changelog",
+
+ },
+ {
+ text: "Suffixes",
+ icon: "pficon-topology",
+ state: {"expanded": true},
+ selectable: false,
+ id: "repl-suffixes",
+ nodes: []
+ }
+ ];
+ let current_node = this.state.node_name;
+ let current_type = this.state.node_type;
+ let replicated = this.state.node_replicated;
+ if (fullReset && treeData.length > 0) {
+ let found = false;
+ for (let i = 0; i < treeData.length; i++) {
+ if (treeData[i].replicated) {
+ treeData[i].icon = "fa fa-clone";
+ replicated = true;
+ if (!found) {
+ // Load the first replicated suffix we find
+ treeData[i].selected = true;
+ current_node = treeData[i].id;
+ current_type = treeData[i].type;
+ this.loadReplSuffix(treeData[i].id);
+ found = true;
+ }
+ }
+ this.processBranch(treeData[i].nodes);
+ }
+ if (!found) {
+ // No replicated suffixes, load the first one
+ treeData[0].selected = true;
+ current_node = treeData[0].id;
+ current_type = treeData[0].type;
+ this.loadReplSuffix(treeData[0].id);
+ }
+ } else if (treeData.length > 0) {
+ // Reset current suffix
+ for (let suffix of treeData) {
+ this.processBranch(suffix.nodes);
+ if (suffix.id == current_node) {
+ suffix.selected = true;
+ replicated = suffix.replicated;
+ }
+ if (suffix.replicated) {
+ suffix.icon = "fa fa-clone";
+ }
+ }
+ this.loadReplSuffix(current_node);
+ }
+ basicData[1].nodes = treeData;
+ this.setState(() => ({
+ nodes: basicData,
+ node_name: current_node,
+ node_type: current_type,
+ node_replicated: replicated,
+ }), this.update_tree_nodes);
+ });
+ }
+
+ selectNode(selectedNode) {
+ if (selectedNode.selected) {
+ return;
+ }
+
+ this.setState({
+ disableTree: true // Disable the tree to allow node to be fully loaded
+ });
+
+ if (selectedNode.id == "changelog") {
+ // 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,
+ node_type: selectedNode.id,
+ };
+ });
+ } 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,
+ node_type: selectedNode.type,
+ node_replicated: selectedNode.replicated,
+ disableTree: false,
+ suffixKey: new Date(),
+ };
+ });
+ } else {
+ // Suffix/subsuffix
+ this.loadReplSuffix(selectedNode.id);
+ this.setState(prevState => {
+ return {
+ nodes: this.nodeSelector(prevState.nodes, selectedNode),
+ node_name: selectedNode.id,
+ node_text: selectedNode.text,
+ node_type: selectedNode.type,
+ node_replicated: selectedNode.replicated,
+ suffixKey: new Date(),
+ };
+ });
+ }
+ }
+ }
+
+ 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. This is the last
+ // step of loading the page, so mark it loaded!
+ let elements = document.getElementsByClassName('treeitem-row');
+ for (let el of elements) {
+ el.setAttribute('title', el.innerText);
+ }
+ this.setState({
+ loaded: true
+ });
+ }
+
+ 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
+ });
+ }
+
+ reloadAgmts (suffix) {
+ this.setState({
+ suffixSpinning: true,
+ disabled: true,
+ });
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-"
+ this.props.serverId + ".socket",
+ "repl-agmt", "list", "--suffix", suffix
+ ];
+ log_cmd("reloadAgmts", "get repl agreements", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const obj = JSON.parse(content);
+ let rows = [];
+ for (let idx in obj['items']) {
+ let agmt_attrs = obj['items'][idx]['attrs'];
+ let state = "Enabled";
+ let update_status = "";
+ let agmt_init_status = "";
+
+ // Compute state (enabled by default)
+ if ('nsds5replicaenabled' in agmt_attrs) {
+ if
(agmt_attrs['nsds5replicaenabled'][0].toLowerCase() == 'off') {
+ state = "Disabled";
+ }
+ }
+
+ // Check for status msgs
+ if ('nsds5replicalastupdatestatus' in agmt_attrs) {
+ update_status =
agmt_attrs['nsds5replicalastupdatestatus'][0];
+ }
+ if ('nsds5replicalastinitstatus' in agmt_attrs
&&
+ agmt_attrs['nsds5replicalastinitstatus'][0] !=
"") {
+ agmt_init_status =
agmt_attrs['nsds5replicalastinitstatus'][0];
+ if (agmt_init_status == "Error (0) Total update in
progress" ||
+ agmt_init_status == "Error (0)") {
+ agmt_init_status = <td
key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading
size="sm" /></td>;
+ } else if (agmt_init_status == "Error (0) Total update
succeeded") {
+ agmt_init_status = "Initialized";
+ agmt_init_status = <td
key={agmt_attrs['cn']}><i>Initialized</i></td>;
+ } else {
+ agmt_init_status = <td
key={agmt_attrs['cn']}>{agmt_init_status}</td>;
+ }
+ } else if (agmt_attrs['nsds5replicalastinitstart'][0] ==
"19700101000000Z") {
+ agmt_init_status = "Not initialized";
+ agmt_init_status = <td
key={agmt_attrs['cn']}><i>Not Initialized</i></td>;
+ } else if ('nsds5beginreplicarefresh' in agmt_attrs) {
+ agmt_init_status = <td
key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading
size="sm" /></td>;
+ }
+
+ // Update table
+ rows.push({
+ 'name': agmt_attrs['cn'],
+ 'host': agmt_attrs['nsds5replicahost'],
+ 'port': agmt_attrs['nsds5replicaport'],
+ 'state': [state],
+ 'status': [update_status],
+ 'initstatus': [agmt_init_status]
+ });
+ }
+
+ // Set agmt
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ agmtRows: rows,
+ },
+ suffixSpinning: false,
+ disabled: false,
+ });
+ })
+ .fail(() => {
+ this.setState({
+ suffixSpinning: false,
+ disabled: false,
+ });
+ });
+ }
+
+ reloadWinsyncAgmts (suffix) {
+ this.setState({
+ suffixSpinning: true,
+ disabled: true,
+ });
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-"
+ this.props.serverId + ".socket",
+ "repl-winsync-agmt", "list", "--suffix",
suffix
+ ];
+ log_cmd("reloadWinsyncAgmts", "Get Winsync Agreements",
cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const obj = JSON.parse(content);
+ let ws_rows = [];
+ for (var idx in obj['items']) {
+ let state = "Enabled";
+ let update_status = "";
+ let ws_agmt_init_status = "Initialized";
+ let agmt_attrs = obj['items'][idx]['attrs'];
+ // let agmt_name = agmt_attrs['cn'][0];
+
+ // Compute state (enabled by default)
+ if ('nsds5replicaenabled' in agmt_attrs) {
+ if
(agmt_attrs['nsds5replicaenabled'][0].toLowerCase() == 'off') {
+ state = "Disabled";
+ }
+ }
+
+ if ('nsds5replicalastupdatestatus' in agmt_attrs) {
+ update_status =
agmt_attrs['nsds5replicalastupdatestatus'][0];
+ }
+
+ if ('nsds5replicalastinitstatus' in agmt_attrs
&&
+ agmt_attrs['nsds5replicalastinitstatus'][0] !=
"") {
+ ws_agmt_init_status =
agmt_attrs['nsds5replicalastinitstatus'][0];
+ if (ws_agmt_init_status == "Error (0) Total update in
progress" ||
+ ws_agmt_init_status == "Error (0)") {
+ ws_agmt_init_status = <td
key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading
size="sm" /></td>;
+ } else if (ws_agmt_init_status == "Error (0) Total
update succeeded") {
+ ws_agmt_init_status = <td
key={agmt_attrs['cn']}><i>Initialized</i></td>;
+ } else {
+ ws_agmt_init_status = <td
key={agmt_attrs['cn']}>{ws_agmt_init_status}</td>;
+ }
+ } else if ('nsds5replicalastinitstart' in agmt_attrs
&& agmt_attrs['nsds5replicalastinitstart'][0] ==
"19700101000000Z") {
+ ws_agmt_init_status = <td
key={agmt_attrs['cn']}><i>Not initialized</i></td>;
+ } else if ('nsds5beginreplicarefresh' in agmt_attrs) {
+ ws_agmt_init_status = <td
key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading
size="sm" /></td>;
+ }
+
+ // Update table
+ ws_rows.push({
+ 'name': agmt_attrs['cn'],
+ 'host': agmt_attrs['nsds5replicahost'],
+ 'port': agmt_attrs['nsds5replicaport'],
+ 'state': [state],
+ 'status': [update_status],
+ 'initstatus': [ws_agmt_init_status]
+ });
+ }
+ // Set winsync agmts
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ winsyncRows: ws_rows,
+ },
+ suffixSpinning: false,
+ disabled: false,
+ });
+ })
+ .fail(() => {
+ this.setState({
+ suffixSpinning: false,
+ disabled: false,
+ });
+ });
+ }
+
+ reloadConfig (suffix) {
+ this.setState({
+ suffixSpinning: true,
+ disabled: true,
+ });
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-"
+ this.props.serverId + ".socket",
+ "replication", "get", "--suffix", suffix
+ ];
+ log_cmd("reloadConfig", "Reload suffix repl config", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let current_role = "";
+ let nsds5replicaprecisetombstonepurging = false;
+ if ('nsds5replicaprecisetombstonepurging' in
config['attrs']) {
+ if
(config['attrs']['nsds5replicaprecisetombstonepurging'][0].toLowerCase()
== "on") {
+ nsds5replicaprecisetombstonepurging = true;
+ }
+ }
+ // Set the replica role
+ if (config['attrs']['nsds5replicatype'][0] ==
"3") {
+ current_role = "Master";
+ } else {
+ if (config['attrs']['nsds5flags'][0] ==
"1") {
+ current_role = "Hub";
+ } else {
+ current_role = "Consumer";
+ }
+ }
+
+ this.setState({
+ [suffix]: {
+ role: current_role,
+ nsds5flags:
config['attrs']['nsds5flags'][0],
+ nsds5replicatype:
config['attrs']['nsds5replicatype'][0],
+ nsds5replicaid: 'nsds5replicaid' in
config['attrs'] ? config['attrs']['nsds5replicaid'][0] :
"",
+ nsds5replicabinddn: 'nsds5replicabinddn' in
config['attrs'] ? config['attrs']['nsds5replicabinddn'] :
"",
+ nsds5replicabinddngroup: 'nsds5replicabinddngroup' in
config['attrs'] ? config['attrs']['nsds5replicabinddngroup'][0] :
"",
+ nsds5replicabinddngroupcheckinterval:
'nsds5replicabinddngroupcheckinterval' in config['attrs'] ?
config['attrs']['nsds5replicabinddngroupcheckinterval'][0] :
"",
+ nsds5replicareleasetimeout:
'nsds5replicareleasetimeout' in config['attrs'] ?
config['attrs']['nsds5replicareleasetimeout'][0] : "",
+ nsds5replicapurgedelay: 'nsds5replicapurgedelay' in
config['attrs'] ? config['attrs']['nsds5replicapurgedelay'][0] :
"",
+ nsds5replicatombstonepurgeinterval:
'nsds5replicatombstonepurgeinterval' in config['attrs'] ?
config['attrs']['nsds5replicatombstonepurgeinterval'][0] : "",
+ nsds5replicaprecisetombstonepurging:
nsds5replicaprecisetombstonepurging,
+ nsds5replicaprotocoltimeout:
'nsds5replicaprotocoltimeout' in config['attrs'] ?
config['attrs']['nsds5replicaprotocoltimeout'][0] : "",
+ nsds5replicabackoffmin: 'nsds5replicabackoffmin' in
config['attrs'] ? config['attrs']['nsds5replicabackoffmin'][0] :
"",
+ nsds5replicabackoffmax: 'nsds5replicabackoffmax' in
config['attrs'] ? config['attrs']['nsds5replicabackoffmax'][0] :
"",
+ },
+ suffixSpinning: false,
+ disabled: false,
+ suffixKey: new Date(),
+ });
+ })
+ .fail(() => {
+ this.setState({
+ suffixSpinning: false,
+ disabled: false,
+ });
+ });
+ }
+
+ reloadRUV (suffix) {
+ this.setState({
+ suffixSpinning: true,
+ disabled: true,
+ });
+ // Load suffix RUV
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'replication', 'get-ruv', '--suffix=' + suffix];
+ log_cmd('reloadRUV', 'Get the suffix RUV', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let ruvs = JSON.parse(content);
+ let ruv_rows = [];
+ for (let idx in ruvs['items']) {
+ let ruv = ruvs['items'][idx];
+ // Update table
+ ruv_rows.push({
+ 'rid': ruv['rid'],
+ 'url': ruv['url'],
+ 'csn': ruv['csn'],
+ 'raw_csn': ruv['raw_csn'],
+ 'maxcsn': ruv['maxcsn'],
+ 'raw_maxcsn': ruv['raw_maxcsn'],
+ });
+ }
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ ruvRows: ruv_rows,
+ },
+ suffixSpinning: false,
+ disabled: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (errMsg.desc != "No such object") {
+ this.addNotification(
+ "error",
+ `Error loading suffix RUV - ${errMsg.desc}`
+ );
+ }
+ this.setState({
+ suffixSpinning: false,
+ disabled: false
+ });
+ });
+ }
+
+ loadReplSuffix(suffix) {
+ // Load everything, we must nest cockpit promise so we can proper set
+ // the loading is finished.
+ // - Get Suffix config
+ // - Get Repl agmts
+ // - Get Winsync Agmts
+ // - Get RUV's
+ this.setState({
+ activeKey: 1,
+ suffixLoading: true,
+ [suffix]: {},
+ });
+
+ let cmd = [
+ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-"
+ this.props.serverId + ".socket",
+ "replication", "get", "--suffix", suffix
+ ];
+ log_cmd("loadReplSuffix", "Load suffix repl config", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const config = JSON.parse(content);
+ let current_role = "";
+ let nsds5replicaprecisetombstonepurging = false;
+ if ('nsds5replicaprecisetombstonepurging' in
config['attrs']) {
+ if
(config['attrs']['nsds5replicaprecisetombstonepurging'][0].toLowerCase()
== "on") {
+ nsds5replicaprecisetombstonepurging = true;
+ }
+ }
+ // Set the replica role
+ if (config['attrs']['nsds5replicatype'][0] ==
"3") {
+ current_role = "Master";
+ } else {
+ if (config['attrs']['nsds5flags'][0] ==
"1") {
+ current_role = "Hub";
+ } else {
+ current_role = "Consumer";
+ }
+ }
+
+ this.setState({
+ [suffix]: {
+ role: current_role,
+ nsds5flags:
config['attrs']['nsds5flags'][0],
+ nsds5replicatype:
config['attrs']['nsds5replicatype'][0],
+ nsds5replicaid: 'nsds5replicaid' in
config['attrs'] ? config['attrs']['nsds5replicaid'][0] :
"",
+ nsds5replicabinddn: 'nsds5replicabinddn' in
config['attrs'] ? config['attrs']['nsds5replicabinddn'] :
"",
+ nsds5replicabinddngroup: 'nsds5replicabinddngroup' in
config['attrs'] ? config['attrs']['nsds5replicabinddngroup'][0] :
"",
+ nsds5replicabinddngroupcheckinterval:
'nsds5replicabinddngroupcheckinterval' in config['attrs'] ?
config['attrs']['nsds5replicabinddngroupcheckinterval'][0] :
"",
+ nsds5replicareleasetimeout:
'nsds5replicareleasetimeout' in config['attrs'] ?
config['attrs']['nsds5replicareleasetimeout'][0] : "",
+ nsds5replicapurgedelay: 'nsds5replicapurgedelay' in
config['attrs'] ? config['attrs']['nsds5replicapurgedelay'][0] :
"",
+ nsds5replicatombstonepurgeinterval:
'nsds5replicatombstonepurgeinterval' in config['attrs'] ?
config['attrs']['nsds5replicatombstonepurgeinterval'][0] : "",
+ nsds5replicaprecisetombstonepurging:
nsds5replicaprecisetombstonepurging,
+ nsds5replicaprotocoltimeout:
'nsds5replicaprotocoltimeout' in config['attrs'] ?
config['attrs']['nsds5replicaprotocoltimeout'][0] : "",
+ nsds5replicabackoffmin: 'nsds5replicabackoffmin' in
config['attrs'] ? config['attrs']['nsds5replicabackoffmin'][0] :
"",
+ nsds5replicabackoffmax: 'nsds5replicabackoffmax' in
config['attrs'] ? config['attrs']['nsds5replicabackoffmax'][0] :
"",
+ }
+ });
+
+ // Now load agmts, then the winsync agreement, and finally the RUV
+ let cmd = [
+ "dsconf", "-j",
"ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "repl-agmt", "list", "--suffix",
suffix
+ ];
+ log_cmd("loadReplSuffix", "get repl agreements",
cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ const obj = JSON.parse(content);
+ let rows = [];
+ for (let idx in obj['items']) {
+ let agmt_attrs =
obj['items'][idx]['attrs'];
+ let state = "Enabled";
+ let update_status = "";
+ let agmt_init_status = "";
+
+ // Compute state (enabled by default)
+ if ('nsds5replicaenabled' in agmt_attrs) {
+ if
(agmt_attrs['nsds5replicaenabled'][0].toLowerCase() == 'off') {
+ state = "Disabled";
+ }
+ }
+
+ // Check for status msgs
+ if ('nsds5replicalastupdatestatus' in
agmt_attrs) {
+ update_status =
agmt_attrs['nsds5replicalastupdatestatus'][0];
+ }
+ if ('nsds5replicalastinitstatus' in
agmt_attrs &&
+
agmt_attrs['nsds5replicalastinitstatus'][0] != "") {
+ agmt_init_status =
agmt_attrs['nsds5replicalastinitstatus'][0];
+ if (agmt_init_status == "Error (0) Total
update in progress" ||
+ agmt_init_status == "Error (0)") {
+ agmt_init_status = <td
key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading
size="sm" /></td>;
+ } else if (agmt_init_status == "Error (0)
Total update succeeded") {
+ agmt_init_status = <td
key={agmt_attrs['cn']}><i>Initialized</i></td>;
+ } else {
+ agmt_init_status = <td
key={agmt_attrs['cn']}>{agmt_init_status}</td>;
+ }
+ } else if ('nsds5replicalastinitstart' in
agmt_attrs && agmt_attrs['nsds5replicalastinitstart'][0] ==
"19700101000000Z") {
+ agmt_init_status = <td
key={agmt_attrs['cn']}><i>Not initialized</i></td>;
+ } else if ('nsds5beginreplicarefresh' in
agmt_attrs) {
+ agmt_init_status = <td
key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading
size="sm" /></td>;
+ }
+
+ // Update table
+ rows.push({
+ 'name': agmt_attrs['cn'],
+ 'host':
agmt_attrs['nsds5replicahost'],
+ 'port':
agmt_attrs['nsds5replicaport'],
+ 'state': [state],
+ 'status': [update_status],
+ 'initstatus': [agmt_init_status]
+ });
+ }
+
+ // Set agmt
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ agmtRows: rows,
+ }
+ });
+
+ // Load winsync agreements
+ let cmd = [
+ "dsconf", "-j",
"ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "repl-winsync-agmt", "list",
"--suffix", suffix
+ ];
+ log_cmd("loadReplSuffix", "Get Winsync
Agreements", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err:
"message" })
+ .done(content => {
+ const obj = JSON.parse(content);
+ let ws_rows = [];
+ for (var idx in obj['items']) {
+ let state = "Enabled";
+ let update_status = "";
+ let ws_agmt_init_status =
"Initialized";
+ let agmt_attrs =
obj['items'][idx]['attrs'];
+ // let agmt_name =
agmt_attrs['cn'][0];
+
+ // Compute state (enabled by default)
+ if ('nsds5replicaenabled' in
agmt_attrs) {
+ if
(agmt_attrs['nsds5replicaenabled'][0].toLowerCase() == 'off') {
+ state = "Disabled";
+ }
+ }
+
+ if
('nsds5replicalastupdatestatus' in agmt_attrs) {
+ update_status =
agmt_attrs['nsds5replicalastupdatestatus'][0];
+ }
+
+ if ('nsds5replicalastinitstatus'
in agmt_attrs &&
+
agmt_attrs['nsds5replicalastinitstatus'][0] != "") {
+ ws_agmt_init_status =
agmt_attrs['nsds5replicalastinitstatus'][0];
+ if (ws_agmt_init_status ==
"Error (0) Total update in progress" ||
+ ws_agmt_init_status ==
"Error (0)") {
+ ws_agmt_init_status = <td
key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading
size="sm" /></td>;
+ } else if (ws_agmt_init_status ==
"Error (0) Total update succeeded") {
+ ws_agmt_init_status = <td
key={agmt_attrs['cn']}><i>Initialized</i></td>;
+ } else {
+ ws_agmt_init_status = <td
key={agmt_attrs['cn']}>{ws_agmt_init_status}</td>;
+ }
+ } else if
('nsds5replicalastinitstart' in agmt_attrs &&
agmt_attrs['nsds5replicalastinitstart'][0] == "19700101000000Z") {
+ ws_agmt_init_status = <td
key={agmt_attrs['cn']}><i>Not initialized</i></td>;
+ } else if
('nsds5beginreplicarefresh' in agmt_attrs) {
+ ws_agmt_init_status = <td
key={agmt_attrs['cn']}><i>Initializing</i><Spinner loading
size="sm" /></td>;
+ }
+
+ // Update table
+ ws_rows.push({
+ 'name':
agmt_attrs['cn'],
+ 'host':
agmt_attrs['nsds5replicahost'],
+ 'port':
agmt_attrs['nsds5replicaport'],
+ 'state': [state],
+ 'status': [update_status],
+ 'initstatus':
[ws_agmt_init_status]
+ });
+ }
+ // Set winsync agmts
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ winsyncRows: ws_rows,
+ }
+ });
+
+ // Load suffix RUV
+ let cmd = ['dsconf', '-j',
'ldapi://%2fvar%2frun%2fslapd-' + this.props.serverId + '.socket',
+ 'replication', 'get-ruv',
'--suffix=' + suffix];
+ log_cmd('loadReplSuffix', 'Get
the suffix RUV', cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err:
"message" })
+ .done(content => {
+ let ruvs = JSON.parse(content);
+ let ruv_rows = [];
+ for (let idx in
ruvs['items']) {
+ let ruv =
ruvs['items'][idx];
+ // Update table
+ ruv_rows.push({
+ 'rid':
ruv['rid'],
+ 'url':
ruv['url'],
+ 'csn':
ruv['csn'],
+ 'raw_csn':
ruv['raw_csn'],
+ 'maxcsn':
ruv['maxcsn'],
+ 'raw_maxcsn':
ruv['raw_maxcsn'],
+ });
+ }
+
+ this.setState({
+ [suffix]: {
+ ...this.state[suffix],
+ ruvRows: ruv_rows,
+ },
+ suffixLoading: false,
+ disableTree: false
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ if (errMsg.desc != "No such
object") {
+ this.addNotification(
+ "error",
+ `Error loading suffix RUV
- ${errMsg.desc}`
+ );
+ }
+ this.setState({
+ suffixLoading: false,
+ disableTree: false
+ });
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error loading winsync agreements -
${errMsg.desc}`
+ );
+ this.setState({
+ suffixLoading: false,
+ disableTree: false
+ });
+ });
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Error loading replication agreements configuration -
${errMsg.desc}`
+ );
+ this.setState({
+ suffixLoading: false,
+ disableTree: false
+ });
+ });
+ })
+ .fail(() => {
+ this.setState({
+ suffixLoading: false,
+ disableTree: false,
+ node_replicated: false
+ });
+ });
+ }
+
+ 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 => {
+ let errMsg = JSON.parse(err);
+ this.addNotification(
+ "error",
+ `Failed to get attributes - ${errMsg.desc}`
+ );
+ });
+ }
+
+ enableTree () {
+ this.setState({
+ disableTree: false
+ });
+ }
+
+ disableTree () {
+ this.setState({
+ disableTree: true
+ });
+ }
+
+ render() {
+ const { nodes } = this.state;
+ let repl_page = "";
+ let disabled = "tree-view-container";
+ if (this.state.disableTree) {
+ disabled = "tree-view-container ds-disabled";
+ }
+ let repl_element =
+ <h4>There are currently no databases to configure for
replication</h4>;
+ if (this.state.loaded) {
+ // We have a suffix, or database link
+ if (this.state.node_type == "suffix" || this.state.node_type ==
"subsuffix") {
+ if (this.state.suffixLoading) {
+ repl_element =
+ <div className="ds-margin-top ds-loading-spinner
ds-center">
+ <h4>Loading replication configuration for
<b>{this.state.node_name} ...</b></h4>
+ <Spinner className="ds-margin-top-lg" loading
size="md" />
+ </div>;
+ } else {
+ if (this.state.node_name in this.state) {
+ repl_element =
+ <div>
+ <ReplSuffix
+ serverId={this.props.serverId}
+ suffix={this.state.node_name}
+ role={this.state[this.state.node_name].role}
+ data={this.state[this.state.node_name]}
+ addNotification={this.addNotification}
+ agmtRows={this.state[this.state.node_name].agmtRows}
+
winsyncRows={this.state[this.state.node_name].winsyncRows}
+ ruvRows={this.state[this.state.node_name].ruvRows}
+ reloadAgmts={this.reloadAgmts}
+ reloadWinsyncAgmts={this.reloadWinsyncAgmts}
+ reloadRUV={this.reloadRUV}
+ reloadConfig={this.reloadConfig}
+ reload={this.loadSuffixTree}
+ attrs={this.state.attributes}
+ replicated={this.state.node_replicated}
+ enableTree={this.enableTree}
+ disableTree={this.disableTree}
+ key={this.state.suffixKey}
+ disabled={this.state.disabled}
+ spinning={this.state.suffixSpinning}
+ />
+ </div>;
+ } else {
+ // Suffix is not replicated
+ repl_element =
+ <ReplSuffix
+ serverId={this.props.serverId}
+ suffix={this.state.node_name}
+ role=""
+ data=""
+ addNotification={this.addNotification}
+ disableWSAgmtTable={this.state.disableWSAgmtTable}
+ disableAgmtTable={this.state.disableAgmtTable}
+ reloadAgmts={this.reloadAgmts}
+ reloadWinsyncAgmts={this.reloadWinsyncAgmts}
+ reloadRUV={this.reloadRUV}
+ reloadConfig={this.reloadConfig}
+ reload={this.loadSuffixTree}
+ attrs={this.state.attributes}
+ replicated={this.state.node_replicated}
+ enableTree={this.enableTree}
+ disableTree={this.disableTree}
+ spinning={this.state.suffixSpinning}
+ disabled={this.state.disabled}
+ key={this.state.node_name}
+ />;
+ }
+ }
+ } else if (this.state.node_type == "changelog") {
+ repl_element =
+ <Changelog
+ serverId={this.props.serverId}
+ clDir={this.state.clDir}
+ clMaxEntries={this.state.clMaxEntries}
+ clMaxAge={this.state.clMaxAge}
+ clCompactInt={this.state.clCompactInt}
+ clTrimInt={this.state.clTrimInt}
+ clEncrypt={this.state.clEncrypt}
+ addNotification={this.addNotification}
+ enableTree={this.enableTree}
+ reload={this.reloadChangelog}
+ loading={this.state.clLoading}
+ key={this.state.clDir}
+ />;
+ }
+ repl_page =
+ <div className="container-fluid">
+ <NotificationController
+ notifications={this.state.notifications}
+ removeNotificationAction={this.removeNotification}
+ />
+ <div className="ds-container">
+ <div>
+ <div className="ds-tree">
+ <div className={disabled} id="repl-tree"
+ style={treeViewContainerStyles}>
+ <TreeView
+ nodes={nodes}
+ highlightOnHover
+ highlightOnSelect
+ selectNode={this.selectNode}
+ />
+ </div>
+ </div>
+ </div>
+ <div className="ds-tree-content">
+ {repl_element}
+ </div>
+ </div>
+ </div>;
+ } else {
+ repl_page =
+ <div className="ds-margin-top ds-loading-spinner
ds-center">
+ <h4>Loading Replication Information ...</h4>
+ <Spinner className="ds-margin-top-lg" loading
size="md" />
+ </div>;
+ }
+
+ return (
+ <div>
+ {repl_page}
+ </div>
+ );
+ }
+}
+
+// Property types and defaults
+
+Replication.propTypes = {
+ serverId: PropTypes.string
+};
+
+Replication.defaultProps = {
+ serverId: ""
+};
+
+export default Replication;
diff --git a/src/cockpit/389-console/src/schema.html
b/src/cockpit/389-console/src/schema.html
index 35fcdfc..36f61be 100644
--- a/src/cockpit/389-console/src/schema.html
+++ b/src/cockpit/389-console/src/schema.html
@@ -1,6 +1,6 @@
<div id="schema-content">
- <div id="objectclass-page" class="all-pages" hidden>
+ <div id="objectclass-page" class="all-pages ds-margin-left"
hidden>
<h3 class="ds-config-header">Objectclasses</h3>
<div>
<input type="checkbox" class="ds-config-checkbox"
id="oc-user-defined"><label
@@ -9,12 +9,11 @@
<table id="oc-table" class="display ds-repl-table"
cellspacing="0" width="100%">
<thead>
<tr class="ds-table-header">
- <th>Objectclass Name</th>
- <th>OID</th>
- <th>Parent</th>
- <th>Required Attributes</th>
- <th>Allowed Attributes</th>
- <th>Action</th>
+ <th class="ds-center">Objectclass Name</th>
+ <th class="ds-center">OID</th>
+ <th class="ds-center">Required Attributes</th>
+ <th class="ds-center">Allowed Attributes</th>
+ <th class="ds-center">Action</th>
</tr>
</thead>
<tbody>
@@ -22,7 +21,8 @@
</table>
<button id="add-oc-button" name="create-oc"
data-toggle="modal" data-target="#add-edit-oc-form" class="btn
btn-primary ds-button">Create Objectclass</button>
</div>
- <div id="attribute-page" class="all-pages" hidden>
+
+ <div id="attribute-page" class="all-pages ds-margin-left"
hidden>
<h3 class="ds-config-header">Attributes</h3>
<div>
<input type="checkbox" class="ds-config-checkbox"
id="attr-user-defined"><label
@@ -31,14 +31,11 @@
<table id="attr-table" class="display ds-repl-table"
cellspacing="0" width="100%">
<thead>
<tr class="ds-table-header">
- <th>Attribute Name</th>
- <th>OID</th>
- <th>Syntax</th>
- <th>Multivalued</th>
- <th title="Equality matching rules">Equality Rules</th>
- <th title="Ordering matching rules">Ordering Rules</th>
- <th title="Substring matching rules">Substring
Rules</th>
- <th>Action</th>
+ <th class="ds-center">Attribute Name</th>
+ <th class="ds-center">OID</th>
+ <th class="ds-center">Syntax</th>
+ <th class="ds-center">Multivalued</th>
+ <th class="ds-center">Action</th>
</tr>
</thead>
<tbody>
@@ -46,7 +43,8 @@
</table>
<button id="create-attr-button" data-toggle="modal"
data-target="#add-edit-attr-form" class="btn btn-primary
ds-button">Create Attribute</button>
</div>
- <div id="schema-mr" class="all-pages" hidden>
+
+ <div id="schema-mr" class="all-pages ds-margin-left" hidden>
<div class="">
<h3 class="ds-config-header">Matching Rules</h3>
<table id="schema-mr-table" class="display ds-table"
cellspacing="0" width="100%">
@@ -67,6 +65,80 @@
<!-- Modals/Popups/Wizards -->
+ <!-- View Attribute modal -->
+ <div class="modal fade" id="view-attr-form"
data-backdrop="static" tabindex="-1" role="dialog"
aria-labelledby="view-attr-header" aria-hidden="true">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close"
data-dismiss="modal" aria-hidden="true"
aria-label="Close">
+ <span class="pficon pficon-close"></span>
+ </button>
+ <h4 class="modal-title">View Attribute</h4>
+ </div>
+ <div class="modal-body">
+ <div class="ds-inline">
+ <div>
+ <label for="attr-name-view"
class="ds-config-label-lrg" title="The attribute name"><b
+ >Attribute Name</b></label><input
class="ds-input" type="text" id="attr-name-view"
size="40" readonly />
+ </div>
+ <div>
+ <label for="attr-desc-view"
class="ds-config-label-lrg" title="The attribute
description"><b
+ >Description</b></label><input
class="ds-input" type="text" id="attr-desc-view"
size="40" readonly />
+ </div>
+ <div>
+ <label for="attr-oid-view"
class="ds-config-label-lrg" title="The attribute name"><b
+ >OID</b></label><input
class="ds-input" type="text" id="attr-oid-view"
size="40" readonly />
+ </div>
+ <div>
+ <label for="attr-parent-view"
class="ds-config-label-lrg" title="The parent
attribute"><b>Parent Attribute</b></label><input
+ class="ds-input" type="text"
id="attr-parent-view" size="40" readonly />
+ </div>
+ <div>
+ <label for="attr-syntax-view"
class="ds-config-label-lrg" title="The attribute
syntax"><b>Attribute Syntax</b></label><input
+ class="ds-input" type="text"
id="attr-syntax-view" size="40" readonly />
+ </div>
+ <div>
+ <label for="attr-usage-view"
class="ds-config-label-lrg" title="The parent
attribute"><b>Attribute Usage</b></label><input
+ class="ds-input" type="text"
id="attr-usage-view" size="40" readonly />
+ </div>
+ <div>
+ <input type="checkbox"
class="ds-config-checkbox" id="attr-multivalued-view"
disabled="disabled" /><label
+ for="attr-multivalued-view"
class="ds-label"> Attribute Multi-Valued </label>
+ </div>
+ <div>
+ <input type="checkbox"
class="ds-config-checkbox" id="attr-no-user-mod-view"
disabled="disabled" /><label
+ for="attr-no-user-mod-view"
class="ds-label"> Read-only (NO-USER-MODIFICATION flag) </label>
+ </div>
+ <div>
+ <label for="attr-alias-view"
class="ds-config-label-lrg" title="The attribute alias list separated by
space"><b
+ >Attribute Aliases</b></label><input
class="ds-input" type="text" id="attr-alias-view"
size="40" readonly />
+ </div>
+ <div class="panel panel-default ds-margin-top">
+ <div
class="panel-heading"><strong>Matching
rules</strong></div>
+ <div class="panel-body">
+ <div>
+ <label for="attr-eq-mr-select-view"
class="ds-config-label-lrg"><b>Equality</b></label><input
+ class="ds-input"
type="text" id="attr-eq-mr-select-view" size="35" readonly
/>
+ </div>
+ <div>
+ <label for="attr-order-mr-select-view"
class="ds-config-label-lrg"><b>Ordering</b></label><input
+ class="ds-input" type="text"
id="attr-order-mr-select-view" size="35" readonly />
+ </div>
+ <div>
+ <label for="attr-sub-mr-select-view"
class="ds-config-label-lrg"><b>Substring</b></label><input
+ class="ds-input" type="text"
id="attr-sub-mr-select-view" size="35" readonly />
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn
btn-default" data-dismiss="modal">Close</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
<!-- Add/edit Attribute modal -->
<div class="modal fade" id="add-edit-attr-form"
data-backdrop="static" tabindex="-1" role="dialog"
aria-labelledby="add-edit-attr-header" aria-hidden="true">
<div class="modal-dialog">
@@ -81,22 +153,22 @@
<div class="ds-inline">
<p class="ds-modal-error"></p>
<div>
- <label for="attr-parent" class="ds-config-label-lrg"
title="The parent attribute"><b>Parent
Attribute</b></label><select
- class="btn btn-default dropdown ds-oc-dropdown"
id="attr-parent">
- <option value=""></option>
- </select>
- </div>
- <div>
<label for="attr-name" class="ds-config-label-lrg"
title="The attribute name"><b
Attribute Name</b></label><input
class="ds-input" type="text" id="attr-name"
size="40"/>
</div>
<div>
<label for="attr-desc" class="ds-config-label-lrg"
title="The attribute description"><b
- >Attribute Description</b></label><input
class="ds-input" type="text" id="attr-desc"
size="40"/>
+ >Description</b></label><input class="ds-input"
type="text" id="attr-desc" size="40"/>
</div>
<div>
<label for="attr-oid" class="ds-config-label-lrg"
title="The attribute name"><b
- >Attribute OID</b></label><input
class="ds-input" type="text" id="attr-oid"
size="40"/>
+ >OID</b></label><input class="ds-input"
type="text" id="attr-oid" size="40"/>
+ </div>
+ <div>
+ <label for="attr-parent" class="ds-config-label-lrg"
title="The parent attribute"><b>Parent
Attribute</b></label><select
+ class="btn btn-default dropdown ds-oc-dropdown"
id="attr-parent">
+ <option value=""></option>
+ </select>
</div>
<div>
<label for="attr-syntax" class="ds-config-label-lrg"
title="The attribute syntax"><b>Attribute
Syntax</b></label><select
@@ -121,28 +193,28 @@
<label for="attr-alias" class="ds-config-label-lrg"
title="The attribute alias list separated by space"><b
Attribute Aliases</b></label><input
class="ds-input" type="text" id="attr-alias"
size="40"/>
</div>
- <div class="panel panel-default">
- <div class="panel-heading"><strong>Matching
rules</strong></div>
- <div class="panel-body">
- <div>
- <label for="attr-eq-mr-select"
class="ds-config-label-lrg"><b>Equality</b></label><select
- class="btn btn-default dropdown ds-oc-dropdown"
id="attr-eq-mr-select">
- <option></option>
- </select>
- </div>
- <div>
- <label for="attr-order-mr-select"
class="ds-config-label-lrg"><b>Ordering</b></label><select
- class="btn btn-default dropdown ds-oc-dropdown"
id="attr-order-mr-select">
- <option></option>
- </select>
- </div>
- <div>
- <label for="attr-sub-mr-select"
class="ds-config-label-lrg"><b>Substring</b></label><select
- class="btn btn-default dropdown ds-oc-dropdown"
id="attr-sub-mr-select">
- <option></option>
- </select>
- </div>
- </div>
+ <div class="panel panel-default ds-margin-top">
+ <div class="panel-heading"><strong>Matching
rules</strong></div>
+ <div class="panel-body">
+ <div>
+ <label for="attr-eq-mr-select"
class="ds-config-label-lrg"><b>Equality</b></label><select
+ class="btn btn-default dropdown ds-oc-dropdown"
id="attr-eq-mr-select">
+ <option></option>
+ </select>
+ </div>
+ <div>
+ <label for="attr-order-mr-select"
class="ds-config-label-lrg"><b>Ordering</b></label><select
+ class="btn btn-default dropdown ds-oc-dropdown"
id="attr-order-mr-select">
+ <option></option>
+ </select>
+ </div>
+ <div>
+ <label for="attr-sub-mr-select"
class="ds-config-label-lrg"><b>Substring</b></label><select
+ class="btn btn-default dropdown ds-oc-dropdown"
id="attr-sub-mr-select">
+ <option></option>
+ </select>
+ </div>
+ </div>
</div>
</div>
<div id="save-attr-spinner" class="ds-center"
hidden>
@@ -159,6 +231,65 @@
</div>
+ <!-- View Objectclass -->
+ <div class="modal fade" id="view-objectclass-form"
aria-labelledby="view-objectclass-form" data-backdrop="static"
tabindex="-1" role="dialog" 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">View Objectclass</h4>
+ </div>
+ <div class="modal-body">
+ <form class="form-horizontal">
+ <div class="ds-inline">
+ <div>
+ <label for="oc-name-view"
class="ds-config-label-lrg" title="The objectclass name"><b
+ >Objectclass Name</b></label><input
class="ds-input" type="text" id="oc-name-view"
size="40" readonly />
+ </div>
+ <div>
+ <label for="oc-desc-view"
class="ds-config-label-lrg" title="The objectClass
description"><b
+ >Description</b></label><input
class="ds-input" type="text" id="oc-desc-view"
size="40" readonly/>
+ </div>
+ <div>
+ <label for="oc-oid-view"
class="ds-config-label-lrg" title="Objectclass OID
(optional)"><b
+ >OID (optional)</b></label><input
class="ds-input" value="" type="text"
id="oc-oid-view" size="40" readonly/>
+ </div>
+ <div>
+ <label for="oc-parent-view"
class="ds-config-label-lrg" title="The parent
objectclass"><b>Parent Objectclass</b></label><input
+ class="ds-input" value=""
type="text" id="oc-parent-view" size="40" readonly />
+ </div>
+ <div>
+ <label for="oc-kind-view"
class="ds-config-label-lrg" title="The parent
objectclass"><b>Objectclass Kind</b></label><input
+ class="ds-input" value=""
type="text" id="oc-kind-view" size="40" readonly />
+ </div>
+ <hr>
+ <div class="ds-container">
+ <div>
+ <label class="ds-config-label"
for="oc-required-list-view" title=
+ "Attributes allowed by the
objectclass"><b>Required Attributes</b></label>
+ <select id="oc-required-list-view"
class="ds-may-must-list" multiple>
+ </select>
+ </div>
+ <div class="ds-divider"></div>
+ <div>
+ <label class="ds-config-label"
for="oc-allowed-list-view" title=
+ "Attributes allowed by the
objectclass"><b>Allowed Attributes</b></label>
+ <select id="oc-allowed-list-view"
class="ds-may-must-list" multiple>
+ </select>
+ </div>
+ </div>
+ </div>
+ </form>
+ <div class="modal-footer">
+ <button type="button" class="btn
btn-default" data-dismiss="modal">Close</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
<!-- Add/Edit Objectclass -->
<div class="modal fade" id="add-edit-oc-form"
data-backdrop="static" tabindex="-1" role="dialog"
aria-labelledby="add-edit-oc-header" aria-hidden="true">
<div class="modal-dialog">
@@ -174,32 +305,32 @@
<div class="ds-inline">
<p class="ds-modal-error"></p>
<div>
- <label for="oc-parent" class="ds-label-sm"
title="The parent objectclass"><b>Parent
Objectclass</b></label><select
- class="btn btn-default dropdown ds-oc-dropdown"
id="oc-parent">
- </select>
- </div>
- <div>
- <label for="oc-name" class="ds-label-sm"
title="The objectclass name"><b
+ <label for="oc-name" class="ds-config-label-lrg"
title="The objectclass name"><b
Objectclass Name</b></label><input
class="ds-input" type="text" id="oc-name"
size="40" required />
</div>
<div>
- <label for="oc-desc" class="ds-label-sm"
title="The objectClass description"><b
- >ObjectClass Description</b></label><input
class="ds-input" type="text" id="oc-desc"
size="40"/>
+ <label for="oc-desc" class="ds-config-label-lrg"
title="The objectClass description"><b
+ >Description</b></label><input
class="ds-input" type="text" id="oc-desc"
size="40"/>
</div>
<div>
- <label for="oc-oid" class="ds-label-sm"
title="Objectclass OID (optional)"><b
+ <label for="oc-oid" class="ds-config-label-lrg"
title="Objectclass OID (optional)"><b
OID (optional)</b></label><input
class="ds-input" value="" type="text" id="oc-oid"
size="40"/>
</div>
<div>
- <label for="oc-kind" class="ds-label-sm"
title="The parent objectclass"><b>Objectclass
Kind</b></label><select
+ <label for="oc-parent" class="ds-config-label-lrg"
title="The parent objectclass"><b>Parent
Objectclass</b></label><select
+ class="btn btn-default dropdown ds-oc-dropdown"
id="oc-parent">
+ </select>
+ </div>
+ <div>
+ <label for="oc-kind" class="ds-config-label-lrg"
title="The parent objectclass"><b>Objectclass
Kind</b></label><select
class="btn btn-default dropdown ds-oc-dropdown"
id="oc-kind">
<option value=""></option>
- </select>
+ </select>
</div>
<hr>
<div class="ds-container">
<div name="available-attrs">
- <label class="ds-config-label" for="schema-list"
title="The available attributes to choose from."><b>Available
Attributes</b></label>
+ <label for="schema-list" title="The available
attributes to choose from."><b>Available Attributes</b></label>
<select id="schema-list" class="ds-oc-form-list"
name="availattrs" multiple>
</select>
</div>
diff --git a/src/cockpit/389-console/src/schema.js
b/src/cockpit/389-console/src/schema.js
index 94cebe5..ea6159b 100644
--- a/src/cockpit/389-console/src/schema.js
+++ b/src/cockpit/389-console/src/schema.js
@@ -53,14 +53,14 @@ $.fn.dataTable.ext.search.push(
var x_origin;
if ( settings.sTableId == "attr-table" ) {
if ( $("#attr-user-defined").is(":checked") ) {
- x_origin = rowData[10];
+ x_origin = rowData[7];
if (!is_x_origin_user_defined(x_origin)) {
return false;
}
}
} else {
if ( $("#oc-user-defined").is(":checked") ) {
- x_origin = rowData[6];
+ x_origin = rowData[5];
if (!is_x_origin_user_defined(x_origin)) {
return false;
}
@@ -76,7 +76,6 @@ function clear_oc_form() {
$(".ds-modal-error").hide();
$("#oc-name").attr('disabled', false);
$("#oc-name").val("");
- $(".ds-input").css("border-color", "initial");
$("#oc-oid").val("");
$("#oc-kind").prop('selectedIndex',0);
$("#oc-desc").val("");
@@ -93,7 +92,6 @@ function clear_attr_form() {
$(".ds-modal-error").hide();
$("#attr-name").attr('disabled', false);
$("#attr-name").val("");
- $(".ds-input").css("border-color", "initial");
$("#attr-syntax").val("");
$("#attr-desc").val("");
$("#attr-parent").prop('selectedIndex',0);
@@ -163,9 +161,6 @@ function get_and_set_schema_tables() {
if (item.oid === undefined) {
item.oid = "";
}
- if (item.sup === undefined) {
- item.sup = "";
- }
if (item.must === undefined) {
item.must = [];
}
@@ -181,17 +176,20 @@ function get_and_set_schema_tables() {
if (item.desc === undefined) {
item.desc = "";
}
+ if (item.sup === undefined) {
+ item.sup = "";
+ }
data.push.apply(data, [[
item.name,
item.oid,
- item.sup,
item.must.join(" "),
item.may.join(" "),
oc_btn,
item.x_origin,
oc_kind_opts[item.kind],
- item.desc
+ item.desc,
+ item.sup
]]);
}
// Update html table
@@ -206,10 +204,10 @@ function get_and_set_schema_tables() {
"search": "Search Objectclasses"
},
"columnDefs": [ {
- "targets": 5,
+ "targets": 4,
"orderable": false
}, {
- "targets": 6,
+ "targets": 5,
"visible": false
}]
});
@@ -251,7 +249,7 @@ function get_and_set_schema_tables() {
}
$.each(syntax_list, function (i, syntax) {
if (syntax.id === item.syntax) {
- syntax_name = '<div title="' + syntax.id +
'">' + syntax.name + '</div>';
+ syntax_name = '<div title="' + syntax.name +
'">' + syntax.name + '</div>';
}
});
// If attribute is user defined them the action button is enabled
@@ -265,18 +263,9 @@ function get_and_set_schema_tables() {
if (item.sup === undefined) {
item.sup = "";
}
- if (item.equality === undefined) {
- item.equality = "";
- }
- if (item.ordering === undefined) {
- item.ordering = "";
- }
if (item.x_origin === undefined) {
item.x_origin = "";
}
- if (item.substr === undefined) {
- item.substr = "";
- }
if (item.no_user_mod === undefined) {
item.no_user_mod = "";
}
@@ -289,22 +278,31 @@ function get_and_set_schema_tables() {
if (item.aliases === undefined) {
item.aliases = "";
}
+ if (item.equality === undefined) {
+ item.equality = "";
+ }
+ if (item.ordering === undefined) {
+ item.ordering = "";
+ }
+ if (item.substr === undefined) {
+ item.substr = "";
+ }
data.push.apply(data, [[
item.name,
item.oid,
syntax_name,
multivalued,
- item.equality,
- item.ordering,
- item.substr,
attr_btn,
item.desc,
item.aliases,
item.x_origin,
attr_usage_opts[item.usage],
item.no_user_mod,
- item.sup
+ item.sup,
+ item.equality,
+ item.ordering,
+ item.substr,
]]);
}
// Update html table
@@ -319,13 +317,14 @@ function get_and_set_schema_tables() {
"search": "Search Attributes"
},
"columnDefs": [ {
- "targets": 7,
+ "targets": 4,
"orderable": false
}, {
- "targets": 8,
+ "targets": 5,
"visible": false
}]
});
+ update_progress();
}).fail(function(syntax_data) {
console.log("Get syntaxes failed: " + syntax_data.message);
check_inst_alive(1);
@@ -353,6 +352,7 @@ function get_and_set_schema_tables() {
});
console.log("Finished loading schema.");
+ update_progress();
}).fail(function(oc_data) {
console.log("Get all schema objects failed: " + oc_data.message);
check_inst_alive(1);
@@ -468,13 +468,13 @@ $(document).ready( function() {
schema_oc_table.row.add( [
item.name,
item.oid,
- item.sup,
item.must.join(" "),
item.may.join(" "),
oc_btn_html,
item.x_origin,
oc_kind_opts[item.kind],
- item.desc
+ item.desc,
+ item.sup
] ).draw( false );
}).
fail(function(oc_data) {
@@ -564,7 +564,7 @@ $(document).ready( function() {
var attr_syntax_text = $("#attr-syntax :selected").text();
var attr_usage = $('#attr-usage').val();
var attr_desc = $('#attr-desc').val();
- var attr_x_origin= "user defined";
+ var attr_x_origin = "user defined";
var attr_parent = $('#attr-parent').val();
var attr_aliases = $('#attr-alias').val().split(" ");
var eq_mr= $('#attr-eq-mr-select').val();
@@ -659,16 +659,17 @@ $(document).ready( function() {
item.oid,
attr_syntax_name,
multiple,
- item.equality,
- item.ordering,
- item.substr,
attr_btn_html,
item.desc,
item.aliases,
item.x_origin,
attr_usage_opts[item.usage],
item.no_user_mod,
- item.sup
+ item.sup,
+ item.equality,
+ item.ordering,
+ item.substr,
+
] ).draw( false );
$("#attr-name").attr('disabled', false);
}).
@@ -699,24 +700,15 @@ $(document).ready( function() {
var edit_attr_oid = data[1];
var edit_attr_syntax = $.parseHTML(data[2])[0].title;
var edit_attr_multivalued = data[3];
- var edit_attr_eq_mr = data[4];
- var edit_attr_order_mr = data[5];
- var edit_attr_sub_mr = data[6];
- var edit_attr_desc = data[8];
- var edit_attr_aliases = data[9];
- var edit_attr_x_origin = data[10];
- var edit_attr_usage = data[11];
- var edit_attr_no_user_mod = data[12];
- var edit_attr_parent = data[13];
- if (edit_attr_eq_mr) {
- edit_attr_eq_mr = data[4];
- }
- if (edit_attr_order_mr) {
- edit_attr_order_mr = data[5];
- }
- if (edit_attr_sub_mr) {
- edit_attr_sub_mr = data[6];
- }
+ var edit_attr_desc = data[5];
+ var edit_attr_aliases = data[6];
+ var edit_attr_x_origin = data[7];
+ var edit_attr_usage = data[8];
+ var edit_attr_no_user_mod = data[9];
+ var edit_attr_parent = data[10];
+ var edit_attr_eq_mr = data[11];
+ var edit_attr_order_mr = data[12];
+ var edit_attr_sub_mr = data[13];
$("#add-edit-attr-header").html('Edit Attribute: ' +
edit_attr_name);
$("#attr-name").val(edit_attr_name);
@@ -746,13 +738,50 @@ $(document).ready( function() {
$("#add-edit-attr-form").modal('toggle');
}
+ function load_view_attr_form(element) {
+ clear_attr_form();
+ var data = schema_at_table.row(element.parents('tr') ).data();
+ var edit_attr_name = data[0];
+ var edit_attr_oid = data[1];
+ var edit_attr_syntax = $.parseHTML(data[2])[0].title;
+ var edit_attr_multivalued = data[3];
+ var edit_attr_desc = data[5];
+ var edit_attr_aliases = data[6];
+ var edit_attr_x_origin = data[7];
+ var edit_attr_usage = data[8];
+ var edit_attr_no_user_mod = data[9];
+ var edit_attr_parent = data[10];
+ var edit_attr_eq_mr = data[11];
+ var edit_attr_order_mr = data[12];
+ var edit_attr_sub_mr = data[13];
+
+ $("#attr-name-view").val(edit_attr_name);
+ $("#attr-oid-view").val(edit_attr_oid);
+ $("#attr-usage-view")[0].value = edit_attr_usage;
+ $("#attr-parent-view")[0].value = edit_attr_parent;
+ $("#attr-desc-view").val(edit_attr_desc);
+ if (edit_attr_aliases) {
+ $("#attr-alias-view").val(edit_attr_aliases.join(" "));
+ }
+ $("#attr-syntax-view").val(edit_attr_syntax);
+ $("#attr-multivalued-view").prop('checked', false);
+ if (edit_attr_multivalued == "yes") {
+ $("#attr-multivalued-view").prop('checked', true);
+ }
+ $("#attr-no-user-mod-view").prop('checked', false);
+ if (edit_attr_no_user_mod) {
+ $("#attr-no-user-mod-view").prop('checked', true);
+ }
+ $("#attr-eq-mr-select-view").val(edit_attr_eq_mr);
+ $("#attr-order-mr-select-view").val(edit_attr_order_mr);
+ $("#attr-sub-mr-select-view").val(edit_attr_sub_mr);
+
+ $("#view-attr-form").modal('toggle');
+ }
+
$(document).on('click', '.attr-view-btn', function(e) {
e.preventDefault();
- load_attr_form($(this));
- var edit_attr_name = schema_at_table.row($(this).parents('tr')
).data()[0];
- $("#add-edit-attr-header").html('View Attribute: ' +
edit_attr_name);
- $("#save-attr-button").attr('title', 'Only user-defined
attributes can be modified');
- $("#save-attr-button").attr('disabled', true);
+ load_view_attr_form($(this));
});
$(document).on('click', '.attr-edit-btn', function(e) {
@@ -780,29 +809,64 @@ $(document).ready( function() {
});
});
- function load_oc_form(element) {
+ function load_view_oc_form(element) {
clear_oc_form();
var data = schema_oc_table.row(element.parents('tr') ).data();
var edit_oc_name = data[0];
var edit_oc_oid = data[1];
- var edit_oc_parent = data[2];
- var edit_oc_required = data[3].split(" ");
- var edit_oc_allowed = data[4].split(" ");
- var edit_oc_x_origin = data[6];
- var edit_oc_kind = data[7];
- var edit_oc_desc = data[8];
- if (edit_oc_parent) {
- edit_oc_parent = data[2];
+ var edit_oc_required = data[2].split(" ");
+ var edit_oc_allowed = data[3].split(" ");
+ var edit_oc_x_origin = data[5];
+ var edit_oc_kind = data[6];
+ var edit_oc_desc = data[7];
+ var edit_oc_parent = data[8];
+
+ $("#oc-name-view").val(edit_oc_name);
+ $("#oc-oid-view").val(edit_oc_oid);
+ $("#oc-kind-view")[0].value = edit_oc_kind;
+ $("#oc-desc-view").val(edit_oc_desc);
+ $("#oc-parent-view")[0].value = edit_oc_parent;
+ $.each(edit_oc_required, function (i, item) {
+ if (item) {
+ $("#oc-required-list-view").append($('<option>', {
+ value: item,
+ text : item
+ }));
}
+ });
+ $.each(edit_oc_allowed, function (i, item) {
+ if (item) {
+ $("#oc-allowed-list-view").append($('<option>', {
+ value: item,
+ text : item
+ }));
+ }
+ });
+
+ // Update modal html header and fields and show()
+ $("#view-objectclass-form").modal('toggle');
+ }
+
+ function load_oc_form(element) {
+ clear_oc_form();
+ var data = schema_oc_table.row(element.parents('tr') ).data();
+ var edit_oc_name = data[0];
+ var edit_oc_oid = data[1];
+ var edit_oc_required = data[2].split(" ");
+ var edit_oc_allowed = data[3].split(" ");
+ var edit_oc_x_origin = data[5];
+ var edit_oc_kind = data[6];
+ var edit_oc_desc = data[7];
+ var edit_oc_parent = data[8];
$("#save-oc-spinner").show();
$("#add-edit-oc-header").html('Edit Objectclass: ' +
edit_oc_name);
$("#oc-name").attr('disabled', true);
$("#oc-name").val(edit_oc_name);
$("#oc-oid").val(edit_oc_oid);
- $("#oc-kind")[0].value = edit_oc_kind;
+ $("#oc-kind").val(edit_oc_kind);
$("#oc-desc").val(edit_oc_desc);
- $("#oc-parent")[0].value = edit_oc_parent;
+ $("#oc-parent").val(edit_oc_parent);
$.each(edit_oc_required, function (i, item) {
if (item) {
$("#oc-required-list").append($('<option>', {
@@ -827,11 +891,7 @@ $(document).ready( function() {
$(document).on('click', '.oc-view-btn', function(e) {
e.preventDefault();
- load_oc_form($(this));
- var edit_oc_name = schema_oc_table.row($(this).parents('tr') ).data()[0];
- $("#add-edit-oc-header").html('View Objectclass: ' +
edit_oc_name);
- $("#save-oc-button").attr('title', 'Only user-defined
objectClasses can be modified');
- $("#save-oc-button").attr('disabled', true);
+ load_view_oc_form($(this));
});
$(document).on('click', '.oc-edit-btn', function(e) {
diff --git a/src/cockpit/389-console/src/security.jsx
b/src/cockpit/389-console/src/security.jsx
index 43edf49..2e61f52 100644
--- a/src/cockpit/389-console/src/security.jsx
+++ b/src/cockpit/389-console/src/security.jsx
@@ -2,7 +2,7 @@ import cockpit from "cockpit";
import React from "react";
import Switch from "react-switch";
import { NotificationController, ConfirmPopup } from
"./lib/notifications.jsx";
-import { log_cmd } from "./lib/tools.jsx";
+import { log_cmd, valid_port } from "./lib/tools.jsx";
import { Typeahead } from "react-bootstrap-typeahead";
import { CertificateManagement } from
"./lib/security/certificateManagement.jsx";
import { SecurityEnableModal } from "./lib/security/securityModals.jsx";
@@ -18,6 +18,7 @@ import {
ControlLabel,
Button,
Checkbox,
+ Icon,
Spinner
} from "patternfly-react";
import PropTypes from "prop-types";
@@ -475,6 +476,26 @@ export class Security extends React.Component {
}
saveSecurityConfig () {
+ // Validate some setting first
+ let sslMin = this.state._sslVersionMin;
+ let sslMax = this.state._sslVersionMax;
+ if (this.state._sslVersionMin != this.state.sslVersionMin) {
+ sslMin = this.state.sslVersionMin;
+ }
+ if (this.state._sslVersionMax != this.state.sslVersionMax) {
+ sslMax = this.state.sslVersionMax;
+ }
+
+ if (sslMin > sslMax) {
+ this.addNotification(
+ "error",
+ `The TLS minimum version but be less than or equal to the TLS maximum
version`
+ );
+ // Reset page
+ this.loadSecurityConfig();
+ return;
+ }
+
let cmd = [
'dsconf', '-j', 'ldapi://%2fvar%2frun%2fslapd-' +
this.props.serverId + '.socket',
'security', 'set'
@@ -493,6 +514,15 @@ export class Security extends React.Component {
cmd.push("--tls-client-auth=" + this.state.clientAuth);
}
if (this.state._securePort != this.state.securePort) {
+ if (!valid_port(this.state.securePort)) {
+ this.addNotification(
+ "error",
+ `The Secure Port is invalid, it must be a number between 1 and
65535`
+ );
+ // Reset page
+ this.loadSecurityConfig();
+ return;
+ }
cmd.push("--secure-port=" + this.state.securePort);
}
if (this.state._secureListenhost != this.state.secureListenhost) {
@@ -522,7 +552,7 @@ export class Security extends React.Component {
if (cmd.length > 5) {
log_cmd("saveSecurityConfig", "Applying security config
change", cmd);
- let msg = "Successfully updated security configuration. You must
restart the server for these changes to take effect.";
+ let msg = "Successfully updated security configuration. You must
restart the Directory Server for these changes to take effect.";
this.setState({
// Start the spinner
@@ -592,30 +622,29 @@ export class Security extends React.Component {
render() {
let securityPage = "";
let serverCert = [this.state.nssslpersonalityssl];
-
if (this.state.loaded && !this.state.saving) {
let configPage = "";
if (this.state.securityEnabled) {
configPage =
<div>
<Row className="ds-margin-top" title="The
server's secure port number (nsslapd-secureport).">
- <Col componentClass={ControlLabel} sm={2}>
+ <Col componentClass={ControlLabel} sm={3}>
Server Secure Port
</Col>
<Col sm={4}>
- <input id="securePort"
className="ds-input-auto" onChange={this.handleChange} type="text"
defaultValue={this.state.securePort} />
+ <input id="securePort"
className="ds-input-auto" onChange={this.handleChange} type="text"
value={this.state.securePort} />
</Col>
</Row>
<Row className="ds-margin-top" title="This
parameter can be used to restrict the Directory Server instance to a single IP interface
(hostname, or IP address). This parameter specifically sets what interface to use for TLS
traffic. Requires restart. (nsslapd-securelistenhost).">
- <Col componentClass={ControlLabel} sm={2}>
+ <Col componentClass={ControlLabel} sm={3}>
Secure Listen Host
</Col>
<Col sm={4}>
- <input id="secureListenhost"
className="ds-input-auto" type="text" onChange={this.handleChange}
defaultValue={this.state.secureListenhost} />
+ <input id="secureListenhost"
className="ds-input-auto" type="text" onChange={this.handleChange}
value={this.state.secureListenhost} />
</Col>
</Row>
<Row className="ds-margin-top" title="The name,
or nickname, of the server certificate inthe NSS datgabase the server should use
(nsSSLPersonalitySSL).">
- <Col className="ds-no-padding" sm={2}>
+ <Col sm={3}>
<ControlLabel>Server Certificate
Name</ControlLabel>
</Col>
<Col sm={4}>
@@ -631,12 +660,11 @@ export class Security extends React.Component {
</Col>
</Row>
<Row className="ds-margin-top" title="The
minimum SSL/TLS version the server will accept (sslversionmin).">
- <Col componentClass={ControlLabel} sm={2}>
+ <Col componentClass={ControlLabel} sm={3}>
Minimum TLS Version
</Col>
<Col sm={4}>
- <select id="sslVersionMin"
className="btn btn-default dropdown ds-select" onChange={this.handleChange}
defaultValue={this.state.sslVersionMin}>
- <option />
+ <select id="sslVersionMin"
className="btn btn-default dropdown ds-select" onChange={this.handleChange}
value={this.state.sslVersionMin}>
<option>TLS1.3</option>
<option>TLS1.2</option>
<option>TLS1.1</option>
@@ -646,12 +674,11 @@ export class Security extends React.Component {
</Col>
</Row>
<Row className="ds-margin-top" title="The
maximum SSL/TLS version the server will accept (sslversionmax).">
- <Col componentClass={ControlLabel} sm={2}>
+ <Col componentClass={ControlLabel} sm={3}>
Maximum TLS Version
</Col>
<Col sm={4}>
- <select id="sslVersionMax"
className="btn btn-default dropdown ds-select" onChange={this.handleChange}
defaultValue={this.state.sslVersionMax}>
- <option />
+ <select id="sslVersionMax"
className="btn btn-default dropdown ds-select" onChange={this.handleChange}
value={this.state.sslVersionMax}>
<option>TLS1.3</option>
<option>TLS1.2</option>
<option>TLS1.1</option>
@@ -661,11 +688,11 @@ export class Security extends React.Component {
</Col>
</Row>
<Row className="ds-margin-top" title="Sets how
the Directory Server enforces TLS client authentication (nsSSLClientAuth).">
- <Col componentClass={ControlLabel} sm={2}>
+ <Col componentClass={ControlLabel} sm={3}>
Client Authentication
</Col>
<Col sm={4}>
- <select id="clientAuth" className="btn
btn-default dropdown ds-select" onChange={this.handleChange}
defaultValue={this.state.clientAuth}>
+ <select id="clientAuth" className="btn
btn-default dropdown ds-select" onChange={this.handleChange}
value={this.state.clientAuth}>
<option>off</option>
<option>allowed</option>
<option>required</option>
@@ -673,11 +700,11 @@ export class Security extends React.Component {
</Col>
</Row>
<Row className="ds-margin-top" title="Validate
server's certificate expiration date (nsslapd-validate-cert).">
- <Col componentClass={ControlLabel} sm={2}>
+ <Col componentClass={ControlLabel} sm={3}>
Validate Certificate
</Col>
<Col sm={4}>
- <select id="validateCert"
className="btn btn-default dropdown ds-select" onChange={this.handleChange}
defaultValue={this.state.validateCert}>
+ <select id="validateCert"
className="btn btn-default dropdown ds-select" onChange={this.handleChange}
value={this.state.validateCert}>
<option>warn</option>
<option>on</option>
<option>off</option>
@@ -761,13 +788,20 @@ export class Security extends React.Component {
<Col componentClass={ControlLabel}
sm={2}>
Security Enabled
</Col>
- <Col sm={2}>
+ <Col sm={1}>
<Switch
+ className="ds-switch"
onChange={this.handleSwitchChange}
checked={this.state.securityEnabled}
height={20}
/>
</Col>
+ <Col>
+ <Icon
className="ds-left-margin ds-refresh"
+ type="fa"
name="refresh" title="Refresh security settings"
+
onClick={this.loadSecurityConfig}
+ />
+ </Col>
</Row>
<hr />
{configPage}
diff --git a/src/cockpit/389-console/src/servers.html
b/src/cockpit/389-console/src/servers.html
index 820c29c..f60bd5d 100644
--- a/src/cockpit/389-console/src/servers.html
+++ b/src/cockpit/389-console/src/servers.html
@@ -4,7 +4,7 @@
<!--
General Config Settings
-->
- <div id="server-config" class="all-pages">
+ <div id="server-config" class="all-pages ds-margin-left">
<h3 class="ds-config-header">Server Configuration
Settings</h3>
<div class="ds-container">
<div class="ds-inline">
@@ -64,8 +64,8 @@
</div>
<button class="accordion ds-accordion" id="config-accordion"
type="button">► Show Advanced Settings</button>
- <div class="ds-accordion-panel">
- <div class="ds-container">
+ <div class="ds-accordion-panel ds-indent">
+ <div class="ds-container ds-indent">
<div class="ds-inline">
<div>
<input type="checkbox" class="ds-config-checkbox"
id="nsslapd-schemacheck" checked><label
@@ -95,24 +95,24 @@
<div class="ds-divider"></div>
<div class="ds-inline">
<div>
- <input type="checkbox" class="ds-config-checkbox"
id="nsslapd-attribute-name-exceptions" checked><label
+ <input type="checkbox" class="ds-config-checkbox"
id="nsslapd-attribute-name-exceptions"><label
for="nsslapd-attribute-name-exceptions" class="ds-label"
title="Allows non-standard characters in attribute names to be used for backwards
compatibility with older servers"> Allow Attribute Naming Exceptions
</label>
</div>
<div>
- <input type="checkbox" class="ds-config-checkbox"
id="nsslapd-dn-validate-strict" checked><label
+ <input type="checkbox" class="ds-config-checkbox"
id="nsslapd-dn-validate-strict"><label
for="nsslapd-dn-validate-strict" class="ds-label"
title="Enables strict syntax validation for DNs, according to section 3 in RFC 4514
(nsslapd-dn-validate-strict)."> Enable Strict DN Syntax Validation</label>
</div>
<div>
- <input type="checkbox" class="ds-config-checkbox"
id="nsslapd-entryusn-global" checked><label
+ <input type="checkbox" class="ds-config-checkbox"
id="nsslapd-entryusn-global"><label
for="nsslapd-entryusn-global" class="ds-label"
title="For USN plugin - maintain unique USNs across all back end databases
(nsslapd-entryusn-global)."> Enable Unique USNs Across All Backends</label>
</div>
<div>
- <input type="checkbox" class="ds-config-checkbox"
id="nsslapd-ignore-time-skew" checked><label
+ <input type="checkbox" class="ds-config-checkbox"
id="nsslapd-ignore-time-skew"><label
for="nsslapd-ignore-time-skew" class="ds-label"
title="Ignore time skew when generating CSNs"> Ignore CSN Time
Skew</label>
</div>
<div>
- <input type="checkbox" class="ds-config-checkbox"
id="nsslapd-readonly-server" checked><label
- for="nsslapd-readonly-server" class="ds-label"
title="Make entire server read-only (nsslapd-readonly)"> Server
Read-Only</label>
+ <input type="checkbox" class="ds-config-checkbox"
id="nsslapd-readonly"><label
+ for="nsslapd-readonly" class="ds-label" title="Make
entire server read-only (nsslapd-readonly)"> Server Read-Only</label>
</div>
</div>
@@ -136,15 +136,15 @@
<form>
<div>
<label for="nsslapd-rootdn" class="ds-config-label"
title="The DN of the unrestricted directory manager
(nsslapd-rootdn).">Directory Manager DN</label><input
- class="ds-input" type="text"
autocomplete="username" id="nsslapd-rootdn"
placeholder="cn=directory manager" value="cn=Directory Manager"
size="40"/>
+ class="ds-input" type="text" readonly
id="nsslapd-rootdn" value="cn=Directory Manager"
size="40"/>
</div>
<div>
<label for="nsslapd-rootpw" class="ds-config-label"
title="The Directory Manager password (nsslapd-rootpw).">Directory Manager
Password</label><input
- class="ds-input" type="password"
autocomplete="new-password" id="nsslapd-rootpw"
size="40"/>
+ class="ds-input" type="password"
id="nsslapd-rootpw" size="40"/>
</div>
<div>
<label for="nsslapd-rootpw-confirm"
class="ds-config-label" title="Confirm directory manager
password.">Confirm Password</label><input
- class="ds-input" type="password"
autocomplete="new-password" id="nsslapd-rootpw-confirm"
size="40"/>
+ class="ds-input" type="password"
id="nsslapd-rootpw-confirm" size="40"/>
</div>
<div>
<label for="nsslapd-rootpwstoragescheme"
class="ds-config-label" title="Set the Directory Manager password storage
scheme (nsslapd-rootpwstoragescheme).">Password Storage
Scheme</label><select
@@ -177,7 +177,7 @@
<!--
SASL Settings
-->
- <div id="server-sasl" class="all-pages" hidden>
+ <div id="server-sasl" class="all-pages ds-margin-left"
hidden>
<h3 class="ds-config-header">SASL Settings</h3>
<div class="ds-inline">
<div>
@@ -218,9 +218,9 @@
<!--
- Password Policy Settings
+ Password Policy Settings -> for REACT JS this should be tabbed navigation
-->
- <div id="global-password-policy" class="all-pages" hidden>
+ <div id="global-password-policy" class="all-pages
ds-margin-left" hidden>
<h3 class="ds-config-header">Global Password Policy
Settings</h3>
<div class="ds-container">
<div class="ds-split">
@@ -228,8 +228,8 @@
<h4>General Settings</h4>
<hr class="ds-hr">
<div>
- <label for="passwordstoragescheme"
class="ds-server-label" title="Set the password storage scheme
(passwordstoragescheme).">Password Storage Scheme</label><select
- class="btn btn-default dropdown ds-passwd-dropdownZ"
id="passwordstoragescheme">
+ <label for="passwordstoragescheme"
class="ds-spacing-sm" title="Set the password storage scheme
(passwordstoragescheme).">Password Storage Scheme</label><select
+ class="btn btn-default dropdown"
id="passwordstoragescheme">
<option>PBKDF2_SHA256</option>
<option>SSHA512</option>
<option>SSHA384</option>
@@ -269,7 +269,7 @@
</div>
<div>
<p></p>
- <label for="passwordAdminDN"
class="ds-server-labelZ" title="The DN for a password administrator or
administrator group (passwordAdminDN)">Password
Administrator</label><input
+ <label for="passwordAdminDN" title="The DN for a password
administrator or administrator group (passwordAdminDN)">Password
Administrator</label><input
class="ds-input" type="text"
id="passwordAdminDN" size="40"/>
</div>
</div>
@@ -310,15 +310,15 @@
<div class="ds-expired-div" id="expiration-attrs">
<div>
<label for="passwordmaxage"
class="ds-expire-label" title="The server's local hostname
(passwordMaxAge).">Password Expiration Time (in seconds)</label><input
- class="ds-input" type="text"
id="passwordmaxage" size="5"/>
+ class="ds-input" type="text"
id="passwordmaxage" size="6"/>
</div>
<div>
<label for="passwordgracelimit"
class="ds-expire-label" title="The server's local hostname
(passwordGraceLimit).">Allowed Logins After Password
Expires</label><input
- class="ds-input" type="text"
id="passwordgracelimit" size="5"/>
+ class="ds-input" type="text"
id="passwordgracelimit" size="6"/>
</div>
<div>
<label for="passwordwarning"
class="ds-expire-label" title="Set the time (in seconds), before a password
is about to expire, to send a warning. (passwordWarning).">Send Password Expiring
Warning (in seconds)</label><input
- class="ds-input" type="text"
id="passwordwarning" size="5"/>
+ class="ds-input" type="text"
id="passwordwarning" size="6"/>
</div>
<div>
<input type="checkbox"
class="ds-send-expiring-checkbox"
id="passwordsendexpiringtime"><label
@@ -410,16 +410,6 @@
<div class="ds-divider"></div>
<div class="ds-inline">
<div>
- <label for="passwordbadwords"
class="ds-expire-label" title=
- "A space-separated list of words that are not allowed to be
contained in the new password (passwordBadWords).">Reject Passwords That Contain
These Words </label><input
- class="ds-input" type="text"
id="passwordbadwords" size="20"/>
- </div>
- <div>
- <label for="passworduserattributes"
class="ds-expire-label" title=
- "A space-separated list of entry attributes to compare to the new
password (passwordUserAttributes).">Entry Attributes To Compare
</label><input
- class="ds-input" type="text"
id="passworduserattributes" size="20"/>
- </div>
- <div>
<label for="passwordmaxrepeats"
class="ds-expire-label" title=
"The maximum number of times the same character can sequentially
appear in a password (passwordMaxRepeats).">Maximum Number Of Repeated Characters
</label><input
class="ds-input" type="text"
id="passwordmaxrepeats" size="5"/>
@@ -442,15 +432,27 @@
<div>
<label for="passwordpalindrome"
class="ds-expire-label" title=
"Reject a password if it is a palindrome
(passwordPalindrome).">Reject Passwords that Are Palindromes
</label><input
- class="ds-checkbox-group" type="checkbox"
id="passwordpalindrome"/>
+ class="ds-margin-top" type="checkbox"
id="passwordpalindrome"/>
</div>
<div>
<label for="passworddictcheck"
class="ds-expire-label" title=
"Check the password against the system's CrackLib dictionary
(passwordDictCheck).">Check Password Contains Dictionary Word
</label><input
- class="ds-checkbox-group" type="checkbox"
id="passworddictcheck"/>
+ class="ds-margin-top" type="checkbox"
id="passworddictcheck"/>
</div>
</div>
</div>
+ <div class="ds-margin-left-sm ds-margin-top">
+ <div>
+ <label for="passwordbadwords" title=
+ "A space-separated list of words that are not allowed to be
contained in the new password (passwordBadWords).">Reject Passwords That Contain
These Words </label><input
+ class="ds-input-auto" type="text"
id="passwordbadwords"/>
+ </div>
+ <div class="ds-margin-top">
+ <label for="passworduserattributes" title=
+ "A space-separated list of entry attributes to compare to the new
password (passwordUserAttributes).">Entry Attributes To Compare
</label><input
+ class="ds-input-auto" type="text"
id="passworduserattributes"/>
+ </div>
+ </div>
<div class="ds-footer">
<button class="btn btn-primary
save-button">Save</button>
</div>
@@ -460,9 +462,9 @@
<!-- -------------------------------------
Local Password Policies
---------------------------------------- -->
- <div id="local-password-policy" class="all-pages" hidden>
+ <div id="local-password-policy" class="all-pages
ds-margin-left" hidden>
<h3 class="ds-config-header">Local Password Policies </h3>
- <label class="ds-config-label-med"
for="local-pwp-suffix">Database Suffix</Label> <select
+ <label for="local-pwp-suffix">Database Suffix</Label>
<select
class="btn btn-default dropdown" id="local-pwp-suffix">
</select>
<div class="ds-page-content">
@@ -488,7 +490,7 @@
-->
<!-- Access logging -->
- <div id="server-access-log" class="all-pages" hidden>
+ <div id="server-access-log" class="all-pages ds-margin-left"
hidden>
<h3 class="ds-config-header">Access Log Settings</h3>
<input type="checkbox" class="ds-config-checkbox"
id="nsslapd-accesslog-logging-enabled"><label
for="nsslapd-accesslog-logging-enabled" class="ds-label"
title="Enable access logging (nsslapd-accesslog-logging-enabled)."> Enable
Access Logging</label>
@@ -523,8 +525,8 @@
<option>day</option>
<option>week</option>
<option>month</option>
- </select> at <input class="ds-input"
type="text" title="Hour"
id="nsslapd-accesslog-logrotationsynchour" placeholder="0"
size="1"/> : <input class="ds-input" type="text"
placeholder="0"
- title="Minute"
id="nsslapd-accesslog-logrotationsyncminute" size="1"/>
+ </select> at <input class="ds-input"
type="text" title="Hour"
id="nsslapd-accesslog-logrotationsynchour" placeholder="0"
size="1"/> : <input class="ds-input" type="text"
placeholder="0"
+ title="Minute"
id="nsslapd-accesslog-logrotationsyncmin" size="1"/>
</div>
</div>
<p></p>
@@ -581,7 +583,7 @@
</div>
</div>
- <div id="server-audit-log" class="all-pages" hidden>
+ <div id="server-audit-log" class="all-pages ds-margin-left"
hidden>
<h3 class="ds-config-header">Audit Log Settings</h3>
<input type="checkbox" class="ds-config-checkbox"
id="nsslapd-auditlog-logging-enabled"><label
for="nsslapd-auditlog-logging-enabled" class="ds-label"
title="Enable audit logging (nsslapd-auditlog-logging-enabled)."> Enable
Audit Logging</label>
@@ -613,7 +615,7 @@
<option>week</option>
<option>month</option>
</select> at <input class="ds-input" type="text"
title="Hour" id="nsslapd-auditlog-logrotationsynchour"
placeholder="0" size="1"/> : <input class="ds-input"
type="text" placeholder="0"
- title="Minute"
id="nsslapd-auditlog-logrotationsyncminute" size="1"/>
+ title="Minute" id="nsslapd-auditlog-logrotationsyncmin"
size="1"/>
</div>
</div>
<p></p>
@@ -644,7 +646,7 @@
</div>
<!-- Auditfail logging -->
- <div id="server-auditfail-log" class="all-pages" hidden>
+ <div id="server-auditfail-log" class="all-pages ds-margin-left"
hidden>
<h3 class="ds-config-header">Audit Failure Log Settings</h3>
<input type="checkbox" class="ds-config-checkbox"
id="nsslapd-auditfaillog-logging-enabled"><label
for="nsslapd-auditfaillog-logging-enabled" class="ds-label"
title="Enable audit failure logging (nsslapd-auditfaillog-logging-enabled).">
Enable Audit Failure Logging</label>
@@ -672,7 +674,7 @@
<option>week</option>
<option>month</option>
</select> at <input class="ds-input" type="text"
title="Hour" id="nsslapd-auditfaillog-logrotationsynchour"
placeholder="0" size="1"/> : <input class="ds-input"
type="text" placeholder="0"
- title="Minute"
id="nsslapd-auditfaillog-logrotationsyncminute" size="1"/>
+ title="Minute"
id="nsslapd-auditfaillog-logrotationsyncmin" size="1"/>
</div>
</div>
<h4 class="ds-sub-header">Deletion Policy</h4>
@@ -702,7 +704,7 @@
</div>
<!-- Error logging -->
- <div id="server-errors-log" class="all-pages" hidden>
+ <div id="server-errors-log" class="all-pages ds-margin-left"
hidden>
<h3 class="ds-config-header">Error Log Settings</h3>
<input type="checkbox" class="ds-config-checkbox"
id="nsslapd-errorlog-logging-enabled" checked><label
for="nsslapd-errorlog-logging-enabled" class="ds-label"
title="Enable error logging (nsslapd-errorlog-logging-enabled)."> Enable
Error Logging</label>
@@ -729,7 +731,7 @@
<option>week</option>
<option>month</option>
</select> at <input class="ds-input" type="text"
title="Hour" id="nsslapd-errorlog-logrotationsynchour"
placeholder="0" size="1"/> : <input class="ds-input"
type="text" placeholder="0"
- title="Minute"
id="nsslapd-errorlog-logrotationsyncminute" size="1"/>
+ title="Minute" id="nsslapd-errorlog-logrotationsyncmin"
size="1"/>
</div>
</div>
@@ -835,7 +837,7 @@
<!--
Tuning
-->
- <div id="server-tuning" class="all-pages" hidden>
+ <div id="server-tuning" class="all-pages ds-margin-left"
hidden>
<h3 class="ds-config-header">Server Tuning & Limits</h3>
<div class="ds-container">
<!-- Attribute list -->
@@ -916,7 +918,7 @@
</div>
</div>
<div class="ds-divider"></div>
- <div class="ds-inline ds-checkbox-group">
+ <div class="ds-inline ds-margin-top">
<div>
<input type="checkbox" class="ds-config-checkbox"
id="nsslapd-ignore-virtual-attrs" checked><label
for="nsslapd-ignore-virtual-attrs" class="ds-label"
title=
@@ -944,27 +946,18 @@
<!--
LDAPI & Autobind Settings
-->
- <div id="server-ldapi" class="all-pages" hidden>
+ <div id="server-ldapi" class="all-pages ds-margin-left"
hidden>
<h3 class="ds-config-header">LDAPI & Autobind
Settings</h3>
- <div>
- <input type="checkbox" class="ds-config-checkbox"
id="nsslapd-ldapilisten" checked><label
- for="nsslapd-ldapilisten" class="ds-label" title="Enable
LDAPI (nsslapd-ldapilisten)."> Enable LDAPI</label>
- </div>
<div class="ldapi-attrs ds-inline" hidden>
<div>
- <label for="nsslapd-ldapifilepath"
class="ds-config-indent-sm-label" title="The Unix socket file
(nsslapd-ldapifilepath).">LDAPI Socket File Path</label><input
- class="ds-input" type="text"
id="nsslapd-ldapifilepath" size="35"/>
- </div>
- <div>
- <p></p>
- <input type="checkbox" class="ds-config-checkbox"
id="nsslapd-ldapiautobind"><label
- for="nsslapd-ldapiautobind" class="ds-label"
title="Enable autobind (nsslapd-ldapiautobind)."> Enable
Autobind</label>
+ <label for="nsslapd-ldapifilepath" class="ds-config-label"
title="The Unix socket file (nsslapd-ldapifilepath). The UI requires this exact path
so it is a read-only setting.">LDAPI Socket File Path</label><input
+ class="ds-input" type="text"
id="nsslapd-ldapifilepath" size="35" readonly/>
</div>
<div class="ds-inline">
- <div class="autobind-attrs" hidden>
+ <div class="autobind-attrs">
<div>
- <label for="nsslapd-ldapimaprootdn"
class="ds-config-indent-sm-label" title="Map the Unix root entry to this
Directory Manager DN (nsslapd-ldapimaprootdn).">DN to map "root"
To</label><input
- class="ds-input" type="text"
id="nsslapd-ldapimaprootdn" placeholder="e.g. cn=Directory Manager"
size="35"/>
+ <label for="nsslapd-ldapimaprootdn"
class="ds-config-label" title="Map the Unix root entry to this Directory
Manager DN (nsslapd-ldapimaprootdn). The UI requires this to be set to the current root
DN so it is a read-only setting">DN to map "root"
To</label><input
+ class="ds-input" type="text"
id="nsslapd-ldapimaprootdn" readonly size="35"/>
</div>
<div>
<p></p>
@@ -1023,7 +1016,7 @@
<label for="sasl-map-regex"
class="ds-config-label" title="SASL mapping regular expression">
SASL Mapping Regex</label><input class="ds-input
sasl-input" type="text" id="sasl-map-regex"/><label
for="test-sasl-regex"
class="ds-left-margin">Test Regex</label><input
type="checkbox"
- class="ds-left-margin ds-checkbox-group"
id="test-sasl-regex">
+ class="ds-left-margin ds-margin-top"
id="test-sasl-regex">
</div>
<div id="sasl-test-div" hidden>
<div class="ds-inline">
@@ -1064,7 +1057,7 @@
<!-- Create Local Password Policy -->
<div class="modal fade" id="local-pwp-form"
data-backdrop="static" tabindex="-1" role="dialog"
aria-labelledby="local-pwp-header" aria-hidden="true">
<div class="modal-dialog">
- <div class="modal-content ds-modal-wide">
+ <div class="ds-modal-wide modal-content">
<div class="modal-header">
<button type="button" class="close"
data-dismiss="modal" aria-hidden="true"
aria-label="Close">
<span class="pficon pficon-close"></span>
@@ -1201,7 +1194,7 @@
<input type="checkbox" class="ds-config-checkbox
ds-pwp-checkbox" id="local-passwordchecksyntax"><label
for="local-passwordchecksyntax" class="ds-label"
title="Enable account lockout (passwordCheckSyntax).">Check Password
Syntax</label>
<div class="ds-container ds-expired-div"
id="local-syntax-attrs">
- <div>
+ <div class="ds-split">
<label for="local-passwordminlength"
class="ds-expire-label" title=
"The minimum number of characters in the password
(passwordMinLength).">Password Minimum Length </label><input
class="ds-pw-input ds-pwp-input" type="text"
id="local-passwordminlength" size="5"/>
@@ -1230,15 +1223,7 @@
"The smallest attribute value used when checking if the password
contains any of the user's account information
(passwordMinTokenLength).">Minimum Token Length </label><input
class="ds-pw-input ds-pwp-input" type="text"
id="local-passwordmintokenlength" size="5"/>
</div>
- <div class="ds-divider"></div>
- <div class="ds-divider"></div>
- <div>
- <label for="local-passwordbadwords"
class="ds-pw-list-label" title=
- "A space-separated list of words that are not allowed to be
contained in the new password (passwordBadWords).">Reject Passwords That Contain
These Words </label><input
- class="ds-pw-list-input ds-pwp-input" type="text"
id="local-passwordbadwords"/>
- <label for="local-passworduserattributes"
class="ds-pw-list-label" title=
- "A space-separated list of entry attributes to compare to the new
password (passwordUserAttributes).">Entry Attributes To Compare
</label><input
- class="ds-pw-list-input ds-pwp-input" type="text"
id="local-passworduserattributes"/>
+ <div class="ds-split">
<label for="local-passwordmaxrepeats"
class="ds-expire-label" title=
"The maximum number of times the same character can sequentially
appear in a password (passwordMaxRepeats).">Maximum Number Of Repeated Characters
</label><input
class="ds-pw-input ds-pwp-input" type="text"
id="local-passwordmaxrepeats"/>
@@ -1253,12 +1238,24 @@
class="ds-pw-input ds-pwp-input" type="text"
id="local-passwordmaxclasschars"/>
<label for="local-passwordpalindrome"
class="ds-expire-label" title=
"Reject a password if it is a palindrome
(passwordPalindrome).">Reject Passwords that Are Palindromes
</label><input
- class="ds-checkbox-group ds-pwp-checkbox"
type="checkbox" id="local-passwordpalindrome"/>
+ class="ds-margin-top ds-pwp-checkbox"
type="checkbox" id="local-passwordpalindrome"/>
<label for="local-passworddictcheck"
class="ds-expire-label" title=
"Check the password against the system's CrackLib dictionary
(passwordDictCheck).">Check Password Contains Dictionary Word
</label><input
- class="ds-checkbox-group ds-pwp-checkbox"
type="checkbox" id="local-passworddictcheck"/>
+ class="ds-margin-top ds-pwp-checkbox"
type="checkbox" id="local-passworddictcheck"/>
</div>
</div>
+ <div class="ds-margin-left-sm ds-margin-top">
+ <div>
+ <label for="passwordbadwords" title=
+ "A space-separated list of words that are not allowed to be
contained in the new password (passwordBadWords).">Reject Passwords That Contain
These Words </label><input
+ class="ds-input-auto" type="text"
id="local-passwordbadwords"/>
+ </div>
+ <div class="ds-margin-top">
+ <label for="passworduserattributes" title=
+ "A space-separated list of entry attributes to compare to the
new password (passwordUserAttributes).">Entry Attributes To Compare
</label><input
+ class="ds-input-auto" type="text"
id="local-passworduserattributes"/>
+ </div>
+ </div>
</form>
</div>
</div>
diff --git a/src/cockpit/389-console/src/servers.js
b/src/cockpit/389-console/src/servers.js
index 1aa9783..7e34a5a 100644
--- a/src/cockpit/389-console/src/servers.js
+++ b/src/cockpit/389-console/src/servers.js
@@ -1,7 +1,7 @@
var sasl_action_html =
'<div class="dropdown">' +
- '<button class="btn btn-default dropdown-toggle
ds-agmt-dropdown-button" type="button" id="dropdownMenu1"
data-toggle="dropdown">' +
+ '<button class="btn btn-default dropdown-toggle"
type="button" id="dropdownMenu1"
data-toggle="dropdown">' +
'Choose Action...' +
'<span class="caret"></span>' +
'</button>' +
@@ -13,7 +13,7 @@ var sasl_action_html =
var local_pwp_html =
'<div class="dropdown" >' +
- '<button class="btn btn-default dropdown-toggle
ds-agmt-dropdown-button" type="button" id="menu1"
data-toggle="dropdown">Choose Action...' +
+ '<button class="btn btn-default dropdown-toggle"
type="button" id="menu1" data-toggle="dropdown">Choose
Action...' +
'<span class="caret"></span></button>' +
'<ul id="test-drop" class="dropdown-menu
ds-agmt-dropdown" role="menu" aria-labelledby="menu1">' +
'<li role="policy-role"><a role="pwpolicy"
tabindex="0" class="edit-local-pwp" href="#">View/Edit
Policy</a></li>' +
@@ -115,8 +115,8 @@ function clear_inst_form() {
$("#create-inst-rootdn").val("cn=Directory Manager");
$("#rootdn-pw").val("");
$("#rootdn-pw-confirm").val("");
- $("#backend-suffix").val("");
- $("#backend-name").val("");
+ $("#backend-suffix").val("dc=example,dc=com");
+ $("#backend-name").val("userRoot");
$("#create-sample-entries").prop('checked', false);
$("#create-inst-tls").prop('checked', true);
$(".ds-inst-input").css("border-color", "initial");
@@ -165,6 +165,7 @@ function get_and_set_config () {
$(".ds-accesslog-table").prop('checked', false);
$(".ds-errorlog-table").prop('checked', false);
config_values = {};
+ update_progress();
for (var attr in obj['attrs']) {
var val = obj['attrs'][attr][0];
@@ -189,6 +190,7 @@ function get_and_set_config () {
// Do the log level tables
if (attr == "nsslapd-accesslog-level") {
+ config_values[attr] = val;
var level_val = parseInt(val);
for ( var level in accesslog_levels ) {
if (level_val & accesslog_levels[level]) {
@@ -196,6 +198,7 @@ function get_and_set_config () {
}
}
} else if (attr == "nsslapd-errorlog-level") {
+ config_values[attr] = val;
var level_val = parseInt(val);
for ( var level in errorlog_levels ) {
if (level_val & errorlog_levels[level]) {
@@ -208,7 +211,7 @@ function get_and_set_config () {
config_loaded = 1;
check_inst_alive();
}).fail(function(data) {
- popup_err("Error", "Failed to set config\n" + data.message);
+ popup_err("Error", "Failed to get config\n" + data.message);
check_inst_alive(1);
});
}
@@ -232,6 +235,7 @@ function update_suffix_dropdowns () {
$("#" + dropdowns[list]).append('<option value="' +
obj['items'][idx] + '" selected="selected">' +
obj['items'][idx] +'</option>');
}
}
+ update_progress();
}).fail(function(data) {
if (quiet === undefined) {
popup_err("Error", "Failed to get backend suffix list\n" +
data.message);
@@ -251,6 +255,7 @@ function get_and_set_localpwp (quiet) {
log_cmd('get_and_set_localpwp', 'Get local password policies', cmd);
cockpit.spawn(cmd, { superuser: true, "err": "message",
"environ": [ENV]}).done(function(data) {
var obj = JSON.parse(data);
+ update_progress();
// Empty table
pwp_table.clear().draw();
@@ -276,13 +281,13 @@ function get_and_set_sasl () {
log_cmd('get_and_set_sasl', 'Get SASL mappings', cmd);
cockpit.spawn(cmd, { superuser: true, "err": "message",
"environ": [ENV]}).done(function(data) {
var obj = JSON.parse(data);
+ update_progress();
sasl_table.clear().draw();
for (var idx in obj['items']) {
var map_cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id
+ '.socket','sasl', 'get', obj['items'][idx] ];
log_cmd('get_and_set_sasl', 'Get SASL mapping', map_cmd);
cockpit.spawn(map_cmd, { superuser: true, "err": "message",
"environ": [ENV]}).done(function(data) {
var map_obj = JSON.parse(data);
-
// Update html table
var sasl_priority = '100';
if ( map_obj['attrs'].hasOwnProperty('nssaslmappriority') ){
@@ -306,34 +311,56 @@ function get_and_set_sasl () {
}
function apply_mods(mods) {
- var mod = mods.pop();
+ let mod = mods.pop();
- if (!mod){
- popup_success("Successfully updated configuration");
- return; /* all done*/
+ if (!mod) {
+ return 0; /* all done*/
}
- var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id +
'.socket','config', 'replace'];
+ let cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id +
'.socket', 'config', 'replace'];
cmd.push(mod.attr + "=" + mod.val);
cockpit.spawn(cmd, { superuser: true, "err": "message",
"environ": [ENV]}).then(function() {
config_values[mod.attr] = mod.val;
// Continue with next mods (if any))
apply_mods(mods);
- }, function(ex) {
- popup_err("Failed to update attribute: " + mod.attr, ex.message);
+ }, function(ex, data) {
+ popup_err("Failed to update attribute: " + mod.attr, data);
// Reset HTML for remaining values that have not been processed
$("#" + mod.attr).val(config_values[mod.attr]);
for (remaining in mods) {
$("#" + remaining.attr).val(config_values[remaining.attr]);
}
check_inst_alive(0);
- return; // Stop on error
+ return -1; // Stop on error
+ });
+}
+
+function delete_mods(mods) {
+ let mod = mods.pop();
+
+ if (!mod) {
+ return 0; /* all done*/
+ }
+ var cmd = [DSCONF, '-j', 'ldapi://%2fvar%2frun%2f' + server_id +
'.socket', 'config', 'delete', mod.attr];
+ cockpit.spawn(cmd, { superuser: true, "err": "message",
"environ": [ENV]}).then(function() {
+ config_values[mod.attr] = "";
+ // Continue with next mods (if any))
+ delete_mods(mods);
+ }, function(ex, data) {
+ popup_err("Failed to delete attribute: " + mod.attr, data);
+ // Reset HTML for remaining values that have not been processed
+ $("#" + mod.attr).val(config_values[mod.attr]);
+ for (remaining in mods) {
+ $("#" + remaining.attr).val(config_values[remaining.attr]);
+ }
+ check_inst_alive(0);
+ return -1; // Stop on error
});
}
function save_config() {
// Loop over current config_values check for differences
- var mod_list = [];
-
+ let mod_list = [];
+ let del_list = [];
for (var attr in config_values) {
var mod = {};
if ( $("#" + attr).is(':checkbox')) {
@@ -355,20 +382,52 @@ function save_config() {
} else {
// Normal input
var val = $("#" + attr).val();
-
// But first check for rootdn-pw changes and check confirm input matches
- if (attr == "nsslapd-rootpw" && (val != config_values[attr] ||
val != $("#nsslapd-rootpw-confirm").val())) {
- // Password change, make sure passwords match
- if (val != $("#nsslapd-rootpw-confirm").val()){
- popup_msg("Passwords do not match!", "The Directory Manager
passwords do not match, please correct before saving again.");
- return;
+ if (attr == "nsslapd-rootpw") {
+ if (val != config_values[attr] || val !=
$("#nsslapd-rootpw-confirm").val()) {
+ // Password change, make sure passwords match
+ if (val != $("#nsslapd-rootpw-confirm").val()){
+ popup_msg("Passwords do not match!", "The Directory Manager
passwords do not match, please correct before saving again.");
+ return;
+ }
}
+ if (val.length < 8) {
+ popup_msg("Password is too short!", "The Directory Manager
password must be at least 8 characters long.");
+ $("#nsslapd-rootpw").val(config_values[attr]);
+ $("#nsslapd-rootpw-confirm").val(config_values[attr]);
+ return;
+ }
+ }
+
+ if (attr == "nsslapd-port") {
+ if (!valid_port(val)) {
+ popup_msg("Port number is not valid");
+ $("#nsslapd-port").val(config_values[attr]);
+ }
}
- if ( val && val != config_values[attr]) {
+ if (attr.indexOf("logrotationsynchour") != -1) {
+ if (!valid_num(val) || val < 0 || val > 23) {
+ popup_msg("Invalid value", "You must use a number between 0
- 23 for: " + attr);
+ $("#" + attr).val(config_values[attr])
+ return;
+ }
+ }
+ if (attr.indexOf("logrotationsyncmin") != -1) {
+ if (!valid_num(val) || val < 0 || val > 59){
+ popup_msg("Invalid value", "You must use a number between 0
- 59 for: " + attr);
+ $("#" + attr).val(config_values[attr])
+ return;
+ }
+ }
+
+ if (val && val != config_values[attr]) {
mod['attr'] = attr;
mod['val'] = val;
mod_list.push(mod);
+ } else if (val == "" && val != config_values[attr]) {
+ mod['attr'] = attr;
+ del_list.push(mod);
}
}
}
@@ -382,10 +441,15 @@ function save_config() {
access_log_level += val;
}
});
- mod = {}
- mod['attr'] = "nsslapd-accesslog-level";
- mod['val'] = access_log_level;
- mod_list.push(mod);
+ if (config_values["nsslapd-accesslog-level"] === undefined) {
+ config_values["nsslapd-accesslog-level"] = "256";
+ }
+ if (config_values["nsslapd-accesslog-level"] != access_log_level) {
+ mod = {}
+ mod['attr'] = "nsslapd-accesslog-level";
+ mod['val'] = access_log_level;
+ mod_list.push(mod);
+ }
// Save error log levels
var error_log_level = 0;
@@ -396,14 +460,32 @@ function save_config() {
error_log_level += val;
}
});
- mod = {}
- mod['attr'] = "nsslapd-errorlog-level";
- mod['val'] = error_log_level;
- mod_list.push(mod);
+ if (config_values["nsslapd-errorlog-level"] === undefined ||
+ config_values["nsslapd-errorlog-level"] == "16384")
+ {
+ config_values["nsslapd-errorlog-level"] = "0";
+ }
+ if (config_values["nsslapd-errorlog-level"] != error_log_level) {
+ mod = {}
+ mod['attr'] = "nsslapd-errorlog-level";
+ mod['val'] = error_log_level;
+ mod_list.push(mod);
+ }
// Build dsconf commands to apply all the mods
- if (mod_list.length) {
- apply_mods(mod_list);
+ if (mod_list.length || del_list.length) {
+ let err = 0;
+ if (mod_list.length) {
+ if (apply_mods(mod_list) == -1) {
+ return;
+ }
+ }
+ if (del_list.length) {
+ if (delete_mods(del_list) == -1) {
+ return;
+ }
+ }
+ popup_success("Successfully updated configuration");
} else {
// No changes to save, log msg? popup_msg()
}
@@ -931,45 +1013,6 @@ $(document).ready( function() {
});
// LDAPI form control
- $("#nsslapd-ldapilisten").change(function() {
- if(this.checked) {
- $('.ldapi-attrs').show();
- if ( $("#nsslapd-ldapiautobind").is(":checked") ){
- $(".autobind-attrs").show();
- if ( $("#nsslapd-ldapimaptoentries").is(":checked") ){
- $(".autobind-entry-attrs").show();
- } else {
- $(".autobind-entry-attrs").hide();
- }
- } else {
- $(".autobind-attrs").hide();
- $(".autobind-entry-attrs").hide();
- $("#nsslapd-ldapimaptoentries").prop("checked", false );
- }
- } else {
- $('.ldapi-attrs').hide();
- $(".autobind-attrs").hide();
- $(".autobind-entry-attrs").hide();
- $("#nsslapd-ldapiautobind").prop("checked", false );
- $("#nsslapd-ldapimaptoentries").prop("checked", false );
- }
- });
-
- $("#nsslapd-ldapiautobind").change(function() {
- if (this.checked){
- $(".autobind-attrs").show();
- if ( $("#nsslapd-ldapimaptoentries").is(":checked") ){
- $(".autobind-entry-attrs").show();
- } else {
- $(".autobind-entry-attrs").hide();
- }
- } else {
- $(".autobind-attrs").hide();
- $(".autobind-entry-attrs").hide();
- $("#nsslapd-ldapimaptoentries").prop("checked", false );
- }
- });
-
$("#nsslapd-ldapimaptoentries").change(function() {
if (this.checked){
$(".autobind-entry-attrs").show();
@@ -1007,6 +1050,10 @@ $(document).ready( function() {
* Get all the current values from the form.
*/
var policy_name = $("#local-entry-dn").val();
+ if (policy_name == "" || !valid_dn(policy_name)) {
+ popup_msg("Error", "You must enter a valid DN for the local
password policy");
+ return;
+ }
var pwp_track = "off";
if ( $("#local-passwordtrackupdatetime").is(":checked") ) {
pwp_track = "on";
@@ -1479,19 +1526,33 @@ $(document).ready( function() {
var new_server_id = $("#create-inst-serverid").val();
if (new_server_id == ""){
report_err($("#create-inst-serverid"), 'You must provide an
Instance name');
+ $("#create-inst-serverid").css("border-color",
"red");
return;
} else {
new_server_id = new_server_id.replace(/^slapd-/i, ""); // strip
"slapd-"
- setup_inf = setup_inf.replace('INST_NAME', new_server_id);
+ if (new_server_id.length > 128) {
+ report_err($("#create-inst-serverid"), 'Instance name is too
long, it must not exceed 128 characters');
+ $("#create-inst-serverid").css("border-color",
"red");
+ return;
+ }
+ if (new_server_id.match(/^[#%:A-Za-z0-9_\-]+$/g)) {
+ setup_inf = setup_inf.replace('INST_NAME', new_server_id);
+ } else {
+ report_err($("#create-inst-serverid"), 'Instance name can only
contain letters, numbers, and: # % : - _');
+ $("#create-inst-serverid").css("border-color",
"red");
+ return;
+ }
}
// Port
var server_port = $("#create-inst-port").val();
if (server_port == ""){
report_err($("#create-inst-port"), 'You must provide a port
number');
+ $("#create-inst-port").css("border-color", "red");
return;
- } else if (!valid_num(server_port)) {
- report_err($("#create-inst-port"), 'Port must be a number!');
+ } else if (!valid_port(server_port)) {
+ report_err($("#create-inst-port"), 'Port must be a number between 1
and 65534!');
+ $("#create-inst-port").css("border-color", "red");
return;
} else {
setup_inf = setup_inf.replace('PORT', server_port);
@@ -1501,9 +1562,11 @@ $(document).ready( function() {
var secure_port = $("#create-inst-secureport").val();
if (secure_port == ""){
report_err($("#create-inst-secureport"), 'You must provide a secure
port number');
+ $("#create-inst-secureport").css("border-color",
"red");
return;
- } else if (!valid_num(secure_port)) {
+ } else if (!valid_port(secure_port)) {
report_err($("#create-inst-secureport"), 'Secure port must be a
number!');
+ $("#create-inst-secureport").css("border-color",
"red");
return;
} else {
setup_inf = setup_inf.replace('SECURE_PORT', secure_port);
@@ -1513,6 +1576,7 @@ $(document).ready( function() {
var server_rootdn = $("#create-inst-rootdn").val();
if (server_rootdn == ""){
report_err($("#create-inst-rootdn"), 'You must provide a Directory
Manager DN');
+ $("#create-inst-rootdn").css("border-color",
"red");
return;
} else {
setup_inf = setup_inf.replace('ROOTDN', server_rootdn);
@@ -1536,6 +1600,10 @@ $(document).ready( function() {
report_err($("#rootdn-pw"), 'Directory Manager password can not be
empty!');
$("#rootdn-pw-confirm").css("border-color",
"red");
return;
+ } else if (root_pw.length < 8) {
+ report_err($("#rootdn-pw"), 'Directory Manager password must have
at least 8 characters');
+ $("#rootdn-pw-confirm").css("border-color",
"red");
+ return;
} else {
setup_inf = setup_inf.replace('ROOTPW', root_pw);
}
@@ -1546,9 +1614,11 @@ $(document).ready( function() {
if ( (backend_name != "" && backend_suffix == "") ||
(backend_name == "" && backend_suffix != "") ) {
if (backend_name == ""){
report_err($("#backend-name"), 'If you specify a backend suffix,
you must also specify a backend name');
+ $("#backend-name").css("border-color", "red");
return;
} else {
report_err($("#backend-suffix"), 'If you specify a backend name,
you must also specify a backend suffix');
+ $("#backend-suffix").css("border-color", "red");
return;
}
}
@@ -1564,8 +1634,8 @@ $(document).ready( function() {
}
if ( $("#create-sample-entries").is(":checked") ) {
setup_inf += '\nsample_entries = yes\n';
- } else {
- setup_inf += '\nsample_entries = no\n';
+ } else if ( $("#create-suffix-entry").is(":checked") ) {
+ setup_inf += '\ncreate_suffix_entry = yes\n';
}
}
@@ -1579,9 +1649,9 @@ $(document).ready( function() {
* [5] Create the instance
* [6] Remove setup file
*/
- cockpit.spawn(["hostname", "--fqdn"], { superuser: true,
"err": "message" }).fail(function(ex) {
+ cockpit.spawn(["hostname", "--fqdn"], { superuser: true,
"err": "message" }).fail(function(ex, data) {
// Failed to get FQDN
- popup_err("Failed to get hostname!", ex.message);
+ popup_err("Failed to get hostname!", data);
}).done(function (data){
/*
* We have FQDN, so set the hostname in inf file, and create the setup file
@@ -1590,38 +1660,38 @@ $(document).ready( function() {
var setup_file = "/tmp/389-setup-" + (new Date).getTime() +
".inf";
var rm_cmd = ['rm', setup_file];
var create_file_cmd = ['touch', setup_file];
- cockpit.spawn(create_file_cmd, { superuser: true, "err":
"message" }).fail(function(ex) {
+ cockpit.spawn(create_file_cmd, { superuser: true, "err":
"message" }).fail(function(ex, data) {
// Failed to create setup file
- popup_err("Failed to create installation file!", ex.message);
+ popup_err("Failed to create installation file!", data);
}).done(function (){
/*
* We have our new setup file, now set permissions on that setup file before we
add sensitive data
*/
var chmod_cmd = ['chmod', '600', setup_file];
- cockpit.spawn(chmod_cmd, { superuser: true, "err":
"message" }).fail(function(ex) {
+ cockpit.spawn(chmod_cmd, { superuser: true, "err":
"message" }).fail(function(ex, data) {
// Failed to set permissions on setup file
cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with clear
text password
$("#create-inst-spinner").hide();
- popup_err("Failed to set permission on setup file " + setup_file +
": ", ex.message);
+ popup_err("Failed to set permission on setup file " + setup_file +
": ", data);
}).done(function (){
/*
* Success we have our setup file and it has the correct permissions.
* Now populate the setup file...
*/
- var cmd = ["/bin/sh", "-c", '/usr/bin/echo -e
"' + setup_inf + '" >> ' + setup_file];
- cockpit.spawn(cmd, { superuser: true, "err": "message"
}).fail(function(ex) {
+ let cmd = ["/bin/sh", "-c", '/usr/bin/echo -e
"' + setup_inf + '" >> ' + setup_file];
+ cockpit.spawn(cmd, { superuser: true, "err": "message"
}).fail(function(ex, data) {
// Failed to populate setup file
- popup_err("Failed to populate installation file!", ex.message);
+ popup_err("Failed to populate installation file!", data);
}).done(function (){
/*
* Next, create the instance...
*/
cmd = [DSCREATE, 'from-file', setup_file];
- cockpit.spawn(cmd, { superuser: true, "err": "message",
"environ": [ENV] }).fail(function(ex) {
+ cockpit.spawn(cmd, { superuser: true, "err": "message",
"environ": [ENV] }).fail(function(ex, data) {
// Failed to create the new instance!
cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with
clear text password
$("#create-inst-spinner").hide();
- popup_err("Failed to create instance!", ex.message);
+ popup_err("Failed to create instance!", data);
}).done(function (){
// Success!!! Now cleanup everything up...
cockpit.spawn(rm_cmd, { superuser: true }); // Remove Inf file with
clear text password
diff --git a/src/cockpit/389-console/webpack.config.js
b/src/cockpit/389-console/webpack.config.js
index 941a6e8..026eb59 100644
--- a/src/cockpit/389-console/webpack.config.js
+++ b/src/cockpit/389-console/webpack.config.js
@@ -30,8 +30,6 @@ var info = {
"fonts",
"images",
"index.html",
- "replication.html",
- "replication.js",
"schema.html",
"schema.js",
"servers.html",
@@ -157,6 +155,17 @@ module.exports = {
test: /\.css$/,
include: /node_modules/,
use: ['style-loader', 'css-loader'],
+ },
+ {
+ test: /\.(png|jpg|gif)$/i,
+ use: [
+ {
+ loader: 'url-loader',
+ options: {
+ limit: 8192
+ }
+ }
+ ]
}
]
},
diff --git a/src/lib389/cli/dsctl b/src/lib389/cli/dsctl
index 04c83a8..3be9540 100755
--- a/src/lib389/cli/dsctl
+++ b/src/lib389/cli/dsctl
@@ -23,6 +23,7 @@ from lib389.cli_ctl import dbtasks as cli_dbtasks
from lib389.cli_base import disconnect_instance, setup_script_logger
from lib389.cli_base import format_error_to_dict
from lib389.cli_ctl.instance import instance_remove_all
+from lib389._constants import DSRC_CONTAINER
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose',
@@ -48,7 +49,7 @@ parser.add_argument('--remove-all', nargs="?",
default=False, const=None,
subparsers = parser.add_subparsers(help="action")
# We can only use the instance tools like start/stop etc in a non-container
# environment. If we are in a container, we only allow the tasks.
-if not os.path.exists('/data/config/container.inf'):
+if not os.path.exists(DSRC_CONTAINER):
cli_instance.create_parser(subparsers)
cli_dbtasks.create_parser(subparsers)
diff --git a/src/lib389/cli/dsidm b/src/lib389/cli/dsidm
index 05fcb64..da246b5 100755
--- a/src/lib389/cli/dsidm
+++ b/src/lib389/cli/dsidm
@@ -11,8 +11,8 @@
# PYTHON_ARGCOMPLETE_OK
import ldap
-import argparse, argcomplete
-import logging
+import argparse
+import argcomplete
import sys
import signal
from lib389._constants import DSRC_HOME
@@ -23,6 +23,7 @@ from lib389.cli_idm import group as cli_group
from lib389.cli_idm import posixgroup as cli_posixgroup
from lib389.cli_idm import user as cli_user
from lib389.cli_idm import client_config as cli_client_config
+from lib389.cli_idm import role as cli_role
from lib389.cli_base import connect_instance, disconnect_instance, setup_script_logger
from lib389.cli_base.dsrc import dsrc_to_ldap, dsrc_arg_concat
from lib389.cli_base import format_error_to_dict
@@ -74,6 +75,7 @@ cli_ou.create_parser(subparsers)
cli_posixgroup.create_parser(subparsers)
cli_user.create_parser(subparsers)
cli_client_config.create_parser(subparsers)
+cli_role.create_parser(subparsers)
argcomplete.autocomplete(parser)
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
index 6e2e54a..0ff4335 100644
--- a/src/lib389/lib389/__init__.py
+++ b/src/lib389/lib389/__init__.py
@@ -463,7 +463,7 @@ class DirSrv(SimpleLDAPObject, object):
self.binddn = binddn
self.bindpw = password
self.state = DIRSRV_STATE_ALLOCATED
- self.log.info("Allocate local instance %s with %s", self.__class__,
self.ldapuri)
+ self.log.debug("Allocate local instance %s with %s", self.__class__,
self.ldapuri)
def remote_simple_allocate(self, ldapuri, binddn='cn=Directory Manager',
password=None):
"""Allocate an instance, and perform a simple bind. This instance
is remote, so
@@ -494,7 +494,7 @@ class DirSrv(SimpleLDAPObject, object):
self.binddn = binddn
self.bindpw = password
self.state = DIRSRV_STATE_ALLOCATED
- self.log.info("Allocate %s with %s", self.__class__, self.ldapuri)
+ self.log.debug("Allocate %s with %s", self.__class__, self.ldapuri)
# Should there be an extra boolean to this function to determine to use
# ldapi or not? Or does the settings presence indicate intent?
@@ -2940,7 +2940,7 @@ class DirSrv(SimpleLDAPObject, object):
json_result = {'type': 'list', 'items': []}
for backup in dirlist:
bak = bakdir + "/" + backup
- bak_date = os.path.getctime(bak)
+ bak_date = os.path.getmtime(bak)
bak_date = datetime.fromtimestamp(bak_date).strftime('%Y-%m-%d
%H:%M:%S')
bak_size = subprocess.check_output(['du', '-sh',
bak]).split()[0].decode('utf-8')
if use_json:
@@ -2980,12 +2980,13 @@ class DirSrv(SimpleLDAPObject, object):
json_result = {'type': 'list', 'items': []}
for ldif in dirlist:
fullpath = ldifdir + "/" + ldif
- ldif_date = os.path.getctime(fullpath)
+ ldif_date = os.path.getmtime(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 = "???"
+ # This is not a valid LDIF file
+ ldif_suffix = "Invalid LDIF"
if use_json:
json_item = [ldif, ldif_date, ldif_size, ldif_suffix]
json_result['items'].append(json_item)
diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py
index 3953a67..010d964 100644
--- a/src/lib389/lib389/_mapped_object.py
+++ b/src/lib389/lib389/_mapped_object.py
@@ -282,7 +282,7 @@ class DSLdapObject(DSLogging):
mods = []
for arg in args:
- if isinstance(arg[1], list):
+ if isinstance(arg[1], list) or isinstance(arg[1], tuple):
value = ensure_list_bytes(arg[1])
else:
value = [ensure_bytes(arg[1])]
@@ -945,7 +945,7 @@ class DSLdapObjects(DSLogging):
:type instance: lib389.DirSrv
"""
- def __init__(self, instance):
+ def __init__(self, instance, basedn=""):
self._childobject = DSLdapObject
self._instance = instance
super(DSLdapObjects, self).__init__(self._instance.verbose)
@@ -953,7 +953,7 @@ class DSLdapObjects(DSLogging):
self._filterattrs = []
self._list_attrlist = ['dn']
# Copy this from the child if we need.
- self._basedn = ""
+ self._basedn = basedn
self._scope = ldap.SCOPE_SUBTREE
self._server_controls = None
self._client_controls = None
@@ -1138,14 +1138,19 @@ class DSLdapObjects(DSLogging):
# Now actually commit the creation req
return co.ensure_state(rdn, properties, self._basedn)
- def filter(self, search):
+ def filter(self, search, scope=None):
# This will yield and & filter for objectClass with as many terms as needed.
- search_filter = _gen_and([self._get_objectclass_filter(), search])
- self._log.debug('list filter = %s' % search_filter)
+ if search:
+ search_filter = _gen_and([self._get_objectclass_filter(), search])
+ else:
+ search_filter = self._get_objectclass_filter()
+ if scope is None:
+ scope = self._scope
+ self._log.debug(f'list filter = {search_filter} with scope {scope}')
try:
results = self._instance.search_ext_s(
base=self._basedn,
- scope=self._scope,
+ scope=scope,
filterstr=search_filter,
attrlist=self._list_attrlist,
serverctrls=self._server_controls, clientctrls=self._client_controls
diff --git a/src/lib389/lib389/agreement.py b/src/lib389/lib389/agreement.py
index a9ee68c..a0d4597 100644
--- a/src/lib389/lib389/agreement.py
+++ b/src/lib389/lib389/agreement.py
@@ -61,8 +61,8 @@ class Agreement(DSLdapObject):
self.set('nsds5BeginReplicaRefresh', 'start')
def check_reinit(self):
- """Check the status of a reinit. Returns done and error. A
correct
- reinit will return (True, False).
+ """Check the status of a reinit. Returns done, inprogress, and
error text. A correct
+ reinit will return (True, False, False).
:returns: tuple(done, error), where done, error are bool.
"""
@@ -74,16 +74,16 @@ class Agreement(DSLdapObject):
if not status:
pass
elif 'replica busy' in status:
- error = True
+ error = status
elif 'Total update succeeded' in status:
done = True
inprogress = False
elif 'Replication error' in status:
- error = True
+ error = status
elif 'Total update in progress' in status:
inprogress = True
elif 'LDAP error' in status:
- error = True
+ error = status
return (done, inprogress, error)
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
index 5405e20..91b3711 100644
--- a/src/lib389/lib389/backend.py
+++ b/src/lib389/lib389/backend.py
@@ -23,7 +23,7 @@ from lib389.replica import Replicas
# We need to be a factor to the backend monitor
from lib389.monitor import MonitorBackend
from lib389.index import Index, Indexes, VLVSearches, VLVSearch
-from lib389.tasks import ImportTask, ExportTask, Tasks
+from lib389.tasks import ImportTask, ExportTask, CleanAllRUVTask, Tasks
from lib389.encrypted_attributes import EncryptedAttr, EncryptedAttrs
@@ -874,4 +874,3 @@ class DatabaseConfig(DSLdapObject):
self._create_objectclasses = ['top', 'extensibleObject']
self._protected = True
self._dn = "cn=config,cn=ldbm database,cn=plugins,cn=config"
-
diff --git a/src/lib389/lib389/chaining.py b/src/lib389/lib389/chaining.py
index a25bbb6..5f6ac68 100644
--- a/src/lib389/lib389/chaining.py
+++ b/src/lib389/lib389/chaining.py
@@ -115,7 +115,7 @@ class ChainingLink(DSLdapObject):
Delete chaining entry
"""
- rdn = self.get_attr_val_utf8_l('cn')
+ rdn = self.get_attr_val_utf8_l('nsslapd-suffix')
try:
mt = self._mts.get(selector=rdn)
mt.delete()
@@ -124,7 +124,7 @@ class ChainingLink(DSLdapObject):
pass
# Delete the monitoring entry
- monitor = self.get_monitor(rdn)
+ monitor = self.get_monitor()
monitor.delete()
# Delete the link
diff --git a/src/lib389/lib389/cli_base/__init__.py
b/src/lib389/lib389/cli_base/__init__.py
index 245dc8f..12b1c51 100644
--- a/src/lib389/lib389/cli_base/__init__.py
+++ b/src/lib389/lib389/cli_base/__init__.py
@@ -12,11 +12,12 @@ import logging
import sys
import json
import ldap
+from ldap.dn import is_dn
from getpass import getpass
from lib389 import DirSrv
from lib389.utils import assert_c, get_ldapurl_from_serverid
-from lib389.properties import *
+from lib389.properties import SER_ROOT_PW, SER_ROOT_DN
def _get_arg(args, msg=None, hidden=False, confirm=False):
@@ -38,6 +39,13 @@ def _get_arg(args, msg=None, hidden=False, confirm=False):
return input("%s : " % msg)
+def _get_dn_arg(args, msg=None):
+ dn_arg = _get_arg(args, msg)
+ if not is_dn(dn_arg):
+ raise ValueError(f"{dn_arg} is not a valid DN")
+ return dn_arg
+
+
def _get_args(args, kws):
kwargs = {}
while len(kws) > 0:
diff --git a/src/lib389/lib389/cli_conf/backend.py
b/src/lib389/lib389/cli_conf/backend.py
index 36e32ec..93bf044 100644
--- a/src/lib389/lib389/cli_conf/backend.py
+++ b/src/lib389/lib389/cli_conf/backend.py
@@ -8,9 +8,16 @@
# --- END COPYRIGHT BLOCK ---
from lib389.backend import Backend, Backends, DatabaseConfig
+from lib389.configurations.sample import (
+ create_base_domain,
+ create_base_org,
+ create_base_orgunit,
+ create_base_cn,
+ )
from lib389.chaining import (ChainingLinks)
from lib389.index import Index, VLVIndex, VLVSearches
from lib389.monitor import MonitorLDBM
+from lib389.replica import Replicas
from lib389.utils import ensure_str, is_a_dn, is_dn_parent
from lib389._constants import *
from lib389.cli_base import (
@@ -172,6 +179,29 @@ def backend_create(inst, basedn, log, args):
be = Backend(inst)
be.create(properties=props)
+ if args.create_suffix and not args.create_entries:
+ # Set basic ACIs (taken from instance/setup.py)
+ o_aci = '(targetattr="o || description ||
objectClass")(targetfilter="(objectClass=organization)")(version 3.0; acl
"Enable anyone o read"; allow (read, search,
compare)(userdn="ldap:///anyone");)'
+ dc_aci = '(targetattr="dc || description ||
objectClass")(targetfilter="(objectClass=domain)")(version 3.0; acl
"Enable anyone domain read"; allow (read, search,
compare)(userdn="ldap:///anyone");)',
+ ou_aci = '(targetattr="ou || description ||
objectClass")(targetfilter="(objectClass=organizationalUnit)")(version 3.0;
acl "Enable anyone ou read"; allow (read, search,
compare)(userdn="ldap:///anyone");)'
+ cn_aci = '(targetattr="cn || description ||
objectClass")(targetfilter="(objectClass=nscontainer)")(version 3.0; acl
"Enable anyone cn read"; allow (read, search,
compare)(userdn="ldap:///anyone");)'
+ suffix_rdn_attr = args.suffix.split('=')[0].lower()
+ if suffix_rdn_attr == 'dc':
+ domain = create_base_domain(inst, args.suffix)
+ domain.add('aci', dc_aci)
+ elif suffix_rdn_attr == 'o':
+ org = create_base_org(inst, args.suffix)
+ org.add('aci', o_aci)
+ elif suffix_rdn_attr == 'ou':
+ orgunit = create_base_orgunit(inst, args.suffix)
+ orgunit.add('aci', ou_aci)
+ elif suffix_rdn_attr == 'cn':
+ cn = create_base_cn(inst, args.suffix)
+ cn.add('aci', cn_aci)
+ else:
+ # Unsupported rdn
+ raise ValueError("Suffix RDN is not supported for creating suffix
object. Only 'dc', 'o', 'ou', and 'cn' are
supported.")
+
print("The database was sucessfully created")
@@ -252,10 +282,19 @@ def backend_export(inst, basedn, log, args):
def is_db_link(inst, rdn):
links = ChainingLinks(inst).list()
for link in links:
- cn = ensure_str(link.get_attr_val('cn')).lower()
+ cn = link.get_attr_val_utf8('cn').lower()
if cn == rdn.lower():
return True
return False
+
+
+def is_db_replicated(inst, suffix):
+ replicas = Replicas(inst)
+ try:
+ replica = replicas.get(suffix)
+ return True
+ except:
+ return False
def backend_get_subsuffixes(inst, basedn, log, args):
@@ -314,8 +353,6 @@ def build_node(suffix, be_name, subsuf=False, link=False,
replicated=False):
if link:
icon = "glyphicon glyphicon-link"
suffix_type = "dblink"
- if replicated:
- suffix_type = "replicated"
return {
"text": suffix,
@@ -323,6 +360,7 @@ def build_node(suffix, be_name, subsuf=False, link=False,
replicated=False):
"selectable": True,
"icon": icon,
"type": suffix_type,
+ "replicated": replicated,
"be": be_name,
"nodes": []
}
@@ -343,18 +381,20 @@ def backend_build_tree(inst, be_insts, nodes):
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_parent = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
sub_be = mt.get_attr_val_utf8_l('nsslapd-backend')
- if sub == be_suffix:
+ sub_suffix = mt.get_attr_val_utf8_l('cn')
+ if sub_parent == 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'),
+ link = is_db_link(inst, sub_be)
+ replicated = is_db_replicated(inst, sub_suffix)
+ node['nodes'].append(build_node(sub_suffix,
sub_be,
subsuf=True,
- link=link))
+ link=link,
+ replicated=replicated))
# Recurse over the new subsuffixes
backend_build_tree(inst, be_insts, node['nodes'])
@@ -386,7 +426,8 @@ def backend_get_tree(inst, basedn, log, args):
sub = mt.get_attr_val_utf8_l('nsslapd-parent-suffix')
if sub is not None:
continue
- nodes.append(build_node(suffix, be_name))
+ replicated = is_db_replicated(inst, suffix)
+ nodes.append(build_node(suffix, be_name, replicated=replicated))
# No suffixes, return empty list
if len(nodes) == 0:
@@ -1052,6 +1093,8 @@ def create_parser(subparsers):
create_parser.add_argument('--suffix', required=True, help='The database
suffix DN, for example "dc=example,dc=com"')
create_parser.add_argument('--be-name', required=True, help='The database
backend name, for example "userroot"')
create_parser.add_argument('--create-entries', action='store_true',
help='Create sample entries in the database')
+ create_parser.add_argument('--create-suffix', action='store_true',
+ help="Create the suffix object entry in the database. Only suffixes using
the attributes 'dc', 'o', 'ou', or 'cn' are supported in
this feature")
#######################################################
# Delete backend
diff --git a/src/lib389/lib389/cli_conf/monitor.py
b/src/lib389/lib389/cli_conf/monitor.py
index 53637e1..87eb75f 100644
--- a/src/lib389/lib389/cli_conf/monitor.py
+++ b/src/lib389/lib389/cli_conf/monitor.py
@@ -71,7 +71,7 @@ def create_parser(subparsers):
monitor_parser = subparsers.add_parser('monitor', help="Monitor the
state of the instance")
subcommands = monitor_parser.add_subparsers(help='action')
- server_parser = subcommands.add_parser('server', help="Monitor the
server statistics, connectinos and operations")
+ server_parser = subcommands.add_parser('server', help="Monitor the
server statistics, connections and operations")
server_parser.set_defaults(func=monitor)
ldbm_parser = subcommands.add_parser('ldbm', help="Monitor the ldbm
statistics, such as dbcache")
@@ -86,4 +86,4 @@ def create_parser(subparsers):
chaining_parser = subcommands.add_parser('chaining', help="Monitor
database chaining statistics")
chaining_parser.add_argument('backend', nargs='?',
help="Optional name of the chaining backend to monitor")
- chaining_parser.set_defaults(func=chaining_monitor)
\ No newline at end of file
+ chaining_parser.set_defaults(func=chaining_monitor)
diff --git a/src/lib389/lib389/cli_conf/plugins/posix_winsync.py
b/src/lib389/lib389/cli_conf/plugins/posix_winsync.py
index e2856af..0512418 100644
--- a/src/lib389/lib389/cli_conf/plugins/posix_winsync.py
+++ b/src/lib389/lib389/cli_conf/plugins/posix_winsync.py
@@ -24,6 +24,21 @@ def winsync_edit(inst, basedn, log, args):
generic_object_edit(plugin, log, args, arg_to_attr)
+def do_fixup(inst, basedn, log, args):
+ plugin = POSIXWinsyncPlugin(inst)
+ log.info('Attempting to add task entry...')
+ if not plugin.status():
+ log.error(f"'{plugin.rdn}' is disabled. Fix up task can't be
executed")
+ return
+ fixup_task = plugin.fixup(args.DN, args.filter)
+ fixup_task.wait()
+ exitcode = fixup_task.get_exit_code()
+ if exitcode != 0:
+ log.error(f'MemberUID task for {args.DN} has failed. Please, check
logs')
+ else:
+ log.info('Successfully added task entry')
+
+
def _add_parser_args(parser):
parser.add_argument('--create-memberof-task', choices=['true',
'false'], type=str.lower,
help='Sets whether to run the memberOf fix-up task
immediately after a sync run in order '
@@ -51,3 +66,10 @@ def create_parser(subparsers):
edit.set_defaults(func=winsync_edit)
_add_parser_args(edit)
+ fixup = subcommands.add_parser('fixup', help='Run the memberOf fix-up
task to correct mismatched member and uniquemember values for synced users')
+ fixup.set_defaults(func=do_fixup)
+ fixup.add_argument('DN', help="Base DN that contains entries to fix
up")
+ fixup.add_argument('-f', '--filter',
+ help='Filter for entries to fix up.\n If omitted, all entries
with objectclass '
+ 'inetuser/inetadmin/nsmemberof under the specified base
will have '
+ 'their memberOf attribute regenerated.')
diff --git a/src/lib389/lib389/cli_conf/replication.py
b/src/lib389/lib389/cli_conf/replication.py
index 282fe91..58bbd13 100644
--- a/src/lib389/lib389/cli_conf/replication.py
+++ b/src/lib389/lib389/cli_conf/replication.py
@@ -104,6 +104,27 @@ def _args_to_attrs(args):
#
# Replica config
#
+def get_ruv(inst, basedn, log, args):
+ replicas = Replicas(inst)
+ replica = replicas.get(args.suffix)
+ ruv = replica.get_ruv()
+ ruv_dict = ruv.format_ruv()
+ ruvs = ruv_dict['ruvs']
+ if args and args.json:
+ log.info(json.dumps({"type": "list", "items":
ruvs}))
+ else:
+ add_gap = False
+ for ruv in ruvs:
+ if add_gap:
+ log.info("")
+ log.info("RUV: " + ruv['raw_ruv'])
+ log.info("Replica ID: " + ruv['rid'])
+ log.info("LDAP URL: " + ruv['url'])
+ log.info("Min CSN: " + ruv['csn'] + " (" +
ruv['raw_csn'] + ")")
+ log.info("Max CSN: " + ruv['maxcsn'] + " (" +
ruv['raw_maxcsn'] + ")")
+ add_gap = True
+
+
def enable_replication(inst, basedn, log, args):
repl_root = args.suffix
role = args.role.lower()
@@ -670,7 +691,7 @@ def check_init_agmt(inst, basedn, log, args):
elif inprogress:
status = "Agreement initialization in progress."
elif error:
- status = "Agreement initialization failed."
+ status = "Agreement initialization failed: " + error
if args.json:
log.info(json.dumps(status))
else:
@@ -893,7 +914,6 @@ def get_winsync_agmt_status(inst, basedn, log, args):
status = agmt.status(winsync=True, use_json=args.json)
log.info(status)
-
#
# Tasks
#
@@ -978,7 +998,9 @@ def dump_cl(inst, basedn, log, args):
log.addHandler(fh)
replicas = Replicas(inst)
if not args.changelog_ldif:
- replicas.process_and_dump_changelog(replica_roots=args.replica_roots,
csn_only=args.csn_only)
+ replicas.process_and_dump_changelog(replica_roots=args.replica_roots,
+ csn_only=args.csn_only,
+ preserve_ldif_done=args.preserve_ldif_done)
else:
try:
assert os.path.exists(args.changelog_ldif)
@@ -1013,6 +1035,10 @@ def create_parser(subparsers):
repl_disable_parser.set_defaults(func=disable_replication)
repl_disable_parser.add_argument('--suffix', required=True, help='The DN
of the suffix to have replication disabled')
+ repl_ruv_parser = repl_subcommands.add_parser('get-ruv', help='Get the
database RUV entry for his suffix')
+ repl_ruv_parser.set_defaults(func=get_ruv)
+ repl_ruv_parser.add_argument('--suffix', required=True, help='The DN of
the replicated suffix')
+
repl_list_parser = repl_subcommands.add_parser('list', help='List all the
replicated suffixes')
repl_list_parser.set_defaults(func=list_suffixes)
@@ -1081,6 +1107,8 @@ def create_parser(subparsers):
repl_set_cl.set_defaults(func=dump_cl)
repl_set_cl.add_argument('-c', '--csn-only',
action='store_true',
help="Dump and interpret CSN only. This option can be
used with or without -i option.")
+ repl_set_cl.add_argument('-l', '--preserve-ldif-done',
action='store_true',
+ help="Preserve generated ldif.done files from
changelogdir.")
repl_set_cl.add_argument('-i', '--changelog-ldif',
help="If you already have a ldif-like changelog, but
the changes in that file are encoded,"
" you may use this option to decode that ldif-like
changelog. It should be base64 encoded.")
diff --git a/src/lib389/lib389/cli_conf/security.py
b/src/lib389/lib389/cli_conf/security.py
index 20f2574..0273817 100644
--- a/src/lib389/lib389/cli_conf/security.py
+++ b/src/lib389/lib389/cli_conf/security.py
@@ -91,7 +91,10 @@ def _security_generic_set(inst, basedn, logs, args, attrs_map):
if arg is None:
continue
dsobj = props.cls(inst)
- dsobj.replace(props.attr, arg)
+ if arg != "":
+ dsobj.replace(props.attr, arg)
+ else:
+ dsobj.remove_all(props.attr)
def _security_generic_get_parser(parent, attrs_map, help):
diff --git a/src/lib389/lib389/cli_idm/__init__.py
b/src/lib389/lib389/cli_idm/__init__.py
index fcf0175..28648ad 100644
--- a/src/lib389/lib389/cli_idm/__init__.py
+++ b/src/lib389/lib389/cli_idm/__init__.py
@@ -7,8 +7,6 @@
# --- END COPYRIGHT BLOCK ---
from getpass import getpass
-from lib389 import DirSrv
-from lib389.properties import *
import json
diff --git a/src/lib389/lib389/cli_idm/account.py b/src/lib389/lib389/cli_idm/account.py
index f95c6c6..025ea1e 100644
--- a/src/lib389/lib389/cli_idm/account.py
+++ b/src/lib389/lib389/cli_idm/account.py
@@ -7,9 +7,13 @@
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
+import ldap
+import math
+import time
+from datetime import datetime
import argparse
-from lib389.idm.account import Account, Accounts
+from lib389.idm.account import Account, Accounts, AccountState
from lib389.cli_base import (
_generic_get,
_generic_get_dn,
@@ -17,66 +21,115 @@ from lib389.cli_base import (
_generic_delete,
_generic_modify_dn,
_get_arg,
+ _get_dn_arg,
_warn,
)
+from lib389.utils import gentime_to_posix_time
+
MANY = Accounts
SINGULAR = Account
+
def list(inst, basedn, log, args):
_generic_list(inst, basedn, log.getChild('_generic_list'), MANY, args)
+
def get_dn(inst, basedn, log, args):
- dn = _get_arg( args.dn, msg="Enter dn to retrieve")
+ dn = _get_dn_arg(args.dn, msg="Enter dn to retrieve")
_generic_get_dn(inst, basedn, log.getChild('_generic_get_dn'), MANY, dn,
args)
+
def delete(inst, basedn, log, args, warn=True):
- dn = _get_arg( args.dn, msg="Enter dn to delete")
+ dn = _get_dn_arg(args.dn, msg="Enter dn to delete")
if warn:
_warn(dn, msg="Deleting %s %s" % (SINGULAR.__name__, dn))
_generic_delete(inst, basedn, log.getChild('_generic_delete'), SINGULAR, dn,
args)
+
def modify(inst, basedn, log, args, warn=True):
- dn = _get_arg( args.dn, msg="Enter dn to modify")
+ dn = _get_dn_arg(args.dn, msg="Enter dn to modify")
_generic_modify_dn(inst, basedn, log.getChild('_generic_modify'), MANY, dn,
args)
-def status(inst, basedn, log, args):
- dn = _get_arg( args.dn, msg="Enter dn to check")
+
+def _print_entry_status(status, dn, log):
+ log.info(f'Entry DN: {dn}')
+ for name, value in status["params"].items():
+ if "Time" in name and value is not None:
+ inactivation_date = datetime.fromtimestamp(status["calc_time"] +
value)
+ log.info(f"Entry {name}: {int(math.fabs(value))} seconds
({inactivation_date.strftime('%Y-%m-%d %H:%M:%S')})")
+ elif "Date" in name and value is not None:
+ log.info(f"Entry {name}: {value.strftime('%Y%m%d%H%M%SZ')}
({value.strftime('%Y-%m-%d %H:%M:%S')})")
+ log.info(f'Entry State:
{status["state"].describe(status["role_dn"])}\n')
+
+
+def entry_status(inst, basedn, log, args):
+ dn = _get_dn_arg(args.dn, msg="Enter dn to check")
accounts = Accounts(inst, basedn)
acct = accounts.get(dn=dn)
- acct_str = "locked: %s" % acct.is_locked()
- log.info('dn: %s' % dn)
- log.info(acct_str)
+ status = acct.status()
+ _print_entry_status(status, dn, log)
+
+
+def subtree_status(inst, basedn, log, args):
+ basedn = _get_dn_arg(args.basedn, msg="Enter basedn to check")
+ filter = ""
+ scope = ldap.SCOPE_SUBTREE
+ epoch_inactive_time = None
+ if args.scope == "one":
+ scope = ldap.SCOPE_ONELEVEL
+ if args.filter:
+ filter = args.filter
+ if args.become_inactive_on:
+ datetime_inactive_time = datetime.strptime(args.become_inactive_on,
'%Y-%m-%dT%H:%M:%S')
+ epoch_inactive_time = datetime.timestamp(datetime_inactive_time)
+
+ account_list = Accounts(inst, basedn).filter(filter, scope)
+ if not account_list:
+ raise ValueError(f"No entries were found under {basedn}")
+
+ for entry in account_list:
+ status = entry.status()
+ state = status["state"]
+ params = status["params"]
+ if args.inactive_only and state == AccountState.ACTIVATED:
+ continue
+ if args.become_inactive_on:
+ if epoch_inactive_time is None or params["Time Until Inactive"] is
None or \
+ epoch_inactive_time <= (params["Time Until Inactive"] +
status["calc_time"]):
+ continue
+ _print_entry_status(status, entry.dn, log)
+
def lock(inst, basedn, log, args):
- dn = _get_arg( args.dn, msg="Enter dn to check")
+ dn = _get_dn_arg(args.dn, msg="Enter dn to check")
accounts = Accounts(inst, basedn)
acct = accounts.get(dn=dn)
acct.lock()
- log.info('locked %s' % dn)
+ log.info(f'Entry {dn} is locked')
+
def unlock(inst, basedn, log, args):
- dn = _get_arg( args.dn, msg="Enter dn to check")
+ dn = _get_dn_arg(args.dn, msg="Enter dn to check")
accounts = Accounts(inst, basedn)
acct = accounts.get(dn=dn)
acct.unlock()
- log.info('unlocked %s' % dn)
+ log.info(f'Entry {dn} is unlocked')
+
def reset_password(inst, basedn, log, args):
- dn = _get_arg(args.dn, msg="Enter dn to reset password")
- new_password = _get_arg(args.new_password, hidden=True, confirm=True,
- msg="Enter new password for %s" % dn)
+ dn = _get_dn_arg(args.dn, msg="Enter dn to reset password")
+ new_password = _get_arg(args.new_password, hidden=True, confirm=True, msg="Enter
new password for %s" % dn)
accounts = Accounts(inst, basedn)
acct = accounts.get(dn=dn)
acct.reset_password(new_password)
log.info('reset password for %s' % dn)
+
def change_password(inst, basedn, log, args):
- dn = _get_arg(args.dn, msg="Enter dn to change password")
- cur_password = _get_arg(args.current_password, hidden=True, confirm=False,
- msg="Enter current password for %s" % dn)
- new_password = _get_arg(args.new_password, hidden=True, confirm=True,
- msg="Enter new password for %s" % dn)
+ dn = _get_dn_arg(args.dn, msg="Enter dn to change password")
+ cur_password = _get_arg(args.current_password, hidden=True, confirm=False,
msg="Enter current password for %s" % dn)
+ new_password = _get_arg(args.new_password, hidden=True, confirm=True, msg="Enter
new password for %s" % dn)
accounts = Accounts(inst, basedn)
acct = accounts.get(dn=dn)
acct.change_password(cur_password, new_password)
@@ -109,14 +162,25 @@ like modify, locking and unlocking. To create an account, see
"user" subcommand
lock_parser.set_defaults(func=lock)
lock_parser.add_argument('dn', nargs='?', help='The dn to
lock')
- status_parser = subcommands.add_parser('status', help='status')
- status_parser.set_defaults(func=status)
- status_parser.add_argument('dn', nargs='?', help='The dn to
check')
-
unlock_parser = subcommands.add_parser('unlock', help='unlock')
unlock_parser.set_defaults(func=unlock)
unlock_parser.add_argument('dn', nargs='?', help='The dn to
unlock')
+ status_parser = subcommands.add_parser('entry-status', help='status of a
single entry')
+ status_parser.set_defaults(func=entry_status)
+ status_parser.add_argument('dn', nargs='?', help='The single
entry dn to check')
+ status_parser.add_argument('-V', '--details',
action='store_true', help="Print more account policy details about the
entry")
+
+ status_parser = subcommands.add_parser('subtree-status', help='status of
a subtree')
+ status_parser.set_defaults(func=subtree_status)
+ status_parser.add_argument('basedn', help="Search base for finding
entries")
+ status_parser.add_argument('-V', '--details',
action='store_true', help="Print more account policy details about the
entries")
+ status_parser.add_argument('-f', '--filter', help="Search filter
for finding entries")
+ status_parser.add_argument('-s', '--scope', choices=['one',
'sub'], help="Search scope (one, sub - default is sub")
+ status_parser.add_argument('-i', '--inactive-only',
action='store_true', help="Only display inactivated entries")
+ status_parser.add_argument('-o', '--become-inactive-on',
+ help="Only display entries that will become inactive
before specified date (in a format 2007-04-25T14:30)")
+
reset_pw_parser = subcommands.add_parser('reset_password', help='Reset
the password of an account. This should be performed by a directory admin.')
reset_pw_parser.set_defaults(func=reset_password)
reset_pw_parser.add_argument('dn', nargs='?', help='The dn to
reset the password for')
@@ -127,5 +191,3 @@ like modify, locking and unlocking. To create an account, see
"user" subcommand
change_pw_parser.add_argument('dn', nargs='?', help='The dn to
change the password for')
change_pw_parser.add_argument('new_password', nargs='?',
help='The new password to set')
change_pw_parser.add_argument('current_password', nargs='?',
help='The accounts current password')
-
-
diff --git a/src/lib389/lib389/cli_idm/role.py b/src/lib389/lib389/cli_idm/role.py
new file mode 100644
index 0000000..922dadc
--- /dev/null
+++ b/src/lib389/lib389/cli_idm/role.py
@@ -0,0 +1,126 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2019, Red Hat inc,
+# Copyright (C) 2018, William Brown <william(a)blackhats.net.au>
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+
+import ldap
+import argparse
+
+from lib389.idm.role import Role, Roles, RoleState
+from lib389.cli_base import (
+ _generic_get,
+ _generic_get_dn,
+ _generic_list,
+ _generic_delete,
+ _generic_modify_dn,
+ _get_arg,
+ _get_dn_arg,
+ _warn,
+ )
+
+MANY = Roles
+SINGULAR = Role
+
+
+def list(inst, basedn, log, args):
+ _generic_list(inst, basedn, log.getChild('_generic_list'), MANY, args)
+
+
+def get_dn(inst, basedn, log, args):
+ dn = _get_dn_arg(args.dn, msg="Enter dn to retrieve")
+ _generic_get_dn(inst, basedn, log.getChild('_generic_get_dn'), MANY, dn,
args)
+
+
+def delete(inst, basedn, log, args, warn=True):
+ dn = _get_dn_arg(args.dn, msg="Enter dn to delete")
+ if warn:
+ _warn(dn, msg="Deleting %s %s" % (SINGULAR.__name__, dn))
+ _generic_delete(inst, basedn, log.getChild('_generic_delete'), SINGULAR, dn,
args)
+
+
+def modify(inst, basedn, log, args, warn=True):
+ dn = _get_dn_arg(args.dn, msg="Enter dn to modify")
+ _generic_modify_dn(inst, basedn, log.getChild('_generic_modify'), MANY, dn,
args)
+
+
+def entry_status(inst, basedn, log, args):
+ dn = _get_dn_arg(args.dn, msg="Enter dn to check")
+ roles = Roles(inst, basedn)
+ role = roles.get(dn=dn)
+ status = role.status()
+ log.info(f'Entry DN: {dn}')
+ log.info(f'Entry State:
{status["state"].describe(status["role_dn"])}\n')
+
+
+def subtree_status(inst, basedn, log, args):
+ basedn = _get_dn_arg(args.basedn, msg="Enter basedn to check")
+ filter = ""
+ scope = ldap.SCOPE_SUBTREE
+
+ role_list = Roles(inst, basedn).filter(filter, scope)
+ if not role_list:
+ raise ValueError(f"No entries were found under {basedn} or the user
doesn't have an access")
+
+ for entry in role_list:
+ status = entry.status()
+ log.info(f'Entry DN: {entry.dn}')
+ log.info(f'Entry State:
{status["state"].describe(status["role_dn"])}\n')
+
+
+def lock(inst, basedn, log, args):
+ dn = _get_dn_arg(args.dn, msg="Enter dn to check")
+ role = Role(inst, dn=dn)
+ role.lock()
+ log.info(f'Entry {dn} is locked')
+
+
+def unlock(inst, basedn, log, args):
+ dn = _get_dn_arg(args.dn, msg="Enter dn to check")
+ role = Role(inst, dn=dn)
+ role.unlock()
+ log.info(f'Entry {dn} is unlocked')
+
+
+def create_parser(subparsers):
+ role_parser = subparsers.add_parser('role', help='''Manage
generic roles, with tasks
+like modify, locking and unlocking.''')
+
+ subcommands = role_parser.add_subparsers(help='action')
+
+ list_parser = subcommands.add_parser('list', help='list roles that could
login to the directory')
+ list_parser.set_defaults(func=list)
+
+ get_dn_parser = subcommands.add_parser('get-by-dn', help='get-by-dn
<dn>')
+ get_dn_parser.set_defaults(func=get_dn)
+ get_dn_parser.add_argument('dn', nargs='?', help='The dn to get
and display')
+
+ modify_dn_parser = subcommands.add_parser('modify-by-dn',
help='modify-by-dn <dn>
<add|delete|replace>:<attribute>:<value> ...')
+ modify_dn_parser.set_defaults(func=modify)
+ modify_dn_parser.add_argument('dn', nargs=1, help='The dn to get and
display')
+ modify_dn_parser.add_argument('changes', nargs='+', help="A list
of changes to apply in format:
<add|delete|replace>:<attribute>:<value>")
+
+ delete_parser = subcommands.add_parser('delete', help='deletes the
role')
+ delete_parser.set_defaults(func=delete)
+ delete_parser.add_argument('dn', nargs='?', help='The dn of the
role to delete')
+
+ lock_parser = subcommands.add_parser('lock', help='lock')
+ lock_parser.set_defaults(func=lock)
+ lock_parser.add_argument('dn', nargs='?', help='The dn to
lock')
+
+ unlock_parser = subcommands.add_parser('unlock', help='unlock')
+ unlock_parser.set_defaults(func=unlock)
+ unlock_parser.add_argument('dn', nargs='?', help='The dn to
unlock')
+
+ status_parser = subcommands.add_parser('entry-status', help='status of a
single entry')
+ status_parser.set_defaults(func=entry_status)
+ status_parser.add_argument('dn', nargs='?', help='The single
entry dn to check')
+
+ status_parser = subcommands.add_parser('subtree-status', help='status of
a subtree')
+ status_parser.set_defaults(func=subtree_status)
+ status_parser.add_argument('basedn', help="Search base for finding
entries")
+ status_parser.add_argument('-f', '--filter', help="Search filter
for finding entries")
+ status_parser.add_argument('-s', '--scope', choices=['base',
'one', 'sub'], help="Search scope (base, one, sub - default is
sub")
diff --git a/src/lib389/lib389/config.py b/src/lib389/lib389/config.py
index 23ab9f2..53f1b16 100644
--- a/src/lib389/lib389/config.py
+++ b/src/lib389/lib389/config.py
@@ -64,7 +64,7 @@ class Config(DSLdapObject):
return DN_CONFIG
def replace(self, key, value):
- if key.lower() == 'nsslapd-secureport' and selinux_present():
+ if selinux_present() and (key.lower() == 'nsslapd-secureport' or
key.lower() == 'nsslapd-port'):
# Get old port and remove label
old_port = self.get_attr_val_utf8(key)
self.log.debug("Removing old port's selinux label...")
diff --git a/src/lib389/lib389/configurations/config_001004000.py
b/src/lib389/lib389/configurations/config_001004000.py
index 697a911..dbeab1f 100644
--- a/src/lib389/lib389/configurations/config_001004000.py
+++ b/src/lib389/lib389/configurations/config_001004000.py
@@ -7,16 +7,21 @@
# --- END COPYRIGHT BLOCK ---
from .config import baseconfig, configoperation
-from .sample import sampleentries, create_base_domain
-
+from .sample import (
+ sampleentries,
+ create_base_domain,
+ create_base_org,
+ create_base_orgunit,
+ create_base_cn,
+)
from lib389.idm.organizationalunit import OrganizationalUnits
from lib389.idm.group import Groups
from lib389.idm.posixgroup import PosixGroups
from lib389.idm.user import nsUserAccounts
from lib389.idm.services import ServiceAccounts
-
from lib389.idm.nscontainer import nsHiddenContainers
+
class c001004000_sample_entries(sampleentries):
def __init__(self, instance, basedn):
super(c001004000_sample_entries, self).__init__(instance, basedn)
@@ -24,11 +29,27 @@ class c001004000_sample_entries(sampleentries):
# All checks done, apply!
def _apply(self):
- # Create the base domain object
- domain = create_base_domain(self._instance, self._basedn)
- domain.add('aci', [
+ suffix_rdn_attr = self._basedn.split('=')[0].lower()
+ if suffix_rdn_attr == 'dc':
+ suffix_obj = create_base_domain(self._instance, self._basedn)
+ aci_vals = ['dc', 'domain']
+ elif suffix_rdn_attr == 'o':
+ suffix_obj = create_base_org(self._instance, self._basedn)
+ aci_vals = ['o', 'organization']
+ elif suffix_rdn_attr == 'ou':
+ suffix_obj = create_base_orgunit(self._instance, self._basedn)
+ aci_vals = ['ou', 'organizationalunit']
+ elif suffix_rdn_attr == 'cn':
+ suffix_obj = create_base_cn(self._instance, self._basedn)
+ aci_vals = ['cn', 'nscontainer']
+ else:
+ # Unsupported rdn
+ raise ValueError("Suffix RDN is not supported for creating sample
entries. Only 'dc', 'o', 'ou', and 'cn' are
supported.")
+
+ # Create the base object
+ suffix_obj.add('aci', [
# Allow reading the base domain object
- '(targetattr="dc || description ||
objectClass")(targetfilter="(objectClass=domain)")(version 3.0; acl
"Enable anyone domain read"; allow (read, search,
compare)(userdn="ldap:///anyone");)',
+ '(targetattr="' + aci_vals[0] + ' || description ||
objectClass")(targetfilter="(objectClass=' + aci_vals[1] +
')")(version 3.0; acl "Enable anyone domain read"; allow (read, search,
compare)(userdn="ldap:///anyone");)',
# Allow reading the ou
'(targetattr="ou ||
objectClass")(targetfilter="(objectClass=organizationalUnit)")(version 3.0;
acl "Enable anyone ou read"; allow (read, search,
compare)(userdn="ldap:///anyone");)'
])
diff --git a/src/lib389/lib389/configurations/sample.py
b/src/lib389/lib389/configurations/sample.py
index 25a1b32..f30b8d6 100644
--- a/src/lib389/lib389/configurations/sample.py
+++ b/src/lib389/lib389/configurations/sample.py
@@ -9,6 +9,9 @@
from ldap import dn
from lib389.idm.domain import Domain
+from lib389.idm.organization import Organization
+from lib389.idm.organizationalunit import OrganizationalUnit
+from lib389.idm.nscontainer import nsContainer
from lib389.utils import ensure_str
@@ -42,3 +45,53 @@ def create_base_domain(instance, basedn):
return domain
+
+def create_base_org(instance, basedn):
+ """Create the base organization object"""
+
+ org = Organization(instance, dn=basedn)
+ # Explode the dn to get the first bit.
+ avas = dn.str2dn(basedn)
+ o_ava = avas[0][0][1]
+
+ org.create(properties={
+ # I think in python 2 this forces unicode return ...
+ 'o': o_ava,
+ 'description': basedn,
+ })
+
+ return org
+
+
+def create_base_orgunit(instance, basedn):
+ """Create the base org unit object for a org unit"""
+
+ orgunit = OrganizationalUnit(instance, dn=basedn)
+ # Explode the dn to get the first bit.
+ avas = dn.str2dn(basedn)
+ ou_ava = avas[0][0][1]
+
+ orgunit.create(properties={
+ # I think in python 2 this forces unicode return ...
+ 'ou': ou_ava,
+ 'description': basedn,
+ })
+
+ return orgunit
+
+
+def create_base_cn(instance, basedn):
+ """Create the base nsContainer object"""
+
+ cn = nsContainer(instance, dn=basedn)
+ # Explode the dn to get the first bit.
+ avas = dn.str2dn(basedn)
+ cn_ava = avas[0][0][1]
+
+ cn.create(properties={
+ # I think in python 2 this forces unicode return ...
+ 'cn': cn_ava,
+ 'description': basedn,
+ })
+
+ return cn
diff --git a/src/lib389/lib389/dbgen.py b/src/lib389/lib389/dbgen.py
index 791d688..f104ce8 100644
--- a/src/lib389/lib389/dbgen.py
+++ b/src/lib389/lib389/dbgen.py
@@ -99,6 +99,7 @@ carLicense: 21SJJAG
l: {LOCATION}
ou: {OU}
mail: {UID}(a)example.com
+mail: {UIDNUMBER}(a)example.com
postalAddress: 518, Dept #851, Room#{OU}
title: {TITLE}
usercertificate;binary:: MIIBvjCCASegAwIBAgIBAjANBgkqhkiG9w0BAQQFADAnMQ8wDQYD
@@ -168,6 +169,7 @@ def dbgen(instance, number, ldif_file, suffix, pseudol10n=False):
output.write(DBGEN_TEMPLATE.format(
DN=dn,
UID=uid,
+ UIDNUMBER=i,
FIRST=first,
LAST=last,
INITIALS=initials,
diff --git a/src/lib389/lib389/idm/account.py b/src/lib389/lib389/idm/account.py
index 4a5c5c4..c95e7a9 100644
--- a/src/lib389/lib389/idm/account.py
+++ b/src/lib389/lib389/idm/account.py
@@ -1,4 +1,5 @@
# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2019 Red Hat, Inc.
# Copyright (C) 2017, William Brown <william at blackhats.net.au>
# All rights reserved.
#
@@ -6,12 +7,33 @@
# See LICENSE for details.
# --- END COPYRIGHT BLOCK ---
+import os
+import time
+import subprocess
+from enum import Enum
+import ldap
+
from lib389._mapped_object import DSLdapObject, DSLdapObjects, _gen_or, _gen_filter,
_term_gen
from lib389._constants import SER_ROOT_DN, SER_ROOT_PW
-from lib389.utils import ds_is_older
+from lib389.utils import ds_is_older, gentime_to_posix_time, gentime_to_datetime
+from lib389.plugins import AccountPolicyPlugin, AccountPolicyConfig, AccountPolicyEntry
+from lib389.cos import CosTemplates
+from lib389.mappingTree import MappingTrees
+from lib389.idm.role import Roles
+
+
+class AccountState(Enum):
+ ACTIVATED = "activated"
+ DIRECTLY_LOCKED = "directly locked through nsAccountLock"
+ INDIRECTLY_LOCKED = "indirectly locked through a Role"
+ INACTIVITY_LIMIT_EXCEEDED = "inactivity limit exceeded"
+
+ def describe(self, role_dn=None):
+ if self.name == "INDIRECTLY_LOCKED" and role_dn is not None:
+ return f'{self.value} - {role_dn}'
+ else:
+ return f'{self.value}'
-import os
-import subprocess
class Account(DSLdapObject):
"""A single instance of Account entry
@@ -22,23 +44,152 @@ class Account(DSLdapObject):
:type dn: str
"""
- def is_locked(self):
- """Check if nsAccountLock is set
-
- :returns: True if account is locked
+ def _format_status_message(self, message, create_time, modify_time, last_login_time,
limit, role_dn=None):
+ params = {}
+ now = time.time()
+ params["Creation Date"] = gentime_to_datetime(create_time)
+ params["Modification Date"] = gentime_to_datetime(modify_time)
+ params["Last Login Date"] = None
+ params["Time Until Inactive"] = None
+ params["Time Since Inactive"] = None
+ if last_login_time:
+ params["Last Login Date"] = gentime_to_datetime(last_login_time)
+ if limit:
+ remaining_time = float(limit) + gentime_to_posix_time(last_login_time) -
now
+ if remaining_time <= 0:
+ if message == AccountState.INACTIVITY_LIMIT_EXCEEDED:
+ params["Time Since Inactive"] = remaining_time
+ else:
+ params["Time Until Inactive"] = remaining_time
+ result = {"state": message, "params": params,
"calc_time": now, "role_dn": None}
+ if role_dn is not None:
+ result["role_dn"] = role_dn
+ return result
+
+ def _dict_get_with_ignore_indexerror(self, dict, attr):
+ try:
+ return dict[attr][0]
+ except IndexError:
+ return ""
+
+ def status(self):
+ """Check if account is locked by Account Policy plugin or
+ nsAccountLock (directly or indirectly)
+
+ :returns: a dict in a format -
+ {"status": status, "params": activity_data,
"calc_time": epoch_time}
"""
- return self.present('nsAccountLock')
+ inst = self._instance
+
+ # Fetch Account Policy data if its enabled
+ plugin = AccountPolicyPlugin(inst)
+ state_attr = ""
+ alt_state_attr = ""
+ limit = ""
+ spec_attr = ""
+ limit_attr = ""
+ process_account_policy = False
+ try:
+ process_account_policy = plugin.status()
+ except IndexError:
+ self._log.debug("The bound user doesn't have rights to access
Account Policy settings. Not checking.")
+
+ if process_account_policy:
+ config_dn = plugin.get_attr_val_utf8("nsslapd-pluginarg0")
+ config = AccountPolicyConfig(inst, config_dn)
+ config_settings = config.get_attrs_vals_utf8(["stateattrname",
"altstateattrname",
+ "specattrname",
"limitattrname"])
+ state_attr = self._dict_get_with_ignore_indexerror(config_settings,
"stateattrname")
+ alt_state_attr = self._dict_get_with_ignore_indexerror(config_settings,
"altstateattrname")
+ spec_attr = self._dict_get_with_ignore_indexerror(config_settings,
"specattrname")
+ limit_attr = self._dict_get_with_ignore_indexerror(config_settings,
"limitattrname")
+
+ cos_entries = CosTemplates(inst, self.dn)
+ accpol_entry_dn = ""
+ for cos in cos_entries.list():
+ if cos.present(spec_attr):
+ accpol_entry_dn = cos.get_attr_val_utf8_l(spec_attr)
+ if accpol_entry_dn:
+ accpol_entry = AccountPolicyEntry(inst, accpol_entry_dn)
+ else:
+ accpol_entry = config
+ limit = accpol_entry.get_attr_val_utf8_l(limit_attr)
+
+ # Fetch account data
+ account_data = self.get_attrs_vals_utf8(["createTimestamp",
"modifyTimeStamp",
+ "nsAccountLock", state_attr])
+
+ last_login_time = self._dict_get_with_ignore_indexerror(account_data,
state_attr)
+ if not last_login_time:
+ last_login_time = self._dict_get_with_ignore_indexerror(account_data,
alt_state_attr)
+
+ create_time = self._dict_get_with_ignore_indexerror(account_data,
"createTimestamp")
+ modify_time = self._dict_get_with_ignore_indexerror(account_data,
"modifyTimeStamp")
+
+ acct_roles = self.get_attr_vals_utf8_l("nsRole")
+ mapping_trees = MappingTrees(inst)
+ root_suffix = ""
+ try:
+ root_suffix = mapping_trees.get_root_suffix_by_entry(self.dn)
+ except ldap.NO_SUCH_OBJECT:
+ self._log.debug("The bound user doesn't have rights to access
disabled roles settings. Not checking.")
+ if root_suffix:
+ roles = Roles(inst, root_suffix)
+ try:
+ disabled_roles = roles.get_disabled_roles()
+
+ # Locked indirectly through a role
+ locked_indirectly_role_dn = ""
+ for role in acct_roles:
+ if str.lower(role) in [str.lower(role.dn) for role in
disabled_roles.keys()]:
+ locked_indirectly_role_dn = role
+ if locked_indirectly_role_dn:
+ return self._format_status_message(AccountState.INDIRECTLY_LOCKED,
create_time, modify_time,
+ last_login_time, limit,
locked_indirectly_role_dn)
+ except ldap.NO_SUCH_OBJECT:
+ pass
+
+ # Locked directly
+ if self._dict_get_with_ignore_indexerror(account_data, "nsAccountLock")
== "true":
+ return self._format_status_message(AccountState.DIRECTLY_LOCKED,
+ create_time, modify_time, last_login_time,
limit)
+
+ # Locked indirectly through Account Policy plugin
+ if process_account_policy and last_login_time:
+ # Now check the Acount Policy Plugin inactivity limits
+ remaining_time = float(limit) - (time.time() -
gentime_to_posix_time(last_login_time))
+ if remaining_time <= 0:
+ return
self._format_status_message(AccountState.INACTIVITY_LIMIT_EXCEEDED,
+ create_time, modify_time,
last_login_time, limit)
+ # All checks are passed - we are active
+ return self._format_status_message(AccountState.ACTIVATED, create_time,
modify_time, last_login_time, limit)
+
+ def ensure_lock(self):
+ """Ensure nsAccountLock is set to
'true'"""
+
+ self.replace('nsAccountLock', 'true')
+
+ def ensure_unlock(self):
+ """Unset nsAccountLock if it's set"""
+
+ self.ensure_removed('nsAccountLock', None)
def lock(self):
"""Set nsAccountLock to 'true'"""
+ current_status = self.status()
+ if current_status["state"] == AccountState.DIRECTLY_LOCKED:
+ raise ValueError("Account is already active")
self.replace('nsAccountLock', 'true')
def unlock(self):
"""Unset nsAccountLock"""
- self.ensure_removed('nsAccountLock', None)
+ current_status = self.status()
+ if current_status["state"] == AccountState.ACTIVATED:
+ raise ValueError("Account is already active")
+ self.remove('nsAccountLock', None)
# If the account can be bound to, this will attempt to do so. We don't check
# for exceptions, just pass them back!
@@ -143,6 +294,7 @@ class Account(DSLdapObject):
self._instance.passwd_s(self._dn, current_password, new_password,
serverctrls=self._server_controls, clientctrls=self._client_controls,
escapehatch='i am sure')
+
class Accounts(DSLdapObjects):
"""DSLdapObjects that represents Account entry
diff --git a/src/lib389/lib389/idm/group.py b/src/lib389/lib389/idm/group.py
index 29ca17f..ce6edc2 100644
--- a/src/lib389/lib389/idm/group.py
+++ b/src/lib389/lib389/idm/group.py
@@ -95,10 +95,10 @@ class Groups(DSLdapObjects):
]
self._filterattrs = [RDN]
self._childobject = Group
- if rdn:
- self._basedn = '{},{}'.format(ensure_str(rdn), ensure_str(basedn))
- else:
+ if rdn is None:
self._basedn = ensure_str(basedn)
+ else:
+ self._basedn = '{},{}'.format(ensure_str(rdn), ensure_str(basedn))
class UniqueGroup(DSLdapObject):
@@ -137,10 +137,11 @@ class UniqueGroups(DSLdapObjects):
]
self._filterattrs = [RDN]
self._childobject = UniqueGroup
- if rdn:
- self._basedn = '{},{}'.format(ensure_str(rdn), ensure_str(basedn))
- else:
+ if rdn is None:
self._basedn = ensure_str(basedn)
+ else:
+ self._basedn = '{},{}'.format(ensure_str(rdn), ensure_str(basedn))
+
diff --git a/src/lib389/lib389/idm/posixgroup.py b/src/lib389/lib389/idm/posixgroup.py
index a765016..263fce3 100644
--- a/src/lib389/lib389/idm/posixgroup.py
+++ b/src/lib389/lib389/idm/posixgroup.py
@@ -7,7 +7,7 @@
# --- END COPYRIGHT BLOCK ---
from lib389._mapped_object import DSLdapObject, DSLdapObjects
-from lib389.utils import ds_is_older
+from lib389.utils import ds_is_older, ensure_str
MUST_ATTRIBUTES = [
'cn',
@@ -76,6 +76,7 @@ class PosixGroups(DSLdapObjects):
]
self._filterattrs = [RDN]
self._childobject = PosixGroup
- self._basedn = '{},{}'.format(rdn, basedn)
-
-
+ if rdn is None:
+ self._basedn = ensure_str(basedn)
+ else:
+ self._basedn = '{},{}'.format(ensure_str(rdn), ensure_str(basedn))
diff --git a/src/lib389/lib389/idm/role.py b/src/lib389/lib389/idm/role.py
index b630bff..fe91aab 100644
--- a/src/lib389/lib389/idm/role.py
+++ b/src/lib389/lib389/idm/role.py
@@ -7,157 +7,334 @@
# --- END COPYRIGHT BLOCK ----
+from enum import Enum
+import ldap
from lib389._mapped_object import DSLdapObject, DSLdapObjects
+from lib389.cos import CosTemplates, CosClassicDefinitions
+from lib389.mappingTree import MappingTrees
+from lib389.idm.nscontainer import nsContainers
-class FilterRole(DSLdapObject):
- """A single instance of FilterRole entry to create FilterRole role.
+class RoleState(Enum):
+ ACTIVATED = "activated"
+ DIRECTLY_LOCKED = "directly locked through nsDisabledRole"
+ INDIRECTLY_LOCKED = "indirectly locked through a Role"
+ PROBABLY_ACTIVATED = '''probably activated or nsDisabledRole setup and
its CoS entries are not
+in a valid state or there is no access to the settings.'''
- :param instance: An instance
- :type instance: lib389.DirSrv
- :param dn: Entry DN
- :type dn: str
- Usages:
- user1 = 'cn=anuj,ou=people,dc=example,ed=com'
- user2 = 'cn=unknownuser,ou=people,dc=example,ed=com'
-
role=FilterRole(topo.standalone,'cn=NameofRole,ou=People,dc=example,dc=com')
- role_props={'cn':'Anuj',
'nsRoleFilter':'cn=anuj*'}
- role.create(properties=role_props, basedn=SUFFIX)
- The user1 entry matches the filter (possesses the cn=anuj* attribute with the
value anuj)
- therefore, it is a member of this filtered role automatically.
+ def describe(self, role_dn=None):
+ if self.name == "INDIRECTLY_LOCKED" and role_dn is not None:
+ return f'{self.value} - {role_dn}'
+ else:
+ return f'{self.value}'
+
+
+class Role(DSLdapObject):
+ """A single instance of Role entry
+
+ :param instance: An instance
+ :type instance: lib389.DirSrv
+ :param dn: Entry DN
+ :type dn: str
"""
+
def __init__(self, instance, dn=None):
- super(FilterRole, self).__init__(instance, dn)
+ super(Role, self).__init__(instance, dn)
self._rdn_attribute = 'cn'
self._create_objectclasses = [
'top',
+ 'LDAPsubentry',
'nsRoleDefinition',
- 'nsComplexRoleDefinition',
- 'nsFilteredRoleDefinition'
]
+ def _format_status_message(self, message, role_dn=None):
+ return {"state": message, "role_dn": role_dn}
+
+ def status(self):
+ """Check if role is locked in nsDisabledRole (directly or
indirectly)
-class FilterRoles(DSLdapObjects):
- """DSLdapObjects that represents all filtertrole entries in suffix.
-
- This instance is used mainly for search operation filtred role
-
- :param instance: An instance
- :type instance: lib389.DirSrv
- :param basedn: Suffix DN
- :type basedn: str
- :param rdn: The DN that will be combined wit basedn
- :type rdn: str
- Usages:
- role_props={'cn':'Anuj', 'nsRoleFilter':'cn=*'}
- FilterRoles(topo.standalone, DEFAULT_SUFFIX).create(properties=role_props)
- FilterRoles(topo.standalone, DEFAULT_SUFFIX).list()
- user1 = 'cn=anuj,ou=people,dc=example,ed=com'
- user2 = 'uid=unknownuser,ou=people,dc=example,ed=com'
- The user1 entry matches the filter (possesses the cn=* attribute with the value
cn)
- therefore, it is a member of this filtered role automatically.
+ :returns: a dict
"""
+
+ inst = self._instance
+ disabled_roles = {}
+ try:
+ mapping_trees = MappingTrees(inst)
+ root_suffix = mapping_trees.get_root_suffix_by_entry(self.dn)
+ roles = Roles(inst, root_suffix)
+ disabled_roles = roles.get_disabled_roles()
+ nested_roles = NestedRoles(inst, root_suffix)
+ disabled_role = nested_roles.get("nsDisabledRole")
+ inact_containers = nsContainers(inst, basedn=root_suffix)
+ inact_container = inact_containers.get('nsAccountInactivationTmp')
+
+ cos_templates = CosTemplates(inst, inact_container.dn)
+ cos_template = cos_templates.get(f'{disabled_role.dn}')
+ cos_template.present('cosPriority', '1')
+ cos_template.present('nsAccountLock', 'true')
+
+ cos_classic_defs = CosClassicDefinitions(inst, root_suffix)
+ cos_classic_def = cos_classic_defs.get('nsAccountInactivation_cos')
+ cos_classic_def.present('cosAttribute', 'nsAccountLock
operational')
+ cos_classic_def.present('cosTemplateDn', inact_container.dn)
+ cos_classic_def.present('cosSpecifier', 'nsRole')
+ except ldap.NO_SUCH_OBJECT:
+ return self._format_status_message(RoleState.PROBABLY_ACTIVATED)
+
+ for role, parent in disabled_roles.items():
+ if str.lower(self.dn) == str.lower(role.dn):
+ if parent is None:
+ return self._format_status_message(RoleState.DIRECTLY_LOCKED)
+ else:
+ return self._format_status_message(RoleState.INDIRECTLY_LOCKED,
parent)
+
+ return self._format_status_message(RoleState.ACTIVATED)
+
+ def lock(self):
+ """Set the entry dn to nsDisabledRole and ensure it
exists"""
+
+ current_status = self.status()
+ if current_status["state"] == RoleState.DIRECTLY_LOCKED:
+ raise ValueError(f"Role is already
{current_status['state'].describe()}")
+
+ inst = self._instance
+
+ mapping_trees = MappingTrees(inst)
+ root_suffix = ""
+ root_suffix = mapping_trees.get_root_suffix_by_entry(self.dn)
+
+ if root_suffix:
+ managed_roles = ManagedRoles(inst, root_suffix)
+ managed_role = managed_roles.ensure_state(properties={"cn":
"nsManagedDisabledRole"})
+ nested_roles = NestedRoles(inst, root_suffix)
+ try:
+ disabled_role = nested_roles.get("nsDisabledRole")
+ except ldap.NO_SUCH_OBJECT:
+ # We don't use "ensure_state" because we want to preserve
the existing attributes
+ disabled_role = nested_roles.create(properties={"cn":
"nsDisabledRole",
+ "nsRoleDN":
managed_role.dn})
+ disabled_role.add("nsRoleDN", self.dn)
+
+ inact_containers = nsContainers(inst, basedn=root_suffix)
+ inact_container = inact_containers.ensure_state(properties={'cn':
'nsAccountInactivationTmp'})
+
+ cos_templates = CosTemplates(inst, inact_container.dn)
+ cos_templates.ensure_state(properties={'cosPriority': '1',
+ 'nsAccountLock':
'true',
+ 'cn':
f'{disabled_role.dn}'})
+
+ cos_classic_defs = CosClassicDefinitions(inst, root_suffix)
+ cos_classic_defs.ensure_state(properties={'cosAttribute':
'nsAccountLock operational',
+ 'cosSpecifier':
'nsRole',
+ 'cosTemplateDn':
inact_container.dn,
+ 'cn':
'nsAccountInactivation_cos'})
+
+ def unlock(self):
+ """Remove the entry dn from nsDisabledRole if it
exists"""
+
+ inst = self._instance
+ current_status = self.status()
+ if current_status["state"] == RoleState.ACTIVATED:
+ raise ValueError("Role is already active")
+
+ mapping_trees = MappingTrees(inst)
+ root_suffix = mapping_trees.get_root_suffix_by_entry(self.dn)
+ roles = NestedRoles(inst, root_suffix)
+ try:
+ disabled_role = roles.get("nsDisabledRole")
+ # Still we want to ensure that it is not locked directly too
+ disabled_role.ensure_removed("nsRoleDN", self.dn)
+ except ldap.NO_SUCH_OBJECT:
+ pass
+
+ # Notify if it's locked indirectly
+ if current_status["state"] == RoleState.INDIRECTLY_LOCKED:
+ raise ValueError(f"Role is
{current_status['state'].describe(current_status['role_dn'])}. Please,
deal with it separately")
+
+
+class Roles(DSLdapObjects):
+ """DSLdapObjects that represents all Roles entries
+
+ :param instance: An instance
+ :type instance: lib389.DirSrv
+ :param basedn: Suffix DN
+ :type basedn: str
+ """
+
def __init__(self, instance, basedn):
- super(FilterRoles, self).__init__(instance)
+ super(Roles, self).__init__(instance)
self._objectclasses = [
'top',
+ 'LDAPsubentry',
'nsRoleDefinition',
- 'nsComplexRoleDefinition',
- 'nsFilteredRoleDefinition'
]
self._filterattrs = ['cn']
self._basedn = basedn
- self._childobject = FilterRole
-
+ self._childobject = Role
-class ManagedRole(DSLdapObject):
- """A single instance of ManagedRole entry to create ManagedRole role.
+ def get_with_type(self, selector=[], dn=None):
+ """Get the correct role type
- :param instance: An instance
- :type instance: lib389.DirSrv
- :param dn: Entry DN
+ :param dn: DN of wanted entry
:type dn: str
+ :param selector: An additional filter to search for, i.e. 'backend_name'.
The attributes
+ selected are based on object type, ie user will search for uid
and cn.
+ :type dn: str
+
+ :returns: FilteredRole, ManagedRole or NestedRole
+ """
+
+ ROLE_OBJECTCLASSES = {FilteredRole: ['nscomplexroledefinition',
+ 'nsfilteredroledefinition'],
+ ManagedRole: ['nssimpleroledefinition',
+ 'nsmanagedroledefinition'],
+ NestedRole: ['nscomplexroledefinition',
+ 'nsnestedroledefinition']}
+ entry = self.get(selector=selector, dn=dn, json=False)
+ entry_objectclasses = entry.get_attr_vals_utf8_l("objectClass")
+ role_found = False
+ for role, objectclasses in ROLE_OBJECTCLASSES.items():
+ role_found = all(oc in entry_objectclasses for oc in objectclasses)
+ if role_found:
+ return role(self._instance, entry.dn)
+ if not role_found:
+ raise ldap.NO_SUCH_OBJECT("Role definition was not found")
+
+ def get_disabled_roles(self):
+ """Get disabled roles that are usually defined in the
cn=nsDisabledRole,ROOT_SUFFIX
+
+ :returns: A dict {role: its_parent, }
+ """
+
+ disabled_role = self.get("nsDisabledRole")
+ roles_inactive = {}
+ result = {}
+
+ # Do this on 0 level of nestedness
+ for role_dn in disabled_role.get_attr_vals_utf8_l("nsRoleDN"):
+ roles_inactive[role_dn] = None
+
+ # We go through the list and check if the role is Nested and
+ # then add its 'nsrole' attributes to the processing list
+ while roles_inactive.items():
+ processing_role_dn, parent = roles_inactive.popitem()
+ # Check if already seen the role and skip it then
+ if processing_role_dn in result.keys():
+ continue
+
+ processing_role = self.get_with_type(dn=processing_role_dn)
+ if isinstance(processing_role, NestedRole):
+ for role_dn in
processing_role.get_attr_vals_utf8_l("nsRoleDN"):
+ # We don't need to process children which are already present in
the list
+ if role_dn in result.keys() or role_dn in roles_inactive.keys():
+ continue
+ # We are deeper - return its children to the processing and assign
the original parent
+ if parent in [role.dn for role in result.keys()]:
+ roles_inactive[role_dn] = parent
+ else:
+ roles_inactive[role_dn] = processing_role_dn
+ # Set the processed role to list
+ result[processing_role] = parent
+
+ return result
+class FilteredRole(Role):
+ """A single instance of FilteredRole entry to create FilteredRole
role
+
+ :param instance: An instance
+ :type instance: lib389.DirSrv
+ :param dn: Entry DN
+ :type dn: str
"""
+
+ def __init__(self, instance, dn=None):
+ super(FilteredRole, self).__init__(instance, dn)
+ self._rdn_attribute = 'cn'
+ self._create_objectclasses = ['nsComplexRoleDefinition',
'nsFilteredRoleDefinition']
+
+
+
+class FilteredRoles(Roles):
+ """DSLdapObjects that represents all filtered role entries
+
+ :param instance: An instance
+ :type instance: lib389.DirSrv
+ :param basedn: Suffix DN
+ :type basedn: str
+ """
+
+ def __init__(self, instance, basedn):
+ super(FilteredRoles, self).__init__(instance, basedn)
+ self._objectclasses = ['LDAPsubentry', 'nsComplexRoleDefinition',
'nsFilteredRoleDefinition']
+ self._filterattrs = ['cn']
+ self._basedn = basedn
+ self._childobject = FilteredRole
+
+
+class ManagedRole(Role):
+ """A single instance of Managed Role entry
+
+ :param instance: An instance
+ :type instance: lib389.DirSrv
+ :param dn: Entry DN
+ :type dn: str
+ """
+
def __init__(self, instance, dn=None):
super(ManagedRole, self).__init__(instance, dn)
self._rdn_attribute = 'cn'
- self._create_objectclasses = [
- 'top',
- 'nsRoleDefinition',
- 'nsSimpleRoleDefinition',
- 'nsManagedRoleDefinition'
- ]
+ self._create_objectclasses = ['nsSimpleRoleDefinition',
'nsManagedRoleDefinition']
-class ManagedRoles(DSLdapObjects):
- """DSLdapObjects that represents all ManagedRoles entries in suffix.
+class ManagedRoles(Roles):
+ """DSLdapObjects that represents all Managed Roles entries
- This instance is used mainly for search operation ManagedRoles role
+ :param instance: An instance
+ :type instance: lib389.DirSrv
+ :param basedn: Suffix DN
+ :type basedn: str
+ :param rdn: The DN that will be combined wit basedn
+ :type rdn: str
+ """
- :param instance: An instance
- :type instance: lib389.DirSrv
- :param basedn: Suffix DN
- :type basedn: str
- :param rdn: The DN that will be combined wit basedn
- :type rdn: str
- """
def __init__(self, instance, basedn):
- super(ManagedRoles, self).__init__(instance)
- self._objectclasses = [
- 'top',
- 'nsRoleDefinition',
- 'nsSimpleRoleDefinition',
- 'nsManagedRoleDefinition'
- ]
+ super(ManagedRoles, self).__init__(instance, basedn)
+ self._objectclasses = ['LDAPsubentry', 'nsSimpleRoleDefinition',
'nsManagedRoleDefinition']
self._filterattrs = ['cn']
self._basedn = basedn
self._childobject = ManagedRole
-class NestedRole(DSLdapObject):
- """A single instance of NestedRole entry to create NestedRole role.
-
- :param instance: An instance
- :type instance: lib389.DirSrv
- :param dn: Entry DN
- :type dn: str
+class NestedRole(Role):
+ """A single instance of Nested Role entry
+ :param instance: An instance
+ :type instance: lib389.DirSrv
+ :param dn: Entry DN
+ :type dn: str
"""
+
def __init__(self, instance, dn=None):
super(NestedRole, self).__init__(instance, dn)
self._must_attributes = ['cn', 'nsRoleDN']
self._rdn_attribute = 'cn'
- self._create_objectclasses = [
- 'top',
- 'nsRoleDefinition',
- 'nsComplexRoleDefinition',
- 'ldapSubEntry',
- 'nsNestedRoleDefinition'
- ]
+ self._create_objectclasses = ['nsComplexRoleDefinition',
'nsNestedRoleDefinition']
-class NestedRoles(DSLdapObjects):
+class NestedRoles(Roles):
"""DSLdapObjects that represents all NestedRoles entries in suffix.
- This instance is used mainly for search operation NestedRoles role
+ :param instance: An instance
+ :type instance: lib389.DirSrv
+ :param basedn: Suffix DN
+ :type basedn: str
+ :param rdn: The DN that will be combined wit basedn
+ :type rdn: str
+ """
- :param instance: An instance
- :type instance: lib389.DirSrv
- :param basedn: Suffix DN
- :type basedn: str
- :param rdn: The DN that will be combined wit basedn
- :type rdn: str
- """
def __init__(self, instance, basedn):
- super(NestedRoles, self).__init__(instance)
- self._objectclasses = [
- 'top',
- 'nsRoleDefinition',
- 'nsComplexRoleDefinition',
- 'ldapSubEntry',
- 'nsNestedRoleDefinition'
- ]
+ super(NestedRoles, self).__init__(instance, basedn)
+ self._objectclasses = ['LDAPsubentry', 'nsComplexRoleDefinition',
'nsNestedRoleDefinition']
self._filterattrs = ['cn']
self._basedn = basedn
self._childobject = NestedRole
diff --git a/src/lib389/lib389/instance/options.py
b/src/lib389/lib389/instance/options.py
index 5ee69a3..dcc7f63 100644
--- a/src/lib389/lib389/instance/options.py
+++ b/src/lib389/lib389/instance/options.py
@@ -114,7 +114,7 @@ class General2Base(Options2):
self._type['full_machine_name'] = str
self._helptext['full_machine_name'] = "Sets the fully qualified
hostname (FQDN) of this system. When installing this instance with GSSAPI authentication
behind a load balancer, set this parameter to the FQDN of the load balancer and,
additionally, set \"strict_host_checking\" to \"false\"."
- self._options['strict_host_checking'] = True
+ self._options['strict_host_checking'] = False
self._type['strict_host_checking'] = bool
self._helptext['strict_host_checking'] = "Sets whether the server
verifies the forward and reverse record set in the \"full_machine_name\"
parameter. When installing this instance with GSSAPI authentication behind a load
balancer, set this parameter to \"false\". Container installs imply
\"false\"."
@@ -166,8 +166,9 @@ class Slapd2Base(Options2):
self._helptext['root_password'] = ("Sets the password of the account
specified in the \"root_dn\" parameter. " +
"You can either set this parameter to a
plain text password dscreate hashes " +
"during the installation or to a
\"{algorithm}hash\" string generated by the " +
- "pwdhash utility. Note that setting a
plain text password can be a security " +
- "risk if unprivileged users can read this
INF file!")
+ "pwdhash utility. The password must be at
least 8 characters long. Note " +
+ "that setting a plain text password can
be a security risk if unprivileged " +
+ "users can read this INF file!")
self._options['prefix'] = ds_paths.prefix
self._type['prefix'] = str
diff --git a/src/lib389/lib389/instance/remove.py b/src/lib389/lib389/instance/remove.py
index e85e866..c9a872e 100644
--- a/src/lib389/lib389/instance/remove.py
+++ b/src/lib389/lib389/instance/remove.py
@@ -9,6 +9,7 @@
import os
import shutil
import subprocess
+import logging
from lib389.nss_ssl import NssSsl
from lib389.utils import selinux_label_port, assert_c
@@ -98,10 +99,16 @@ def remove_ds_instance(dirsrv, force=False):
if dirsrv.ds_paths.with_systemd:
# Remove the systemd symlink
_log.debug("Removing the systemd symlink")
- subprocess.check_call(["systemctl", "disable",
"dirsrv(a){}".format(dirsrv.serverid)])
+
+ result = subprocess.run(["systemctl", "disable",
"dirsrv(a){}".format(dirsrv.serverid)],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ _log.debug(f"CMD: {' '.join(result.args)} ; STDOUT: {result.stdout}
; STDERR: {result.stderr}")
_log.debug("Removing %s" % tmpfiles_d_path)
- shutil.rmtree(tmpfiles_d_path, ignore_errors=True)
+ try:
+ os.remove(tmpfiles_d_path)
+ except OSError as e:
+ _log.debug("Failed to remove tmpfile: " + str(e))
# Nor can we assume we have selinux. Try docker sometime ;)
if dirsrv.ds_paths.with_selinux:
@@ -147,4 +154,3 @@ def remove_ds_instance(dirsrv, force=False):
# Done!
_log.debug("Complete")
-
diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py
index 347eba9..bb0ff32 100644
--- a/src/lib389/lib389/instance/setup.py
+++ b/src/lib389/lib389/instance/setup.py
@@ -8,6 +8,7 @@
# --- END COPYRIGHT BLOCK ---
import os
+import logging
import sys
import shutil
import pwd
@@ -40,6 +41,10 @@ from lib389.utils import (
ds_paths = Paths()
+# We need this to decide if we should remove after a failed install - useful
+# for tests ONLY which is why it's the env flag still.
+DEBUGGING = os.getenv('DEBUGGING', default=False)
+
def get_port(port, default_port, secure=False):
# Get the port number for the interactive installer and validate it
@@ -238,8 +243,11 @@ class SetupDs(object):
print('===========================================')
# Set the defaults
- general = {'config_version': 2, 'full_machine_name':
socket.getfqdn(),
- 'strict_host_checking': True, 'selinux': True,
'systemd': ds_paths.with_systemd,
+ general = {'config_version': 2,
+ 'full_machine_name': socket.getfqdn(),
+ 'strict_host_checking': False,
+ 'selinux': True,
+ 'systemd': ds_paths.with_systemd,
'defaults': '999999999', 'start': True}
slapd = {'self_sign_cert_valid_months': 24,
@@ -283,24 +291,6 @@ class SetupDs(object):
if val != "":
general['full_machine_name'] = val
- # Strict host name checking
- msg = ("\nUse strict hostname verification (set to \"no\" if using
GSSAPI behind a load balancer) [yes]: ")
- while 1:
- val = input(msg).rstrip().lower()
- if val != "":
- if val == "no" or val == "n":
- slapd['strict_host_checking'] = False
- break
- if val == "yes" or val == "y":
- # Use default
- break
-
- # Unknown value
- print ("Value \"{}\" is invalid, please use
\"yes\" or \"no\"".format(val))
- continue
- else:
- break
-
# Instance name - adjust defaults once set
while 1:
slapd['instance_name'] =
general['full_machine_name'].split('.', 1)[0]
@@ -407,6 +397,11 @@ class SetupDs(object):
print('Password can not be empty')
continue
+ if len(rootpw1) < 8:
+ print('Password must be at least 8 characters long')
+ continue
+
+
rootpw2 = getpass.getpass('Confirm the Directory Manager Password:
').rstrip()
if rootpw1 != rootpw2:
print('Passwords do not match')
@@ -581,6 +576,9 @@ class SetupDs(object):
assert_c(is_a_dn(slapd['root_dn']), "root_dn in section [slapd] is
not a well formed LDAP DN")
assert_c(slapd['root_password'] is not None and
slapd['root_password'] != '',
"Configuration attribute 'root_password' in section [slapd]
not found")
+ if len(slapd['root_password']) < 8:
+ raise ValueError("root_password must be at least 8 characters
long")
+
# Check if pre-hashed or not.
# !!!!!!!!!!!!!!
@@ -635,7 +633,9 @@ class SetupDs(object):
Actually does the setup. this is what you want to call as an api.
"""
- self.log.info("\nStarting installation...")
+ self.log.debug("START: Starting installation...")
+ if not self.verbose:
+ self.log.info("Starting installation...")
# Check we have privs to run
self.log.debug("READY: Preparing installation for %s...",
slapd['instance_name'])
@@ -653,8 +653,11 @@ class SetupDs(object):
try:
self._install_ds(general, slapd, backends)
except ValueError as e:
- self.log.fatal("Error: " + str(e) + ", removing incomplete
installation...")
- self._remove_failed_install(slapd['instance_name'])
+ if DEBUGGING is False:
+ self.log.fatal("Error: " + str(e) + ", removing
incomplete installation...")
+ self._remove_failed_install(slapd['instance_name'])
+ else:
+ self.log.fatal("Error: " + str(e) + ", preserving
incomplete installation for analysis...")
raise ValueError("Instance creation failed!")
# Call the child api to do anything it needs.
@@ -662,6 +665,7 @@ class SetupDs(object):
self.log.debug("FINISH: Completed installation for %s",
slapd['instance_name'])
if not self.verbose:
self.log.info("Completed installation for %s",
slapd['instance_name'])
+
return True
def _install_ds(self, general, slapd, backends):
@@ -777,9 +781,9 @@ class SetupDs(object):
# If we are on the correct platform settings, systemd
if general['systemd']:
# Should create the symlink we need, but without starting it.
- subprocess.check_call(["systemctl",
- "enable",
- "dirsrv@%s" %
slapd['instance_name']])
+ result = subprocess.run(["systemctl", "enable",
"dirsrv@%s" % slapd['instance_name']],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ self.log.debug(f"CMD: {' '.join(result.args)} ; STDOUT:
{result.stdout} ; STDERR: {result.stderr}")
# Setup tmpfiles_d
tmpfile_d = ds_paths.tmpfiles_d + "/dirsrv-" +
slapd['instance_name'] + ".conf"
@@ -794,7 +798,6 @@ class SetupDs(object):
# Bind sockets to our type?
-
# Create certdb in sysconfidir
self.log.debug("ACTION: Creating certificate database is %s",
slapd['cert_dir'])
diff --git a/src/lib389/lib389/ldclt.py b/src/lib389/lib389/ldclt.py
index f220d60..0410650 100644
--- a/src/lib389/lib389/ldclt.py
+++ b/src/lib389/lib389/ldclt.py
@@ -66,7 +66,7 @@ loginShell: /bin/false
digits = len('%s' % max)
cmd = [
- '%s/bin/ldclt' % self.ds.prefix,
+ '%s/ldclt' % self.ds.get_bin_dir(),
'-h',
self.ds.host,
'-p',
@@ -94,11 +94,25 @@ loginShell: /bin/false
raise(e)
self.log.debug(result)
+ def _run_ldclt(self, cmd):
+ result = None
+ self.log.debug("ldclt loadtest ...")
+ self.log.debug(format_cmd_list(cmd))
+ try:
+ result = subprocess.check_output(cmd)
+ # If verbose, capture / log the output.
+ except subprocess.CalledProcessError as e:
+ print(format_cmd_list(cmd))
+ print(result)
+ raise(e)
+ self.log.debug(result)
+ return result
+
def bind_loadtest(self, subtree, min=1000, max=9999, rounds=3):
# The bind users will be uid=userXXXX
digits = len('%s' % max)
cmd = [
- '%s/bin/ldclt' % self.ds.prefix,
+ '%s/ldclt' % self.ds.get_bin_dir(),
'-h',
self.ds.host,
'-p',
@@ -114,14 +128,27 @@ loginShell: /bin/false
'-e',
'bindonly',
]
- result = None
- self.log.debug("ldclt loadtest ...")
- self.log.debug(format_cmd_list(cmd))
- try:
- result = subprocess.check_output(cmd)
- # If verbose, capture / log the output.
- except subprocess.CalledProcessError as e:
- print(format_cmd_list(cmd))
- print(result)
- raise(e)
- self.log.debug(result)
+ self._run_ldclt(cmd)
+
+ def search_loadtest(self, subtree, fpattern, min=1000, max=9999, rounds=3):
+ digits = len('%s' % max)
+ cmd = [
+ '%s/ldclt' % self.ds.get_bin_dir(),
+ '-h',
+ self.ds.host,
+ '-p',
+ '%s' % self.ds.port,
+ '-N',
+ '%s' % rounds,
+ '-f',
+ fpattern,
+ '-e',
+ 'esearch,random',
+ '-r%s' % min,
+ '-R%s' % max,
+ '-I',
+ '32',
+ '-e',
+ 'randomattrlist=cn:uid:ou',
+ ]
+ self._run_ldclt(cmd)
diff --git a/src/lib389/lib389/mappingTree.py b/src/lib389/lib389/mappingTree.py
index 7a59020..45f1a9a 100644
--- a/src/lib389/lib389/mappingTree.py
+++ b/src/lib389/lib389/mappingTree.py
@@ -7,7 +7,7 @@
# --- END COPYRIGHT BLOCK ---
import ldap
-import ldap.dn
+from ldap.dn import str2dn, dn2str
import six
from lib389._constants import *
@@ -395,7 +395,7 @@ class MappingTree(DSLdapObject):
super(MappingTree, self).__init__(instance, dn)
self._rdn_attribute = 'cn'
self._must_attributes = ['cn']
- self._create_objectclasses = ['top', 'extensibleObject',
MT_OBJECTCLASS_VALUE]
+ self._create_objectclasses = ['top', 'extensibleObject',
'nsMappingTree']
self._protected = False
def set_parent(self, parent):
@@ -422,9 +422,31 @@ class MappingTrees(DSLdapObjects):
def __init__(self, instance):
super(MappingTrees, self).__init__(instance=instance)
- self._objectclasses = [MT_OBJECTCLASS_VALUE]
- self._filterattrs = ['cn', 'nsslapd-backend' ]
+ self._objectclasses = ['nsMappingTree']
+ self._filterattrs = ['cn', 'nsslapd-backend']
self._childobject = MappingTree
self._basedn = DN_MAPPING_TREE
+ def get_root_suffix_by_entry(self, entry_dn):
+ """Get the root suffix to which the entry belongs
+ :param entry_dn: An entry DN
+ :type entry_dn: str
+ :returns: str
+ """
+
+ mapping_tree_list = sorted(self.list(), key=lambda b: len(b.dn), reverse=True)
+
+ entry_dn_parts = str2dn(entry_dn)
+ processing = True
+ while processing:
+ compare_dn = dn2str(entry_dn_parts)
+ for mapping_tree in mapping_tree_list:
+ if str.lower(compare_dn) == str.lower(mapping_tree.rdn):
+ processing = False
+ return mapping_tree.rdn
+ if entry_dn_parts:
+ entry_dn_parts.pop(0)
+ else:
+ processing = False
+ raise ldap.NO_SUCH_OBJECT(f"{entry_dn} doesn't belong to any
suffix")
diff --git a/src/lib389/lib389/monitor.py b/src/lib389/lib389/monitor.py
index 9ee16b2..5ca967c 100644
--- a/src/lib389/lib389/monitor.py
+++ b/src/lib389/lib389/monitor.py
@@ -246,3 +246,16 @@ class MonitorSNMP(DSLdapObject):
def get_status(self, use_json=False):
return self.get_attrs_vals_utf8(self._snmp_keys)
+
+
+class MonitorDiskSpace(DSLdapObject):
+ """A class for representing "cn=disk space,cn=monitor"
entry"""
+
+ def __init__(self, instance, dn=None):
+ super(MonitorDiskSpace, self).__init__(instance=instance, dn=dn)
+ self._dn = "cn=disk space,cn=monitor"
+
+ def get_disks(self):
+ """Get an information about partitions which contains a Directory
Server data"""
+
+ return self.get_attr_vals_utf8_l("dsDisk")
diff --git a/src/lib389/lib389/plugins.py b/src/lib389/lib389/plugins.py
index 8271e03..a8b8985 100644
--- a/src/lib389/lib389/plugins.py
+++ b/src/lib389/lib389/plugins.py
@@ -1340,6 +1340,25 @@ class POSIXWinsyncPlugin(Plugin):
def __init__(self, instance, dn="cn=Posix Winsync
API,cn=plugins,cn=config"):
super(POSIXWinsyncPlugin, self).__init__(instance, dn)
+ def fixup(self, basedn, _filter=None):
+ """Create a memberuid task
+
+ :param basedn: Basedn to fix up
+ :type basedn: str
+ :param _filter: a filter for entries to fix up
+ :type _filter: str
+
+ :returns: an instance of Task(DSLdapObject)
+ """
+
+ task = tasks.MemberUidFixupTask(self._instance)
+ task_properties = {'basedn': basedn}
+ if _filter is not None:
+ task_properties['filter'] = _filter
+ task.create(properties=task_properties)
+
+ return task
+
class PAMPassThroughAuthPlugin(Plugin):
"""A single instance of PAM Pass Through Auth plugin entry
@@ -1820,6 +1839,40 @@ class AccountPolicyConfigs(DSLdapObjects):
self._basedn = basedn
+class AccountPolicyEntry(DSLdapObject):
+ """A single instance of Account Policy Plugin entry which is used for
CoS
+
+ :param instance: An instance
+ :type instance: lib389.DirSrv
+ :param dn: Entry DN
+ :type dn: str
+ """
+
+ def __init__(self, instance, dn=None):
+ super(AccountPolicyConfig, self).__init__(instance, dn)
+ self._rdn_attribute = 'cn'
+ self._must_attributes = ['cn']
+ self._create_objectclasses = ['top', 'accountpolicy']
+ self._protected = False
+
+
+class AccountPolicyEntries(DSLdapObjects):
+ """A DSLdapObjects entity which represents Account Policy Plugin entry
which is used for CoS
+
+ :param instance: An instance
+ :type instance: lib389.DirSrv
+ :param basedn: Base DN for all account entries below
+ :type basedn: str
+ """
+
+ def __init__(self, instance, basedn):
+ super(AccountPolicyConfigs, self).__init__(instance)
+ self._objectclasses = ['top', 'accountpolicy']
+ self._filterattrs = ['cn']
+ self._childobject = AccountPolicyEntry
+ self._basedn = basedn
+
+
class DNAPlugin(Plugin):
"""A single instance of Distributed Numeric Assignment plugin entry
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
index e0b41a9..7145e86 100644
--- a/src/lib389/lib389/replica.py
+++ b/src/lib389/lib389/replica.py
@@ -26,7 +26,7 @@ from lib389.passwd import password_generate
from lib389.mappingTree import MappingTrees
from lib389.agreement import Agreements
from lib389.tombstone import Tombstones
-
+from lib389.tasks import CleanAllRUVTask
from lib389.idm.domain import Domain
from lib389.idm.group import Groups
from lib389.idm.services import ServiceAccounts
@@ -897,9 +897,11 @@ class RUV(object):
ruvs.append({"raw_ruv": self._rid_rawruv.get(rid),
"rid": rid,
"url": self._rid_url.get(rid),
- "csn": parse_csn(self._rid_csn.get(rid,
'00000000000000000000')),
- "maxcsn": parse_csn(self._rid_maxcsn.get(rid,
'00000000000000000000')),
- "modts": parse_csn(self._rid_modts.get(rid,
'00000000'))})
+ "csn": RUV().parse_csn(self._rid_csn.get(rid,
'00000000000000000000')),
+ "raw_csn": self._rid_csn.get(rid,
'00000000000000000000'),
+ "maxcsn": RUV().parse_csn(self._rid_maxcsn.get(rid,
'00000000000000000000')),
+ "raw_maxcsn": self._rid_maxcsn.get(rid,
'00000000000000000000'),
+ "modts": RUV().parse_csn(self._rid_modts.get(rid,
'00000000'))})
result["ruvs"] = ruvs
return result
@@ -1153,13 +1155,27 @@ class Replica(DSLdapObject):
return False
return True
+ def cleanRUV(self, rid):
+ """Run a cleanallruv task, only on a master, after deleting or
demoting
+ it. It is okay if it fails.
+ """
+ if rid != '65535':
+ properties = {'replica-base-dn':
self.get_attr_val_utf8('nsDS5ReplicaRoot'),
+ 'replica-id': rid,
+ 'replica-force-cleaning': 'yes'}
+ try:
+ clean_task = CleanAllRUVTask(self._instance)
+ clean_task.create(properties=properties)
+ except ldap.LDAPError as e:
+ self._log.debug("Failed to run cleanAllRUV task: " + str(e))
+
def delete(self):
"""Delete a replica related to the provided suffix.
If this replica role was ReplicaRole.HUB or ReplicaRole.MASTER, it
also deletes the changelog associated to that replica. If it
exists some replication agreement below that replica, they are
- deleted.
+ deleted. If this is a master we also clean the database ruv.
:returns: None
:raises: - InvalidArgumentError - if suffix is missing
@@ -1167,6 +1183,7 @@ class Replica(DSLdapObject):
"""
# Delete the agreements
self._delete_agreements()
+
# Delete the replica
return super(Replica, self).delete()
@@ -1264,15 +1281,15 @@ class Replica(DSLdapObject):
raise ValueError('Failed to update replica: ' + str(e))
elif replicarole == ReplicaRole.CONSUMER and newrole == ReplicaRole.MASTER:
try:
- self.replace_many([(REPL_TYPE, str(REPLICA_RDWR_TYPE)),
- (REPL_FLAGS, str(REPLICA_FLAGS_WRITE)),
- (REPL_ID, str(rid))])
+ self.replace_many((REPL_TYPE, str(REPLICA_RDWR_TYPE)),
+ (REPL_FLAGS, str(REPLICA_FLAGS_WRITE)),
+ (REPL_ID, str(rid)))
except ldap.LDAPError as e:
raise ValueError('Failed to update replica: ' + str(e))
elif replicarole == ReplicaRole.HUB and newrole == ReplicaRole.MASTER:
try:
- self.replace_many([(REPL_TYPE, str(REPLICA_RDWR_TYPE)),
- (REPL_ID, str(rid))])
+ self.replace_many((REPL_TYPE, str(REPLICA_RDWR_TYPE)),
+ (REPL_ID, str(rid)))
except ldap.LDAPError as e:
raise ValueError('Failed to update replica: ' + str(e))
@@ -1288,21 +1305,22 @@ class Replica(DSLdapObject):
# Check the role type
replicarole = self.get_role()
+ rid = self.get_attr_val_utf8(REPL_ID)
if newrole.value >= replicarole.value:
raise ValueError('Can not demote replica to higher or the same role: {}
-> {}'.format(replicarole.name, newrole.name))
# Demote it - set the replica type, flags and rid
if replicarole == ReplicaRole.MASTER and newrole == ReplicaRole.HUB:
try:
- self.replace_many([(REPL_TYPE, str(REPLICA_RDONLY_TYPE)),
- (REPL_ID, str(CONSUMER_REPLICAID))])
+ self.replace_many((REPL_TYPE, str(REPLICA_RDONLY_TYPE)),
+ (REPL_ID, str(CONSUMER_REPLICAID)))
except ldap.LDAPError as e:
raise ValueError('Failed to update replica: ' + str(e))
elif replicarole == ReplicaRole.MASTER and newrole == ReplicaRole.CONSUMER:
try:
- self.replace_many([(REPL_TYPE, str(REPLICA_RDONLY_TYPE)),
- (REPL_FLAGS, str(REPLICA_FLAGS_RDONLY)),
- (REPL_ID, str(CONSUMER_REPLICAID))])
+ self.replace_many((REPL_TYPE, str(REPLICA_RDONLY_TYPE)),
+ (REPL_FLAGS, str(REPLICA_FLAGS_RDONLY)),
+ (REPL_ID, str(CONSUMER_REPLICAID)))
except ldap.LDAPError as e:
raise ValueError('Failed to update replica: ' + str(e))
elif replicarole == ReplicaRole.HUB and newrole == ReplicaRole.CONSUMER:
@@ -1310,6 +1328,9 @@ class Replica(DSLdapObject):
self.set(REPL_FLAGS, str(REPLICA_FLAGS_RDONLY))
except ldap.LDAPError as e:
raise ValueError('Failed to update replica: ' + str(e))
+ if replicarole == ReplicaRole.MASTER:
+ # We are no longer a master, clean up the old RID
+ self.cleanRUV(rid)
def get_role(self):
"""Return the replica role
@@ -1557,7 +1578,7 @@ class Replicas(DSLdapObjects):
replica._populate_suffix()
return replica
- def process_and_dump_changelog(self, replica_roots=[], csn_only=False):
+ def process_and_dump_changelog(self, replica_roots=[], csn_only=False,
preserve_ldif_done=False):
"""Dump and decode Directory Server replication change log
:param replica_roots: Replica suffixes that need to be processed
@@ -1602,7 +1623,12 @@ class Replicas(DSLdapObjects):
cl_ldif.grep_csn()
else:
cl_ldif.decode()
- os.rename(file_path, f'{file_path}.done')
+
+ if preserve_ldif_done:
+ os.rename(file_path, f'{file_path}.done')
+ else:
+ os.remove(file_path)
+
if not got_ldif:
self._log.info("LDIF file: Not found")
diff --git a/src/lib389/lib389/schema.py b/src/lib389/lib389/schema.py
index 60d444c..929e673 100755
--- a/src/lib389/lib389/schema.py
+++ b/src/lib389/lib389/schema.py
@@ -90,9 +90,9 @@ class Schema(DSLdapObject):
@staticmethod
def _validate_ldap_schema_value(value):
- """Validate the values that we suppl to ldap.schema.models
+ """Validate the values that we supply to ldap.schema.models
because it expects some exact values.
- It should tuple, not list.
+ It should be tuple, not list.
It should be None or () if we don't want """
if type(value) == list:
@@ -227,11 +227,11 @@ class Schema(DSLdapObject):
raise ValueError('Wrong parameter name was specified: %s' %
oc_param)
if value is not None:
value = self._validate_ldap_schema_value(value)
- setattr(schema_object, oc_param.lower(), value)
+ setattr(schema_object, oc_param, value)
else:
if getattr(schema_object, oc_param, False):
# Need to set the correct "type" for the empty value
- if oc_param in ['may', 'must', 'x-origin',
'sup']:
+ if oc_param in ['may', 'must', 'x-origin',
'sup']:
# Expects tuple
setattr(schema_object, oc_param, ())
elif oc_param in ['desc', 'oid']:
diff --git a/src/lib389/lib389/tasks.py b/src/lib389/lib389/tasks.py
index f12943f..af97634 100644
--- a/src/lib389/lib389/tasks.py
+++ b/src/lib389/lib389/tasks.py
@@ -139,6 +139,21 @@ class FixupLinkedAttributesTask(Task):
super(FixupLinkedAttributesTask, self).__init__(instance, dn)
+class MemberUidFixupTask(Task):
+ """A single instance of memberOf task entry
+
+ :param instance: An instance
+ :type instance: lib389.DirSrv
+ """
+
+ def __init__(self, instance, dn=None):
+ self.cn = 'memberUid_fixup_' + Task._get_task_date()
+ dn = f"cn={self.cn},cn=memberuid task,cn=tasks,cn=config"
+
+ super(MemberUidFixupTask, self).__init__(instance, dn)
+ self._must_attributes.extend(['basedn'])
+
+
class MemberOfFixupTask(Task):
"""A single instance of memberOf task entry
diff --git a/src/lib389/lib389/tools.py b/src/lib389/lib389/tools.py
index 919dc85..89423b4 100644
--- a/src/lib389/lib389/tools.py
+++ b/src/lib389/lib389/tools.py
@@ -52,7 +52,8 @@ from lib389.utils import (
getdefaultsuffix,
ensure_bytes,
ensure_str,
- socket_check_open,)
+ socket_check_open,
+ ds_is_older,)
from lib389.passwd import password_hash, password_generate
@@ -862,7 +863,7 @@ class DirSrvTools(object):
# We just want to make sure it's in there somewhere
if expectedHost in words:
return True
- except AssertionError:
+ except AssertionError:
raise AssertionError(
"Error: %s should contain '%s' host for %s" %
('/etc/hosts', expectedHost, ipPattern))
@@ -903,53 +904,55 @@ class DirSrvTools(object):
/prefix/lib[64]/dirsrv/slapd-INSTANCE/
'''
-
- libdir = os.path.join(_ds_paths.lib_dir, 'dirsrv')
-
- # Gather all the instances so we can adjust the permissions, otherwise
- servers = []
- path = os.path.join(_ds_paths.sysconf_dir, 'dirsrv')
- for files in os.listdir(path):
- if files.startswith('slapd-') and not
files.endswith('.removed'):
- servers.append(os.path.join(libdir, files))
-
- if len(servers) == 0:
- # This should not happen
- log.fatal('runUpgrade: no servers found!')
- assert False
-
- '''
- The setup script calls things like /lib/dirsrv/slapd-instance/db2bak,
- etc, and when we run the setup perl script it gets permission denied
- as the default permissions are 750. Adjust the permissions to 755.
- '''
- for instance in servers:
- for files in os.listdir(instance):
- os.chmod(os.path.join(instance, files), 755)
-
- # Run the "upgrade"
- try:
- prog = os.path.join(_ds_paths.sbin_dir, PATH_SETUP_DS)
- process = subprocess.Popen([prog, '--update'], shell=False,
+ if ds_is_older('1.4.0'):
+ libdir = os.path.join(_ds_paths.lib_dir, 'dirsrv')
+
+ # Gather all the instances so we can adjust the permissions, otherwise
+ servers = []
+ path = os.path.join(_ds_paths.sysconf_dir, 'dirsrv')
+ for files in os.listdir(path):
+ if files.startswith('slapd-') and not
files.endswith('.removed'):
+ servers.append(os.path.join(libdir, files))
+
+ if len(servers) == 0:
+ # This should not happen
+ log.fatal('runUpgrade: no servers found!')
+ assert False
+
+ '''
+ The setup script calls things like /lib/dirsrv/slapd-instance/db2bak,
+ etc, and when we run the setup perl script it gets permission denied
+ as the default permissions are 750. Adjust the permissions to 755.
+ '''
+ for instance in servers:
+ for files in os.listdir(instance):
+ os.chmod(os.path.join(instance, files), 755)
+
+ # Run the "upgrade"
+ try:
+ prog = os.path.join(_ds_paths.sbin_dir, PATH_SETUP_DS)
+ process = subprocess.Popen([prog, '--update'], shell=False,
stdin=subprocess.PIPE)
- # Answer the interactive questions, as "--update" currently does
- # not work with INF files
- process.stdin.write('yes\n')
- if(online):
- process.stdin.write('online\n')
- for x in servers:
- process.stdin.write(DN_DM + '\n')
- process.stdin.write(PW_DM + '\n')
- else:
- process.stdin.write('offline\n')
- process.stdin.close()
- process.wait()
- if process.returncode != 0:
- log.fatal('runUpgrade failed! Error: %s ' % process.returncode)
- assert(False)
- except:
- log.fatal('runUpgrade failed!')
- raise
+ # Answer the interactive questions, as "--update" currently
does
+ # not work with INF files
+ process.stdin.write(b'yes\n')
+ if(online):
+ process.stdin.write(b'online\n')
+ for x in servers:
+ process.stdin.write(ensure_bytes(DN_DM + '\n'))
+ process.stdin.write(ensure_bytes(PW_DM + '\n'))
+ else:
+ process.stdin.write(b'offline\n')
+ process.stdin.close()
+ process.wait()
+ if process.returncode != 0:
+ log.fatal('runUpgrade failed! Error: %s ' %
process.returncode)
+ assert(False)
+ except:
+ log.fatal('runUpgrade failed!')
+ raise
+ else:
+ pass
@staticmethod
def searchFile(filename, pattern):
diff --git a/src/lib389/lib389/utils.py b/src/lib389/lib389/utils.py
index 10c8cae..c271d86 100644
--- a/src/lib389/lib389/utils.py
+++ b/src/lib389/lib389/utils.py
@@ -31,6 +31,7 @@ import shutil
import ldap
import socket
import time
+from datetime import datetime
import sys
import filecmp
import pwd
@@ -196,13 +197,6 @@ def selinux_present():
# No python module, so who knows what state we are in.
log.error('selinux python module not found, will not relabel files.' )
- try:
- if status:
- # Only if we know it's enabled, check if we can manage ports too.
- import sepolicy
- except ImportError:
- log.error('sepolicy python module not found, will not relabel ports.' )
-
return status
@@ -230,6 +224,30 @@ def selinux_restorecon(path):
log.debug("Failed to run restorecon on: " + path)
+def _get_selinux_port_policies(port):
+ """Get a list of selinux port policies for the specified port,
'tcp' protocol and
+ excluding 'unreserved_port_t', 'reserved_port_t',
'ephemeral_port_t' labels"""
+
+ # [2:] - cut first lines containing the headers. [:-1] - empty line
+ policy_lines = subprocess.check_output(["semanage", "port",
"-l"], encoding='utf-8').split("\n")[2:][:-1]
+ policies = []
+ for line in policy_lines:
+ data = line.split()
+ ports_list = []
+ for p in data[2:]:
+ if "," in p:
+ p = p[:-1]
+ if "-" in p:
+ p = range(int(p.split("-")[0]),
int(p.split("-")[1]))
+ else:
+ p = [int(p)]
+ ports_list.extend(p)
+ if data[1] == 'tcp' and port in ports_list and \
+ data[0] not in ['unreserved_port_t', 'reserved_port_t',
'ephemeral_port_t']:
+ policies.append({'protocol': data[1], 'type': data[0],
'ports': ports_list})
+ return policies
+
+
def selinux_label_port(port, remove_label=False):
"""
Either set or remove an SELinux label(ldap_port_t) for a TCP port
@@ -246,12 +264,6 @@ def selinux_label_port(port, remove_label=False):
log.debug('selinux python module not found, skipping port labeling.')
return
- try:
- import sepolicy
- except ImportError:
- log.debug('sepolicy python module not found, skipping port labeling.')
- return
-
if not selinux.is_selinux_enabled():
log.debug('selinux is disabled, skipping port relabel')
return
@@ -263,23 +275,17 @@ def selinux_label_port(port, remove_label=False):
if port in selinux_default_ports:
log.debug('port {} already in {}, skipping port relabel'.format(port,
selinux_default_ports))
return
-
label_set = False
label_ex = None
- policies = [p for p in sepolicy.info(sepolicy.PORT)
- if p['protocol'] == 'tcp'
- if port in range(p['low'], p['high'] + 1)
- if p['type'] not in ['unreserved_port_t',
'reserved_port_t', 'ephemeral_port_t']]
+ policies = _get_selinux_port_policies(port)
for policy in policies:
if "ldap_port_t" == policy['type']:
label_set = True # Port already has our label
- if policy['low'] != policy['high']:
- # We have a range
- if port in range(policy['low'], policy['high'] + 1):
- # The port is within the range, just return
- return
+ if port in policy['ports']:
+ # The port is within the range, just return
+ return
break
elif not remove_label:
# Port belongs to someone else (bad)
@@ -289,10 +295,13 @@ def selinux_label_port(port, remove_label=False):
if (remove_label and label_set) or (not remove_label and not label_set):
for i in range(5):
try:
- subprocess.check_call(["semanage", "port",
- "-d" if remove_label else
"-a",
- "-t", "ldap_port_t",
- "-p", "tcp", str(port)])
+ result = subprocess.run(["semanage", "port",
+ "-d" if remove_label else
"-a",
+ "-t", "ldap_port_t",
+ "-p", "tcp", str(port)],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ log.debug(f"CMD: {' '.join(result.args)} ; STDOUT:
{result.stdout} ; STDERR: {result.stderr}")
return
except (OSError, subprocess.CalledProcessError) as e:
label_ex = e
@@ -1085,6 +1094,29 @@ def ds_is_newer(*ver):
return ds_is_related('newer', *ver)
+def gentime_to_datetime(gentime):
+ """Convert Generalized time to datetime object
+
+ :param gentime: Time in the format - YYYYMMDDHHMMSSZ (20170126120000Z)
+ :type password: str
+ :returns: datetime.datetime object
+ """
+
+ return datetime.strptime(gentime, '%Y%m%d%H%M%SZ')
+
+
+def gentime_to_posix_time(gentime):
+ """Convert Generalized time to POSIX time format
+
+ :param gentime: Time in the format - YYYYMMDDHHMMSSZ (20170126120000Z)
+ :type password: str
+ :returns: Epoch time int
+ """
+
+ target_timestamp = gentime_to_datetime(gentime)
+ return datetime.timestamp(target_timestamp)
+
+
def getDateTime():
"""
Return the date and time:
diff --git a/src/lib389/setup.py b/src/lib389/setup.py
index ce8b512..61de441 100644
--- a/src/lib389/setup.py
+++ b/src/lib389/setup.py
@@ -63,6 +63,7 @@ setup(
'cli/dsconf',
'cli/dscreate',
'cli/dsidm',
+ 'cli/dscontainer',
]),
('/usr/share/man/man8', [
'man/dsctl.8',
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.