[389-ds-base] branch 389-ds-base-1.4.2 updated: Issue 50812 - dscontainer executable should be placed under /usr/libexec/dirsrv/
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new 22d53d0 Issue 50812 - dscontainer executable should be placed under /usr/libexec/dirsrv/
22d53d0 is described below
commit 22d53d0cb3dabf8f9044daf967d1554b99ed7986
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Mon Jan 13 14:40:49 2020 -0500
Issue 50812 - dscontainer executable should be placed under /usr/libexec/dirsrv/
Description: dscontainer is not a user-runnable executable. Per packaging
guidelines it should be placed under /usr/libexec/dirsrv/
relates: https://pagure.io/389-ds-base/issue/50812
Reviewed by: firstyear & mhonek (Thanks!!)
---
docker/389-ds-fedora/Dockerfile | 4 ++--
docker/389-ds-suse/Dockerfile | 4 ++--
docker/389-ds-suse/Dockerfile.release | 2 +-
rpm/389-ds-base.spec.in | 2 +-
src/lib389/setup.py | 4 +++-
5 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/docker/389-ds-fedora/Dockerfile b/docker/389-ds-fedora/Dockerfile
index ba901e1..bdd56fd 100644
--- a/docker/389-ds-fedora/Dockerfile
+++ b/docker/389-ds-fedora/Dockerfile
@@ -46,6 +46,6 @@ VOLUME /data
#USER dirsrv
HEALTHCHECK --start-period=5m --timeout=5s --interval=5s --retries=2 \
- CMD /usr/sbin/dscontainer -H
+ CMD /usr/libexec/dirsrv/dscontainer -H
-CMD [ "/usr/sbin/dscontainer", "-r" ]
+CMD [ "/usr/libexec/dirsrv/dscontainer", "-r" ]
diff --git a/docker/389-ds-suse/Dockerfile b/docker/389-ds-suse/Dockerfile
index 1e56e1f..6022d04 100644
--- a/docker/389-ds-suse/Dockerfile
+++ b/docker/389-ds-suse/Dockerfile
@@ -76,7 +76,7 @@ VOLUME /data
# USER dirsrv
HEALTHCHECK --start-period=5m --timeout=5s --interval=5s --retries=2 \
- CMD /usr/sbin/dscontainer -H
+ CMD /usr/libexec/dirsrv/dscontainer -H
-CMD [ "/usr/sbin/dscontainer", "-r" ]
+CMD [ "/usr/libexec/dirsrv/dscontainer", "-r" ]
diff --git a/docker/389-ds-suse/Dockerfile.release b/docker/389-ds-suse/Dockerfile.release
index c934eda..6f4adf7 100644
--- a/docker/389-ds-suse/Dockerfile.release
+++ b/docker/389-ds-suse/Dockerfile.release
@@ -69,4 +69,4 @@ VOLUME /data
# here and ds should do the right thing if a non root user runs the server.
# USER dirsrv
-CMD [ "/usr/sbin/dscontainer", "-r" ]
+CMD [ "/usr/libexec/dirsrv/dscontainer", "-r" ]
diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
index 481d349..a235396 100644
--- a/rpm/389-ds-base.spec.in
+++ b/rpm/389-ds-base.spec.in
@@ -808,7 +808,7 @@ exit 0
%{_mandir}/man8/dsctl.8.gz
%{_sbindir}/dsidm
%{_mandir}/man8/dsidm.8.gz
-%{_sbindir}/dscontainer
+%{_libexecdir}/%{pkgname}/dscontainer
%files -n cockpit-389-ds -f cockpit.list
%{_datarootdir}/metainfo/389-console/org.port389.cockpit_console.metainfo.xml
diff --git a/src/lib389/setup.py b/src/lib389/setup.py
index 123348a..e76b660 100644
--- a/src/lib389/setup.py
+++ b/src/lib389/setup.py
@@ -63,7 +63,6 @@ setup(
'cli/dsconf',
'cli/dscreate',
'cli/dsidm',
- 'cli/dscontainer',
]),
('/usr/share/man/man8', [
'man/dsctl.8',
@@ -71,6 +70,9 @@ setup(
'man/dscreate.8',
'man/dsidm.8',
]),
+ ('/usr/libexec/dirsrv/', [
+ 'cli/dscontainer',
+ ]),
],
install_requires=[
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch 389-ds-base-1.4.1 updated: Issue 50816 - dsconf allows the root password to be set to nothing
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.1
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.1 by this push:
new 696c0f3 Issue 50816 - dsconf allows the root password to be set to nothing
696c0f3 is described below
commit 696c0f35e0ec5edb02c4e3ff27066c1fb5258ace
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Mon Jan 13 17:58:52 2020 -0500
Issue 50816 - dsconf allows the root password to be set to nothing
Bug Description: dsconf allows you to set the root DN password to nothing/
Fix Description: Do not allow the root DN password to be set to nothing
relates: https://pagure.io/389-ds-base/issue/50816
Reviewed by: firstyear(Thanks!)
---
src/lib389/lib389/idm/directorymanager.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/lib389/lib389/idm/directorymanager.py b/src/lib389/lib389/idm/directorymanager.py
index bb3b583..4c573e7 100644
--- a/src/lib389/lib389/idm/directorymanager.py
+++ b/src/lib389/lib389/idm/directorymanager.py
@@ -31,6 +31,8 @@ class DirectoryManager(Account):
self._protected = True
def change_password(self, new_password):
+ if new_password == "":
+ raise ValueError("You can not set the Directory Manager password to nothing")
self._instance.config.set('nsslapd-rootpw', new_password)
def bind(self, password=PW_DM, *args, **kwargs):
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch 389-ds-base-1.4.2 updated: Issue 50816 - dsconf allows the root password to be set to nothing
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new f5bd9e7 Issue 50816 - dsconf allows the root password to be set to nothing
f5bd9e7 is described below
commit f5bd9e77d871288cc1ddc788838138352e3f306e
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Mon Jan 13 17:58:52 2020 -0500
Issue 50816 - dsconf allows the root password to be set to nothing
Bug Description: dsconf allows you to set the root DN password to nothing/
Fix Description: Do not allow the root DN password to be set to nothing
relates: https://pagure.io/389-ds-base/issue/50816
Reviewed by: firstyear(Thanks!)
---
src/lib389/lib389/idm/directorymanager.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/lib389/lib389/idm/directorymanager.py b/src/lib389/lib389/idm/directorymanager.py
index bb3b583..4c573e7 100644
--- a/src/lib389/lib389/idm/directorymanager.py
+++ b/src/lib389/lib389/idm/directorymanager.py
@@ -31,6 +31,8 @@ class DirectoryManager(Account):
self._protected = True
def change_password(self, new_password):
+ if new_password == "":
+ raise ValueError("You can not set the Directory Manager password to nothing")
self._instance.config.set('nsslapd-rootpw', new_password)
def bind(self, password=PW_DM, *args, **kwargs):
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch 389-ds-base-1.4.2 updated: Issue 50798 - incorrect bytes in format string(fix import issue)
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new ad4d3df Issue 50798 - incorrect bytes in format string(fix import issue)
ad4d3df is described below
commit ad4d3dffff9274ed6f7aecb435d61b558e502efe
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Mon Jan 13 17:17:40 2020 -0500
Issue 50798 - incorrect bytes in format string(fix import issue)
Description: The previous commit did not import ensure_list_str() from
utils.py
relates: https://pagure.io/389-ds-base/issue/50798
Reviewed by: mreynolds (one line commit rule)
---
src/lib389/lib389/instance/remove.py | 2 +-
src/lib389/lib389/instance/setup.py | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/lib389/lib389/instance/remove.py b/src/lib389/lib389/instance/remove.py
index 41eaeb4..e54e64e 100644
--- a/src/lib389/lib389/instance/remove.py
+++ b/src/lib389/lib389/instance/remove.py
@@ -11,7 +11,7 @@ import shutil
import subprocess
import logging
from lib389.nss_ssl import NssSsl
-from lib389.utils import selinux_label_port, assert_c
+from lib389.utils import selinux_label_port, assert_c, ensure_list_str
######################## WARNING #############################
diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py
index ae814d1..1bedd11 100644
--- a/src/lib389/lib389/instance/setup.py
+++ b/src/lib389/lib389/instance/setup.py
@@ -33,6 +33,7 @@ from lib389.utils import (
assert_c,
is_a_dn,
ensure_str,
+ ensure_list_str,
normalizeDN,
socket_check_open,
selinux_label_port,
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch master updated: Issue 50798 - incorrect bytes in format string(fix import issue)
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch master
in repository 389-ds-base.
The following commit(s) were added to refs/heads/master by this push:
new 828aad0 Issue 50798 - incorrect bytes in format string(fix import issue)
828aad0 is described below
commit 828aad0769cee2c58e073ac446558cc614ed7a96
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Mon Jan 13 17:17:40 2020 -0500
Issue 50798 - incorrect bytes in format string(fix import issue)
Description: The previous commit did not import ensure_list_str() from
utils.py
relates: https://pagure.io/389-ds-base/issue/50798
Reviewed by: mreynolds (one line commit rule)
---
src/lib389/lib389/instance/remove.py | 2 +-
src/lib389/lib389/instance/setup.py | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/lib389/lib389/instance/remove.py b/src/lib389/lib389/instance/remove.py
index 41eaeb4..e54e64e 100644
--- a/src/lib389/lib389/instance/remove.py
+++ b/src/lib389/lib389/instance/remove.py
@@ -11,7 +11,7 @@ import shutil
import subprocess
import logging
from lib389.nss_ssl import NssSsl
-from lib389.utils import selinux_label_port, assert_c
+from lib389.utils import selinux_label_port, assert_c, ensure_list_str
######################## WARNING #############################
diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py
index ead3db1..6ee71b5 100644
--- a/src/lib389/lib389/instance/setup.py
+++ b/src/lib389/lib389/instance/setup.py
@@ -33,6 +33,7 @@ from lib389.utils import (
assert_c,
is_a_dn,
ensure_str,
+ ensure_list_str,
normalizeDN,
socket_check_open,
selinux_label_port,
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch 389-ds-base-1.4.1 updated: Bump version to 1.4.1.13
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.1
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.1 by this push:
new ea060ef Bump version to 1.4.1.13
ea060ef is described below
commit ea060ef7618d08e62b19e421e2e14168197fc5f4
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Mon Jan 13 16:09:32 2020 -0500
Bump version to 1.4.1.13
---
VERSION.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/VERSION.sh b/VERSION.sh
index c7da4e3..26b4ee3 100644
--- a/VERSION.sh
+++ b/VERSION.sh
@@ -10,7 +10,7 @@ vendor="389 Project"
# PACKAGE_VERSION is constructed from these
VERSION_MAJOR=1
VERSION_MINOR=4
-VERSION_MAINT=1.12
+VERSION_MAINT=1.13
# NOTE: VERSION_PREREL is automatically set for builds made out of a git tree
VERSION_PREREL=
VERSION_DATE=$(date -u +%Y%m%d)
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch 389-ds-base-1.4.2 updated: Bump version to 1.4.2.6
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new e84bbce Bump version to 1.4.2.6
e84bbce is described below
commit e84bbce3f254d7daa8593d3d5e326e37e2f58d9f
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Mon Jan 13 15:41:42 2020 -0500
Bump version to 1.4.2.6
---
VERSION.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/VERSION.sh b/VERSION.sh
index 374fae8..9fd3b72 100644
--- a/VERSION.sh
+++ b/VERSION.sh
@@ -10,7 +10,7 @@ vendor="389 Project"
# PACKAGE_VERSION is constructed from these
VERSION_MAJOR=1
VERSION_MINOR=4
-VERSION_MAINT=2.5
+VERSION_MAINT=2.6
# NOTE: VERSION_PREREL is automatically set for builds made out of a git tree
VERSION_PREREL=
VERSION_DATE=$(date -u +%Y%m%d)
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch master updated: Bump version to 1.4.3.1
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch master
in repository 389-ds-base.
The following commit(s) were added to refs/heads/master by this push:
new a08202a Bump version to 1.4.3.1
a08202a is described below
commit a08202a5b755d61ae145716d02d78ba3d3a71b96
Author: Mark Reynolds <mreynolds(a)redhat.com>
AuthorDate: Mon Jan 13 15:06:25 2020 -0500
Bump version to 1.4.3.1
---
VERSION.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/VERSION.sh b/VERSION.sh
index e960876..90c0512 100644
--- a/VERSION.sh
+++ b/VERSION.sh
@@ -10,7 +10,7 @@ vendor="389 Project"
# PACKAGE_VERSION is constructed from these
VERSION_MAJOR=1
VERSION_MINOR=4
-VERSION_MAINT=3.0
+VERSION_MAINT=3.1
# NOTE: VERSION_PREREL is automatically set for builds made out of a git tree
VERSION_PREREL=
VERSION_DATE=$(date -u +%Y%m%d)
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch 389-ds-base-1.4.2 updated: Ticket 50798 - incorrect bytes in format string
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.2
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.2 by this push:
new cec2cb3 Ticket 50798 - incorrect bytes in format string
cec2cb3 is described below
commit cec2cb3cf1ac11867ae4a7a1ea1d6e2cecf38cc1
Author: William Brown <william(a)blackhats.net.au>
AuthorDate: Mon Dec 30 14:18:16 2019 +1100
Ticket 50798 - incorrect bytes in format string
Bug Description: We did not use ensure_bytes on a command output in
format strings. Python 3 subprocess returens bytes, but format string
expects utf8
Fix Description: Wrap the values in the correct safety wrappers.
https://pagure.io/389-ds-base/issue/50798
Author: William Brown <william(a)blackhats.net.au>
Review by: mreynolds, mhonek (Thanks)
---
src/lib389/lib389/instance/remove.py | 5 ++++-
src/lib389/lib389/instance/setup.py | 5 ++++-
src/lib389/lib389/utils.py | 5 ++++-
3 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/lib389/lib389/instance/remove.py b/src/lib389/lib389/instance/remove.py
index c9a872e..41eaeb4 100644
--- a/src/lib389/lib389/instance/remove.py
+++ b/src/lib389/lib389/instance/remove.py
@@ -102,7 +102,10 @@ def remove_ds_instance(dirsrv, force=False):
result = subprocess.run(["systemctl", "disable", "dirsrv@{}".format(dirsrv.serverid)],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- _log.debug(f"CMD: {' '.join(result.args)} ; STDOUT: {result.stdout} ; STDERR: {result.stderr}")
+ args = ' '.join(ensure_list_str(result.args))
+ stdout = ensure_str(result.stdout)
+ stderr = ensure_str(result.stderr)
+ _log.debug(f"CMD: {args} ; STDOUT: {stdout} ; STDERR: {stderr}")
_log.debug("Removing %s" % tmpfiles_d_path)
try:
diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py
index 073c7c7..ae814d1 100644
--- a/src/lib389/lib389/instance/setup.py
+++ b/src/lib389/lib389/instance/setup.py
@@ -783,7 +783,10 @@ class SetupDs(object):
# Should create the symlink we need, but without starting it.
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}")
+ args = ' '.join(ensure_list_str(result.args))
+ stdout = ensure_str(result.stdout)
+ stderr = ensure_str(result.stderr)
+ self.log.debug(f"CMD: {args} ; STDOUT: {stdout} ; STDERR: {stderr}")
# Setup tmpfiles_d
tmpfile_d = ds_paths.tmpfiles_d + "/dirsrv-" + slapd['instance_name'] + ".conf"
diff --git a/src/lib389/lib389/utils.py b/src/lib389/lib389/utils.py
index a25f1c1..aaae256 100644
--- a/src/lib389/lib389/utils.py
+++ b/src/lib389/lib389/utils.py
@@ -309,7 +309,10 @@ def selinux_label_port(port, remove_label=False):
"-p", "tcp", str(port)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
- log.debug(f"CMD: {' '.join(result.args)} ; STDOUT: {result.stdout} ; STDERR: {result.stderr}")
+ args = ' '.join(ensure_list_str(result.args))
+ stdout = ensure_str(result.stdout)
+ stderr = ensure_str(result.stderr)
+ log.debug(f"CMD: {args} ; STDOUT: {stdout} ; STDERR: {stderr}")
return
except (OSError, subprocess.CalledProcessError) as e:
label_ex = e
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months
[389-ds-base] branch 389-ds-base-1.4.1 updated: Issue 50545 - Add the new replication monitor functionality to UI
by pagure@pagure.io
This is an automated email from the git hooks/post-receive script.
mreynolds pushed a commit to branch 389-ds-base-1.4.1
in repository 389-ds-base.
The following commit(s) were added to refs/heads/389-ds-base-1.4.1 by this push:
new dbfa7a1 Issue 50545 - Add the new replication monitor functionality to UI
dbfa7a1 is described below
commit dbfa7a13588ec715b906b616f2a8b364514f1e74
Author: Simon Pichugin <spichugi(a)redhat.com>
AuthorDate: Tue Oct 8 22:59:03 2019 +0200
Issue 50545 - Add the new replication monitor functionality to UI
Description: As we ported repl-monitor.pl to dscon CLI
we should add the functionality to WebUI.
It is important to keep in mind that we shouldn't expose
user's password so the interactive option should be carried out.
Improve replication monitor CLI JSON output consistency.
Add Full Replication report functionality with ability of
continuous refresh.
https://pagure.io/389-ds-base/issue/50545
Reviewed by: mreynolds (Thanks!)
---
src/cockpit/389-console/.eslintrc.json | 2 +-
src/cockpit/389-console/src/css/ds.css | 4 +
.../389-console/src/lib/monitor/monitorModals.jsx | 762 +++++++++++++----
.../389-console/src/lib/monitor/monitorTables.jsx | 944 ++++++++++++++++++++-
.../389-console/src/lib/monitor/replMonitor.jsx | 873 ++++++++++++++++---
src/cockpit/389-console/src/lib/tools.jsx | 8 +-
src/cockpit/389-console/src/monitor.jsx | 3 +
src/lib389/lib389/agreement.py | 30 +-
src/lib389/lib389/cli_conf/backend.py | 2 +-
src/lib389/lib389/cli_conf/replication.py | 95 ++-
src/lib389/lib389/replica.py | 46 +-
11 files changed, 2403 insertions(+), 366 deletions(-)
diff --git a/src/cockpit/389-console/.eslintrc.json b/src/cockpit/389-console/.eslintrc.json
index 03ff3c4..2f3d88f 100644
--- a/src/cockpit/389-console/.eslintrc.json
+++ b/src/cockpit/389-console/.eslintrc.json
@@ -21,7 +21,7 @@
"ObjectExpression": "first",
"CallExpression": { "arguments": "first" },
"MemberExpression": 2,
- "ignoredNodes": ["JSXAttribute"]
+ "ignoredNodes": ["JSXAttribute", "JSXElement", "JSXAttribute *", "JSXElement *"]
}
],
"newline-per-chained-call": ["error", { "ignoreChainWithDepth": 2 }],
diff --git a/src/cockpit/389-console/src/css/ds.css b/src/cockpit/389-console/src/css/ds.css
index e6bb1da..662d8de 100644
--- a/src/cockpit/389-console/src/css/ds.css
+++ b/src/cockpit/389-console/src/css/ds.css
@@ -885,6 +885,10 @@ option {
padding-left: 5px;
}
+.ds-raise-field {
+ margin-top: -3px;
+}
+
.content-view-pf-pagination > div > span:last-child {
position: relative;
}
diff --git a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
index 96007ea..e48809a 100644
--- a/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/monitorModals.jsx
@@ -8,157 +8,16 @@ import {
Button,
Form,
noop,
+ FormGroup,
+ FormControl,
Spinner,
+ Checkbox
} from "patternfly-react";
import PropTypes from "prop-types";
import { get_date_string } from "../tools.jsx";
-import { LagReportTable } from "./monitorTables.jsx";
+import { ReportSingleTable, ReportConsumersTable } from "./monitorTables.jsx";
import "../../css/ds.css";
-class ReplLoginModal extends React.Component {
- render() {
- const {
- showModal,
- closeHandler,
- handleChange,
- doReport,
- spinning,
- error
- } = this.props;
-
- let spinner = "";
- if (spinning) {
- spinner =
- <Row className="ds-margin-top">
- <hr />
- <div className="ds-modal-spinner">
- <Spinner loading inline size="lg" />Authenticating to all the replicas ...
- </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>
- Replication Login Credentials
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <Form horizontal autoComplete="off">
- <p>
- In order to get the replication agreement lag times and state the
- authentication credentials to the remote replicas must be provided.
- This only works if the bind credentials used are valid on all the
- replicas.
- </p>
- <hr />
- <Row>
- <Col sm={3}>
- <ControlLabel>
- Bind DN
- </ControlLabel>
- </Col>
- <Col sm={9}>
- <input
- className={error.binddn ? "ds-input-auto-bad" : "ds-input-auto"}
- onChange={handleChange} defaultValue="cn=Directory Manager"
- type="text" id="binddn"
- />
- </Col>
- </Row>
- <Row className="ds-margin-top">
- <Col sm={3}>
- <ControlLabel>
- Password
- </ControlLabel>
- </Col>
- <Col sm={9}>
- <input
- className={error.bindpw ? "ds-input-auto-bad" : "ds-input-auto"}
- onChange={handleChange} type="password" id="bindpw"
- />
- </Col>
- </Row>
- {spinner}
- </Form>
- </Modal.Body>
- <Modal.Footer>
- <Button
- bsStyle="default"
- className="btn-cancel"
- onClick={closeHandler}
- >
- Close
- </Button>
- <Button
- bsStyle="primary"
- onClick={doReport}
- >
- Get Report
- </Button>
- </Modal.Footer>
- </div>
- </Modal>
- );
- }
-}
-
-class ReplLagReportModal extends React.Component {
- render() {
- const {
- showModal,
- closeHandler,
- agmts,
- pokeAgmt,
- viewAgmt
- } = this.props;
-
- return (
- <Modal backdrop="static" contentClassName="ds-lag-report" show={showModal} onHide={closeHandler}>
- <Modal.Header>
- <button
- className="close"
- onClick={closeHandler}
- aria-hidden="true"
- aria-label="Close"
- >
- <Icon type="pf" name="close" />
- </button>
- <Modal.Title>
- Replication Lag Report
- </Modal.Title>
- </Modal.Header>
- <Modal.Body>
- <LagReportTable
- agmts={agmts}
- pokeAgmt={pokeAgmt}
- viewAgmt={viewAgmt}
- />
- </Modal.Body>
- <Modal.Footer>
- <Button
- bsStyle="default"
- className="btn-cancel"
- onClick={closeHandler}
- >
- Close
- </Button>
- </Modal.Footer>
- </Modal>
- );
- }
-}
-
class TaskLogModal extends React.Component {
render() {
const {
@@ -224,6 +83,16 @@ class AgmtDetailsModal extends React.Component {
convertedDate[attr] = get_date_string(agmt[attr]);
}
}
+ let initButton = null;
+ if (!this.props.isRemoteAgmt) {
+ initButton = <Button
+ bsStyle="default"
+ className="btn-primary ds-float-left"
+ onClick={this.props.initAgmt}
+ >
+ Initialize Agreement
+ </Button>;
+ }
return (
<Modal show={showModal} onHide={closeHandler}>
@@ -327,13 +196,7 @@ class AgmtDetailsModal extends React.Component {
</Form>
</Modal.Body>
<Modal.Footer>
- <Button
- bsStyle="default"
- className="btn-primary ds-float-left"
- onClick={this.props.initAgmt}
- >
- Initialize Agreement
- </Button>
+ {initButton}
<Button
bsStyle="default"
className="btn-cancel"
@@ -645,12 +508,494 @@ class ConflictCompareModal extends React.Component {
}
}
+class ReportCredentialsModal extends React.Component {
+ render() {
+ const {
+ handleFieldChange,
+ showModal,
+ closeHandler,
+ newEntry,
+ hostname,
+ port,
+ binddn,
+ pwInputInterractive,
+ bindpw,
+ addConfig,
+ editConfig
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>{newEntry ? "Add" : "Edit"} Report Credentials</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal autoComplete="off">
+ <FormGroup controlId="credsHostname">
+ <Col sm={3}>
+ <ControlLabel title="A regex for hostname">
+ Hostname
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={hostname}
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="credsPort">
+ <Col sm={3}>
+ <ControlLabel title="A regex for port">
+ Port
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={port}
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="credsBinddn">
+ <Col sm={3}>
+ <ControlLabel title="Bind DN for the specified instances">
+ Bind DN
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={binddn}
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="credsBindpw">
+ <Col sm={3}>
+ <ControlLabel title="Bind password for the specified instances">
+ Password
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="password"
+ value={bindpw}
+ onChange={handleFieldChange}
+ disabled={pwInputInterractive}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="interractiveInput">
+ <Col sm={3}>
+ <ControlLabel title="Input the password interactively">
+ Interractive Input
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <Checkbox
+ checked={pwInputInterractive}
+ id="pwInputInterractive"
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button bsStyle="default" className="btn-cancel" onClick={closeHandler}>
+ Cancel
+ </Button>
+ <Button bsStyle="primary" onClick={newEntry ? addConfig : editConfig}>
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class ReportAliasesModal extends React.Component {
+ render() {
+ const {
+ handleFieldChange,
+ showModal,
+ closeHandler,
+ newEntry,
+ hostname,
+ port,
+ alias,
+ addConfig,
+ editConfig
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>{newEntry ? "Add" : "Edit"} Report Credentials</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Col sm={12}>
+ <Form horizontal>
+ <FormGroup controlId="aliasName">
+ <Col sm={3}>
+ <ControlLabel title="Alias name for the instance">
+ Alias
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={alias}
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="aliasHostname">
+ <Col sm={3}>
+ <ControlLabel title="An instance hostname">
+ Hostname
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={hostname}
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="aliasPort">
+ <Col sm={3}>
+ <ControlLabel title="An instance port">
+ Port
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="number"
+ value={port}
+ onChange={handleFieldChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Col>
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button bsStyle="default" className="btn-cancel" onClick={closeHandler}>
+ Cancel
+ </Button>
+ <Button bsStyle="primary" onClick={newEntry ? addConfig : editConfig}>
+ Save
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class ReportLoginModal extends React.Component {
+ render() {
+ const {
+ showModal,
+ closeHandler,
+ handleChange,
+ processCredsInput,
+ instanceName,
+ disableBinddn,
+ loginBinddn,
+ loginBindpw
+ } = this.props;
+
+ return (
+ <Modal show={showModal} onHide={closeHandler}>
+ <div className="ds-no-horizontal-scrollbar">
+ <Modal.Header>
+ <button
+ className="close"
+ onClick={closeHandler}
+ aria-hidden="true"
+ aria-label="Close"
+ >
+ <Icon type="pf" name="close" />
+ </button>
+ <Modal.Title>Replication Login Credentials for {instanceName}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form horizontal autoComplete="off">
+ <p>
+ In order to get the replication agreement lag times and state the
+ authentication credentials to the remote replicas must be provided.
+ </p>
+ <hr />
+ <p>
+ Bind DN was acquired from <b>Replica Credentials</b> table. If you want
+ to bind as another user, change or remove the Bind DN there.
+ </p>
+ <br />
+ <FormGroup controlId="loginBinddn">
+ <Col sm={3}>
+ <ControlLabel title="Bind DN for the instance">
+ Bind DN
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="text"
+ value={loginBinddn}
+ onChange={handleChange}
+ disabled={disableBinddn}
+ />
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="loginBindpw">
+ <Col sm={3}>
+ <ControlLabel title="Password for the Bind DN">
+ Password
+ </ControlLabel>
+ </Col>
+ <Col sm={9}>
+ <FormControl
+ type="password"
+ value={loginBindpw}
+ onChange={handleChange}
+ />
+ </Col>
+ </FormGroup>
+ </Form>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button bsStyle="default" className="btn-cancel" onClick={closeHandler}>
+ Close
+ </Button>
+ <Button bsStyle="primary" onClick={processCredsInput}>
+ Confirm Credentials Input
+ </Button>
+ </Modal.Footer>
+ </div>
+ </Modal>
+ );
+ }
+}
+
+class FullReportContent extends React.Component {
+ constructor (props) {
+ super(props);
+ this.state = {
+ oneTableReport: false,
+ showDisabledAgreements: false
+ };
+
+ this.handleSwitchChange = this.handleSwitchChange.bind(this);
+ }
+
+ handleSwitchChange(e) {
+ if (typeof e === "boolean") {
+ // Handle Switch object
+ this.setState({
+ oneTableReport: e
+ });
+ } else {
+ this.setState({
+ [e.target.id]: e.target.checked
+ });
+ }
+ }
+
+ render() {
+ const {
+ reportData,
+ handleRefresh,
+ reportRefreshing,
+ reportLoading
+ } = this.props;
+
+ let suppliers = [];
+ let supplierName;
+ let supplierData;
+ let resultRows = [];
+ let spinner = <ControlLabel />;
+ if (reportLoading) {
+ spinner = (
+ <div>
+ <ControlLabel title="Do the refresh every few seconds">
+ {reportRefreshing ? "Refreshing" : "Loading"} the report...
+ </ControlLabel>
+ <Spinner inline loading size="sm" />
+ </div>
+ );
+ }
+ let reportHeader = "";
+ if (reportData.length > 0) {
+ reportHeader = (
+ <Form horizontal autoComplete="off">
+ <FormGroup controlId="showDisabledAgreements">
+ <Col sm={8}>
+ <Checkbox
+ checked={this.state.showDisabledAgreements}
+ id="showDisabledAgreements"
+ onChange={this.handleSwitchChange}
+ title="Display all agreements including the disabled ones and the ones we failed to connect to"
+ >
+ Show All (Including Disabled Agreements)
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ <FormGroup controlId="oneTableReport">
+ <Col sm={6} title="Show all data in one table (it makes it easier to check lag times)">
+ <Checkbox
+ checked={this.state.oneTableReport}
+ onChange={this.handleSwitchChange}
+ id="oneTableReport"
+ title="Display all agreements including the disabled ones and the ones we failed to connect to"
+ >
+ Table View
+ </Checkbox>
+ </Col>
+ </FormGroup>
+ <Button
+ className="ds-margin-top"
+ bsStyle="default"
+ onClick={handleRefresh}
+ >
+ Refresh Report
+ </Button>
+ <hr />
+ </Form>
+ );
+ } else {
+ reportHeader = spinner;
+ }
+ if (this.state.oneTableReport) {
+ for (let supplier of reportData) {
+ for (let replica of supplier.data) {
+ resultRows = resultRows.concat(replica.agmts_status);
+ }
+ suppliers.push(supplierData);
+ }
+ suppliers = [(<div>
+ <ReportSingleTable
+ rows={resultRows}
+ viewAgmt={this.props.viewAgmt}
+ />
+ </div>
+ )];
+ } else {
+ for (let supplier of reportData) {
+ let s_data = supplier.data;
+ if (s_data.length === 1 && s_data[0].replica_status.startsWith("Unavailable")) {
+ supplierData = (
+ <div>
+ <h4>
+ <b>Can not get replication information from Replica</b>
+ </h4>
+ <h4 title="Supplier availability status">
+ <b>Replica Status:</b> {s_data[0].replica_status}
+ </h4>
+ </div>
+ );
+ } else {
+ supplierData = supplier.data.map(replica => (
+ <div key={replica.replica_root + replica.replica_id}>
+ <h4 title="Replica Root suffix">
+ <b>Replica Root:</b> {replica.replica_root}
+ </h4>
+ <h4 title="Replica ID">
+ <b>Replica ID:</b> {replica.replica_id}
+ </h4>
+ <h4 title="Replica Status">
+ <b>Replica Status:</b> {replica.replica_status}
+ </h4>
+ <h4 title="Max CSN">
+ <b>Max CSN:</b> {replica.maxcsn}
+ </h4>
+ {"agmts_status" in replica &&
+ replica.agmts_status.length > 0 &&
+ "agmt-name" in replica.agmts_status[0] ? (
+ <ReportConsumersTable
+ rows={replica.agmts_status}
+ viewAgmt={this.props.viewAgmt}
+ />
+ ) : (
+ <h4>
+ <b>No Agreements Were Found</b>
+ </h4>
+ )}
+ </div>
+ ));
+ }
+ supplierName = (
+ <div key={supplier.name}>
+ <center>
+ <h2 title="Supplier host:port (and alias if applicable)">
+ <b>Supplier:</b> {supplier.name}
+ </h2>
+ </center>
+ <hr />
+ {supplierData}
+ </div>
+ );
+ suppliers.push(supplierName);
+ }
+ }
+
+ let report = suppliers.map(supplier => (
+ <div key={supplier.key}>
+ {supplier}
+ <hr />
+ </div>
+ ));
+ if (reportLoading) {
+ report =
+ <Col sm={12} className="ds-center ds-margin-top">
+ {spinner}
+ </Col>;
+ }
+
+ return (
+ <div>
+ {reportHeader}
+ {report}
+ </div>
+ );
+ }
+}
// Prototypes and defaultProps
AgmtDetailsModal.propTypes = {
showModal: PropTypes.bool,
closeHandler: PropTypes.func,
agmt: PropTypes.object,
initAgmt: PropTypes.func,
+ isRemoteAgmt: PropTypes.bool
};
AgmtDetailsModal.defaultProps = {
@@ -658,6 +1003,7 @@ AgmtDetailsModal.defaultProps = {
closeHandler: noop,
agmt: {},
initAgmt: noop,
+ isRemoteAgmt: false
};
WinsyncAgmtDetailsModal.propTypes = {
@@ -686,24 +1032,6 @@ TaskLogModal.defaultProps = {
agreement: "",
};
-ReplLoginModal.propTypes = {
- showModal: PropTypes.bool,
- closeHandler: PropTypes.func,
- handleChange: PropTypes.func,
- doReport: PropTypes.func,
- spinning: PropTypes.bool,
- error: PropTypes.object,
-};
-
-ReplLoginModal.defaultProps = {
- showModal: false,
- closeHandler: noop,
- handleChange: noop,
- doReport: noop,
- spinning: false,
- error: {},
-};
-
ConflictCompareModal.propTypes = {
showModal: PropTypes.bool,
conflictEntry: PropTypes.object,
@@ -722,11 +1050,101 @@ ConflictCompareModal.defaultProps = {
closeHandler: noop,
};
+ReportCredentialsModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleFieldChange: PropTypes.func,
+ hostname: PropTypes.string,
+ port: PropTypes.string,
+ binddn: PropTypes.string,
+ bindpw: PropTypes.string,
+ pwInputInterractive: PropTypes.bool,
+ newEntry: PropTypes.bool,
+ addConfig: PropTypes.func,
+ editConfig: PropTypes.func
+};
+
+ReportCredentialsModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleFieldChange: noop,
+ hostname: "",
+ port: "",
+ binddn: "",
+ bindpw: "",
+ pwInputInterractive: false,
+ newEntry: false,
+ addConfig: noop,
+ editConfig: noop,
+};
+
+ReportAliasesModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleFieldChange: PropTypes.func,
+ hostname: PropTypes.string,
+ port: PropTypes.number,
+ alias: PropTypes.string,
+ newEntry: PropTypes.bool,
+ addConfig: PropTypes.func,
+ editConfig: PropTypes.func
+};
+
+ReportAliasesModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleFieldChange: noop,
+ hostname: "",
+ port: 389,
+ alias: "",
+ newEntry: false,
+ addConfig: noop,
+ editConfig: noop,
+};
+
+ReportLoginModal.propTypes = {
+ showModal: PropTypes.bool,
+ closeHandler: PropTypes.func,
+ handleChange: PropTypes.func,
+ processCredsInput: PropTypes.func,
+ instanceName: PropTypes.string,
+ disableBinddn: PropTypes.bool,
+ loginBinddn: PropTypes.string,
+ loginBindpw: PropTypes.string
+};
+
+ReportLoginModal.defaultProps = {
+ showModal: false,
+ closeHandler: noop,
+ handleChange: noop,
+ processCredsInput: noop,
+ instanceName: "",
+ disableBinddn: false,
+ loginBinddn: "",
+ loginBindpw: ""
+};
+
+FullReportContent.propTypes = {
+ reportData: PropTypes.array,
+ handleRefresh: PropTypes.func,
+ reportRefreshing: PropTypes.bool
+};
+
+FullReportContent.defaultProps = {
+ handleFieldChange: noop,
+ reportData: [],
+ handleRefresh: noop,
+ reportRefreshTimeout: 5,
+ reportRefreshing: false
+};
+
export {
TaskLogModal,
AgmtDetailsModal,
- ReplLagReportModal,
- ReplLoginModal,
WinsyncAgmtDetailsModal,
ConflictCompareModal,
+ ReportCredentialsModal,
+ ReportAliasesModal,
+ ReportLoginModal,
+ FullReportContent
};
diff --git a/src/cockpit/389-console/src/lib/monitor/monitorTables.jsx b/src/cockpit/389-console/src/lib/monitor/monitorTables.jsx
index 21c0128..d38e2fc 100644
--- a/src/cockpit/389-console/src/lib/monitor/monitorTables.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/monitorTables.jsx
@@ -11,7 +11,7 @@ import {
import { DSTable, DSShortTable } from "../dsTable.jsx";
import PropTypes from "prop-types";
import "../../css/ds.css";
-import { get_date_string } from "../tools.jsx";
+import { get_date_string, searchFilter } from "../tools.jsx";
class AbortCleanALLRUVTable extends React.Component {
constructor(props) {
@@ -542,16 +542,16 @@ class WinsyncAgmtTable extends React.Component {
formatters: [
(value, { rowData }) => {
return [
- <td key={rowData['agmt-name']}>
- <DropdownButton id={rowData['agmt-name']}
+ <td key={rowData['agmt-name'][0]}>
+ <DropdownButton id={rowData['agmt-name'][0]}
bsStyle="default" title="Actions">
<MenuItem eventKey="1" onClick={() => {
- this.props.viewAgmt(rowData['agmt-name']);
+ this.props.viewAgmt(rowData['agmt-name'][0]);
}}>
View Agreement Details
</MenuItem>
<MenuItem eventKey="2" onClick={() => {
- this.props.pokeAgmt(rowData['agmt-name']);
+ this.props.pokeAgmt(rowData['agmt-name'][0]);
}}>
Poke Agreement
</MenuItem>
@@ -760,16 +760,16 @@ class AgmtTable extends React.Component {
formatters: [
(value, { rowData }) => {
return [
- <td key={rowData['agmt-name']}>
- <DropdownButton id={rowData['agmt-name']}
+ <td key={rowData['agmt-name'][0]}>
+ <DropdownButton id={rowData['agmt-name'][0]}
bsStyle="default" title="Actions">
<MenuItem eventKey="1" onClick={() => {
- this.props.viewAgmt(rowData['agmt-name']);
+ this.props.viewAgmt(rowData['agmt-name'][0]);
}}>
View Agreement Details
</MenuItem>
<MenuItem eventKey="2" onClick={() => {
- this.props.pokeAgmt(rowData['agmt-name']);
+ this.props.pokeAgmt(rowData['agmt-name'][0]);
}}>
Poke Agreement
</MenuItem>
@@ -1175,16 +1175,16 @@ class LagReportTable extends React.Component {
formatters: [
(value, { rowData }) => {
return [
- <td key={rowData['agmt-name']}>
- <DropdownButton id={rowData['agmt-name']}
+ <td key={rowData['agmt-name'][0]}>
+ <DropdownButton id={rowData['agmt-name'][0]}
bsStyle="default" title="Actions">
<MenuItem eventKey="1" onClick={() => {
- this.props.viewAgmt(rowData['agmt-name']);
+ this.props.viewAgmt(rowData['agmt-name'][0]);
}}>
View Agreement Details
</MenuItem>
<MenuItem eventKey="2" onClick={() => {
- this.props.pokeAgmt(rowData['agmt-name']);
+ this.props.pokeAgmt(rowData['agmt-name'][0]);
}}>
Poke Agreement
</MenuItem>
@@ -1662,6 +1662,770 @@ class DiskTable extends React.Component {
header: {
label: "Available Space",
props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+
+ ]
+ };
+ this.getColumns = this.getColumns.bind(this);
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ render() {
+ return (
+ <div className="ds-margin-top-xlg">
+ <DSShortTable
+ getColumns={this.getColumns}
+ rowKey={this.state.rowKey}
+ rows={this.props.disks}
+ disableLoadingSpinner
+ />
+ </div>
+ );
+ }
+}
+
+class ReportAliasesTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+
+ this.state = {
+ searchField: "Aliases",
+ fieldsToSearch: ["alias", "connData"],
+
+ columns: [
+ {
+ property: "alias",
+ header: {
+ label: "Alias",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "connData",
+ header: {
+ label: "Connection Data",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.alias}>
+ <DropdownButton
+ id={rowData.alias}
+ bsStyle="default"
+ title="Actions"
+ >
+ <MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+ >
+ Edit Alias
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+ >
+ Delete Alias
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Instance Aliases",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ render() {
+ let reportAliasTable;
+ if (this.props.rows.length < 1) {
+ reportAliasTable = (
+ <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{ msg: "No alias entries" }]}
+ disableLoadingSpinner
+ />
+ );
+ } else {
+ reportAliasTable = (
+ <DSShortTable
+ getColumns={this.getColumns}
+ rowKey="alias"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ />
+ );
+ }
+
+ return <div className="ds-margin-top-xlg">{reportAliasTable}</div>;
+ }
+}
+
+class ReportCredentialsTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+
+ this.state = {
+ columns: [
+ {
+ property: "connData",
+ header: {
+ label: "Connection Data",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "credsBinddn",
+ header: {
+ label: "Bind DN",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.connData}>
+ {value == "" ? <i>Edit To Add a Bind DN Data</i> : value }
+ </td>
+ ];
+ }
+ ]
+ }
+ },
+ {
+ property: "credsBindpw",
+ header: {
+ label: "Password",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [
+ (value, { rowData }) => {
+ let pwField = <i>Interractive Input is set</i>;
+ if (!rowData.pwInputInterractive) {
+ if (value == "") {
+ pwField = <i>Both Password or Interractive Input flag are not set</i>;
+ } else {
+ pwField = "********";
+ }
+ }
+ return [
+ <td key={rowData.connData}>
+ {pwField}
+ </td>
+ ];
+ }
+ ]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.connData}>
+ <DropdownButton
+ id={rowData.connData}
+ bsStyle="default"
+ title="Actions"
+ >
+ <MenuItem
+ eventKey="1"
+ onClick={() => {
+ this.props.editConfig(rowData);
+ }}
+ >
+ Edit Connection
+ </MenuItem>
+ <MenuItem divider />
+ <MenuItem
+ eventKey="2"
+ onClick={() => {
+ this.props.deleteConfig(rowData);
+ }}
+ >
+ Delete Connection
+ </MenuItem>
+ </DropdownButton>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Replica Credentials Table",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ render() {
+ let reportConnTable;
+ if (this.props.rows.length < 1) {
+ reportConnTable = (
+ <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{ msg: "No connection entries" }]}
+ disableLoadingSpinner
+ />
+ );
+ } else {
+ reportConnTable = (
+ <DSShortTable
+ getColumns={this.getColumns}
+ rowKey="connData"
+ rows={this.props.rows}
+ disableLoadingSpinner
+ />
+ );
+ }
+
+ return <div className="ds-margin-top-xlg">{reportConnTable}</div>;
+ }
+}
+
+class ReportSingleTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+
+ this.state = {
+ searchField: "Replica",
+ fieldsToSearch: [
+ "supplierName",
+ "replicaName",
+ "replicaStatus",
+ "agmt-name",
+ "replica",
+ "replicaStatus",
+ "replica-enabled",
+ "replication-lag-time"
+ ],
+
+ columns: [
+ {
+ property: "supplierName",
+ header: {
+ label: "Supplier",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replicaName",
+ header: {
+ label: "Suffix:ReplicaID",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replicaStatus",
+ header: {
+ label: "Replica Status",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "agmt-name",
+ header: {
+ label: "Agreement",
+ props: {
+ index: 3,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 3
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.rowKey}>
+ {value || <i>No Agreements Were Found</i>}
+ </td>
+ ];
+ }
+ ]
+ }
+ },
+ {
+ property: "replica",
+ header: {
+ label: "Consumer",
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replica-enabled",
+ header: {
+ label: "Is Enabled",
+ props: {
+ index: 5,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 5
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replication-lag-time",
+ header: {
+ label: "Lag Time",
+ props: {
+ index: 6,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 6
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 7,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 7
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.rowKey}>
+ <Button
+ onClick={() => {
+ this.props.viewAgmt(rowData['supplierName'][0],
+ rowData['replicaName'][0],
+ rowData['agmt-name'][0]);
+ }}
+ >
+ View Data
+ </Button>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
+ ]
+ };
+ }
+
+ getColumns() {
+ return this.state.columns;
+ }
+
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "All In One Report",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
+ render() {
+ let reportSingleTable;
+ let filteredRows = this.props.rows;
+ if (!this.props.showDisabledAgreements) {
+ filteredRows = searchFilter("on", ["replica-enabled"], filteredRows);
+ }
+ if (filteredRows.length < 1) {
+ reportSingleTable = (
+ <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{ msg: "No replica entries" }]}
+ disableLoadingSpinner
+ noSearchBar
+ />
+ );
+ } else {
+ reportSingleTable = (
+ <DSShortTable
+ getColumns={this.getColumns}
+ rowKey="rowKey"
+ rows={filteredRows}
+ disableLoadingSpinner
+ noSearchBar
+ />
+ );
+ }
+
+ return <div>{reportSingleTable}</div>;
+ }
+}
+
+class ReportConsumersTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getColumns = this.getColumns.bind(this);
+ this.getSingleColumn = this.getSingleColumn.bind(this);
+
+ this.state = {
+ searchField: "Agreements",
+ fieldsToSearch: [
+ "agmt-name",
+ "replica-enabled",
+ "replication-status",
+ "replication-lag-time"
+ ],
+
+ columns: [
+ {
+ property: "agmt-name",
+ header: {
+ label: "Agreement Name",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replica-enabled",
+ header: {
+ label: "Is Enabled",
+ props: {
+ index: 1,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 1
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replication-status",
+ header: {
+ label: "Replication Status",
+ props: {
+ index: 2,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 2
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ {
+ property: "replication-lag-time",
+ header: {
+ label: "Replication Lag Time",
+ props: {
index: 3,
rowSpan: 1,
colSpan: 1,
@@ -1678,30 +2442,104 @@ class DiskTable extends React.Component {
formatters: [tableCellFormatter]
}
},
-
+ {
+ property: "actions",
+ header: {
+ props: {
+ index: 4,
+ rowSpan: 1,
+ colSpan: 1
+ },
+ formatters: [actionHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 4
+ },
+ formatters: [
+ (value, { rowData }) => {
+ return [
+ <td key={rowData.rowKey}>
+ <Button
+ onClick={() => {
+ this.props.viewAgmt(rowData['supplierName'][0],
+ rowData['replicaName'][0],
+ rowData['agmt-name'][0]);
+ }}
+ >
+ View Data
+ </Button>
+ </td>
+ ];
+ }
+ ]
+ }
+ }
]
};
- this.getColumns = this.getColumns.bind(this);
}
getColumns() {
return this.state.columns;
}
+ getSingleColumn () {
+ return [
+ {
+ property: "msg",
+ header: {
+ label: "Report Consumers",
+ props: {
+ index: 0,
+ rowSpan: 1,
+ colSpan: 1,
+ sort: true
+ },
+ transforms: [],
+ formatters: [],
+ customFormatters: [sortableHeaderCellFormatter]
+ },
+ cell: {
+ props: {
+ index: 0
+ },
+ formatters: [tableCellFormatter]
+ }
+ },
+ ];
+ }
+
render() {
- return (
- <div className="ds-margin-top-xlg">
+ let reportConsumersTable;
+ let filteredRows = this.props.rows;
+ if (!this.props.showDisabledAgreements) {
+ filteredRows = searchFilter("on", ["replica-enabled"], filteredRows);
+ }
+ if (filteredRows.length < 1) {
+ reportConsumersTable = (
+ <DSShortTable
+ getColumns={this.getSingleColumn}
+ rowKey={"msg"}
+ rows={[{ msg: "No agreement entries" }]}
+ disableLoadingSpinner
+ noSearchBar
+ />
+ );
+ } else {
+ reportConsumersTable = (
<DSShortTable
getColumns={this.getColumns}
- rowKey={this.state.rowKey}
- rows={this.props.disks}
+ rowKey="rowKey"
+ rows={filteredRows}
disableLoadingSpinner
+ noSearchBar
/>
- </div>
- );
+ );
+ }
+
+ return <div>{reportConsumersTable}</div>;
}
}
-
// Proptypes and defaults
LagReportTable.propTypes = {
@@ -1790,6 +2628,62 @@ GlueTable.defaultProps = {
deleteGlue: noop,
};
+ReportCredentialsTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+ReportCredentialsTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+ReportAliasesTable.propTypes = {
+ rows: PropTypes.array,
+ editConfig: PropTypes.func,
+ deleteConfig: PropTypes.func
+};
+
+ReportAliasesTable.defaultProps = {
+ rows: [],
+ editConfig: noop,
+ deleteConfig: noop
+};
+
+ReportConsumersTable.propTypes = {
+ showDisabledAgreements: PropTypes.bool,
+ rows: PropTypes.array,
+ viewAgmt: PropTypes.func
+};
+
+ReportConsumersTable.defaultProps = {
+ showDisabledAgreements: false,
+ rows: [],
+ viewAgmt: noop
+};
+
+ReportSingleTable.propTypes = {
+ showDisabledAgreements: PropTypes.bool,
+ rows: PropTypes.array,
+ viewAgmt: PropTypes.func
+};
+
+ReportSingleTable.defaultProps = {
+ showDisabledAgreements: false,
+ rows: [],
+ viewAgmt: noop
+};
+
+DiskTable.defaultProps = {
+ rows: PropTypes.array
+};
+
+DiskTable.defaultProps = {
+ rows: []
+};
+
export {
ConnectionTable,
AgmtTable,
@@ -1799,5 +2693,9 @@ export {
AbortCleanALLRUVTable,
ConflictTable,
GlueTable,
- DiskTable,
+ ReportCredentialsTable,
+ ReportAliasesTable,
+ ReportConsumersTable,
+ ReportSingleTable,
+ DiskTable
};
diff --git a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
index ffbb5cc..482834b 100644
--- a/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
+++ b/src/cockpit/389-console/src/lib/monitor/replMonitor.jsx
@@ -5,6 +5,8 @@ import PropTypes from "prop-types";
import "../../css/ds.css";
import { ConfirmPopup } from "../notifications.jsx";
import {
+ ReportCredentialsTable,
+ ReportAliasesTable,
AgmtTable,
WinsyncAgmtTable,
CleanALLRUVTable,
@@ -13,11 +15,13 @@ import {
GlueTable,
} from "./monitorTables.jsx";
import {
+ FullReportContent,
+ ReportLoginModal,
+ ReportCredentialsModal,
+ ReportAliasesModal,
TaskLogModal,
AgmtDetailsModal,
WinsyncAgmtDetailsModal,
- ReplLagReportModal,
- ReplLoginModal,
ConflictCompareModal,
} from "./monitorModals.jsx";
import {
@@ -29,39 +33,151 @@ import {
Button,
noop
} from "patternfly-react";
+import CustomCollapse from "../customCollapse.jsx";
+
+const _ = cockpit.gettext;
export class ReplMonitor extends React.Component {
+ componentDidUpdate(prevProps, prevState) {
+ if (!(prevState.showReportLoginModal) && (this.state.showReportLoginModal)) {
+ // When the login modal turned on
+ // We set timeout to close it and stop the report
+ if (this.timer) window.clearTimeout(this.timer);
+
+ this.timer = window.setTimeout(() => {
+ this.setState({
+ showFullReportModal: false
+ });
+ this.timer = null;
+ }, 300);
+ }
+ if ((prevState.showReportLoginModal) && !(this.state.showReportLoginModal)) {
+ // When the login modal turned off
+ // We clear the timeout
+ if (this.timer) window.clearTimeout(this.timer);
+ }
+ }
+
+ componentWillUnmount() {
+ // It's important to do so we don't get the error
+ // on the unmounted component
+ if (this.timer) window.clearTimeout(this.timer);
+ }
+
+ componentDidMount() {
+ if (this.state.initCreds) {
+ let cmd = ["dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "config", "get", "nsslapd-port", "nsslapd-localhost", "nsslapd-rootdn"];
+ log_cmd("ReplMonitor", "add credentials during componentDidMount", cmd);
+ cockpit
+ .spawn(cmd, { superuser: true, err: "message" })
+ .done(content => {
+ let config = JSON.parse(content);
+ this.setState(prevState => ({
+ credentialsList: [
+ ...prevState.credentialsList,
+ {
+ connData: `${config.attrs["nsslapd-localhost"]}:${config.attrs["nsslapd-port"]}`,
+ credsBinddn: config.attrs["nsslapd-rootdn"],
+ credsBindpw: "",
+ pwInputInterractive: true
+ }
+ ]
+ }));
+ for (let agmt of this.props.data.replAgmts) {
+ this.setState(prevState => ({
+ credentialsList: [
+ ...prevState.credentialsList,
+ {
+ connData: `${agmt.replica}`,
+ credsBinddn: config.attrs["nsslapd-rootdn"],
+ credsBindpw: "",
+ pwInputInterractive: true
+ }
+ ],
+ initCreds: false
+ }));
+ }
+ })
+ .fail(err => {
+ let errMsg = JSON.parse(err);
+ this.props.addNotification(
+ "error",
+ `Failed to get config nsslapd-port, nsslapd-localhost and nasslapd-rootdn: ${errMsg.desc}`
+ );
+ });
+ }
+ this.props.enableTree();
+ }
+
constructor (props) {
super(props);
this.state = {
activeKey: 1,
+ activeReportKey: 1,
logData: "",
showBindModal: false,
showLogModal: false,
showAgmtModal: false,
+ isRemoteAgmt: false,
+ showFullReportModal: false,
+ showReportLoginModal: false,
+ showCredentialsModal: false,
+ showAliasesModal: false,
showWinsyncAgmtModal: false,
showInitWinsyncConfirm: false,
showInitConfirm: false,
- showLoginModal: false,
- showLagReport: false,
showCompareModal: false,
showConfirmDeleteGlue: false,
showConfirmConvertGlue: false,
showConfirmSwapConflict: false,
showConfirmConvertConflict: false,
showConfirmDeleteConflict: false,
- reportLoading: false,
lagAgmts: [],
+ credsData: [],
+ aliasData: [],
+ reportData: [],
agmt: "",
convertRDN: "",
glueEntry: "",
conflictEntry: "",
binddn: "cn=Directory Manager",
bindpw: "",
- errObj: {}
+ errObj: {},
+ aliasList: [],
+ newEntry: false,
+ initCreds: true,
+
+ fullReportProcess: {},
+ interruptLoginCredsInput: false,
+ doFullReportCleanup: false,
+ reportRefreshing: false,
+ reportLoading: false,
+
+ credsInstanceName: "",
+ disableBinddn: false,
+ loginBinddn: "",
+ loginBindpw: "",
+ inputLoginData: false,
+
+ credsHostname: "",
+ credsPort: "",
+ credsBinddn: "cn=Directory Manager",
+ credsBindpw: "",
+ pwInputInterractive: false,
+
+ aliasHostname: "",
+ aliasPort: 389,
+ aliasName: "",
+
+ credentialsList: [],
+ dynamicCredentialsList: [],
+ aliasesList: []
};
+ this.handleFieldChange = this.handleFieldChange.bind(this);
this.handleNavSelect = this.handleNavSelect.bind(this);
+ this.handleReportNavSelect = this.handleReportNavSelect.bind(this);
this.pokeAgmt = this.pokeAgmt.bind(this);
this.initAgmt = this.initAgmt.bind(this);
this.initWinsyncAgmt = this.initWinsyncAgmt.bind(this);
@@ -71,17 +187,37 @@ export class ReplMonitor extends React.Component {
this.closeInitWinsyncConfirm = this.closeInitWinsyncConfirm.bind(this);
this.pokeWinsyncAgmt = this.pokeWinsyncAgmt.bind(this);
this.showAgmtModal = this.showAgmtModal.bind(this);
+ this.showAgmtModalRemote = this.showAgmtModalRemote.bind(this);
this.closeAgmtModal = this.closeAgmtModal.bind(this);
this.showWinsyncAgmtModal = this.showWinsyncAgmtModal.bind(this);
this.closeWinsyncAgmtModal = this.closeWinsyncAgmtModal.bind(this);
- this.getLagReportCreds = this.getLagReportCreds.bind(this);
- this.doLagReport = this.doLagReport.bind(this);
- this.closeLagReport = this.closeLagReport.bind(this);
this.viewCleanLog = this.viewCleanLog.bind(this);
this.viewAbortLog = this.viewAbortLog.bind(this);
this.closeLogModal = this.closeLogModal.bind(this);
- this.handleLoginModal = this.handleLoginModal.bind(this);
- this.closeLoginModal = this.closeLoginModal.bind(this);
+ this.closeReportLoginModal = this.closeReportLoginModal.bind(this);
+
+ // Replication report functions
+ this.addCreds = this.addCreds.bind(this);
+ this.editCreds = this.editCreds.bind(this);
+ this.removeCreds = this.removeCreds.bind(this);
+ this.openCredsModal = this.openCredsModal.bind(this);
+ this.showAddCredsModal = this.showAddCredsModal.bind(this);
+ this.showEditCredsModal = this.showEditCredsModal.bind(this);
+ this.closeCredsModal = this.closeCredsModal.bind(this);
+
+ this.addAliases = this.addAliases.bind(this);
+ this.editAliases = this.editAliases.bind(this);
+ this.removeAliases = this.removeAliases.bind(this);
+ this.openAliasesModal = this.openAliasesModal.bind(this);
+ this.showAddAliasesModal = this.showAddAliasesModal.bind(this);
+ this.showEditAliasesModal = this.showEditAliasesModal.bind(this);
+ this.closeAliasesModal = this.closeAliasesModal.bind(this);
+
+ this.doFullReport = this.doFullReport.bind(this);
+ this.processCredsInput = this.processCredsInput.bind(this);
+ this.closeReportModal = this.closeReportModal.bind(this);
+ this.refreshFullReport = this.refreshFullReport.bind(this);
+
// Conflict entry functions
this.convertConflict = this.convertConflict.bind(this);
this.swapConflict = this.swapConflict.bind(this);
@@ -105,8 +241,18 @@ export class ReplMonitor extends React.Component {
this.closeConfirmSwapConflict = this.closeConfirmSwapConflict.bind(this);
}
- componentDidMount() {
- this.props.enableTree();
+ handleFieldChange(e) {
+ let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
+ if (e.target.type === 'number') {
+ if (e.target.value) {
+ value = parseInt(e.target.value);
+ } else {
+ value = 1;
+ }
+ }
+ this.setState({
+ [e.target.id]: value
+ });
}
convertConflict (dn) {
@@ -341,6 +487,12 @@ export class ReplMonitor extends React.Component {
});
}
+ handleReportNavSelect(key) {
+ this.setState({
+ activeReportKey: key
+ });
+ }
+
closeLogModal() {
this.setState({
showLogModal: false
@@ -422,6 +574,7 @@ export class ReplMonitor extends React.Component {
if (agmt['agmt-name'] == name) {
this.setState({
showAgmtModal: true,
+ isRemoteAgmt: false,
agmt: agmt
});
break;
@@ -429,6 +582,34 @@ export class ReplMonitor extends React.Component {
}
}
+ showAgmtModalRemote (supplierName, replicaName, agmtName) {
+ if (!agmtName) {
+ this.props.addNotification(
+ "error",
+ `The agreement doesn't exist!`
+ );
+ } else {
+ for (let supplier of this.state.reportData) {
+ if (supplier.name == supplierName) {
+ for (let replica of supplier.data) {
+ if (`${replica.replica_root}:${replica.replica_id}` == replicaName) {
+ for (let agmt of replica.agmts_status) {
+ if (agmt['agmt-name'][0] == agmtName) {
+ this.setState({
+ showAgmtModal: true,
+ isRemoteAgmt: true,
+ agmt: agmt
+ });
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
closeAgmtModal() {
this.setState({
showAgmtModal: false,
@@ -533,108 +714,507 @@ export class ReplMonitor extends React.Component {
});
}
- getLagReportCreds () {
- if (this.props.data.replAgmts.length == 0) {
- // No agreements, don't proceed...
- this.props.addNotification(
- "error", "There are no replication agreements to report on"
- );
+ handleConvertChange(e) {
+ const value = e.target.value;
+ this.setState({
+ convertRDN: value,
+ });
+ }
+
+ changeCreds(action) {
+ const { credentialsList, oldCredsHostname, oldCredsPort, credsHostname,
+ credsPort, credsBinddn, credsBindpw, pwInputInterractive } = this.state;
+
+ if (credsHostname === "" || credsPort === "" || credsBinddn === "") {
+ this.props.addNotification("warning", "Host, Port, and Bind DN are required.");
+ } else if (credsBindpw === "" && !pwInputInterractive) {
+ this.props.addNotification("warning", "Password field can't be empty, if Interractive Input is not selected");
} else {
- this.setState({
- showLoginModal: true,
- errObj: {
- bindpw: true
- }
- });
+ let credsExist = false;
+ if ((action == "add") && (credentialsList.some(row => row.connData === `${credsHostname}:${credsPort}`))) {
+ credsExist = true;
+ }
+ if ((action == "edit") && (credentialsList.some(row => row.connData === `${oldCredsHostname}:${oldCredsPort}`))) {
+ this.setState({
+ credentialsList: credentialsList.filter(
+ row => row.connData !== `${oldCredsHostname}:${oldCredsPort}`
+ )
+ });
+ }
+
+ if (!credsExist) {
+ this.setState(prevState => ({
+ credentialsList: [
+ ...prevState.credentialsList,
+ {
+ connData: `${credsHostname}:${credsPort}`,
+ credsBinddn: credsBinddn,
+ credsBindpw: credsBindpw,
+ pwInputInterractive: pwInputInterractive
+ }
+ ]
+ }));
+ } else {
+ this.props.addNotification(
+ "error",
+ `Credentials "${credsHostname}:${credsPort}" already exists`
+ );
+ }
+ this.closeCredsModal();
}
}
- closeLoginModal () {
+ addCreds() {
+ this.changeCreds("add");
+ }
+
+ editCreds() {
+ this.changeCreds("edit");
+ }
+
+ removeCreds(rowData) {
this.setState({
- showLoginModal: false,
+ credentialsList: this.state.credentialsList.filter(
+ row => row.connData !== rowData.connData
+ )
});
}
- handleLoginModal(e) {
- const value = e.target.value.trim();
- let valueErr = false;
- let errObj = this.state.errObj;
- if (value == "") {
- valueErr = true;
- }
- errObj[e.target.id] = valueErr;
+ openCredsModal() {
this.setState({
- [e.target.id]: value,
- errObj: errObj
+ showCredentialsModal: true
});
}
- closeLagReport() {
+ showAddCredsModal() {
+ this.openCredsModal();
this.setState({
- showLagReport: false
+ newEntry: true,
+ oldCredsHostname: "",
+ oldCredsPort: "",
+ credsHostname: "",
+ credsPort: "",
+ credsBinddn: "cn=Directory Manager",
+ credsBindpw: "",
+ pwInputInterractive: false
});
}
- doLagReport() {
- // Get agmts but this time with with bind credentials, then clear
- // out bind credentials after we use them
+ showEditCredsModal(rowData) {
+ this.openCredsModal();
+ this.setState({
+ newEntry: false,
+ oldCredsHostname: rowData.connData.split(':')[0],
+ oldCredsPort: rowData.connData.split(':')[1],
+ credsHostname: rowData.connData.split(':')[0],
+ credsPort: rowData.connData.split(':')[1],
+ credsBinddn: rowData.credsBinddn,
+ credsBindpw: rowData.credsBindpw,
+ pwInputInterractive: rowData.pwInputInterractive
+ });
+ }
- if (this.state.binddn == "" || this.state.bindpw == "") {
- return;
+ closeCredsModal() {
+ this.setState({
+ showCredentialsModal: false
+ });
+ }
+
+ changeAlias(action) {
+ const { aliasesList, aliasHostname, aliasPort, oldAliasName, aliasName } = this.state;
+
+ if (aliasPort === "" || aliasHostname === "" || aliasName === "") {
+ this.props.addNotification("warning", "Host, Port, and Alias are required.");
+ } else {
+ let aliasExists = false;
+ if ((action == "add") && (aliasesList.some(row => row.alias === aliasName))) {
+ aliasExists = true;
+ }
+ if ((action == "edit") && (aliasesList.some(row => row.alias === oldAliasName))) {
+ this.setState({
+ aliasesList: aliasesList.filter(row => row.alias !== oldAliasName)
+ });
+ }
+
+ if (!aliasExists) {
+ this.setState(prevState => ({
+ aliasesList: [
+ ...prevState.aliasesList,
+ {
+ connData: `${aliasHostname}:${aliasPort}`,
+ alias: aliasName
+ }
+ ]
+ }));
+ } else {
+ this.props.addNotification("error", `Alias "${aliasName}" already exists`);
+ }
+ this.closeAliasesModal();
}
+ }
+ addAliases() {
+ this.changeAlias("add");
+ }
+
+ editAliases() {
+ this.changeAlias("edit");
+ }
+
+ removeAliases(rowData) {
this.setState({
- loginSpinning: true,
+ aliasesList: this.state.aliasesList.filter(row => row.alias !== rowData.alias)
});
+ }
- let cmd = ["dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
- "replication", "status", "--suffix=" + this.props.suffix,
- "--bind-dn=" + this.state.binddn, "--bind-passwd=" + this.state.bindpw];
- log_cmd("doLagReport", "Get agmts for lag report", cmd);
- cockpit
- .spawn(cmd, { superuser: true, err: "message" })
- .done(content => {
- let config = JSON.parse(content);
+ openAliasesModal() {
+ this.setState({
+ showAliasesModal: true,
+ });
+ }
+
+ showAddAliasesModal() {
+ this.openAliasesModal();
+ this.setState({
+ newEntry: true,
+ aliasHostname: "",
+ aliasPort: 389,
+ oldAliasName: "",
+ aliasName: ""
+ });
+ }
+
+ showEditAliasesModal(rowData) {
+ this.openAliasesModal();
+ this.setState({
+ newEntry: false,
+ aliasHostname: rowData.connData.split(':')[0],
+ aliasPort: parseInt(rowData.connData.split(':')[1]),
+ oldAliasName: rowData.alias,
+ aliasName: rowData.alias
+ });
+ }
+
+ closeAliasesModal() {
+ this.setState({
+ showAliasesModal: false
+ });
+ }
+
+ refreshFullReport() {
+ this.doFullReport();
+ this.setState({
+ reportRefreshing: true
+ });
+ }
+
+ doFullReport() {
+ // Initiate the report and continue the processing in the input window
+ this.setState({
+ reportLoading: true,
+ activeReportKey: 2
+ });
+
+ let password = "";
+ let credentials = [];
+ let printCredentials = [];
+ for (let row of this.state.credentialsList) {
+ if (row.pwInputInterractive) {
+ password = "*";
+ } else {
+ password = `${row.credsBindpw}`;
+ }
+ credentials.push(`${row.connData}:${row.credsBinddn}:${password}`);
+ printCredentials.push(`${row.connData}:${row.credsBinddn}:********`);
+ }
+
+ let aliases = [];
+ for (let row of this.state.aliasesList) {
+ aliases.push(`${row.alias}=${row.connData}`);
+ }
+
+ let buffer = "";
+ let cmd = [
+ "dsconf",
+ "-j",
+ "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket",
+ "replication",
+ "monitor"
+ ];
+
+ if (aliases.length != 0) {
+ cmd = [...cmd, "-a"];
+ for (let value of aliases) {
+ cmd = [...cmd, value];
+ }
+ }
+
+ // We should not print the passwords to console.log
+ let printCmd = cmd;
+ if (credentials.length != 0) {
+ cmd = [...cmd, "-c"];
+ for (let value of credentials) {
+ cmd = [...cmd, value];
+ }
+ printCmd = [...printCmd, "-c"];
+ for (let value of printCredentials) {
+ printCmd = [...printCmd, value];
+ }
+ }
+
+ log_cmd("doFullReport", "Get the report for the current instance topology", printCmd);
+ // We need to set it here because 'input' will be run from inside
+ let proc = cockpit.spawn(cmd, { pty: true, environ: ["LC_ALL=C"], superuser: true, err: "message", directory: self.path });
+ // We use it in processCredsInput
+ this.setState({
+ fullReportProcess: proc
+ });
+ proc
+ .done(data => {
+ // Use the buffer from stream. 'data' is empty
+ let report = JSON.parse(buffer);
+ // We need to reparse the report data because agmts json wasn't parsed correctly because it was too nested
+ let agmts_reparsed = [];
+ let replica_reparsed = [];
+ let supplier_reparsed = [];
+ for (let supplier of report.items) {
+ replica_reparsed = [];
+ for (let replica of supplier.data) {
+ agmts_reparsed = [];
+ let agmts_done = false;
+ if (replica.hasOwnProperty("agmts_status")) {
+ for (let agmt of replica.agmts_status) {
+ // We need this for Agreement View Modal
+ agmt["supplierName"] = [supplier.name];
+ agmt["replicaName"] = [`${replica.replica_root}:${replica.replica_id}`];
+ agmt["replicaStatus"] = [`${replica.replica_status}`];
+ agmt["rowKey"] = [`${supplier.name}:${replica.replica_root}:${replica.replica_id}:${agmt["agmt-name"]}`];
+ agmts_reparsed.push(agmt);
+ agmts_done = true;
+ }
+ }
+ if (!agmts_done) {
+ let agmt_empty = {};
+ agmt_empty["supplierName"] = [supplier.name];
+ if (replica.replica_root || replica.replica_id) {
+ agmt_empty["replicaName"] = [`${replica.replica_root || ""}:${replica.replica_id || ""}`];
+ } else {
+ agmt_empty["replicaName"] = [""];
+ }
+ agmt_empty["replicaStatus"] = [`${replica.replica_status}`];
+ agmt_empty["rowKey"] = [`${supplier.name}:${replica.replica_root}:${replica.replica_id}:None`];
+ agmts_reparsed.push(agmt_empty);
+ }
+ replica_reparsed.push({...replica, agmts_status: agmts_reparsed});
+ }
+ supplier_reparsed.push({...supplier, data: replica_reparsed});
+ }
+ const report_reparsed = {...report, items: supplier_reparsed};
this.setState({
- lagAgmts: config.items,
- showLagReport: true,
- showLoginModal: false,
- loginSpinning: false,
- bindpw: ""
+ reportData: report_reparsed.items,
+ showFullReportModal: true,
+ reportLoading: false,
+ doFullReportCleanup: true
});
})
- .fail(err => {
- let errMsg = JSON.parse(err);
+ .fail(_ => {
+ let errMsg = JSON.parse(buffer);
this.props.addNotification(
"error",
- `Failed to get replication status - ${errMsg.desc}`
+ `Sync report has failed - ${errMsg.desc}`
);
this.setState({
- showLoginModal: false,
- loginSpinning: false,
- bindpw: ""
+ dynamicCredentialsList: [],
+ reportLoading: false,
+ doFullReportCleanup: true,
+ activeReportKey: 1
});
+ })
+ // Stream is run each time as a new character arriving
+ .stream(data => {
+ buffer += data;
+ let lines = buffer.split("\n");
+ let last_line = lines[lines.length - 1];
+ let found_creds = false;
+
+ // Interractive Input is required
+ // Check for Bind DN first
+ if (last_line.startsWith("Enter a bind DN") && last_line.endsWith(": ")) {
+ buffer = "";
+ // Get the instance name. We need it for fetching the creds data from stored state list
+ this.setState({
+ credsInstanceName: data.split("a bind DN for ")[1].split(": ")[0]
+ });
+ // First check if DN is in the list already (either from previous run or during this execution)
+ for (let creds of this.state.dynamicCredentialsList) {
+ if (creds.credsInstanceName == this.state.credsInstanceName) {
+ found_creds = true;
+ proc.input(`${creds.binddn}\n`, true);
+ }
+ }
+
+ // If we don't have the creds - open the modal window and ask the user for input
+ if (!found_creds) {
+ this.setState({
+ showReportLoginModal: true,
+ binddnRequired: true,
+ disableBinddn: false,
+ credsInstanceName: this.state.credsInstanceName,
+ loginBinddn: "",
+ loginBindpw: ""
+ });
+ }
+
+ // Check for password
+ } else if (last_line.startsWith("Enter a password") && last_line.endsWith(": ")) {
+ buffer = "";
+ // Do the same logic for password but the string parsing is different
+ this.setState({
+ credsInstanceName: data.split(" on ")[1].split(": ")[0]
+ });
+ for (let creds of this.state.dynamicCredentialsList) {
+ if (creds.credsInstanceName == this.state.credsInstanceName) {
+ found_creds = true;
+ proc.input(`${creds.bindpw}\n`, true);
+ this.setState({
+ credsInstanceName: ""
+ });
+ }
+ }
+
+ if (!found_creds) {
+ this.setState({
+ showReportLoginModal: true,
+ bindpwRequired: true,
+ credsInstanceName: this.state.credsInstanceName,
+ disableBinddn: true,
+ loginBinddn: data.split("Enter a password for ")[1].split(" on")[0],
+ loginBindpw: ""
+ });
+ }
+ }
});
}
- handleConvertChange(e) {
- const value = e.target.value;
+ closeReportLoginModal() {
this.setState({
- convertRDN: value,
+ showReportLoginModal: false,
+ reportLoading: false,
+ activeReportKey: 1
+ });
+ }
+
+ processCredsInput() {
+ const {
+ loginBinddn,
+ loginBindpw,
+ credsInstanceName,
+ fullReportProcess
+ } = this.state;
+
+ if (loginBinddn == "" || loginBindpw == "") {
+ this.props.addNotification("warning", "Bind DN and password are required.");
+ } else {
+ this.setState({
+ showReportLoginModal: false,
+ reportLoading: false
+ });
+
+ // Store the temporary data in state
+ this.setState(prevState => ({
+ dynamicCredentialsList: [
+ ...prevState.dynamicCredentialsList,
+ {
+ binddn: loginBinddn,
+ bindpw: loginBindpw,
+ credsInstanceName: credsInstanceName
+ }
+ ]
+ }));
+
+ // We wait for some input - put the right one here
+ if (this.state.binddnRequired) {
+ fullReportProcess.input(`${loginBinddn}\n`, true);
+ this.setState({
+ binddnRequired: false
+ });
+ } else if (this.state.bindpwRequired) {
+ fullReportProcess.input(`${loginBindpw}\n`, true);
+ this.setState({
+ bindpwRequired: false
+ });
+ }
+ }
+ }
+
+ closeReportModal() {
+ this.setState({
+ showFullReportModal: false,
+ reportLoading: false
});
}
render() {
+ let reportData = this.state.reportData;
+ let credentialsList = this.state.credentialsList;
+ let aliasesList = this.state.aliasesList;
let replAgmts = this.props.data.replAgmts;
let replWinsyncAgmts = this.props.data.replWinsyncAgmts;
let cleanTasks = this.props.data.cleanTasks;
let abortTasks = this.props.data.abortTasks;
let conflictEntries = this.props.data.conflicts;
let glueEntries = this.props.data.glues;
+ let fullReportModal = "";
+ let reportLoginModal = "";
+ let reportCredentialsModal = "";
+ let reportAliasesModal = "";
let agmtDetailModal = "";
let winsyncAgmtDetailModal = "";
let compareConflictModal = "";
+ if (this.state.showReportLoginModal) {
+ reportLoginModal =
+ <ReportLoginModal
+ showModal={this.state.showReportLoginModal}
+ closeHandler={this.closeReportLoginModal}
+ handleChange={this.handleFieldChange}
+ processCredsInput={this.processCredsInput}
+ instanceName={this.state.credsInstanceName}
+ disableBinddn={this.state.disableBinddn}
+ loginBinddn={this.state.loginBinddn}
+ loginBindpw={this.state.loginBindpw}
+ />;
+ }
+ if (this.state.showCredentialsModal) {
+ reportCredentialsModal =
+ <ReportCredentialsModal
+ showModal={this.state.showCredentialsModal}
+ closeHandler={this.closeCredsModal}
+ handleFieldChange={this.handleFieldChange}
+ newEntry={this.state.newEntry}
+ hostname={this.state.credsHostname}
+ port={this.state.credsPort}
+ binddn={this.state.credsBinddn}
+ bindpw={this.state.credsBindpw}
+ pwInputInterractive={this.state.pwInputInterractive}
+ addConfig={this.addCreds}
+ editConfig={this.editCreds}
+ />;
+ }
+ if (this.state.showAliasesModal) {
+ reportAliasesModal =
+ <ReportAliasesModal
+ showModal={this.state.showAliasesModal}
+ closeHandler={this.closeAliasesModal}
+ handleFieldChange={this.handleFieldChange}
+ newEntry={this.state.newEntry}
+ hostname={this.state.aliasHostname}
+ port={this.state.aliasPort}
+ alias={this.state.aliasName}
+ addConfig={this.addAliases}
+ editConfig={this.editAliases}
+ />;
+ }
if (this.state.showAgmtModal) {
agmtDetailModal =
<AgmtDetailsModal
@@ -642,6 +1222,7 @@ export class ReplMonitor extends React.Component {
closeHandler={this.closeAgmtModal}
agmt={this.state.agmt}
initAgmt={this.confirmInit}
+ isRemoteAgmt={this.state.isRemoteAgmt}
/>;
}
@@ -668,6 +1249,105 @@ export class ReplMonitor extends React.Component {
/>;
}
+ let reportContent =
+ <div>
+ <Nav bsClass="nav nav-tabs nav-tabs-pf">
+ <NavItem className="ds-nav-med" eventKey={1}>
+ {_("Prepare")}
+ </NavItem>
+ <NavItem className="ds-nav-med" eventKey={2}>
+ {_("Result")}
+ </NavItem>
+ </Nav>
+ <TabContent>
+ <TabPane eventKey={1}>
+ <div className="ds-indent ds-margin-top-lg">
+ <CustomCollapse textClosed="Show Help" textOpened="Hide Help" className="h3">
+ <h3>How To Use Replication Sync Report</h3>
+ <ol className="ds-left-indent">
+ <li>
+ Fill in <b>Replica Credentials</b>;
+ <ul>
+ <li>• Initially, the list is populated with existing instance agreements and the active instance itself;</li>
+ <li>• You can use regular expressions for the <b>Connection Data</b> field;</li>
+ <li>• It is advised to use an <b>Interactive Input</b> option for a password because it's more secure.</li>
+ </ul>
+ </li>
+ <li>
+ Add <b>Instance Aliases</b> if needed;
+ <ul>
+ <li>• Adding the aliases will make the report more readable;</li>
+ <li>• Each instance can have one alias. For example, you can give names like this:
+ <b> Alias</b>=Main Master, <b>Hostname</b>=192.168.122.01, <b>Port</b>=38901;</li>
+ <li>• In a result, the report will have an entry like this:
+ <b> Supplier: Main Master (192.168.122.01:38901)</b>.</li>
+ </ul>
+ </li>
+ <li>
+ Press <b>Generate Report</b> button;
+ <ul>
+ <li>• It will initiate the report creation;</li>
+ <li>• You may be asked for the credentials while the process is running through the agreements.</li>
+ </ul>
+ </li>
+ <li>
+ Once report is generated you can review it and enable continuous refreshing.
+ <ul>
+ <li>• More consumer replication data is available under the 'View Data' button;</li>
+ <li>• You can set the timeout and the new report will be created by that;</li>
+ <li>• It will use the specified credentials (both preset and from interactive input).</li>
+ </ul>
+ </li>
+ </ol>
+ </CustomCollapse>
+ <ReportCredentialsTable
+ rows={credentialsList}
+ deleteConfig={this.removeCreds}
+ editConfig={this.showEditCredsModal}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="default"
+ onClick={this.showAddCredsModal}
+ >
+ Add Credentials
+ </Button>
+ <ReportAliasesTable
+ rows={aliasesList}
+ deleteConfig={this.removeAliases}
+ editConfig={this.showEditAliasesModal}
+ />
+ <Button
+ className="ds-margin-top"
+ bsStyle="default"
+ onClick={this.showAddAliasesModal}
+ >
+ Add Alias
+ </Button>
+ <p />
+ <Button
+ className="ds-margin-top"
+ bsStyle="primary"
+ onClick={this.doFullReport}
+ title="Use the specified credentials and display full topology report"
+ >
+ Generate Report
+ </Button>
+ </div>
+ </TabPane>
+ <TabPane eventKey={2}>
+ <div className="ds-indent ds-margin-top-lg">
+ <FullReportContent
+ reportData={reportData}
+ viewAgmt={this.showAgmtModalRemote}
+ handleRefresh={this.refreshFullReport}
+ reportRefreshing={this.state.reportRefreshing}
+ reportLoading={this.state.reportLoading}
+ />
+ </div>
+ </TabPane>
+ </TabContent>
+ </div>;
let cleanNavTitle = 'CleanAllRUV Tasks <font size="1">(' + cleanTasks.length + ')</font>';
let abortNavTitle = 'Abort CleanAllRUV Tasks <font size="1">(' + abortTasks.length + ')</font>';
let taskContent =
@@ -750,8 +1430,9 @@ export class ReplMonitor extends React.Component {
</TabContent>
</div>;
- let replAgmtNavTitle = 'Replication Agreements <font size="1">(' + replAgmts.length + ')</font>';
- let winsyncNavTitle = 'Winsync Agreements <font size="1">(' + replWinsyncAgmts.length + ')</font>';
+ let fullReportTitle = 'Sync Report';
+ let replAgmtNavTitle = 'Agreements <font size="1">(' + replAgmts.length + ')</font>';
+ let winsyncNavTitle = 'Winsync <font size="1">(' + replWinsyncAgmts.length + ')</font>';
let tasksNavTitle = 'Tasks <font size="1">(' + (cleanTasks.length + abortTasks.length) + ')</font>';
let conflictsNavTitle = 'Conflicts <font size="1">(' + (conflictEntries.length + glueEntries.length) + ')</font>';
@@ -761,37 +1442,44 @@ export class ReplMonitor extends React.Component {
<div>
<Nav bsClass="nav nav-tabs nav-tabs-pf">
<NavItem eventKey={1}>
- <div dangerouslySetInnerHTML={{__html: replAgmtNavTitle}} />
+ <div dangerouslySetInnerHTML={{__html: fullReportTitle}} />
</NavItem>
<NavItem eventKey={2}>
- <div dangerouslySetInnerHTML={{__html: winsyncNavTitle}} />
+ <div dangerouslySetInnerHTML={{__html: replAgmtNavTitle}} />
</NavItem>
<NavItem eventKey={3}>
- <div dangerouslySetInnerHTML={{__html: tasksNavTitle}} />
+ <div dangerouslySetInnerHTML={{__html: winsyncNavTitle}} />
</NavItem>
<NavItem eventKey={4}>
+ <div dangerouslySetInnerHTML={{__html: tasksNavTitle}} />
+ </NavItem>
+ <NavItem eventKey={5}>
<div dangerouslySetInnerHTML={{__html: conflictsNavTitle}} />
</NavItem>
</Nav>
<TabContent>
<TabPane eventKey={1}>
<div className="ds-indent ds-tab-table">
+ <TabContainer
+ id="task-tabs"
+ defaultActiveKey={1}
+ onSelect={this.handleReportNavSelect}
+ activeKey={this.state.activeReportKey}
+ >
+ {reportContent}
+ </TabContainer>
+ </div>
+ </TabPane>
+ <TabPane eventKey={2}>
+ <div className="ds-indent ds-tab-table">
<AgmtTable
agmts={replAgmts}
pokeAgmt={this.pokeAgmt}
viewAgmt={this.showAgmtModal}
/>
- <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"
- >
- Get Lag Report
- </Button>
</div>
</TabPane>
- <TabPane eventKey={2}>
+ <TabPane eventKey={3}>
<div className="dds-indent ds-tab-table">
<WinsyncAgmtTable
agmts={replWinsyncAgmts}
@@ -800,14 +1488,14 @@ export class ReplMonitor extends React.Component {
/>
</div>
</TabPane>
- <TabPane eventKey={3}>
+ <TabPane eventKey={4}>
<div className="ds-indent ds-tab-table">
<TabContainer id="task-tabs" defaultActiveKey={1}>
{taskContent}
</TabContainer>
</div>
</TabPane>
- <TabPane eventKey={4}>
+ <TabPane eventKey={5}>
<div className="ds-indent ds-tab-table">
<TabContainer id="task-tabs" defaultActiveKey={1}>
{conflictContent}
@@ -838,21 +1526,6 @@ export class ReplMonitor extends React.Component {
msg="Are you really sure you want to reinitialize this replication winsync agreement?"
msgContent={this.state.agmt['agmt-name']}
/>
- <ReplLoginModal
- showModal={this.state.showLoginModal}
- closeHandler={this.closeLoginModal}
- handleChange={this.handleLoginModal}
- doReport={this.doLagReport}
- spinning={this.state.loginSpinning}
- error={this.state.errObj}
- />
- <ReplLagReportModal
- showModal={this.state.showLagReport}
- closeHandler={this.closeLagReport}
- agmts={this.state.lagAgmts}
- pokeAgmt={this.pokeAgmt}
- viewAgmt={this.showAgmtModal}
- />
<ConfirmPopup
showModal={this.state.showConfirmDeleteGlue}
closeHandler={this.closeConfirmDeleteGlue}
@@ -893,6 +1566,10 @@ export class ReplMonitor extends React.Component {
msg="Are you really sure you want to delete this conflict entry?"
msgContent={this.state.conflictEntry}
/>
+ {fullReportModal}
+ {reportLoginModal}
+ {reportCredentialsModal}
+ {reportAliasesModal}
{agmtDetailModal}
{winsyncAgmtDetailModal}
{compareConflictModal}
diff --git a/src/cockpit/389-console/src/lib/tools.jsx b/src/cockpit/389-console/src/lib/tools.jsx
index b37dd86..631135e 100644
--- a/src/cockpit/389-console/src/lib/tools.jsx
+++ b/src/cockpit/389-console/src/lib/tools.jsx
@@ -4,9 +4,11 @@ export function searchFilter(searchFilterValue, columnsToSearch, rows) {
rows.forEach(row => {
let rowToSearch = [];
if (columnsToSearch && columnsToSearch.length) {
- columnsToSearch.forEach(column =>
- rowToSearch.push(row[column])
- );
+ columnsToSearch.forEach(column => {
+ if (column in row) {
+ rowToSearch.push(row[column]);
+ }
+ });
} else {
rowToSearch = row;
}
diff --git a/src/cockpit/389-console/src/monitor.jsx b/src/cockpit/389-console/src/monitor.jsx
index fd2bc0d..1c97497 100644
--- a/src/cockpit/389-console/src/monitor.jsx
+++ b/src/cockpit/389-console/src/monitor.jsx
@@ -828,6 +828,9 @@ export class Monitor extends React.Component {
replLoading: false,
});
});
+ } else {
+ // We should enable it here because ReplMonitor never will be mounted
+ this.enableTree();
}
}
diff --git a/src/lib389/lib389/agreement.py b/src/lib389/lib389/agreement.py
index 93fd728..dfffbed 100644
--- a/src/lib389/lib389/agreement.py
+++ b/src/lib389/lib389/agreement.py
@@ -372,21 +372,21 @@ class Agreement(DSLdapObject):
# Case sensitive?
if use_json:
result = {
- 'agmt-name': ensure_str(status_attrs_dict['cn'][0]),
- 'replica': consumer,
- 'replica-enabled': ensure_str(status_attrs_dict['nsds5replicaenabled'][0]),
- 'update-in-progress': ensure_str(status_attrs_dict['nsds5replicaupdateinprogress'][0]),
- 'last-update-start': ensure_str(status_attrs_dict['nsds5replicalastupdatestart'][0]),
- 'last-update-end': ensure_str(status_attrs_dict['nsds5replicalastupdateend'][0]),
- 'number-changes-sent': ensure_str(status_attrs_dict['nsds5replicachangessentsincestartup'][0]),
- 'number-changes-skipped:': ensure_str(status_attrs_dict['nsds5replicachangesskippedsince'][0]),
- 'last-update-status': ensure_str(status_attrs_dict['nsds5replicalastupdatestatus'][0]),
- 'last-init-start': ensure_str(status_attrs_dict['nsds5replicalastinitstart'][0]),
- 'last-init-end': ensure_str(status_attrs_dict['nsds5replicalastinitend'][0]),
- 'last-init-status': ensure_str(status_attrs_dict['nsds5replicalastinitstatus'][0]),
- 'reap-active': ensure_str(status_attrs_dict['nsds5replicareapactive'][0]),
- 'replication-status': status,
- 'replication-lag-time': lag_time
+ 'agmt-name': ensure_list_str(status_attrs_dict['cn']),
+ 'replica': [consumer],
+ 'replica-enabled': ensure_list_str(status_attrs_dict['nsds5replicaenabled']),
+ 'update-in-progress': ensure_list_str(status_attrs_dict['nsds5replicaupdateinprogress']),
+ 'last-update-start': ensure_list_str(status_attrs_dict['nsds5replicalastupdatestart']),
+ 'last-update-end': ensure_list_str(status_attrs_dict['nsds5replicalastupdateend']),
+ 'number-changes-sent': ensure_list_str(status_attrs_dict['nsds5replicachangessentsincestartup']),
+ 'number-changes-skipped': ensure_list_str(status_attrs_dict['nsds5replicachangesskippedsince']),
+ 'last-update-status': ensure_list_str(status_attrs_dict['nsds5replicalastupdatestatus']),
+ 'last-init-start': ensure_list_str(status_attrs_dict['nsds5replicalastinitstart']),
+ 'last-init-end': ensure_list_str(status_attrs_dict['nsds5replicalastinitend']),
+ 'last-init-status': ensure_list_str(status_attrs_dict['nsds5replicalastinitstatus']),
+ 'reap-active': ensure_list_str(status_attrs_dict['nsds5replicareapactive']),
+ 'replication-status': [status],
+ 'replication-lag-time': [lag_time]
}
return (json.dumps(result))
else:
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
index dc482f4..b62446e 100644
--- a/src/lib389/lib389/cli_conf/backend.py
+++ b/src/lib389/lib389/cli_conf/backend.py
@@ -1111,7 +1111,7 @@ def create_parser(subparsers):
#######################################################
delete_parser = subcommands.add_parser('delete', help='Delete a backend database')
delete_parser.set_defaults(func=backend_delete)
- delete_parser.add_argument('be_name', help='The backend name or suffix to delete')
+ delete_parser.add_argument('be-name', help='The backend name or suffix to delete')
#######################################################
# Get Suffix Tree (for use in web console)
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
index ede2529..c10e3ab 100644
--- a/src/lib389/lib389/cli_conf/replication.py
+++ b/src/lib389/lib389/cli_conf/replication.py
@@ -376,15 +376,15 @@ def get_repl_monitor_info(inst, basedn, log, args):
if connections:
for connection_str in connections:
- if len(connection_str.split(":")) != 4:
- raise ValueError(f"Connection string {connection_str} is in wrong format."
- "It should be host:port:binddn:bindpw")
- host_regex = connection_str.split(":")[0]
- port_regex = connection_str.split(":")[1]
+ connection = connection_str.split(":")
+ if (len(connection) != 4 or not all([len(str) > 0 for str in connection])):
+ raise ValueError(f"Please, fill in all Credential details. It should be host:port:binddn:bindpw")
+ host_regex = connection[0]
+ port_regex = connection[1]
if re.match(host_regex, host) and re.match(port_regex, port):
found = True
- binddn = connection_str.split(":")[2]
- bindpw = connection_str.split(":")[3]
+ binddn = connection[2]
+ bindpw = connection[3]
# Search for the password file or ask the user to write it
if bindpw.startswith("[") and bindpw.endswith("]"):
pwd_file_path = os.path.expanduser(bindpw[1:][:-1])
@@ -404,46 +404,63 @@ def get_repl_monitor_info(inst, basedn, log, args):
"bindpw": bindpw}
repl_monitor = ReplicationMonitor(inst)
- report_dict = repl_monitor.generate_report(get_credentials)
+ report_dict = repl_monitor.generate_report(get_credentials, args.json)
+ report_items = []
+
+ for instance, report_data in report_dict.items():
+ report_item = {}
+ found_alias = False
+ if args.aliases:
+ aliases = {al.split("=")[0]: al.split("=")[1] for al in args.aliases}
+ elif connection_data["aliases"]:
+ aliases = connection_data["aliases"]
+ else:
+ aliases = {}
+ if aliases:
+ for alias_name, alias_host_port in aliases.items():
+ if alias_host_port.lower() == instance.lower():
+ supplier_header = f"{alias_name} ({instance})"
+ found_alias = True
+ break
+ if not found_alias:
+ supplier_header = f"{instance}"
- if args.json:
- log.info(json.dumps({"type": "list", "items": report_dict}))
- else:
- for instance, report_data in report_dict.items():
- found_alias = False
- if args.aliases:
- aliases = {al.split("=")[0]: al.split("=")[1] for al in args.aliases}
- elif connection_data["aliases"]:
- aliases = connection_data["aliases"]
- else:
- aliases = {}
- if aliases:
- for alias_name, alias_host_port in aliases.items():
- if alias_host_port.lower() == instance.lower():
- supplier_header = f"Supplier: {alias_name} ({instance})"
- found_alias = True
- break
- if not found_alias:
- supplier_header = f"Supplier: {instance}"
+ if args.json:
+ report_item["name"] = supplier_header
+ else:
+ supplier_header = f"Supplier: {supplier_header}"
log.info(supplier_header)
- # Draw a line with the same length as the header
+
+ # Draw a line with the same length as the header
+ status = ""
+ if not args.json:
log.info("-".join(["" for _ in range(0, len(supplier_header)+1)]))
- if "status" in report_data and report_data["status"] == "Unavailable":
- status = report_data["status"]
- reason = report_data["reason"]
- log.info(f"Status: {status}")
- log.info(f"Reason: {reason}\n")
- else:
- for replica in report_data:
- replica_root = replica["replica_root"]
- replica_id = replica["replica_id"]
- maxcsn = replica["maxcsn"]
+ if report_data[0]["replica_status"].startswith("Unavailable"):
+ status = report_data[0]["replica_status"]
+ if not args.json:
+ log.info(f"Replica Status: {status}\n")
+ else:
+ for replica in report_data:
+ replica_root = replica["replica_root"]
+ replica_id = replica["replica_id"]
+ replica_status = replica["replica_status"]
+ maxcsn = replica["maxcsn"]
+ if not args.json:
log.info(f"Replica Root: {replica_root}")
log.info(f"Replica ID: {replica_id}")
+ log.info(f"Replica Status: {replica_status}")
log.info(f"Max CSN: {maxcsn}\n")
- for agreement_status in replica["agmts_status"]:
+ for agreement_status in replica["agmts_status"]:
+ if not args.json:
log.info(agreement_status)
+ if args.json:
+ report_item["data"] = report_data
+ report_items.append(report_item)
+
+ if args.json:
+ log.info(json.dumps({"type": "list", "items": report_items}))
+
def create_cl(inst, basedn, log, args):
cl = Changelog5(inst)
diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py
index c026ff7..0fc1275 100644
--- a/src/lib389/lib389/replica.py
+++ b/src/lib389/lib389/replica.py
@@ -2492,12 +2492,16 @@ class ReplicationMonitor(object):
protocol = agmt.get_attr_val_utf8_l('nsds5replicatransportinfo')
# Supply protocol here because we need it only for connection
# and agreement status is already preformatted for the user output
- consumer = f"{host}:{port}:{protocol}"
+ consumer = f"{host}:{port}"
if consumer not in report_data:
- report_data[consumer] = None
- agmts_status.append(agmt.status(use_json))
+ report_data[f"{consumer}:{protocol}"] = None
+ if use_json:
+ agmts_status.append(json.loads(agmt.status(use_json=True)))
+ else:
+ agmts_status.append(agmt.status())
replicas_status.append({"replica_id": replica_id,
"replica_root": replica_root,
+ "replica_status": "Available",
"maxcsn": replica_maxcsn,
"agmts_status": agmts_status})
return replicas_status
@@ -2514,9 +2518,13 @@ class ReplicationMonitor(object):
"""
report_data = {}
- initial_inst_key = f"{self._instance.host.lower()}:{str(self._instance.port).lower()}"
+ initial_inst_key = f"{self._instance.config.get_attr_val_utf8_l('nsslapd-localhost')}:{self._instance.config.get_attr_val_utf8_l('nsslapd-port')}"
# Do this on an initial instance to get the agreements to other instances
- report_data[initial_inst_key] = self._get_replica_status(self._instance, report_data, use_json)
+ try:
+ report_data[initial_inst_key] = self._get_replica_status(self._instance, report_data, use_json)
+ except ldap.LDAPError as e:
+ self._log.debug(f"Connection to consumer ({supplier_hostname}:{supplier_port}) failed, error: {e}")
+ report_data[initial_inst_key] = [{"replica_status": f"Unavailable - {e.args[0]['desc']}"}]
# Check if at least some replica report on other instances was generated
repl_exists = False
@@ -2528,18 +2536,19 @@ class ReplicationMonitor(object):
except IndexError:
break
+ del report_data[supplier]
s_splitted = supplier.split(":")
supplier_hostname = s_splitted[0]
supplier_port = s_splitted[1]
supplier_protocol = s_splitted[2]
+ supplier_hostport_only = ":".join(s_splitted[:2])
# The function should be defined outside and
# it should have all the logic for figuring out the credentials.
# It is done for flexibility purpuses between CLI, WebUI and lib389 API applications
credentials = get_credentials(supplier_hostname, supplier_port)
if not credentials["binddn"]:
- report_data[supplier] = {"status": "Unavailable",
- "reason": "Bind DN was not specified"}
+ report_data[supplier_hostport_only] = [{"replica_status": "Unavailable - Bind DN was not specified"}]
continue
# Open a connection to the consumer
@@ -2557,21 +2566,30 @@ class ReplicationMonitor(object):
supplier_inst.open()
except ldap.LDAPError as e:
self._log.debug(f"Connection to consumer ({supplier_hostname}:{supplier_port}) failed, error: {e}")
- report_data[supplier] = {"status": "Unavailable",
- "reason": e.args[0]['desc']}
+ report_data[supplier_hostport_only] = [{"replica_status": f"Unavailable - {e.args[0]['desc']}"}]
continue
- report_data[supplier] = self._get_replica_status(supplier_inst, report_data, use_json)
+ report_data[supplier_hostport_only] = self._get_replica_status(supplier_inst, report_data, use_json)
repl_exists = True
+ # Get rid of the repeated items
+ report_data_parsed = {}
+ for key, value in report_data.items():
+ current_inst_rids = [val["replica_id"] for val in report_data[key] if "replica_id" in val.keys()]
+ report_data_parsed[key] = sorted(current_inst_rids)
+ report_data_filtered = {}
+ for key, value in report_data_parsed.items():
+ if value not in report_data_filtered.values():
+ report_data_filtered[key] = value
+
# Now remove the protocol from the name
report_data_final = {}
for key, value in report_data.items():
# We take the initial instance only if it is the only existing part of the report
- if key != initial_inst_key or not repl_exists:
+ if key in report_data_filtered.keys() or not repl_exists:
if not value:
- value = {"status": "Unavailable",
- "reason": "No replicas were found"}
- report_data_final[":".join(key.split(":")[:2])] = value
+ value = [{"replica_status": "Unavailable - No replicas were found"}]
+
+ report_data_final[key] = value
return report_data_final
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
4 years, 3 months